Создание клиент серверного приложения в visual studio
Тема сетевого программирования является для разработчиков одной из важнейших в современном цифровом мире. Правда, надо признать, что большая часть сетевого программирования сосредоточена в области написания скриптов исполнения для web-серверов на языках PHP, Python и им подобных. Как следствие - по тематике взаимодействия клиент-сервер при работе с web-серверами написаны терабайты текстов в Интернете. Однако когда я решил посмотреть, что же имеется в Интернете по вопросу программирования сетевых приложений с использованием голых сокетов, то обнаружил интересную вещь: да, такие примеры конечно же есть, но подавляющее большинство написано под *nix-системы с использованием стандартных библиотек (что понятно – в области сетевого программирования Microsoft играет роль сильно отстающего и менее надежного «собрата» *nix-ов). Другими словами все эти примеры просто не будут работать под Windows. При определенных танцах с бубнами код сетевого приложения под Linux можно запустить и под Windows, однако это еще более запутает начинающего программиста, на которого и нацелены большинство статей в Интернете с примерами использования сокетов.
вообще не заработает, т.к. полю Service.sin_addr.s_addr невозможно присвоить значение целого типа, которое возвращает функция inet_addr (возвращает unsigned long). То есть это ни много, ни мало - ошибка! Можно себе представить, сколько пытливых бойцов полегло на этом месте кода.
Сразу оговорюсь, что статья рассчитана на начинающих программистов, которые только входят в сетевое программирование под Windows. Необходимые навыки – базовое знание С++, а также теоретическая подготовка по теме сетевых сокетов и стека технологии TCP/IP.
Теория сокетов за 30 секунд для "dummies"
Начну всё-таки немного с теории в стиле «for dummies». В любой современной операционной системе, все процессы инкапсулируются, т.е. скрываются друг от друга, и не имеют доступа к ресурсам друг друга. Однако существуют специальные разрешенные способы взаимодействия процессов между собой. Все эти способы взаимодействия процессов можно разделить на 3 группы: (1) сигнальные, (2) канальные и (3) разделяемая память.
Когда мы говорим про работу сетевого приложения, то всегда подразумеваем взаимодействие процессов: процесс 1 (клиент) пытается что-то послать или получить от Процесса 2 (сервер). Наиболее простым и понятным способом организации сетевого взаимодействия процессов является построение канала между этими процессами. Именно таким путём и пошли разработчики первых сетевых протоколов. Получившийся способ взаимодействия сетевых процессов в итоге оказался многоуровневым: основной программный уровень - стек сетевой технологии TCP/IP, который позволяет организовать эффективную доставку пакетов информации между различными машинами в сети, а уже на прикладном уровне тот самый «сокет» позволяет разобраться какой пакет какому процессу доставить на конкретной машине.
Иными словами «сокет» - это «розетка» конкретного процесса, в которую надо подключиться, чтобы этому процессу передать какую-либо информацию. Договорились, что эта «розетка» в Сети описывается двумя параметрами – IP-адресом (для нахождения машины в сети) и Портом подключения (для нахождения процесса-адресата на конкретной машине).
Для того, чтобы сокеты заработали под Windows, необходимо при написании программы пройти следующие Этапы:
Инициализация сокетных интерфейсов Win32API.
Инициализация сокета, т.е. создание специальной структуры данных и её инициализация вызовом функции.
«Привязка» созданного сокета к конкретной паре IP-адрес/Порт – с этого момента данный сокет (его имя) будет ассоциироваться с конкретным процессом, который «висит» по указанному адресу и порту.
Для серверной части приложения: запуск процедуры «прослушки» подключений на привязанный сокет.
Для клиентской части приложения: запуск процедуры подключения к серверному сокету (должны знать его IP-адрес/Порт).
Акцепт / Подтверждение подключения (обычно на стороне сервера).
Обмен данными между процессами через установленное сокетное соединение.
Закрытие сокетного соединения.
Итак, попытаемся реализовать последовательность Этапов, указанных выше, для организации простейшего чата между клиентом и сервером. Запускаем Visual Studio, выбираем создание консольного проекта на С++ и поехали.
Этап 0: Подключение всех необходимых библиотек Win32API для работы с сокетами
Сокеты не являются «стандартными» инструментами разработки, поэтому для их активизации необходимо подключить ряд библиотек через заголовочные файлы, а именно:
WinSock2.h – заголовочный файл, содержащий актуальные реализации функций для работы с сокетами.
WS2tcpip.h – заголовочный файл, который содержит различные программные интерфейсы, связанные с работой протокола TCP/IP (переводы различных данных в формат, понимаемый протоколом и т.д.).
Ну и в конце Этапа 0 подключаем стандартные заголовочные файлы iostream и stdio.h
Итого по завершению Этапа 0 в Серверной и Клиентской частях приложения имеем:
Обратите внимание: имя системной библиотеки ws2_32.lib именно такое, как это указано выше. В Сети есть различные варианты написания имени данной библиотеки, что, возможно, связано иным написанием в более ранних версиях ОС Windows. Если вы используете Windows 10, то данная библиотека называется именно ws2_32.lib и находится в стандартной папке ОС: C:/Windows/System32 (проверьте наличие библиотеки у себя, заменив расширение с “lib” на “dll”).
Этап 1: Инициализация сокетных интерфейсов Win32API
Прежде чем непосредственно создать объект сокет, необходимо «запустить» программные интерфейсы для работы с ними. Под Windows это делается в два шага следующим образом:
Нужно определить с какой версией сокетов мы работаем (какую версию понимает наша ОС) и
Запустить программный интерфейс сокетов в Win32API. Ну либо расстроить пользователя тем, что ему не удастся поработать с сокетами до обновления системных библиотек
Первый шаг делается с помощью создания структуры типа WSADATA , в которую автоматически в момент создания загружаются данные о версии сокетов, используемых ОС, а также иная связанная системная информация: WSADATA wsData;
Второй шаг – непосредственный вызов функции запуска сокетов с помощью WSAStartup() . Упрощённый прототип данной функции выглядит так:
Первый аргумент функции – указание диапазона версий реализации сокетов, которые мы хотим использовать и которые должны быть типа WORD . Этот тип данных является внутренним типом Win32API и представляет собой двухбайтовое слово (аналог в С++: unsigned short ). Функция WSAStartup() просит вас передать ей именно WORD , а она уже разложит значение переменной внутри по следующему алгоритму: функция считает, что в старшем байте слова указана минимальная версия реализации сокетов, которую хочет использовать пользователь, а в младшем – максимальная. По состоянию на дату написания этой статьи (октябрь 2021 г.) актуальная версия реализации сокетов в Windows – 2. Соответственно, желательно передать и в старшем, и в младшем байте число 2. Для того, чтобы создать такую переменную типа WORD и передать в её старший и младший байты число 2, можно воспользоваться Win32API функцией MAKEWORD(2,2) .
Второй аргумент функции – просто указатель на структуру WSADATA , которую мы создали ранее и в которую подгрузилась информация о текущей версии реализации сокетов на данной машине.
WSAStartup() в случае успеха возвращает 0, а в случае каких-то проблем возвращает код ошибки, который можно расшифровать последующим вызовом функции WSAGetLastError() .
Важное замечание: поскольку сетевые каналы связи и протоколы в теории считаются ненадежными (это отдельный большой разговор), то критически важно для сетевого приложения анализировать все возможные ошибки, которые возникают в процессе вызовов сокетных функций. По этой причине каждый вызов таких функций мы будем анализировать на ошибки и в случае их обнаружения завершать сетевые сеансы и закрывать открытые сокеты. Используем для этого переменную erStat типа int .
Также важно после работы приложения обязательно закрыть использовавшиеся сокеты с помощью функции closesocket(SOCKET ) и деинициализировать сокеты Win32API через вызов метода WSACleanup() .
Итого код Этапа 1 следующий:
Да, кода мало, а описания много. Так обычно и бывает, когда хочешь глубоко в чем-то разобраться. Так что на лабе будешь в первых рядах.
Этап 2: Создание сокета и его инициализация
Сокет в С++ – это структура данных (не класс) типа SOCKET. Её инициализация проводится через вызов функции socket() , которая привязывает созданный сокет к заданной параметрами транспортной инфраструктуре сети. Выглядит прототип данной функции следующим образом:
Семейство адресов: сокеты могут работать с большим семейством адресов. Наиболее частое семейство – IPv4. Указывается как AF_INET .
Тип сокета: обычно задается тип транспортного протокола TCP ( SOCK_STREAM ) или UDP ( SOCK_DGRAM ). Но бывают и так называемые "сырые" сокеты, функционал которых сам программист определяет в процессе использования. Тип обозначается SOCK_RAW
Тип протокола: необязательный параметр, если тип сокета указан как TCP или UDP – можно передать значение 0. Тут более детально останавливаться не будем, т.к. в 95% случаев используются типы сокетов TCP/UDP.
При необходимости подробно почитать про функцию socket() можно здесь.
Код Этапа 2 будет выглядеть так:
Этап 3: Привязка сокета к паре IP-адрес/Порт
Сокет уже существует, но еще неполноценный, т.к. ему не назначен внешний адрес, по которому его будут находить транспортные протоколы по заданию подключающихся процессов, а также не назначен порт, по которому эти подключающиеся процессы будут идентифицировать процесс-получатель.
Такое назначение делается с помощью функции bind() , имеющей следующий прототип:
Функция bind() возвращает 0 , если удалось успешно привязать сокет к адресу и порту, и код ошибки в ином случае, который можно расшифровать вызовом WSAGetLastError() - см. итоговый код Этапа 3 далее.
Тут надо немножно притормозить и разобраться в том, что за такая структура типа sockaddr передается вторым аргументом в функцию bind() . Она очень важна, но достаточно запутанная.
Итак, если посмотреть в её внутренности, то выглядят они очень просто: в ней всего два поля – (1) первое поле хранит семейство адресов, с которыми мы уже встречались выше при инициализации сокета, а (2) второе поле хранит некие упакованные последовательно и упорядоченные данные в размере 14-ти байт. Бессмысленно разбираться детально как именно эти данные упакованы, достаточно лишь понимать, что в этих 14-ти байтах указан и адрес, и порт, а также дополнительная служебная информация для других системных функций Win32API .
Но как же явно указать адрес и порт для привязки сокета? Для этого нужно воспользоваться другой структурой, родственной sockaddr , которая легко приводится к этому типу - структурой типа sockaddr_in .
В ней уже более понятные пользователю поля, а именно:
Семейство адресов - опять оно ( sin_family )
Вложенная структура типа in_addr , в которой будет храниться сам сетевой адрес ( sin_addr )
Технический массив на 8 байт ( sin_zero[8] )
При приведении типа sockaddr_in к нужному нам типу sockaddr для использования в функции bind() поля Порт (2 байта), Сетевой адрес (4 байта) и Технический массив (8 байт) как раз в сумме дают нам 14 байт, помещающихся в 14 байт, находящихся во втором поле структуры sockaddr . Первые поля у указанных типов совпадают – это семейство адресов сокетов (указываем AF_INET ). Из этого видно, что структуры данных типа sockaddr и sockaddr_in тождественны, содержат одну и ту же информацию, но в разной форме для разных целей.
Соответственно, ввод данных для структуры типа sockaddr_in выглядит следующим образом:
Создание структуры типа sockaddr_in : sockaddr_in servInfo;
Заполнение полей созданной структуры servInfo
servInfo.sin_port = htons(); порт всегда указывается через вызов функции htons() , которая переупаковывает привычное цифровое значение порта типа unsigned short в побайтовый порядок понятный для протокола TCP/IP (протоколом установлен порядок указания портов от старшего к младшему байту или «big-endian»).
Далее нам надо указать сетевой адрес для сокета. Тип этого поля – структура типа in_addr , которая по своей сути представляет просто особый «удобный» системным функциям вид обычного строчного IPv4 адреса. Таким образом, чтобы указать этому полю обычный IPv4 адрес, его нужно сначала преобразовать в особый числовой вид и поместить в структуру типа in_addr .
Благо существует функция, которая переводит обычную строку типа char[] , содержащую IPv4 адрес в привычном виде с точками-разделителями в структуру типа in_addr – функция inet_pton() . Прототип функции следующий:
В случае ошибки функция возвращает значение меньше 0.
Соответственно, если мы хотим привязать сокет к локальному серверу, то наш код по преобразованию IPv4 адреса будет выглядеть так:
erStat = inet_pton(AF_INET, “127.0.0.1”, &ip_to_num);
Результат перевода IP-адреса содержится в структуре ip_to_num. И далее мы передаем уже в нашу переменную типа sockaddr_in значение преобразованного адреса:
Вся нужная информация для привязки сокета теперь у нас есть, и она хранится в структуре servInfo . Можно смело вызывать функцию bind() , не забыв при этом привести servInfo из типа sockaddr_in в требуемый функции sockaddr* . Тогда итоговый код Этапа 3 (слава богу закончили) выглядит так:
Этап 4 (для сервера): «Прослушивание» привязанного порта для идентификации подключений
Серверная часть готова к прослушке подключающихся «Клиентов». Для того, чтобы реализовать данный этап, нужно вызвать функцию listen() , прототип которой:
Второй аргумент: максимально возможное число подключений устанавливается через передачу параметр SOMAXCONN (рекомендуется). Если нужно установить ограничения на количество подключений – нужно указать SOMAXCONN_HINT(N) , где N – кол-во подключений. Если будет подключаться больше пользователей, то они будут сброшены.
После вызова данной функции исполнение программы приостанавливается до тех пор, пока не будет соединения с Клиентом, либо пока не будет возвращена ошибка прослушивания порта. Код Этапа 4 для Сервера:
Этап 4 (для Клиента). Организация подключения к серверу
Код для Клиента до текущего этапа выглядит даже проще: необходимо исполнение Этапов 0, 1 и 2. Привязка сокета к конкретному процессу ( bind() ) не требуется, т.к. сокет будет привязан к серверному Адресу и Порту через вызов функции connect() (по сути аналог bind() для Клиента). Собственно, после создания и инициализации сокета на клиентской стороне, нужно вызвать указанную функцию connect() . Её прототип:
Функция возвращает 0 в случае успешного подключения и код ошибки в ином случае.
Процедура по добавлению данных в структуру sockaddr аналогична тому, как это делалось на Этапе 3 для Сервера при вызове функции bind() . Принципиально важный момент – в эту структуру для клиента должна заноситься информация о сервере, т.е. IPv4-адрес сервера и номер «слушающего» порта на сервере.
Этап 5 (только для Сервера). Подтверждение подключения
После начала прослушивания (вызов функции listen() ) следующей функцией должна идти функция accept() , которую будет искать программа после того, как установится соединение с Клиентом. Прототип функции accept() :
Если подключение подтверждено, то вся информация по текущему соединению передаётся на новый сокет, который будет отвечать со стороны Сервера за конкретное соединение с конкретным Клиентом. Перед вызовом accept() нам надо создать пустую структуру типа sockaddr_in , куда запишутся данные подключившегося Клиента после вызова accept() . Пример кода:
Всё, соединение между Клиентом и Сервером установлено! Самое время попробовать передать информацию от Клиента к Серверу и обратно. Как мы в начале и договорились, мы будет реализовывать простейший чат между ними.
Этап 6: Передача данных между Клиентом и Сервером
Принимать информацию на любой стороне можно с помощью функции recv() , которая при своём вызове блокирует исполнение кода программы до того момента, пока она не получит информацию от другой стороны, либо пока не произойдет ошибка в передаче или соединении.
Отправлять информацию с любой стороны можно с помощью функции send() . При вызове данной функции обычно никакого ожидания и блокировки не происходит, а переданные в неё данные сразу же отправляются другой стороне.
Рассмотрим прототипы функций recv() и send() :
Флаги в большинстве случаев игнорируются – передается значение 0.
Функции возвращают количество переданных/полученных по факту байт.
Как видно из прототипов, по своей структуре и параметрам эти функции совершенно одинаковые. Что важно знать:
и та, и другая функции не гарантируют целостности отправленной/полученной информации. Это значит, что при реализации прикладных задач по взаимодействию Клиента и Сервера с их использованием требуется принимать дополнительные меры для контроля того, что все посланные байты действительно посланы и, что еще более важно, получены в том же объеме на другой стороне
предельно внимательно надо относиться к параметру "размер буфера". Он должен в точности равняться реальному количеству передаваемых байт. Если он будет отличаться, то есть риск потери части информации или «замусориванию» отправляемой порции данных, что ведет к автоматической поломке данных в процессе отправки/приёма. И совсем замечательно будет, если размер буфера по итогу работы функции равен возвращаемому значению функции – размеру принятых/отправленных байт.
В качестве буфера рекомендую использовать не классические массивы в С-стиле, а стандартный класс С++ типа char, т.к. он показал себя как более надежный и гибкий механизм при передаче данных, в особенности при передаче текстовых строк, где важен терминальный символ и «чистота» передаваемого массива.
Сама по себе упаковка и отправка данных делается элементарным использованием функций чтения всей строки до нажатия кнопки Ввода - fgets() с последующим вызовом функции send() , а на другой стороне - приёмом информации через recv() и выводом буфера на экран через cout
Процесс непрерывного перехода от send() к recv() и обратно реализуется через бесконечный цикл, из которого совершается выход по вводу особой комбинации клавиш. Пример блока кода для Серверной части:
Пришло время показать итоговый рабочий код для Сервера и Клиента. Чтобы не загромождать и так большой текст дополнительным кодом, даю ссылки на код на GitHub:
Несколько важных финальных замечаний:
В итоговом коде я не использую проверку на точное получение отосланной информации, т.к. при единичной (не циклической) отсылке небольшого пакета информации накладные расходы на проверку его получения и отправку ответа будут выше, чем выгоды от такой проверки. Иными словами – такие пакеты теряются редко, а проверять их целостность и факт доставки очень долго.
В последующих статьях я покажу реализацию полноценного чата между двумя сторонами (поможет разобраться в понятии «нити процесса»), а также покажу полноценную реализацию прикладного протокола по копированию файлов с Сервера на Клиент.
Предварительные требования
Убедитесь в том, что установлены следующие компоненты:
Создание интерфейсного приложения
В диалоговом окне "Новый проект" выберите Создать новый проект.
В строке поиска вверху выполните поиск по запросу "Angular", а затем выберите Автономный шаблон TypeScript Angular.
После создания проекта вы увидите ряд новых и измененных файлов:
Создание серверного приложения
В обозревателе решений щелкните имя решения правой кнопкой мыши, наведите указатель на пункт Добавить и выберите пункт Новый проект.
После создания проекта обозреватель решений должен выглядеть следующим образом:
Задание свойств проекта
Перейдите в меню "Отладка" и выберите пункт Открыть пользовательский интерфейс профилей запуска отладки. Снимите флажок Запустить браузер.
Затем щелкните правой кнопкой мыши проект Angular, выберите меню Свойства и перейдите к разделу Отладка. Измените запускаемый отладчик на launch.json.
Этот параметр задает расположение файла launch.json. Путь по умолчанию для launch.json представляет собой путь .vscode/launch.json, поэтому вы можете пропустить этот шаг, если используете путь по умолчанию.
Задание запускаемого проекта
Щелкните правой кнопкой мыши решение и выберите Задание запускаемого проекта. Измените один запускаемый проект на несколько запускаемых проектов. В качестве действия для каждого проекта выберите Запуск.
Затем выберите проект серверной части и переместите его над интерфейсным, чтобы он запускался первым.
Запуск проекта
Перед запуском проекта убедитесь, что номера портов совпадают.
Затем перейдите к файлу proxy.conf.js для проекта Angular (в папке src). Обновите целевое свойство, чтобы оно совпадало со свойством applicationUrl в файле launchSettings.json. После обновления значение должно выглядеть так:
Нажмите клавишу F5 или кнопку Запуск в верхней части окна, чтобы запустить проект. Появятся две командные строки:
Должно отобразиться приложение Angular, которое заполняется через API.
Устранение неполадок
Вы можете получать следующую ошибку:
При появлении этой ошибки, скорее всего, интерфейсная часть была запущена до серверной. После того как серверная командная строка запустится, просто обновите приложение Angular в браузере.
Предварительные требования
Убедитесь в том, что установлены следующие компоненты:
Создание интерфейсного приложения
В диалоговом окне "Новый проект" выберите Создать новый проект.
В строке поиска вверху выполните поиск по запросу "React", а затем выберите Автономный шаблон JavaScript React. (Автономный шаблон TypeScript React в настоящее время не поддерживается в этом руководстве.)
После создания проекта вы увидите ряд новых и измененных файлов:
Выберите установленный браузер на панели инструментов "Отладка", например Chrome или Microsoft Edge.
Если нужный браузер еще не установлен, сначала установите браузер, а затем выберите его.
Создание серверного приложения
В обозревателе решений щелкните имя решения правой кнопкой мыши, наведите указатель на пункт Добавить и выберите пункт Новый проект.
После создания проекта обозреватель решений должен выглядеть следующим образом:
Задание свойств проекта
Перейдите в меню "Отладка" и выберите пункт Открыть пользовательский интерфейс профилей запуска отладки. Снимите флажок Запустить браузер.
Затем щелкните правой кнопкой мыши проект React, выберите меню Свойства и перейдите к разделу Отладка. Измените запускаемый отладчик на launch.json.
Этот параметр задает расположение файла launch.json. Путь по умолчанию для launch.json представляет собой путь .vscode/launch.json, поэтому вы можете пропустить этот шаг, если используете путь по умолчанию.
Задание запускаемого проекта
Щелкните правой кнопкой мыши решение и выберите Задание запускаемого проекта. Измените один запускаемый проект на несколько запускаемых проектов. В качестве действия для каждого проекта выберите Запуск.
Затем выберите проект серверной части и переместите его над интерфейсным, чтобы он запускался первым.
Запуск проекта
Затем перейдите к файлу setupProxy.js для проекта React (в папке src). Обновите целевое свойство, чтобы оно совпадало со свойством applicationUrl в файле launchSettings.json. После обновления значение должно выглядеть так:
Нажмите клавишу F5 или кнопку Запуск в верхней части окна, чтобы запустить проект. Появятся две командные строки:
Должно отобразиться приложение React, которое заполняется через API.
Устранение неполадок
Вы можете получать следующую ошибку:
При появлении этой ошибки, скорее всего, интерфейсная часть была запущена до серверной. После того как серверная командная строка запустится, просто обновите приложение React в браузере.
Дано три стороны треугольника: a , b , c .
Используя формулу Герона, разработать приложение, которое находит площадь треугольника. Приложение реализовать как Web-application .
Формула Герона имеет вид:
где p – полупериметр:
a, b, c – длина сторон треугольника.
⇑
Выполнение
1. Запустить MS Visual Studio
Пример создания приложения в MS Visual Studio по шаблону Windows Forms Application подробно описывается в теме:
⇑
2. Создание Web-приложения
Приложения типа Web могут вызываться из любого компьютера, подключенного к сети Internet. Для открытия такого приложения используется Web -браузер (например Opera , Google Chrome , Internet Explorer и другие).
Ниже указаны два способа создания Web -приложения в MS Visual Studio .
⇑
2.1. Создание Web-приложения (способ № 1)
Для этого способа, чтобы создать Web -приложение, нужно сначала вызвать команду (рис. 1):
Рис. 1. Команда создания нового веб-сайта
Например, в нашем случае, файлы будут сохраняться в папке
Место расположения файлов указывается в поле « Web-location» . Доступны три способа размещения файлов:
Выбираем « File system «. В этом случае файлы приложения будут размещаться на локальном сервере ( localhost ), который создается системой. Это означает, что программа-клиент (наше приложение) и программа-сервер (условно отдаленный компьютер в сети) размещены на одном и том же (домашнем) компьютере. Фактически localhost – это IP -адрес, с помощью которого компьютер может обратиться в сети к самому себе, независимо от наличия или вида компьютерной сети.
Рис. 2. Создание Web-сайта
⇑
2.2. Создание Web-приложения (способ № 2)
Существует также и другой способ создания Web -приложения с помощью команды (рис. 3)
Рис. 3. Команда создания нового проекта
Рис. 4. Команда создания Web-приложения
⇑
3. Создание приложения как веб-сайта
Для решения задачи выбираем первый способ.
После выполненных операций создается решение ( Solution ), в котором есть один проект типа веб-сайт (рис. 5).
Рис. 5. Окно « Solution Explorer » после создания веб-сайта
Если запустить на выполнение данный проект, то внизу в правой части экрана ( SySTray ) отобразится окно загруженного локального сервера (рис. 6).
Рис. 6. Загрузка локального сервера для выполнения приложения
Следующим отобразится окно, изображенное на рисунке 7. В этом окне нужно подтвердить на кнопке « ОК «, чтобы модифицировать файл « Web.config » таким образом, чтобы можно было выполнять наше приложение.
Рис. 7. Запрос к модификации файла « Web.config «
В результате запуска, в активном веб-браузере откроется страница с приблизительно таким текстом (рис. 8):
Рис. 8. Текст, который выводится в веб-браузере
Для завершения работы приложения, нужно в MS Visual Studio вызвать команду « Stop Debugging » из меню « Debug «.
⇑
4. Добавление формы к приложению
Добавим новую форму к Web -приложению.
Для этого нужно выделить название приложения в Solution Explorer , сделать клик правой кнопкой «мыши» и в контекстном меню выбрать команду « Add New Item… » (рис. 9).
Рис. 9. Команда « Add New Item… «
Существует и другой способ добавления формы – вызов команды « Add New Item… » из меню « Website «.
Рис. 10. Добавление новой формы
После добавления, в Solution Explorer можно увидеть дополнительные два файла (рис. 11):
С помощью этих двух файлов можно изменять внешний вид формы и организовывать работу в ней.
Рис. 11. Файлы формы « Default.aspx » и « Default.aspx.cs «
С помощью кнопок Design и Source можно переключаться между режимом проектирования и режимом кода страницы Default.aspx (рис. 12).
Рис. 12. Режимы проектирования ( Design ) и кода ( Source )
⇑
5. Конструирование формы
Согласно с условием задачи, форма должна содержать следующие элементы управления:
⇑
5.1. Изменение размеров формы
Переходим в режим проектирования с помощью кнопки « Design » (рис. 12).
С помощью «мышки» увеличиваем размер формы, как показано на рисунке 13 (необязательно).
Рис. 13. Изменение размеров формы в режиме проектирования
⇑
С помощью «мышки» можно выносить на форму различные элементы управления. Работа с Web -формой есть точно такая же как и с формой типа Windows Forms . Текст на форме можно вносить непосредственно.
Для нашей задачи нужно вынести на форму следующие элементы управления:
- три элемента управления типа Label для обозначения « а = «, « b = «, « c = «;
- один элемент управления типа Button ;
- три элемента управления типа TextBox ;
- один элемент управления типа Label для вывода результата.
При вынесении элемента управления в правой нижней части экрана (окно Properties ) можно изменять свойства элементов управления (рис. 14).
Рис. 14. Изменение свойств элементов управления Web -формы
В целом, после построения, форма приложения должна иметь вид как показано на рисунке 15.
Рис. 15. Форма приложения в режиме проектирования
⇑
6. Программирование события клика на кнопке « Calculate «
Последним шагом есть программирование события, которое будет генерироваться при клике на кнопке « Calculate «. Для Web -приложений это осуществляется стандартным для MS Visual Studio способом. Программный код обработки события будет сформирован в файле « Default.aspx.cs «.
Таким образом, выделяем элемент управления Button1 . В списке свойств Properties переходим к вкладке Events . В вкладке Events делаем двойной клик «мышкой» напротив названия события « OnClick «. Система откроет файл « Default.aspx.cs » со следующим кодом:
В обработчик события Button1_Click(…) вводим код расчета площади треугольника по трем сторонам. В целом текст модуля « Default.aspx.cs » будет иметь вид.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class _Default : System.Web.UI.Page < protected void Page_Load( object sender, EventArgs e) < >protected void Button1_Click( object sender, EventArgs e) < double a, b, c, p, s; a = Double .Parse(TextBox1.Text); b = Double .Parse(TextBox2.Text); c = Double .Parse(TextBox3.Text); p = (a + b + c) / 2; s = Math .Sqrt(p * (p - a) * (p - b) * (p - c)); Label4.Text = "S text-align: justify;">После запуска приложения на выполнение можно проконтролировать его работу в Web -браузере (рис. 16).
Рис. 16. Выполнение Web -приложения в браузере Opera
После этого можно выносить приложение на Web-сервер. Но это уже другая тема.
В данной теме описывается один из способов разработки простейшего распределенного приложения, которое использует удаленное взаимодействие между компьютерами в сети. По данному примеру можно научиться разрабатывать приложения, которые взаимодействуют между собой в локальной сети. Тема предусматривает наличие базовых навыков работы в Microsoft Visual Studio 2010.
Содержание
Поиск на других ресурсах:
Условие задачи
1. Разработать распределенное приложение, которое содержит следующие части:
- серверная часть. Сервер должен содержать функции решения математических задач. В данном случае, в качестве примера сервер содержит метод, который решает квадратное уравнение. Серверная часть должна быть разработана как консольное приложение;
- клиентская часть. Клиент содержит программный код, который получает результат из серверной части. Клиент вызывает функцию решения квадратного уравнения, которая реализована и выполняется на сервере. Клиентская часть должна быть разработана как приложение типа Windows Forms .
⇑
Соображения
Для проверки правильности выполнения задачи, нужно наличие как минимум двух компьютеров, которые объединенные между собой в локальную сеть.
Разработка распределенного приложения осуществляется на одном компьютере в MS Visual Studio 2010. Тестирование взаимодействия клиентской и серверной частей осуществляются также на одном компьютере.
После завершения тестирования, серверный код размещается на компьютере-сервере. Клиентский код из компьютера-клиента делает запрос на компьютер-сервер, получает ответ и выводит этот ответ на экран. В нашем случае клиентский код вызывает серверную функцию решения квадратного уравнения.
⇑
Выполнение
1. Запустить Microsoft Visual Studio 2010
⇑
2. Разработка общей сборки с метаданными
2.1. Что такое общая сборка?
Для того, чтобы клиент и сервер взаимодействовали между собой, нужно наличие еще одной части программного кода – общей сборки. Общая сборка содержит метаданные типов, которые являются допустимыми для удаленного вызова. Метаданные типов – это информация о типах данных, которые используются при удаленном вызове. Эта информация нужна как клиенту, так и серверу.
В конечном счете, после создания, программный код общей сборки должен размещаться как на компьютере клиента, так и на компьютере сервера. В противном случае, связь между клиентом и сервером невозможно будет установить.
⇑
2.2. Создание модуля общей сборки
В нашем случае общая сборка не является исполняемым файлом. Это есть библиотека, которая сохраняется в формате *.dll .
В Microsoft Visual Studio 2010 для создания библиотеки используется шаблон Class Library . В данном примере, чтобы создать модуль общей сборки нужно выполнить команду
Рис. 1. Создание библиотеки по шаблону Class Library
После выбора OK , создается проект, который содержит один файл Class1.cs . Листинг файла следующий:
По умолчанию создается класс с именем Class1 в пространстве имен ClassLibrary1 . В нашем случае оставляем все как есть. По желанию можно изменить название класса и пространства имен.
⇑
2.3. Текст модуля общей сборки
В модуль Class1.cs нужно ввести необходимый программный код. Этот код будет выполняться на стороне сервера. А вызов этого кода будет осуществлять клиент.
В соответствии с условием задачи, сервер может решать математические задачи. Поэтому, в класс Class1 нужно вписать функцию решения квадратного уравнения. В нашем случае функция называется CalcEquation() . По желанию, сюда можно вписать другие функции, которые решают разные задачи.
Также класс Class1 наследует данные и методы класса MarshalByRefObject . Класс MarshalByRefObject обеспечивает доступ к объектам в пределах домена приложения в приложениях, которые поддерживают удаленный доступ. Это специально разработанный класс для поддержки распределенного доступа к приложениям.
В классе MarshalRefObject есть метод CreateObjRef() , который будет использован в нашем проекте. Метод CreateObjRef() содержит всю необходимую информацию для генерирования прокси. Прокси применяется чтобы соединиться с удаленным объектом.
Листинг модуля общей сборки следующий (файл Class1.cs ):
⇑
2.4. Объяснение к методу решения квадратного уравнения
Метод решения квадратного уравнения CalcEquation() получает входными параметрами значения коэффициентов a, b, c. Если уравнение имеет решение, то метод возвращает true и значение корней уравнения в параметрах-переменных x1 , x2 . Если уравнение не имеет решения, то метод возвращает false и нулевые значения параметров-переменных x1 , x2 .
⇑
2.5. Компиляция. Создание .dll — файла общей сборки
На этом шаге нужно откомпилировать проект командой
В результате будет создан *.dll -файл общей сборки с именем ClassLibrary1.dll, которая размещается в папке
Этот файл нужно будет разместить в папках с исполняемыми модулями на компьютере-клиенте и на на компьютере-сервере. Файл общей сборки готов.
Рис. 2. Файл ClassLibrary1.dll
⇑
2.6. Закрыть решение (проект)
Общая сборка разработана. Чтобы закрыть решение (Solution) нужно выполнить команду
На следующем этапе будет разрабатываться программный код серверной части распределенного приложения.
⇑
3. Создание программного кода, который будет размещаться на компьютере-сервере
После создания файла (модуля) общей сборки на одном и том же компьютере нужно разработать программный код, который будет размещаться на компьютере-сервере.
Чтобы сервер отвечал на запросы клиента, программный код серверной части должен быть запущен на выполнение в момент запроса.
3.1. Создание приложения типа Console Application
В соответствии с условием задачи, на серверной части нужно создать приложение типа Console Application . Это осуществляется стандартным способом с помощью команды
В результате откроется окно New Project (рисунок 3), в котором нужно:
Рис. 3. Окно создания консольного приложения
В результате будет создано обычное консольное приложение, которое содержит файл Program.cs . В этом файле объявляется пространство имен ConsoleApplication1 , в котором реализован класс Program .
Листинг файла следующий
⇑
3.2. Добавление ссылок на сборки
Чтобы использовать программный код общей сборки нужно подключить:
⇑
В окне Solution Explorer вызвать команду Add Reference… как показано на рисунке 4. Для этого нужно сделать клик правой кнопкой мыши на элементе References .
Рис. 4. Команда Add Reference…
В результате откроется диалоговое окно Add Reference (рисунок 5), в котором нужно:
⇑
3.2.2. Добавление ссылки на сборку ClassLibrary1
В нашем случае выбирается папка (см. п. 2.4):
Рис. 7. Выбор папки и файла общей сборки
После выбора OK , в окне Solution Explorer в перечне References ссылок на использованные сборки появится сборка ClassLibrary1 .
Рис. 8. Сборка ClassLibrary1 в перечне сборок серверного приложения
⇑
После подключения сборок к проекту, нужно подключить необходимые пространства имен в файле Program.cs. Добавляются 4 пространства имен:
На данный момент текст файла Program.cs следующий
⇑
3.3. Написание кода серверной части
3.3.1. Действия, которые нужно выполнить на сервере
На компьютере-сервере нужно выполнить три основных действия:
⇑
3.3.2. Текст кода серверной части
Ниже приведен текст программного кода серверной части
Объясним некоторые фрагменты кода серверной части.
На третьем этапе нужно зарегистрировать сервис как WKO -тип ( Well Known Object ). Это реализуется с помощью класса RemotingConfiguration . В этом классе есть метод RegisterWellKnownServiceType() . Этот метод получает три параметра:
- тип данных, который есть типом нашего созданного класса Class1 в общей сборке ClassLibrary1 . В нашем случае здесь нужно передать typeof(ClassLibrary1.Class1) ;
- строка, которая идентифицирует наш сервис на компьютере-сервере. В нашем случае выбрано «MathFunctions.soap» . По желанию можно выбрать другое название;
- значением из перечисления WellKnownObjectMode.Singleton . Это значит, что серверный объект разделяется всеми клиентами сервера.
В конце вызывается метод Console.Readline() . Этот метод необходим, чтобы зафиксировать выполнение консольного приложения, поскольку серверная часть должна быть все время запущена. В это время клиенты смогут к ней обращаться. Завершение работы сервера – клавиша Enter .
⇑
3.4. Компиляция. Файлы серверной части
На этом шаге нужно откомпилировать текст командой
для получения исполняемого ( *.exe ) файла. В результате будет создан исполняемый файл с именем
На рисунке 9 отображено содержимое папки Debug с файлами проекта серверной части. Как видно из рисунка 9, автоматически подгружается файл общей сборки ClassLibrary1.dll .
Рис. 9. Содержимое папки Debug с исполняемым файлом ConsoleApplication1.exe серверной части и файлом сборки ClassLibrary1.dll
Оба файла ConsoleApplication1.exe и ClassLibrary1.dll нужно будет разместить (скопировать) на компьютере-сервере.
После этого проект, который соответствует серверной части, завершен.
⇑
4. Разработка приложения клиентской части
В сети с несколькими компьютерами, клиентов может быть сколько угодно, а сервер один. Сервер должен отвечать на запросы клиентов. В нашем случае достаточно разработать одно приложение, которое делает запросы к серверу. Затем исполняемый .exe файл (модуль) этого приложения и .dll файл общей сборки нужно скопировать (распространить) по всей сети на клиентские компьютеры.
4.1. Создание приложения клиентской части по шаблону Windows Forms
Проект по шаблону Windows Forms создается стандартным способом:
В результате откроется окно New Project . В окне New Project нужно выбрать вкладку
как показано на рисунке 10.
Рис. 10. Проект типа Windows Forms Application , который будет размещаться на компьютере клиента
В проекте задаются:
- имя проекта WindowsFormsApplication1 ;
- папка проекта D:\Programs .
После выбора OK будет создана новая форма проекта как показано на рисунке 11.
Рис. 11. Новая форма проекта
⇑
4.2. Проектирование формы
4.2.1. Размещение элементов управления Label , Button , TextBox на форме
Нужно разместить на форме следующие элементы управления (рисунок 12):
- четыре элемента управления типа Label . По умолчанию создаются объекты с именами label1 , label2 , label3 , label4 ;
- один элемент управления типа Button . По умолчанию создается объект с именем button1 .
Рис. 12. Размещение элементов управления на форме
⇑
4.2.2. Настройка свойств фомы и элементов управления
Подробное описание того, как осуществляется настройка элементов управления на форме можно найти в теме:
На этом шаге, с помощью окна Properties, нужно настроить следующие свойства формы и элементов управления:
- в форме Form1 , свойство Text = «Решение квадратного уравнения» ;
- в форме Form1 , свойство StartPosition = CenterScreen ;
- в Label1 , свойство Text = «a = « ;
- в Label2 , свойство Text = «b = « ;
- в Label3 , свойство Text = «c = « ;
- в Label4 , свойство Text = «Результат: « .
После настройки элементов управления, окно формы будет иметь вид как показано на рисунке 13.
Рис. 13. Окно программы-клиента после настройки
После этого можно разрабатывать программный код клиента.
⇑
4.3. Текст модуля формы Form1.cs
Главной форме программы-клиента соответствует модуль (файл) с именем Form1.cs .
На данный момент листинг файла Form1.cs имеет следующий вид:
⇑
4.4. Алгоритм работы программы-клиента
На компьютере клиенте нужно выполнить следующие действия:
⇑
4.5. Написание программного кода клиентской части
4.5.1. Добавление ссылок на сборки
Для того, чтобы использовать код общей сборки, на компьютере-клиенте нужно подключить:
Это осуществляется командой «Add Reference…» так же, как было сделано для серверной части (см. п.п. 3.2.1, 3.2.2).
После подключения, вкладка References в окне Solution Explorer будет иметь вид, как показано на рисунке 14.
⇑
4.5.2. Подключение пространств имен
⇑
4.5.3. Написание кода метода Form1_Load()
Создание канала между сервером и клиентом в клиентской части будет выполняться только один раз. После того, как канал создан, многократно может вызываться метод CalcEquation() из серверной части.
Учитывая вышесказанное, целесообразно запрограммировать событие Load формы Form1 . Это событие вызывается в начале создания формы. В нашей программе этому событию соответствует обработчик Form1_Load() . В обработчике нужно разместить программный код создания канала, регистрации канала и создания отдаленного объекта.
Ниже приведен текст обработчика события Form1_Load() :
⇑
4.5.4. Программирование события клика на кнопке «Вычислить». Текст клиентской части
На этом шаге нужно запрограммировать обработку события клика на кнопке «Вычислить» ( button1 ). Подробное описание того как программируется событие описывается в теме:
Текст программы-клиента с реализованным обработчиком события клика на кнопке «Вычислить» приведен ниже
В вышеприведенном коде пока что задается общее имя нашего компьютера localhost . Если будет сеть, то это имя нужно будет заменить на компьютере-клиенте на сетевое имя компьютера сервера. Клиентский проект нужно будет снова перекомпилировать.
⇑
5. Тестирование взаимодействия между клиентом и сервером на одном компьютере без использования локальной сети
Тестирование взаимодействия между клиентом и сервером проводится на одном компьютере, который в системе носит имя localhost . После проведения тестирования это имя будет изменено на имя компьютера-сервера в сети.
Для проведения тестирования нужно:
- запустить серверное приложение (рисунок 15) из папки сервера (см. п. 3);
- запустить на выполнение клиентское приложение (рисунок 16) из папки клиента (см. п. 4);
- ввести в клиентское приложение данные и провести вычисления.
Рис. 15. Выполнение серверной части
Приблизительный результат выполнения клиентского приложения приведен на рисунке 16.
Рис. 16. Клиентская программа
Важно: если закрыть программу-сервер и попробовать провести вычисления на клиенте, то возникнет исключительная ситуация. Система не сможет найти приложение-сервер. Значит, для проведения тестирования, сервер должен все время выполняться.
Клиентов можно запускать сколько угодно и производить запросы к серверу.
⇑
6. Копирование серверной части на другой компьютер
На этом шаге нужно скопировать файлы ConsoleApplication1.exe и ClassLibrary1.dll на компьютер-сервер. Это осуществляется с помощью, например, обычного файлового менеджера. Файлы копируются в заведомо подготовленную папку.
⇑
7. Определение имени сервера в сети из компьютера-клиента
Если компьютеры объединены в локальную сеть, то каждый из них имеет свое уникальное имя. Способ определения имени компьютера в сети зависит от конкретной реализации операционной системы. В общем случае, чтобы определить это имя, нужно вызвать утилиту Control Panel (Панель управления). В этой утилите можно выбрать элемент System (Система). Откроется окно, в котором можно прочитать имя компьютера.
⇑
8. Корректирование клиентского программного кода. Замена имени localhost
Если известно имя компьютера-сервера в локальной сети, тогда нужно в клиентской части сделать изменения. В исходном коде файла Form1.cs в методе Form_Load() вместо строки
где MyComp – имя компьютера-сервера в локальной сети. Скорей всего у вас будет другое имя.
Итак, текст обработчика события загрузки формы в нашем случае следующий:
После этого нужно перекомпилировать клиентский проект, чтобы сформировать новый исполняемый модуль. На рисунке 17 изображен файл WindowsFormsApplication1.exe клиентского модуля. В этой папке также есть общая сборка ClassLibrary1.dll .
Рис. 17. Папка с клиентским исполняемым модулем WindowsFormsApplication1
⇑
9. Тестирование взаимодействия между клиентом и сервером на разных компьютерах, объединенных между собой в локальную сеть
На этом этапе нужно протестовать работу между клиентом и сервером на разных компьютерах, которые объединенные между собой в локальную сеть.
Для этого, нужно:
Важно: чтобы между клиентом и сервером было взаимодействие, компьютер-клиент должен иметь соответствующие права доступа к компьютеру-серверу. Например, на сервере может быть включен общий доступ к его ресурсам. Это все осуществляется соответствующими настройками на компьютере-сервере. В данной теме это не рассматривается.
Читайте также: