Форум программистов, компьютерный форум, киберфорум
Наши страницы
LostCoast
Войти
Регистрация
Восстановить пароль
Рейтинг: 4.50. Голосов: 6.

Обучающая статья по использованию асинхронных сокетов.

Запись от LostCoast размещена 27.11.2012 в 12:52
Обновил(-а) LostCoast 18.12.2012 в 21:01
Метки c-sharp

Предисловие.

Хочу сразу сказать, что я никому не навязываю свою идею программирования клиент-серверных приложений. Моя статья рассчитана на людей, которые хотят повысить свой уровень программирования, и кто только знакомится с клиент-серверными приложениями. Здесь вы сможете найти интересующие вас отдельные моменты, такие как: как установить соединение с сервером, как можно организовать авторизацию\регистрацию пользователей, как передавать сообщения между клиентами и многое другое. Статья будет дополнятся по мере её написания. Пожелания, критику по коду и самому изложению материала оставляйте в комментариях. Содержание статьи может изменится по ходу её написания, на данный момент будет следующий вид:

Содержание:
  1. Базовые функции
    1. Введение
    2. Передача данных
    3. Регистрация
    4. Авторизация
    5. Оптимизация
  2. Возможности для клиентов
    1. Изменение архитектуры
    2. Передача сообщений между клиентами
    3. Статус клиентов
    4. Список пользователей
    5. Передача файлов


Введение.

На сегодняшний день существует огромное количество программ, в основе которых заложена клиент-серверная архитектура. Под этим понятием понимается следующее: как ни странно, это означает, что ваше приложение будет состоять из двух программ: клиентской части, которая предоставляется пользователям, и серверной, которая обрабатывает поступающие запросы от пользователей. В своей статье я хочу показать основы по созданию таких приложений. Для изучения, понимая данной статьи, рекомендуется почитать теоритическую часть по сокетам, для лучшего понимая кодов и идей. Я не буду сильно углубляться в теорию, которую и так можно найти на просторах интернета.
Статья будет посвящена разработке приложения, позволяющие пользователям обмениваться сообщениями и файлами друг с другом, также будет реализована возможность их регистрации и авторизации.
В качестве языка использован С#, а в качестве IDE – Visual Studio 2010( далее vs).

Часть 1. Создание проекта.

Для начала создадим 2 консольных проекта в vs и назовем их ConsoleServer и ConsoleClient. Начнем мы с создания сервера. Для этого переместимся в проект ConsoleServer и добавим в него файл-класс Server. Прежде чем начать писать код, в общих чертах объясню, как происходит создание сервера.
Часть 2. Написание сервера. Основы.

Все начинается с создания класса Socket, в котором указывается семейство адресов, тип протокола и сам протокол. В нашем проекте будем использовать протокол TCP, так как он обеспечивает надежную передачу данных, без их потерь. Далее, необходимо прикрепить его к определенному IP-адресу и порту. После этого, остается запустить сокет в режим прослушивания и можно принимать входящие подключения.
От теории к практике. Добавим ссылки на библиотеки:

C#
1
2
3
using System.Net;
using System.Net.Sockets;
using System.Threadnig;
Добавим в наш класс три переменных:

C#
1
2
3
private IPEndPoint ip;
private Socket socket;
private int max_conn;
Теперь создадим конструктор класс, который будет принимать 3 параметра: первый - IP-адрес; второй - номер порта; третий - максимальное количество одновременных подключений.

