Считывание данных из сокета c таймаутом
Те, кто работал с tcp-ip сокетами, знают про всякие подводные камни, связаные с этим.
Приведу несколько из них.
Первый: данные в tcp-ip стеке могут появляться не все сразу, а кусками. Если клиент послал
нам данные с помощью одной функции send(), это совсем не значит, что они могут быть приняты одной
функцией recv().
Второй: если с отправителем данных что либо случилось, и он не смог данные полностью, то возможны зависоны на функции recv() (при использовании блокирующих сокетов).Аналогично для фукнции send().
Вашему вниманию представлены функции SendNBytes() и RecvNBytes(), которые избавлены от данных недостатков: используется функция select() для установки величины таймаута на однократный прием данных, и используется тот факт, что функция recv() в случае удачного приема возвращает количество байт, которые были реально приняты.
Вот, собственно, функции:
как пользоваться:
Хорошим тоном считается посылка в начале основного сообщения информацию о длине сообщения.
Нужно договориться (разработчикам клиента и сервера), что, например, первые 2 или 4 байта в начале
сообщения будут длиной сообщения. Поэтому, полное сообщение из сокета можно получить примерно так:
Приведу несколько из них.
Первый: данные в 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; } |
