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