Случай, когда блокировка recv () возвращает меньше запрошенных байтов - PullRequest
7 голосов
/ 19 февраля 2010

Справочная страница библиотечной функции recv() упоминает, что:

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

Если мы используем блокирующий вызов recv() и запрашиваем для 100 байтов:

recv(sockDesc, buffer, size, 0); /* Where size is 100. */

и сервер отправляет только 50 байтов, тогда этот recv() блокируется до тех пор, пока не будет доступно 100 байтов, или он вернет получение 50 байтов.

Сценарий может быть таким:*

  • сбой сервера после отправки только 50 байтов

  • неверный дизайн протокола, когда сервер отправляет только 50 байтов, в то время как клиент ожидает 100, а сервер также ожидаетответ клиента (то есть соединение с сокетом не было инициировано сервером, на котором будет возвращено recv)

Я заинтересован в платформе Linux / Solaris.У меня нет среды разработки, чтобы проверить это сам.

Ответы [ 3 ]

16 голосов
/ 19 февраля 2010

recv вернется, когда во внутренних буферах будут данные для возврата. Он не будет ждать, пока не будет 100 байт, если вы запросите 100 байт.

Если вы отправляете 100-байтовые «сообщения», помните, что TCP не предоставляет сообщения, это просто поток. Если вы имеете дело с сообщениями приложения, вам нужно обработать это на уровне приложения, так как TCP этого не сделает.

Существует множество условий, при которых вызов send () длиной 100 байт может не считываться полностью на другом конце, если при вызове recv (..., 100) используется только один вызов recv; Вот только несколько примеров:

  • Отправляющий стек TCP решил связать воедино 15 вызовов записи, и MTU оказался равным 1460, что - в зависимости от синхронизации поступивших данных может привести к тому, что первые 14 вызовов клиентов получат 100 байтов и 15. вызов для извлечения 60 байтов - последние 40 байтов появятся при следующем вызове recv (). (Но если вы вызываете recv с буфером 100, вы можете получить последние 40 байтов предыдущего сообщения «application» и первые 60 байтов следующего сообщения)

  • Буферы отправителя заполнены, возможно, устройство чтения медленное или сеть перегружена. В какой-то момент данные могут пройти, и при очистке буферов последний кусок данных не был кратным 100.

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

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

Во всяком случае. Если вы хотите прочитать 100 байтов из сокета, используйте что-то вроде

int
readn(int f, void *av, int n)
{
    char *a;
    int m, t;

    a = av;
    t = 0;
    while(t < n){
        m = read(f, a+t, n-t);
        if(m <= 0){
            if(t == 0)
                return m;
            break;
        }
        t += m;
    }
    return t;
}

...

if(readn(mysocket,buffer,BUFFER_SZ) != BUFFER_SZ) {
  //something really bad is going on.

}
7 голосов
/ 19 февраля 2010

Поведение определяется двумя вещами. Рекомендованная минимальная отметка уровня воды и прохождение вами флага MSG_WAITALL. Если вы установите этот флаг, вызов будет заблокирован, пока не будет получено запрошенное количество байтов, даже если сервер выйдет из строя. В противном случае он возвращается, как только по крайней мере SO_RCVLOWAT байтов доступно в буфере приема сокета.

SO_RCVLOWAT

Устанавливает минимальное количество байтов в процесс для операций ввода сокетов. Значением по умолчанию для SO_RCVLOWAT является 1. Если для SO_RCVLOWAT установлено большее значение, обычная блокировка входящих вызовов ждать, пока они не получили меньше значения низкого уровня воды или запрашиваемая сумма. (Они могут вернуться ниже отметки уровня воды, если ошибка, сигнал пойман или тип данных следующий в получении очередь отличается от возвращенной, например внеполосные данные). Этот вариант принимает значение int. Обратите внимание, что не все реализации позволяют эту опцию быть установленным.

2 голосов
/ 19 февраля 2010

Если вы читаете цитату точно, наиболее распространенный сценарий:

  • сокет получает данные.Эти 100 байтов займут некоторое время.
  • сделан вызов recv ().
    • Если в буфере больше 0 байтов, recv () возвращает то, что доступно и не ожидает.
    • Пока доступно 0 байтов, он блокирует и гранулярность системы потоковопределяет, как долго это будет.
...