C#
1
2
3
4
5
6
public Server(string ip, int port, int max_conn)
        {
            this.ip = new IPEndPoint(IPAddress.Parse(ip), port);
            this.socket = new Socket(this.ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 
            this.max_conn = max_conn;
        }
Обратите внимание на использование ключевого слова this.Оно предоставляет доступ к переменным класса. Так как у нас получается две переменных с именем ip, если мы будем использовать IP без префикса this, то доступ будет к переменной метода, типа string. Как только будет добавлен префикс this, то доступ будет к переменной класса, типа IPEndPoint.
Как только сокет создан, нужно прикрепить его к порту и адресу и затем поставить в режим прослушивания. Для этого добавим метод Init, который будет это делать.

C#
1
2
3
4
5
public void Init()
        {
            this.socket.Bind(this.ip);
            this.socket.Listen(this.max_conn);
        }
Сокет запущен в режиме прослушивания, а это значит, что он готов принимать поступающие подключения. Но чтобы установить соединение необходимо обработать его. Добавим еще один метод в наш класс, он будет принимать соединения и отправлять их на обработку.
Прежде чем показать следующую часть кода, я немного расскажу, что такое синхронные и асинхронные сокеты. В своем клиент-серверном приложении я буду использовать асинхронную модель сокетов. В чем же отличие асинхронного метода от синхронного метода? Синхронная модель сокетов подразумевает под собой следующее: для обработки одновременных подключений вам придется каждый раз выделять новые потоки для каждого подключившегося клиента. Ввиду этого, количество потоков может быть больше 1000, что уже плохо сказывается на производительности, тем более рано или поздно вы упретесь в предел по потокам. Синхронная модель сокетов хорошо подходит для приложений с малым количеством клиентов. Синхронные сокеты легче в понимании и изучении. Асинхронная модель сокетов устраняет недостатки синхронных. Используя асинхронные сокеты не надо выделять новые потоки для каждого нового клиента, они создаются сами посредством вызова операций (например, прием данных) и завершаются, как только будет выполнен сигнал о завершении операции.

Более подробно можно почитать здесь http://msdn.microsoft.com/ru-ru/library/dd335942.aspx#mainsection .

Перейдем к написанию кода. Добавим новый метод в наш класс и назовем его StartListening и в методе Init добавим вызов этого метода. Внутри StartListening создадим бесконечный цикл while(true), в котором будет вызываться метод для принятия подключения. Называется такой метод BeginAccept. Он принимает в качестве аргументов имя метода для обработки завершения операции и сам сокет сервера.

C#
1
2
3
4
5
6
7
8
private void StartListening()
        {
            Console.WriteLine("Server starting...");
            while (true)
            {
                 this.socket.BeginAccept(new AsyncCallback(AcceptCallBack), this.socket);
            }
        }
Теперь надо создать метод для обработки подключившегося клиента. Объявим еще один метод.

C#
1
2
3
4
5
6
private void AcceptCallBack(IAsyncResult ar)
        {
            Socket socket = (Socket)ar.AsyncState;
            Socket accept_socket = socket.EndAccept(ar);
            Console.WriteLine("A new connection. IP:port = " + accept_socket.RemoteEndPoint.ToString());
        }
Рассмотрим повнимательнее метод AcceptCallBack. Этот метод завершает операцию приема подключения. Это не значит, что мы разорвем соединение и т.п. Это значит, что мы сможем получить результаты операции и завершить созданный поток. Метод принимает в качестве аргумента переменную ar. Эта переменная является результатом операции. В нашем случае переменная ar легко преобразовывается к сокету. Но к какому? Копнем немного глубже.
Переменную ar можно преобразовать к любому типу. Условие, к какому типу преобразовывать, зависит от того, какую переменную мы передадим в асинхронную операцию, обычно последний аргумент метода и является той самой переменной ar. На заметк: все асинхронные операции начинаются с имени Begin… , а операции завершения асинхронных операций, со слова End….
Мы получили экземпляр сокета нашего сервера через переменную ar. Далее нам необходимо завершить асинхронную операцию и получить результат. Для этого вызываем метод EndAccept(ar). Этот метод возвращает сокет подключившегося к нам клиента. Все, что остается, это сохранить этот сокет на сервере, для дальнейшей работы с ним.
Но не торопитесь сразу запускать сервер, так как программа ваша зависнет. Это произойдет из-за того, что у вас запущен бесконечный цикл на прием соединения. Давайте вспомним. Когда мы вызываем асинхронную операцию, то мы создаем новый поток для её выполнения. То есть, метод AcceptCallBack выполняется в отдельном потоке, а это значит что метод BeginAccept закончил свою работу и отдает контроль. Отдав контроль, цикл продолжает делать итерации. В синхронных сокетах такого нет. Вызвав какую-либо операцию, она не отдаст контроль, пока не выполнится. Но так как мы используем асинхронные сокеты, нам надо как-то выкрутиться из этой ситуации. Добавим новую переменную в класс типа ManualResetEvent и назовем ее acceptEvent.

C#
1
private ManualResetEvent acceptEvent = new ManualResetEvent(false);
Переменные такого типа позволяют блокировать дальнейшие действия в методах. Для продолжения работы метода, вызываются методы Set, которые сигнализируют о продолжении действий. Называются такие переменные – событиями.
Нам потребуется добавить некоторые изменения. Сначала, добавим строку над методом BeginAccept – acceptEvent.Reset();. Этот метод сбрасывает событие по умолчанию. Добавим после метода BeginAccept еще один вызов метода события:
acceptEvent.WaitOne();
Вызов такого метода говорит, о том что цикл не сможет повторно вызвать метод BeginAccept, пока переменная acceptEvent не получит сигнал. Сам сигнал мы поместим в метод AcceptCallBack, а именно после завершения асинхронной операции, то есть после строк Socket accept_socket = socket.EndAccept(ar); добавим следующую строчку:
C#
1
acceptEvent.Set();
Чтобы посмотреть как работает наш сервер, достаточно в методе Main инициализировать новый экземпляр класса сервер и вызвать метод Init.
Теперь все нормально и можно переходить к написанию клиента для нашего сервера.
Часть 3. Написание клиента. Основы.

В прошлой части мы написали сервер, который умеет принимать подключения. Нам пока что этого достаточно. Теперь перейдем к созданию клиента, который будет подключаться к нашему серверу.
Перейдем к проекту ConsoleClient. Создадим в нем класс Client.
На начальном этапе создания клиента, все происходит точно так же как и при написании сервера. Первое что нам нужно – это создать сокет и указать ip адрес и порт. Также надо добавить 2 переменные класса: первая типа сокет, вторая переменная, хранит в себе IP-адрес и порт.

C#
1
2
3
4
5
6
7
private Socket client_socket;
private IPEndPoint ip;
public Client(string ip, int port)
{
     this.ip = new IPEndPoint(IPAddress.Parse(ip), port);
     this.client_socket = new Socket(this.ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
}
Теперь все готово для подключения к серверу. Добавим в класс 2 метода: Connect и Disconnect. Как не трудно догадаться один из них будет осуществлять подключение, другой отключение. Начнем с подключения.
Чтобы подключиться к серверу, нужно вызвать метод BeginConnect. Он принимает 3 аргумента: 1 – это наша переменная ip, 2 – метод завершения операции, 3 – сокет, либо любой другой объект. Метод будет выглядеть следующим образом:

C#
1
2
3
4
public void Connect()
        {
            this.client_socket.BeginConnect(this.ip, new AsyncCallback(ConnectCallBack), this.client_socket);
        }
И сразу опишем метод для завершения операции подключения.

C#
1
2
3
4
5
private void ConnectCallBack(IAsyncResult ar)
        {
            Socket handler = (Socket)ar.AsyncState;
            this.client_socket.EndConnect(ar);
        }
Осталось описать метод отключения от сервера. Он будет выглядеть идентично методу Connect, только будет производить отключение от сервера. Ниже представлен его код:

C#
1
2
3
4
5
6
7
8
9
10
public void Disconnect()
        {
                this.client_socket.BeginDisconnect(false, new AsyncCallback(DisconnectCallBack), this.client_socket);
        }
private void DisconnectCallBack(IAsyncResult ar)
        {
            Socket handler = ar.AsyncState as Socket;
            handler.EndDisconnect(ar);
            Console.WriteLine("Connection closed");
        }
Все готово для подключения и отключения клиента к серверу. В методе Main инициализируем новый экземпляр класса Client и вызовем метод Connect.

C#
1
2
Client client = new Client(«192.168.1.1», 5555);
client.Connect();
Надеюсь, читатель умеет определять IP-адрес своего компьютера и с этим не возникнет проблем. Чтобы проверить , что все работает, надо сперва запустить сервер, а потом клиент. Как только подключится клиент, вы увидите на стороне сервера сообщение. Есть еще один нонсенс: при установке соединения на двух компьютерах, они должны быть в одной сети, иначе не получится установить подключение, так как компьютеры не видят друг друга. Но если вы используете приложение в рамках своего одного компьютера, можно написать метод который будет доставать IP-адреса, существующие на компьютере и добавить его вызов вместо подстановки IP-адреса вручную. Сделаем это на сервере и клиенте при их объявлении, т.е. там, где мы вызываем функцию Main надо добавить еще один метод:

C#
1
2
3
4
5
6
7
8
public static string GetIp()
        {
            string ip_addr = String.Empty;
            string host_name = Dns.GetHostName();
            IPHostEntry host_ip = Dns.GetHostByName(host_name);
            ip_addr = host_ip.AddressList[0].ToString();
            return ip_addr;
        }
Метод GetIp получает список всех IP-адресов компьютера, выбирает первый IP-адрес из списка и возвращает его. Теперь класс клиента и сервера можно вызывать следующим образом:

C#
1
2
Client client = new Client(GetIp(), 5555);
Server server = new Server(GetIp(), 5555, 2);
Размещено в Без категории
Просмотров 6423 Комментарии 3
Всего комментариев 3
Комментарии
  1. Старый комментарий
    Аватар для LonerZzz
    Спасибо. Единственный сайт,где нашёл нормальную статью с полным разжёвыванием работы асинхронных сокетов
    Запись от LonerZzz размещена 15.05.2015 в 10:46 LonerZzz вне форума
  2. Старый комментарий
    Аватар для Nowsoud
    Очень интересная статья. Почему нет второй части? Очень интересно о передаче сообщений между клиентами.
    Спасибо.
    Запись от Nowsoud размещена 04.07.2015 в 18:21 Nowsoud вне форума
  3. Старый комментарий
    Nowsoud, не хватило сил, чтобы дописать.
    Если все еще интересно и актуально кому-нибудь, то могу поискать исходники и куда-нибудь их выложить.
    Запись от LostCoast размещена 24.08.2016 в 09:59 LostCoast вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru