Проблема с recv () на TCP-соединении - PullRequest
2 голосов
/ 06 марта 2011

Я имитирую связь TCP на окнах в C. У меня есть отправитель и получатель, связывающийся.

Отправитель отправляет пакеты определенного размера получателю.Получатель получает их и отправляет ACK для каждого полученного пакета обратно отправителю.Если отправитель не получил конкретный пакет (они пронумерованы в заголовке внутри пакета), он снова отправляет пакет получателю.Вот функция getPacket на стороне получателя:

//get the next packet from the socket. set the packetSize to -1
//if it's the first packet.
//return: total bytes read
// return: 0 if socket has shutdown on sender side, -1 error, else number of bytes received
int getPakcet(char* chunkBuff, int packetSize, SOCKET AcceptSocket)
{
    int totalChunkLen = 0;
    int bytesRecv = -1;
    bool firstTime = false;

    if(packetSize == -1)
    {
        packetSize = MAX_PACKET_LENGTH;
        firstTime = true;
    }

    int needToGet = packetSize;

    do
    {
        char* recvBuff;
        recvBuff = (char*)calloc(needToGet, sizeof(char));

        if(recvBuff == NULL)
        {
            fprintf(stderr, "Memory allocation problem\n");
            return -1;
        }

        bytesRecv = recv(AcceptSocket, recvBuff, needToGet, 0);

        if(bytesRecv == SOCKET_ERROR)
        {
            fprintf(stderr, "recv() error %ld.\n", WSAGetLastError());
            totalChunkLen = -1;
            return -1;
        }

        if(bytesRecv == 0)
        {
            fprintf(stderr, "recv(): socket has shutdown on sender side");
            return 0;
        }
        else if(bytesRecv > 0)
        {
            memcpy(chunkBuff + totalChunkLen, recvBuff, bytesRecv);
            totalChunkLen += bytesRecv;
        }

        needToGet -= bytesRecv;
    }
    while((totalChunkLen < packetSize) && (!firstTime));

    return totalChunkLen;
}

Я использую firstTime, потому что в первый раз получатель не знает нормальный размер пакета, который отправитель отправит ему, поэтомуЯ использую MAX_PACKET_LENGTH, чтобы получить пакет, а затем установить нормальный размер пакета равным количеству полученных байтов.

Моя проблема - последний пакет.Это размер меньше, чем размер упаковки.Допустим, последний размер пакета равен 2, а нормальный размер пакета равен 4. Итак, recv() получает два байта, переходит к условию while, затем totalChunkLen < packetSize, потому что 2<4, поэтому он повторяет цикл снова и застревает в recv() потому что он блокирует, потому что отправителю нечего отправить.

Со стороны отправителя я не могу закрыть соединение, потому что я не получил ACK назад, так что это своего рода тупик.Получатель застрял, потому что он ожидает больше пакетов, но отправителю нечего отправить.

Я не хочу использовать тайм-аут для recv() или вставить специальный символ в заголовок пакета, чтобы отметить, что онпоследний.

Что я могу сделать?

Ответы [ 4 ]

2 голосов
/ 06 марта 2011

Вы используете TCP для связи между вашим приемником и передатчиком, а TCP является потоковым протоколом.То есть вы помещаете поток байтов на одном конце, а вы выводите поток на другом конце, в порядке и без потерь.Нет гарантии, что каждый send () будет соответствовать recv () на другом конце, так как данные могут быть разбиты по разным причинам.

Так что если вы делаете следующее с соединением TCP:

char buffer[] = "1234567890";
send(socket, buffer, 10, 0);

И затем на приемнике:

char buffer[10];
int bytes = recv(socket, buffer, 10, 0);

байтов может быть где угодно между 0 и 10 при возврате recv ().

TCP работает по IP, который ориентирован на дейтаграммупротокол.Вот почему реализация TCP может предположить, что когда она отправляет дейтаграмму, она получит всю дейтаграмму на другом конце (или, возможно, нет, или получит ее не в порядке).Если вы хотите смоделировать, что у вас есть как минимум две опции:

  1. Добавьте кадрирование в ваши сообщения TCP, чтобы вы могли извлекать из него пакеты.Это включает добавление таких вещей, как размер пакета, в заголовок, который вы отправляете в поток.Было бы бессмысленно использовать это для симуляции TCP, поскольку все ваши пакеты всегда будут приходить, всегда в порядке и уже использовать базовые механизмы управления потоком TCP / предотвращения перегрузки.
  2. Используйте протокол дейтаграмм, такой как UDP.Это было бы ближе к уровню IP, на котором работает TCP.

Возможно, вам следует использовать вариант 2, но если вы хотите перейти по кадру по TCP, вы можете, например, (грубый быстрый код следует):

// We do this to communicate with machines having different byte ordering
u_long packet_size = htonl(10); // 10 bytes packet
send(socket, &packet_size, 4, 0); // First send the frame size
send(socket, buffer, 10, 0); // Then the frame

Получающий конец:

u_long packet_size; // Hold the size of received packet
int bytes_to_read = 4; // We send 4 bytes on the wire for size and expect 4
int nresult; // hold result of recv()
char *psize = &packet_size; // Point to first byte of size
while( bytes_to_read ) // Keep reading until we have all the bytes for the size
{
  nresult = recv(socket, psize, bytes_to_read, 0);
  if(nresult==0) deal with connection closed.
  bytes_to_read -= nresult;
  psize += nresult;
}
packet_size = ntohl(packet_size);
// Now we know the packet size we can proceed and read it similar to above
2 голосов
/ 06 марта 2011

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

Если это выглядит сложным (и это действительно не всегда легко), вам нужно искать библиотеку, которая инкапсулирует эту работу для вас, например, позволяющую отправлять и получать объект, который будет сериализован, очерчен и десериализован по коду библиотеки. Но работа должна быть выполнена, и транспортный слой не сделает это за вас.

Одно небольшое замечание о показанном коде: вы создали утечку памяти с помощью нескольких распределенных буферов приема ...

1 голос
/ 06 марта 2011

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

1 голос
/ 06 марта 2011

Вы можете указать объем данных в каждом пакете в начале (например, первые 2 байта могут указывать размер пакета) или дополнить последний пакет, чтобы он соответствовал размеру других.

Редактировать: Если вы действительно хотите «смоделировать» TCP, то вам, вероятно, следует использовать recvfrom () и sendto (), и тогда вы получите данные в целых пакетах разных размеров, и у вас не будет этой проблемы .

...