При работе с сокетом большинство программистов используют стандартный набор событий: для отправки данных, приёма данных, соединения с другим сокетом, для установления канала передачи данных при входящем запросе и для закрытия сокета. Возможно есть и другие события для сокетов, но в этой статье мы сосредоточимся на основных. Когда одно из этих событий, ассоциированных с сокетом, происходит, то мы получаем сигнал и производим необходимые действия для обработки данного события.
Вот основные константы, описывающие сетевые события (те, которые будут фигурировать в данной статье): 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 на необходимую константу и, соотвественно изменить константу проверки бита ошибки. |