Считывание данных из сокета c таймаутом
|
Те, кто работал с tcp-ip сокетами, знают про всякие подводные камни, связаные с этим. Приведу несколько из них.
Первый: данные в tcp-ip стеке могут появляться не все сразу, а кусками. Если клиент послал нам данные с помощью одной функции send(), это совсем не значит, что они могут быть приняты одной функцией recv(). Второй: если с отправителем данных что либо случилось, и он не смог данные полностью, то возможны зависоны на функции recv() (при использовании блокирующих сокетов).Аналогично для фукнции send().
Вашему вниманию представлены функции SendNBytes() и RecvNBytes(), которые избавлены от данных недостатков: используется функция select() для установки величины таймаута на однократный прием данных, и используется тот факт, что функция recv() в случае удачного приема возвращает количество байт, которые были реально приняты.
Вот, собственно, функции: #define _SOCKET_TIMEOUT 5000000 //величина таймаута в микросекундах
int SendNBytes(int nSocket, char *pBuffer, int iMessageLength) { int iRC = 0; int iSendStatus = 0; timeval SendTimeout;
//установка величины таймаута SendTimeout.tv_sec = 0; SendTimeout.tv_usec = _SOCKET_TIMEOUT;
fd_set fds; FD_ZERO(&fds); FD_SET(nSocket, &fds);
//..до тех пор, пока нам нужно посылать данные... while(iMessageLength > 0) { iRC = select(0, NULL, &fds, NULL, &SendTimeout);
//истек таймаут, возврат ошибки if(!iRC) return -1;
//произошла ошибка if(iRC < 0) return WSAGetLastError();
//отправить несколько байт iSendStatus = send(nSocket, pBuffer, iMessageLength, 0); //произошла ошибка в момент отправки данных if(iSendStatus < 0) return WSAGetLastError(); else { //обновить буфер и счетчик iMessageLength -= iSendStatus; pBuffer += iSendStatus; } }
return 0; }
|
//функция приема данных int ReceiveNBytes(int nSocket, char *pBuffer, int iStillToReceive) { int iRC = 0; int iReceiveStatus = 0; timeval ReceiveTimeout;
//установка величины таймаута fd_set fds; FD_ZERO(&fds); FD_SET(nSocket, &fds);
ReceiveTimeout.tv_sec = 0; ReceiveTimeout.tv_usec = _SOCKET_TIMEOUT; // 500 ms
//..пока данные не посланы.. while(iStillToReceive > 0) { iRC = select(0, &fds, NULL, NULL, &ReceiveTimeout); //выход по таймауту if(!iRC) return -1;
//произошла какая то ошибка if(iRC < 0) return WSAGetLastError();
//прием нескольких байт iReceiveStatus = recv(nSocket, pBuffer, iStillToReceive, 0);
//произошла ошибка в момент функции recv() if(iReceiveStatus < 0) { return WSAGetLastError(); } else { //изменили величину счетчика и буфер iStillToReceive -= iReceiveStatus; pBuffer += iReceiveStatus; } }
return 0; }
| как пользоваться:
Хорошим тоном считается посылка в начале основного сообщения информацию о длине сообщения. Нужно договориться (разработчикам клиента и сервера), что, например, первые 2 или 4 байта в начале сообщения будут длиной сообщения. Поэтому, полное сообщение из сокета можно получить примерно так: TCHAR *szMessage; TCHAR szLength[4]; int nLength;
if(RecvNBytes(nSocket, szLength, 2) != 0) //приняли первые 2 байта - длину { return FALSE; }
nLength = atoi(szLength); //преобразовали 2 байта в длину szMessage = new TCHAR[szLength];
if(RecvNBytes(nSocket, szMessage, nLength) !=0) //приняли полное сообщение { return FALSE; }
| |