Как написать клиент серверное приложение c
Аннотация
Требования
В этой статье предполагается, что вы знакомы со следующими разделами:
Создание объекта удаленного сервера
Первым шагом в создании приложения-сервера является создание объекта сервера. Объект сервера — это то, с чем клиентские приложения мгновенно взаимодействуют и взаимодействуют на серверном компьютере. Клиентская заявка делает это с помощью прокси-объекта, созданного на клиенте. В этом примере объект сервера находится в библиотеке класса (DLL) и называется myRemoteClass.
В Обозревателе решений переименуем файл кода Class1.cs в ServerClass.cs.
Откройте ServerClass.cs и переименуйте class1 в myRemoteClass. Также необходимо переименовать конструктор по умолчанию для класса таким образом, чтобы он совпадал с именем класса. myRemoteClass должен наследовать от MarshalByRefObject класса. Теперь класс должен отображаться следующим образом:
Создайте проект для создания ServerClass.dll сборки.
Сохранить и закрыть проект.
Создание приложения удаленного сервера
После создания объекта сервера, с который клиент будет общаться, необходимо зарегистрировать этот объект в рамках Remoting. При регистрации объекта необходимо также запустить сервер и подключить сервер к порту для подключения к порту. Для этого необходим тип проекта, который выводит исполняемый файл.
Причина, по которой объект сервера включен в отдельный проект, заключается в том, что вы можете легко ссылаться на объект сервера из клиентского проекта. Если вы включаете его в этот проект, вы не можете ссылаться на него, так как ссылки могут быть заданной только для DLL-файлов.
В Обозревателе решений переименуем файл Class1.cs в RemoteServer.cs.
Добавьте ссылку на сборку ServerClass.dll, созданную в предыдущем разделе.
Объявить соответствующую переменную. Объявить и инициализировать объект, который прослушивает подключение клиентов в определенном порту, который является TcpChannel портом 8085 в этом примере. Используйте метод RegisterChannel для регистрации канала службами канала. Добавьте следующий код объявления в Main Class1 процедуру:
Вызов метода объекта для регистрации объекта с помощью фреймворка Remoting и укажите RegisterWellKnownType RemotingConfiguration следующие ServerClass параметры:
Назови конечную точку, где объект должен быть опубликован как RemoteTest. Клиенты должны знать это имя, чтобы подключиться к объекту.
Используйте режим SingleCall объекта, чтобы указать конечный параметр. Режим объекта указывает срок службы объекта при его активации на сервере. В случае объектов создается новый экземпляр класса для каждого вызова, который совершает клиент, даже если один и тот же клиент вызывает один и тот же SingleCall метод несколько раз. С другой стороны, Singleton объекты создаются только один раз, и все клиенты взаимодействуют с тем же объектом.
Используйте метод ReadLine объекта Console для поддержания работы серверного приложения.
Выполните построение проекта.
Сохранить и закрыть проект.
Ссылки
Здесь я должен сделать отступление и немного рассказать о себе, что бы в дальнейшем было понятнее, почему именно такие шаги в разработке я предпринимал.
Часть 1. Прототипирование рамы
Решив, что из себя будет представлять данный сервис, я принялся искать варианты для реализации. Проще всего было бы найти какой то готовое решение, на которое, как сову на глобус, можно натянуть наши механики и выложить всё это дело на общественное порицание.
Но это же не интересно, никакого челенджа и смысла в этом я не видел, а посему начал изучать веб технологии и методы взаимодействия с ними.
Первая версия сервера включала в себя обработку подключений, отдавала статическое содержимое веб-страниц и включала в себя базу данных пользователей. И для начала я решил строить функционал для работы с сайтом, что бы в последствии прикрутить сюда и обработку приложения на андроиде и ios.
Основной поток, в бесконечном цикле принимающий клиентов:
Сам обработчик клиентов:
И первая база данных построенная на local SQL:
Как можно заметить, эта версия мало отличается от той, что была в статье. По сути здесь только добавилась подгрузка страниц из папки на компьютере и база данных (которая кстати в данной версии не заработала, из-за неверной архитектуры подключения).
Глава 2. Прикручивание колёс
Протестировав работу сервера, я пришёл к выводу, что это будет отличным решением(спойлер: нет), для нашего сервиса, поэтому проект начал обрастать логикой.
Шаг за шагом начали появляться новые модули и функционал сервера разрастался. Сервер обзавёлся тестовым доменом и ssl шифрованием соединения.
Обновлённый вариант сервера, включающий в себя использование сертификата.
А так же новый обработчик клиента с авторизацией по ssl:
Но так как сервер работает исключительно на TCP подключении, то необходимо создать модуль, который мог распознавать контекст запроса. Я решил что здесь подойдёт парсер который будет разбивать запрос от клиента на отдельные части, с которыми я смогу взаимодействовать, что бы отдавать клиенту нужные ответы.
Глава 3. Установка руля, смазывание цепи
Как только парсер отработал, в дело вступает обработчик, отдающий дальнейшие указания серверу и разделяющий управление на две части.
По сути здесь всего одна проверка на авторизацию юзера, после чего начинается обработка запроса.
Если юзер не авторизован, то для него функционал базируется только на отображении профилей пользователя и окне регистрации\авторизации. Код для авторизованного пользователя выглядит примерно так же, поэтому не вижу смысла его дублировать.
Но что бы показывать пользователю его профиль и профили других пользователей я решил использовать RazorEngine, вернее его часть. Он так же включает в себя обработку неверных запросов и выдачу соответствующего кода ошибки.
Ну и конечно же, для того, что бы работала проверка авторизованных пользователей, нужна авторизация. Модуль авторизации взаимодействует с базой данных. Полученные данные из форм на сайте парсятся из контекста, юзер сохраняется и получает взамен куки и доступ к сервису.
А так выглядит обработка базы данных:
И всё работает как часы, авторизация и регистрация работает, минимальный функционал доступа к сервису уже имеется и пришла пора писать приложение и обвязывать всё это дело основными функциями, ради которых всё и делается.
Глава 4. Выбрасывание велосипеда
Начал тестировать дальше, ранее не упоминалось, но на прежнем сервере я ещё реализовывал чат построенный на вебсокетах. Он довольно неплохо работал, но сам принцип взаимодействия через Tcp был удручающим, слишком много лишнего приходилось плодить, что бы грамотно построить взаимодействие двух пользователей с ведением лога переписки. Это и парсинг запроса на предмет переключения соединения и сбор ответа по протоколу RFC 6455. Поэтому в тестовом сервере я решил создать простое вебсокет соединение. Чисто ради интереса.
И оно заработало. Сервер сам настраивал соединение, генерировал ответный ключ. Мне даже не пришлось отдельно настраивать регистрацию сервера по ssl, достаточно того, что в системе уже установлен сертификат на нужном порту.
WCF - Windows Communication Foundation, программный фреймворк, используемый для обмена данными между приложениями.
Почему стоит использовать это решение, а не сделать свой клиент-сервер ?1. Данное решение многопоточное. Это значит, что одновременно могут серверу поступить запросы от многих клиентов и клиенты не будут ждать друг друга в очереди, а каждый запрос будет выполнен в отдельном параллельном потоке.
2. Решение не имеет большого объема кода, а значит понять данный код легче.
3. Вам не придется вдаваться в подробности работы между клиентом и сервером: тот самый случай когда установил и забыл о проблемах.
Интерфейс определяет как будет выглядеть объект, в котором будут методы работы нашего клиент-сервера. В WCF данный интерфейс называется контрактом.
Замечание: если Вы создаете клиент-сервер не для теста в рамках одного приложения, а клиент и сервер будут разными приложениями, то Вам следует создать интерфейс ITransferObject и класс TransferObject в отдельной библиотеке dll. В таком случае Ваше клиентское и серверное приложения будут использовать одни классы и интерфейсы, что является Важным условием для WCF.
В данном примере определим, что данный объект будет иметь 2 простых метода:
GetSum - подсчет суммы двух целых чисел
GetMultiPly - подсчет умножения двух целых чисел
[ServiceContract] - необходимый модификатор чтобы указать что данный интерфейс является контрактом WCF службы.
[OperationContract] - необходимый модификатор чтобы указать что данные методы будут использоваться в контракте.
Класс TransferObject унаследуем от интерфейса ITransferObject. Данный класс будет иметь простую реализацию наших методов.
При вызове метода GetSum будут переданы 2 целых числа.Ответом выполним сложение этих чисел.
Соответственно и с методом GetMultiPly - ответом выполним перемножение этих чисел.
public class TransferObject : ITransferObject < public int GetSum(int a, int b) < return a + b; >public int GetMultiPly(int a, int b) < return a * b; >> static void Main(string[] args) < var serviceAddress = "127.0.0.1:10000"; var serviceName = "MyService"; var host = new ServiceHost(typeof(TransferObject), new Uri($"net.tcp://serviceAddress - это адрес на котором сервер будет ожидать подключения от клиентов.
serviceName - имя сервиса. На каждом адресе может быть большое количество сервисов. Имя сервиса - часть адреса, по которому клиент подключается к серверу.
Далее происходит запуск серверной части WCF. Чтобы приложение не закрылось после отработки всего кода, добавлено Console.ReadKey() - ожидание нажатия любой клавиши.
Строка подключения к серверу такая же как используется в серверной части: адрес_сервера + имя сервиса.
Далее происходит инициализация клиенской части WCF и вызов методов сложения и умножения чисел.
В следующем примере используем TCP, чтобы обеспечить упорядоченные, надежные двусторонние потоки байтов. Построим завершенное приложение, включающее клиент и сервер. Сначала демонстрируем, как сконструировать на потоковых сокетах TCP сервер, а затем клиентское приложение для тестирования нашего сервера.
Сервер TCP
Создание структуры сервера показано на следующей функциональной диаграмме:
Вот полный код программы SocketServer.cs:
Давайте рассмотрим структуру данной программы.
Первый шаг заключается в установлении для сокета локальной конечной точки. Прежде чем открывать сокет для ожидания соединений, нужно подготовить для него адрес локальной конечной точки. Уникальный адрес для обслуживания TCP/IP определяется комбинацией IP-адреса хоста с номером порта обслуживания, которая создает конечную точку для обслуживания.
Класс Dns предоставляет методы, возвращающие информацию о сетевых адресах, поддерживаемых устройством в локальной сети. Если у устройства локальной сети имеется более одного сетевого адреса, класс Dns возвращает информацию обо всех сетевых адресах, и приложение должно выбрать из массива подходящий адрес для обслуживания.
Создадим IPEndPoint для сервера, комбинируя первый IP-адрес хост-компьютера, полученный от метода Dns.Resolve(), с номером порта:
Здесь класс IPEndPoint представляет localhost на порте 11000. Далее новым экземпляром класса Socket создаем потоковый сокет. Установив локальную конечную точку для ожидания соединений, можно создать сокет:
Перечисление AddressFamily указывает схемы адресации, которые экземпляр класса Socket может использовать для разрешения адреса.
В параметре SocketType различаются сокеты TCP и UDP. В нем можно определить в том числе следующие значения:
Dgram
Поддерживает дейтаграммы. Значение Dgram требует указать Udp для типа протокола и InterNetwork в параметре семейства адресов.
Raw
Поддерживает доступ к базовому транспортному протоколу.
Stream
Поддерживает потоковые сокеты. Значение Stream требует указать Tcp для типа протокола.
Третий и последний параметр определяет тип протокола, требуемый для сокета. В параметре РrotocolType можно указать следующие наиболее важные значения - Tcp, Udp, Ip, Raw.
Следующим шагом должно быть назначение сокета с помощью метода Bind(). Когда сокет открывается конструктором, ему не назначается имя, а только резервируется дескриптор. Для назначения имени сокету сервера вызывается метод Bind(). Чтобы сокет клиента мог идентифицировать потоковый сокет TCP, серверная программа должна дать имя своему сокету:
Метод Bind() связывает сокет с локальной конечной точкой. Вызывать метод Bind() надо до любых попыток обращения к методам Listen() и Accept().
В параметре определяется задел (backlog), указывающий максимальное число соединений, ожидающих обработки в очереди. В приведенном коде значение параметра допускает накопление в очереди до десяти соединений.
В состоянии прослушивания надо быть готовым дать согласие на соединение с клиентом, для чего используется метод Accept(). С помощью этого метода получается соединение клиента и завершается установление связи имен клиента и сервера. Метод Accept() блокирует поток вызывающей программы до поступления соединения.
Метод Accept() извлекает из очереди ожидающих запросов первый запрос на соединение и создает для его обработки новый сокет. Хотя новый сокет создан, первоначальный сокет продолжает слушать и может использоваться с многопоточной обработкой для приема нескольких запросов на соединение от клиентов. Никакое серверное приложение не должно закрывать слушающий сокет. Он должен продолжать работать наряду с сокетами, созданными методом Accept для обработки входящих запросов клиентов.
Когда обмен данными между сервером и клиентом завершается, нужно закрыть соединение используя методы Shutdown() и Close():
SocketShutdown — это перечисление, содержащее три значения для остановки: Both - останавливает отправку и получение данных сокетом, Receive - останавливает получение данных сокетом и Send - останавливает отправку данных сокетом.
Сокет закрывается при вызове метода Close(), который также устанавливает в свойстве Connected сокета значение false.
Клиент на TCP
Функции, которые используются для создания приложения-клиента, более или менее напоминают серверное приложение. Как и для сервера, используются те же методы для определения конечной точки, создания экземпляра сокета, отправки и получения данных и закрытия сокета:
Вот полный код для SocketClient.cs и его объяснение:
Единственный новый метод - метод Connect(), используется для соединения с удаленным сервером. На рисунке ниже показаны клиент и сервер в действии:
Читайте также: