Автор: Joseph Dempsey
Источник: http://test.sockets.ru/index.pl?view=1&page=1
Механизм обработки сетевых событий в Winsock2
Обычно, при программировании сокетов под Winsock 1.1, используются
стандартные и, надеюсь известные большинству программистов операторы. При
этом оповещение о событии на сокете проходит через сообщения windows.
Думаю, не секрет, что такой способ порождает массу проблем при разработке
приложений. Однако можно воспользоваться другим методом - в обход
сообщений Windows, а именно через события WSA (WSA Events).
Как это работает?
При работе с сокетом большинство программистов используют стандартный
набор событий: для отправки данных, приёма данных, соединения с другим
сокетом, для установления канала передачи данных при входящем запросе и
для закрытия сокета. Возможно есть и другие события для сокетов, но в этой
статье мы сосредоточимся на основных. Когда одно из этих событий,
ассоциированных с сокетом, происходит, то мы получаем сигнал и производим
необходимые действия для обработки данного события.
Вот основные константы, описывающие сетевые события (те, которые будут
фигурировать в данной статье):
FD_ACCEPT FD_READ FD_WRITE FD_CLOSE
FD_CONNECT
Итак, давайте рассмотрим весь процесс создания, отслеживания и
обработки сетевых событий.
Во-первых нам прийдётся инициализировать библиотеку winsock2. Впринципе
существует нескольколько способов сделать это, например так:
WSADATA wsd;
LPFN_WSASTARTUP lpf = (LPFN_WSA_STARTUP)::GetProcAddress( ::LoadLibrary("WS2_32.DLL"),
"WSAStartup");
lpf(0x0202, &wsd);
Инициализировать можно в любом месте программы, но обязательно до
вызова каких-либо функций winsock. Следующий важный момент - это создание
события, которое мы хотим отслеживать на данном сокете. Для этого будем
использовать вызов Winsock2 API ::WSACreateEvent();
После того, как событие создано, его нужно связать с сокетом,
события которого мы хотим контролировать и обрабатывать. Это делается
функцией WSAEventSelect(...). Следующий пример
показывает, как отследить событие, сигнализирующее о том, что на сокет
пришёл запрос на установление канала связи. Обычно такую операцию можно
проделывать на прослушивающем (listening) сокете. В примере это
SOCKET m_listen . Итак, как это выглядит::
WSAEVENT hEvent = WSA_INVALID_EVENT;
hEvent = WSACreateEvent();
::WSAEventSelect(m_listen, m_hEvent, FD_ACCEPT);
Если мы хотим, чтобы сокет (в данном случае это SOCKET
m_socket ) информировал нас о том, что готов принять или передать
данные, то событие создаётся следующим образом:
WSAEVENT hDataEvent = WSA_INVALID_EVENT;
hDataEvent = WSACreateEvent();
::WSAEventSelect(m_socket, hDataEvent, FD_WRITE | FD_READ | FD_CLOSE);
Необходимо заметить, что для одного и того же сокета невозможно создать
два объекта событий, то есть следующий код неверен:
WSAEVENT hEvent1 = WSA_INVALID_EVENT;
WSAEVENT hEvent2 = WSA_INVALID_EVENT;
hEvent1 = WSACreateEvent();
hEvent2 = WSACreateEvent();
::WSAEventSelect(m_socket, hEvent1, FD_READ);
::WSAEventSelect(m_socket, hEvent2, FD_WRITE);
Обработка уведомлений о событиях
Теперь, когда события заданы, нам необходимо ожидать их и,
соответственно, обрабатывать. Для ожидания событий можно использовать
функцию WSAWaitForMultipleEvents(...). Эта функция будет
работать как поток в спящем режиме до тех пор, пока не произойдёт событие,
на которое мы хотели бы отреагировать. Давайте посмотрим на пример вызова
этой функции:
// m_listen и m_data два существующих сокета:
WSAEVENT hEvent1 = WSACreateEvent();
WSAEVENT hEvent2 = WSACreateEvent();
::WSAEventSelect(m_listen, hEvent1, FD_ACCEPT);
::WSAEventSelect(m_data, hEvent2, FD_READ | FD_CLOSE);
WSAEVENT* pEvents = (WSAEVENT*)::calloc(2, WSAEVENT);
pEvents[0] = hEvent1;
pEvents[1] = hEvent2;
int nReturnCode = ::WSAWaitForMultipleEvents(2, pEvents, FALSE, INFINITE, FALSE);
Если же мы хотим ожидать только одного события, то эту функцию можно
вызвать следующим образом:
int nReturnCode = ::WSAWaitForMultipleEvents(1, &hEvent1, FALSE, INFINITE, FALSE);
Первый параметр - это количество событий, которые мы хотим ожидать.
Второй параметр - это указатель на массив событий, которые мы хотим
ожидать. Третий параметр имеет значение BOOL, которое определяет - будет
ли функция оставаться в спящем режиме до тех пор пока не сработают все
события. Обычно этот параметр задаётся как false, но возможно Вам может
понадобиться ожидать наступления всех событий. Четвёртый параметр
определяет - как долго ожидать наступления события. Обычно я запускаю
отдельный поток и оставляю его как infinite. Но, если Вы будете запускать
функцию в основном потоке, то может понадобиться поставить ограничение в 5
(или больше) секунд, чтобы дать возможность приложению обрабатывать другие
события. Пятый параметр указывает на то, хотим мы или нет получать
алерты.
Теперь необходимо позаботиться об обработчиках каждого события.
Перво-наперво нам необходимо получить достоверную информацию о том, какое
событие возникло. Для этого существует функция
::WSAEnumNetworkEvents(...). Один из параметров которой - это
структура под названием WSANETWORKEVENTS . Принимая во
внимание код, приведённый выше, получим следующее:
WSANETWORKEVENTS hConnectEvent;
WSANETWORKEVENTS hProcessEvent;
::WSAEnumNetworkEvents(m_listen, hConnectEvent, &wsaConnectEvents);
::WSAEnumNetworkEvents(m_data, hProcessEvent, &wsaProcessEvents);
В заключении мы получаем событие, которое возникает на одном из
сокетов. Давайте посмотрим, как выглядит процесс выборки нужного события и
его обработки:
if( (wsaConnectEvents.lNetworkEvents & FD_ACCEPT) && ?
(wsaConnectEvents.iErrorCode[FD_ACCEPT_BIT] == 0) )
{ //м
}
Эта проверка может быть сделана для каждого WSAEVENT, который Вы
установили, и для каждого сетевого события, для которого WSAEVENT будет
сигнализировать. Для обработки другого события, в последнем примере
достаточно изменить FD_ACCEPT на необходимую константу и, соотвественно
изменить константу проверки бита ошибки.
|