Rambler's Top100 Service калинин.ru / программирование / сети /  << 12.12.00 >>

Событийная модель в WinSock

Автор

Автором этого текста является Gregory Liokumovich (liokumovich@mail.ru). Он любезно прислал мне этот текст, связанный с программированием сетей при помощи интерфейса WinSock, чему я лично очень рад: прежде всего потому, что сам имею очень мало опыта использования WinSock, а не упоминать о нем в разделе сетевого программирования совсем нехорошо.

Пользуясь случаем, хочу еще раз поблагодарить Григория за этот текст. Если я правильно понимаю, то он родился после предыдущей заметки, где рассказывалось о неблокирующем connect. Я обойдусь сейчас без комментариев к изложенной ниже точке зрения, но в ближайшее время хочу так же конструктивно рассказать о том, каким я себе представляю скелет сетевой программы.

Сразу оговорюсь, что поскольку я занимаюсь программированием под Windows, то и говорить буду про реализацию сокетов под эту платформу. WinSock позволяет работать с сокетами в рамках трех подходов, против двух классических. А именно, обычная блокирующая передача данных, неблокирующая передача данных, событийно ориентированная передача данных. Лично я предпочитаю третий, и ниже попытаюсь основные изложить причины.

Немного вводной информации

Основные функции:

  • WSACreateEvent() --- создать новое событие;
  • WSACloseEvent() --- удалить событие;
  • WSAEventSelect() --- связать событие с сокетом и его внутренним событием (пришли новые данные, готов к передаче и т.д.);
  • WSAWaitForMultipleEvents() --- ожидание событий.

Таким образом, алгоритм примерно такой: создаем сокет, создаем для него событие, связываем, далее ожидаем от него событий и реагируем на них. После того, как известно, что сокет готов к приему (передаче) данных, я предпочитаю использовать блокирующие операции чтения (отправки) данных. При этом, если например не все данные будут прочитаны из буфера, соответствующее событие будет автоматически активировано снова.

Что интересно тип WSAEVENT является просто переопределением типа HANDLE со всеми вытекающими отсюда возможностями.

Причина первая, эстетическая

Общеизвестный факт, что все ПО развивается в сторону унификации интерфейса, это позволяет значительно упростить и ускорить его использование для пользователя. Вполне логично, что унификация программного API приводит к более эффективному и грамотному его использованию программистами, которые выступают в роли потребителей API. Так вот, с этой точки зрения событийный подход с известной всем windows программистам функцией WaitForMultipleObject (пусть даже с каким-то префиксом) явно упрощает использование сокетов.

Кроме того, лично мне следующий фрагмент кода

res = WSAWaitForMultipleObject(5,  Events, FALSE, WSA_INFINITE, FALSE);
if (res == WSA_WAIT_FAILED)    
{
    // . . .
}
index = res;
WSAEnumNetworkEvents(sockets[index],  Events[index], &info)
if (info. lNetworkEvents & FD_READ)
{
    // принимаем новые данные
}

кажется намного более понятным и приятным чем этот

int res = select(0, &read, &write, &error, NULL)
if (res == SOCKET_ERROR)
{
    // . . .
}
for(int i = 0; i < socket.size(); i++) 
{
     if (FD_ISSET(socket[i], &read) )
        // принимаем данные
}

Как видно и вышенаписанного в событийном подходе мы исходим от события, мы знаем для какого сокета есть новости. Во втором же случае, мы не знаем, что произошло и вынуждены перебирать все варианты. В первом случае мы оперируем массивами и событиями, во втором же случае неочевидными структурами и макросами.

Причина вторая, элегантная

Используя select или WSAWaitForMultipleEvents мы в любом случае не можем удалять или добавлять соединения во время работы данных функций, так как это приведет к сбою. А это наверняка придется делать.

Если в программе, только один поток то проблемы нет, но не надо забывать что обе эти функции поддерживают не более 64 сокетов. А если надо больше? писать select за select'ом и ставить выход по таймауту --- это очень некрасиво, но (что намного более важно) мы тут же теряем скорости реакции или скатываемся до элементарного опроса готов/не готов. А если надо поддерживать 500 соединений? Время реакции будет ужасным. В связи с этим хочу также отметить, что, по-моему, WSAWaitForMultipleEvents работает быстрее, чем select в режиме опроса, но это субъективное мнение --- цифрами подтвердить не могу.

Так вот, единственный разумный выход из описанной ситуации который мне видится --- это многопоточность. Каждый поток работает со своими соединениями и все замечательно. Но тут возникает (как это всегда бывает с потоками) проблемы взаимной синхронизации.

