Механизм обработки сетевых событий в Winsock2
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 на необходимую константу и, соотвественно изменить константу проверки бита ошибки.