Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.223.171.12] |
|
Сообщ.
#1
,
|
|
|
Те, кто работал с 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; } |
Сообщ.
#2
,
|
|
|
А можно и WSASelectEvent, это вроде даж лучше будет, но вот это для винды, о других ОС сказать не могу.
|
Сообщ.
#3
,
|
|
|
Цитата if(RecvNBytes(nSocket, szLength, 2) != 0) //приняли первые 2 байта - длину { return FALSE; } Почему 2 байта, а не 4!? |
Сообщ.
#4
,
|
|
|
Сколька хочешь, дарагой, хоть 20! . В примере макс. длина мессаги - 100 символов.
На самом деле там лучше использовать не atoi(szLength), а strtol с базисом 16, или ещё лучше - посылать (без преобразования в строку) байты числа nLength, здесь это НЕ сделано для наглядности, к тому же - со строкой понятно как работать на разных платформах, а вот, предположим, сборка из 4 -х байтов int на Apple и Intel проходит по разному . |
Сообщ.
#5
,
|
|
|
И что на разныйх платформах у данного кода будут разные результаты?
LPTSTR lpBuffer=NULL; lpBuffer+4=lpData; CopyMemory( lpBuffer, reinterpret_cast<char*>(&dwBytesToWrite), sizeof(DWORD)); Клиент отправляет данные... Сервер получил данные... DWORD dwMsgSize=0; CopyMemory( reinterpret_cast<char*>(&dwMsgSize), lpBuffer, sizeof(DWORD)); А если у тебя отправляется толко 100 символов, тогда можно было и 1 байт Но для реального отправления мне кажется ве-таки лучше использовать первые 4 байта, я думаю ты сам понимаешь почему... |
Сообщ.
#6
,
|
|
|
Дело в этом:
TCHAR szLength[4]; // в szLength - мусор int nLength; // в nLength - тоже if(RecvNBytes(nSocket, szLength, 2) != 0) //приняли первые 2 байта - длину // но в других 2-х байтах все равно мусор! { return FALSE; } nLength = atoi(szLength); //преобразовали 2 байта в длину // не 2, а непонятно сколько. первые два байта по делу, в остальных - неизвестно что, // а ведь еще это безобразие должно еще и нулем кончаться! |
Сообщ.
#7
,
|
|
|
Такой я ещё не пробовал, .. но вроде бы тоже не должно работать.
Дело в том, что младший и старший байты на разных платформах перепутаны. Ты отправил клиенту первым символом - младший байт, а он решил - что это старший, .... ну и т.д. |
Сообщ.
#8
,
|
|
|
2Uncle_Bob
Цитата Дело в этом: Эти пременные у него не изображены внутри какой-либо функции. Мож они глобальные? Тогда всё чики-пики. |
Сообщ.
#9
,
|
|
|
Цитата я думаю ты сам понимаешь почему... это всего лишь иллюстрация, как пользоваться приведёнными функциями, кто как хочет, так и использует. |
Сообщ.
#10
,
|
|
|
M Peter, раздел ФАКа предназначен только для статей и вопросов связанных с ними. Твой вопрос я отделил в тему: Куча ошибок в файле sys/socket |
Сообщ.
#11
,
|
|
|
Цитата ViGOur @ 28.05.04, 10:37 И что на разныйх платформах у данного кода будут разные результаты? LPTSTR lpBuffer=NULL; lpBuffer+4=lpData; CopyMemory( lpBuffer, reinterpret_cast<char*>(&dwBytesToWrite), sizeof(DWORD)); Клиент отправляет данные... Сервер получил данные... DWORD dwMsgSize=0; CopyMemory( reinterpret_cast<char*>(&dwMsgSize), lpBuffer, sizeof(DWORD)); А если у тебя отправляется толко 100 символов, тогда можно было и 1 байт Но для реального отправления мне кажется ве-таки лучше использовать первые 4 байта, я думаю ты сам понимаешь почему... #include <sys/types.h> #include <netinet/in.h> /* if i remember right (on linux)*/ /* on send part of system */ ... put_length_into_packet(u_int16_t length) length = htons(length);/ * convert host order to network order */ memcpy(buffer, &length, sizeof(length)); /* put 2 bytes into start of the packet */ ... /* prepare rest of rthe buffer */ send(fd, buffer, sizeof(buffer), 0); /* on receive part of system */ ... get_packet(...) u_int16_t length; recv(fd, &length, sizeof(length), MSG_PEEK); length = ntohs(length); recv(fd, buffer, length, MSG_WAITALL); For simplisity sake i omit check of recv and send return values |