Предположим поток 1 хочет добавить сокет в обработчик потока 2. В это время поток 2, выполняет select (или WSAWaitForMultipleEvents). Что прикажете делать? Что делать в случае select я, честно говоря, даже не знаю. При использовании событий я нашел, по-моему, неплохой и достаточно красивый выход. Создается одно пустое событие, которое не привязано к конкретному сокету. Это событие добавляется к остальным, которых дожидается WSAWaitForMultipleEvents (при этом число сокетов на поток уменьшается до 63). И вот в тот самый момент, когда мне надо вывести Поток два из "комотозного" состояния, я просто активирую это событие и все. То есть я использую события от сокета вместе и наравне с другими событиями. И отсюда вытекает еще одна более принципиальная причина.

Причина третья, принципиальная

Если задуматься на тему что такое поступление новых данных в сокет или, например, что такое закрытие сокета с другой стороны. Можно придти к простому выводу --- это события. Такие же события, как наступления времени Х или любое другое событие в программе. Более того, абсолютнозаконна постановка вопроса: "ждем пока произойдет какое-нибудь событие или пока придут данные от сокета". Поэтому это правильно и логично, что и работа с сокетами должна строится на событиях.

Резюме

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


Версия для печати


  Рядом в разделе:
"Тонкий" клиент (19.12.00)
   В предыдущей заметке Gregory Liokumovich рассказывал о применении событийной модели WinSock для программирования сетевых приложений. На самом деле, как мне кажется,...   >>>>
Неблокирующий connect() (01.12.00)
   В продолжение темы о замене блокирующего вызова , хочется рассказать о другой функции интерфейса сокетов, . Она имеет следующий прототип: int...   >>>>
  Рядом по дате:
Быстрый и мертвый / The Quick and the Dead, 1995 (13.12.00)
   На этот раз я впервые рассказываю о фильме, который я посмотрел "по телевизору": он был на канале "Боевик" от НТВ+. Собственно,...   >>>>
Заработок в интернете (11.12.00)
   Все-таки, Россия --- оригинальная страна. И интернет в ней --- тоже. Я уже несколько раз "между делом" писал о том, что...   >>>>
  Содержание:
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
   C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Просто так
Студенческое
Туризм
  Байки
Фотографии
Комментарии
   Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Благодарности
Форум
Хронология
 
  В этом разделе:
События ядра в FreeBSD. (16.07.01)
   Обработка большого количества сетевых соединений всегда затруднительна. Мало того, не существует стандартных решений, подходящих для проблем любого вида, в которых возникает...   >>>>
Обзор CORBA (14.01.01)
   CORBA (расшифровывается как Common Object Request Broker) это технология, которая позволяет рассматривать компоненты распределенной системы как объекты, отвечающие некоторым определенным интерфейсам....   >>>>
"Тонкий" клиент (19.12.00)
   В предыдущей заметке Gregory Liokumovich рассказывал о применении событийной модели WinSock для программирования сетевых приложений. На самом деле, как мне кажется,...   >>>>
Событийная модель в WinSock (12.12.00)
   Автором этого текста является Gregory Liokumovich ( ). Он любезно прислал мне этот текст, связанный с программированием сетей при помощи интерфейса...   >>>>
Неблокирующий connect() (01.12.00)
   В продолжение темы о замене блокирующего вызова , хочется рассказать о другой функции интерфейса сокетов, . Она имеет следующий прототип: int...   >>>>
Определение ip-адреса по имени хоста, adns (05.11.00)
   Есть такой, характерный для организации "традиционного" UNIX'а, системный вызов под названием : struct hostent * gethostbyname(const char *name); Традиционен он тем,...   >>>>
lingering close (29.10.00)
   Когда программа выкачивает один файл с удаленного сервера с использованием протокола TCP, а после этого сразу же "отваливается", то проблем, скорее...   >>>>
Содержание раздела полностью...
   Примерно в тоже время
Быстрый и мертвый / The Quick and the Dead, 1995 (13.12.00)
   На этот раз я впервые рассказываю о фильме, который я посмотрел "по телевизору": он был на канале "Боевик" от НТВ+. Собственно,...   >>>>
Заработок в интернете (11.12.00)
   Все-таки, Россия --- оригинальная страна. И интернет в ней --- тоже. Я уже несколько раз "между делом" писал о том, что...   >>>>
Хронология полностью...
   Содержание
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
  C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Туризм
  Байки
Фотографии
Комментарии
  Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Студенческое
Просто так
Благодарности
Форум
Хронология
© 2000-2008, Andrey L. Kalinin
mailto:andrey@kalinin.ru
Rambler's Top100