Как получить более 65000 байт в сокете C ++ с помощью recv () - PullRequest
3 голосов
/ 29 марта 2012

Я занимаюсь разработкой клиент-серверного приложения (TCP) в Linux с использованием C ++. Я хочу отправить более 65,000 байт одновременно. В TCP максимальный размер пакета составляет 65,535 байт.

Как я могу отправить все байты без потерь?

Ниже приведен мой код на стороне сервера.

//Receive the message from client socket
if((iByteCount = recv(GetSocketId(), buffer, MAXRECV, MSG_WAITALL)) > 0) 
{
     printf("\n Received bytes %d\n", iByteCount);

     SetReceivedMessage(buffer);
     return LS_RESULT_OK;
}

Если я использую MSG_WAITALL, получение байтов занимает много времени, так как я могу установить флаг для получения более 1 миллиона байтов за раз.

Редактировать: размер MTU составляет 1500 байт, но абсолютное ограничение на размер пакета TCP составляет 65 535.

Ответы [ 4 ]

8 голосов
/ 29 марта 2012

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

Вы действительно хотите позвонить recv в цикле до тех пор, пока вы не узнаете, что ожидаемый объем данных был получен, или пока не получите результат «нулевое чтение байтов», что означает, что другой конец закрыл соединение.Всегда, без исключений.

Если вам нужно выполнять другие действия одновременно (вероятно, с серверным процессом!), То вам, вероятно, нужно сначала проверить готовность дескриптора с помощью poll или epoll.Это позволяет вам мультиплексировать сокеты по мере их готовности.

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

Даже если вы отправляете, скажем, всего 5000 байт, для TCP вполне допустимо разбивать это значение на 5 (или10 или 20) пакетов, и для recv необходимо возвращать 500 (или 100, или 20, или 1) байтов за раз, каждый раз, когда вы вызываете его. Вот как это работает.
TCP гарантирует, что все, что вы отправите, в конечном итоге попадет на другой конец или выдаст ошибку.И это гарантирует, что все, что вы отправляете, приходит в порядок.Это не гарантирует многое другое.Прежде всего, это не гарантирует, что какой-либо конкретный объем данных будет готов в любой момент времени.
Вы должны быть к этому готовы, и единственный способ сделать это - повторно вызывать recv.В противном случае вы всегда потеряете данные при некоторых обстоятельствах.

MSG_WAITALL должен в принципе заставить работать так, как вы ожидаете, но это плохое поведение, и это не гарантируется.Если сокет (или другая структура в сетевом стеке) работает с мягким или жестким ограничением, он может не и, вероятно, не выполнит ваш запрос.Некоторые ограничения тоже неясны.Например, число для SO_RCVBUF должно быть в два раза больше , чем то, что вы ожидаете получить в Linux, из-за подробностей реализации.

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

Тот факт, что вы видите только неудачи выше некоторого "огромного" предела, - это просто удача (или, скорее,, невезение).Тот факт, что он, по-видимому, «работает» до этого предела, говорит о том, что вы делаете правильно, но это не так.Это несчастливое совпадение, что это работает.

РЕДАКТИРОВАТЬ:
Как указано в комментарии ниже, вот как это может выглядеть ( Код явно не проверен, будьте осторожны, emptor.)

std::vector<char> result;
int size;

char recv_buf[250];

for(;;)
{
    if((size = recv(fd, recv_buf, sizeof(recv_buf), 0)) > 0)
    {
        for(unsigned int i = 0; i < size; ++i)
            result.push_back(recv_buf[i]);
    }
    else if(size == 0)
    {
        if(result.size() < expected_size)
        {
            printf("premature close, expected %u, only got %u\n", expected_size, result.size());
        }
        else
        {
            do_something_with(result);
        }
        break;
    }
    else
    {
        perror("recv");
        exit(1);
    }
}

Это будет получать любой объем данных, который вы хотите (или до тех пор, пока operator new не сгенерирует bad_alloc после выделения вектора размером в несколько сотен мегабайт, но это другая история ...).

Если вы хотите обрабатывать несколько соединений, вам нужно добавить poll или epoll или kqueue или аналогичные функции (или ... fork), я оставлю этов качестве упражнения для читателя.

3 голосов
/ 29 марта 2012

Возможно, ваша проблема связана с размерами буфера сокета ядра. Попробуйте добавить следующее в ваш код:

int buffsize = 1024*1024;
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffsize, sizeof(buffsize));

Возможно, вам также понадобится увеличить некоторые переменные sysctl:

sysctl -w net.core.rmem_max=8388608
sysctl -w net.core.wmem_max=8388608

Обратите внимание, однако, что полагаться на TCP для заполнения всего буфера, как правило, плохая идея. Вы должны вызывать recv () несколько раз. Единственная веская причина, по которой вы хотите получить более 64 КБ, - это повышение производительности. Однако в Linux уже должна быть автонастройка, которая будет постепенно увеличивать размеры буфера по мере необходимости.

3 голосов
/ 29 марта 2012

в пакете TCP шестого максимума - 65 635, байты

Нет, это не так. TCP является протоколом потока байтов по сегментам по IP-пакетам, и протокол имеет неограниченные размеры передачи по любому одному соединению. Посмотрите на все эти 100 МБ загрузки: как вы думаете, как они работают?

Просто отправьте и получите данные. Вы получите это.

0 голосов
/ 16 февраля 2016

Я бы предложил изучить kqueue или что-то подобное. С уведомлением о событии нет необходимости зацикливаться на recv. Просто вызовите простую функцию чтения при событии EV_READ и используйте один вызов функции recv в сокете, который вызвал событие. Ваша функция может иметь размер буфера 10 байт или столько, сколько вам нужно, это не имеет значения, потому что, если вы не прочитали все сообщение в первый раз, вы просто получите другое событие EV_READ в сокете и отзовете свою функцию чтения , Когда данные прочитаны, вы получите событие EOF. Не нужно суетиться с петлями, которые могут блокировать или не блокировать другие входящие соединения.

...