Программирование на C++ глазами хакера

Алгоритм приема/передачи данных


В разд. 4.10.2 мы уже рассмотрели пример, в котором сервер асинхронно ожидает соединения с помощью функции select, и как только происходило подключение, создавался новый поток, который обменивался данными с клиентом. Самое узкое место в этом примере — второй поток, который работал только с одним клиентом.

Я уже говорил о том, что с помощью асинхронной работы сетевых функций можно легко реализовать возможность работы сразу с несколькими клиентами. Да и отдельный поток для обмена сообщениями в данном случае является излишним. В листинге 6.1 приведен пример, в котором сервер ожидает соединения и работает с клиентом в одной функции, но может обслуживать сразу несколько клиентов:

Листинг 6.1. Алгоритм асинхронной работы с клиентом
DWORD WINAPI NetThread(LPVOID lpParam) { SOCKET sServerListen; SOCKET ClientSockets[50]; int TotalSocket=0;

struct sockaddr_in localaddr, clientaddr; int iSize;

sServerListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (sServerListen == SOCKET_ERROR) { MessageBox(0, "Can't load WinSock", "Error", 0); return 0; }

ULONG ulBlock; ulBlock = 1; if (ioctlsocket(sServerListen, FIONBIO, ulBlock) == SOCKET_ERROR) { return 0; }

localaddr.sin_addr.s_addr = htonl(INADDR_ANY); localaddr.sin_family = AF_INET; localaddr.sin_port = htons(5050);

if (bind(sServerListen, (struct sockaddr *)localaddr, sizeof(localaddr)) == SOCKET_ERROR) { MessageBox(0, "Can't bind", "Error", 0); return 1; }

MessageBox(0, "Bind OK", "Error", 0);



listen(sServerListen, 4);

MessageBox(0, "Listen OK", "Error", 0);

FD_SET ReadSet; int ReadySock;

while (1) { FD_ZERO(ReadSet); FD_SET(sServerListen, ReadSet);

for (int i=0; iTotalSocket; i++) if (ClientSockets[i] != INVALID_SOCKET) FD_SET(ClientSockets[i], ReadSet);

if ((ReadySock = select(0, ReadSet, NULL, NULL, NULL)) == SOCKET_ERROR) { MessageBox(0, "Select filed", "Error", 0); }

//We have new connection (Есть новые подключения) if (FD_ISSET(sServerListen, ReadSet)) { iSize = sizeof(clientaddr); ClientSockets[TotalSocket] = accept(sServerListen, (struct sockaddr *)clientaddr,iSize); if (ClientSockets[TotalSocket] == INVALID_SOCKET) { MessageBox(0, "Accept filed", "Error", 0); break; } TotalSocket++; } //We have data from client (Есть данные от клиента) for (int i=0; iTotalSocket; i++) { if (ClientSockets[i] == INVALID_SOCKET) continue; if (FD_ISSET(ClientSockets[i], ReadSet)) { char szRecvBuff[1024], szSendBuff[1024];


int ret = recv(ClientSockets[i], szRecvBuff, 1024, 0); if (ret == 0) { closesocket(ClientSockets[i]); ClientSockets[i]=INVALID_SOCKET; break; } else if (ret == SOCKET_ERROR) { MessageBox(0, "Recive data filed", "Error", 0); break; } szRecvBuff[ret] = '\0';

strcpy(szSendBuff, "Command get OK");

ret = send(ClientSockets[i], szSendBuff, sizeof(szSendBuff), 0); if (ret == SOCKET_ERROR) { break; } } }

} closesocket(sServerListen); return 0; }

Рассмотрим, как работает этот пример. Секрет заключается в том, что объявлено две переменные:

sServerListen — переменная типа socket, которая будет использоваться для прослушивания порта и ожидания соединения со стороны клиента;

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

После этого создается сокет sServerListen для прослушивания сервера, переводится в асинхронный режим, связывается функцией bind с локальным адресом и запускается прослушивание. В этом участке кода никаких изменений не произошло.

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

Дальше — еще интереснее. Первым делом проверяется серверный сокет. Если он готов к чтению, то присоединился клиент. Соединение принимается с помощью функции accept, а результат (сокет для работы с клиентом) сохраняется в последнем (доступном) элементе массива ClientSockets. После этого функция select будет ожидать событий и от этого клиента.

На следующем этапе проверяются все сокеты из массива на готовность чтения данных с их стороны. Если какой-нибудь клиент готов, то читаются данные и отправляется ответ. Если при чтении данные не получены, и функция recv вернула нулевое значение, то клиент отключился от сервера.



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

Не забудьте только заменить массив клиентских сокетов на динамический. Если вы не хотите использовать динамические массивы, то можно поступить проще — перед каждым заполнением структуры FD_SET упорядочивать в ней элементы, чтобы убрать сокеты, равные INVALID_SOCKET. После этого необходимо установить переменную TotalSocket так, чтобы она указывала на следующий после последнего реально существующего элемента массива.

Примечание
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter6\AdvancedTCPServer.

Содержание раздела