Чтение из сокета: гарантированно получено хотя бы x байтов? - PullRequest
5 голосов
/ 09 августа 2009

У меня редкая ошибка, которая возникает при чтении сокета.

Кажется, что при чтении данных иногда я получаю только 1-3 байта пакета данных, который больше этого.

Как я узнал из конвейерного программирования, там я всегда получаю как минимум 512 байт, если отправитель предоставляет достаточно данных.

Кроме того, мой отправитель, по крайней мере, передает> = 4 байта в любое время, когда он что-либо передает, поэтому я думал, что как минимум 4 байта будут получены одновременно в начале (!!) передачи.

В 99,9% случаев мое предположение, похоже, остается в силе ... но в действительно редких случаях получается менее 4 байтов. Мне кажется смешным, почему сетевая система должна это делать?

Кто-нибудь знает больше?

Вот код чтения, который я использую:

mySock, addr = masterSock.accept()
mySock.settimeout(10.0)
result = mySock.recv(BUFSIZE)
# 4 bytes are needed here ...
...
# read remainder of datagram
...

Отправитель отправляет полную дейтаграмму одним вызовом отправки.

Редактировать: все это работает на локальном хосте - поэтому никаких сложных сетевых приложений (маршрутизаторов и т. Д.) Не задействовано. Значение BUFSIZE не менее 512, а отправитель отправляет как минимум 4 байта.

Ответы [ 8 ]

13 голосов
/ 09 августа 2009

Я предполагаю, что вы используете TCP. TCP является потоковым протоколом без представления пакетов или границ сообщений.

Это означает, что при чтении вы можете получить меньше байтов, чем запрашиваете. Например, если ваши данные 128 КБ, вы можете получить только 24 КБ при первом чтении, что потребует повторного чтения, чтобы получить остальные данные.

Например, в C:

int read_data(int sock, int size, unsigned char *buf) {
   int bytes_read = 0, len = 0;
   while (bytes_read < size && 
         ((len = recv(sock, buf + bytes_read,size-bytes_read, 0)) > 0)) {
       bytes_read += len;
   }
   if (len == 0 || len < 0) doerror();
   return bytes_read;
}
9 голосов
/ 09 августа 2009

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

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

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

5 голосов
/ 10 августа 2009

Простой ответ на ваш вопрос «Чтение из сокета: гарантированно получено хотя бы x байтов?» - нет . Посмотрите строки документа для этих методов сокета:

>>> import socket
>>> s = socket.socket()
>>> print s.recv.__doc__
recv(buffersize[, flags]) -> data

Receive up to buffersize bytes from the socket.  For the optional flags
argument, see the Unix manual.  When no data is available, block until
at least one byte is available or until the remote end is closed.  When
the remote end is closed and all data is read, return the empty string.
>>> 
>>> print s.settimeout.__doc__
settimeout(timeout)

Set a timeout on socket operations.  'timeout' can be a float,
giving in seconds, or None.  Setting a timeout of None disables
the timeout feature and is equivalent to setblocking(1).
Setting a timeout of zero is the same as setblocking(0).
>>> 
>>> print s.setblocking.__doc__
setblocking(flag)

Set the socket to blocking (flag is true) or non-blocking (false).
setblocking(True) is equivalent to settimeout(None);
setblocking(False) is equivalent to settimeout(0.0).

Из этого ясно, что recv() не обязан возвращать столько байтов, сколько вы просили. Кроме того, поскольку вы звоните settimeout(10.0), возможно, что некоторые, но не все, данные получены вблизи времени истечения для recv(). В этом случае recv() вернет прочитанное - что будет меньше, чем вы просили (но согласованность <4 байта кажется маловероятной). </p>

Вы упоминаете datagram в своем вопросе, который подразумевает, что вы используете (без установления соединения) UDP-сокеты (не TCP). Различия описаны здесь . Размещенный код не показывает создание сокета, поэтому мы можем только догадываться здесь, однако эта деталь может быть важной. Может помочь, если вы опубликуете более полный пример кода.

Если проблема воспроизводима, вы можете отключить тайм-аут (который, кстати, вы, похоже, не обрабатываете) и посмотреть, решит ли это проблему.

3 голосов
/ 09 августа 2009

Так работает TCP. Вы не собираетесь получать все свои данные сразу. Слишком много проблем синхронизации между отправителем и получателем, включая операционную систему отправителя, сетевую карту, маршрутизаторы, коммутаторы, сами провода, сетевую карту получателя, операционную систему и т. Д. В аппаратном обеспечении и операционной системе имеются буферы.

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

Вот почему recv () не может дать вам все данные одновременно, они могут просто не быть доступными, даже если все работает правильно. Обычно вы вызываете recv () и ловите вывод. Это должно сказать вам, сколько байтов вы получили. Если это меньше, чем вы ожидаете, вам нужно продолжать вызывать recv () (как было предложено), пока вы не получите правильное количество байтов. Имейте в виду, что в большинстве случаев recv () возвращает -1 в случае ошибки, поэтому проверьте это и проверьте в своей документации значения ERRNO. EAGAIN в частности, кажется, вызывает проблемы у людей. Вы можете прочитать об этом в Интернете для получения подробной информации, но, если я вспомню, это означает, что на данный момент нет доступных данных, и вам следует попробовать еще раз.

Кроме того, из вашего поста звучит так, что вы уверены, что отправитель отправляет данные, которые вам нужны, но для полной проверки отметьте это: http://beej.us/guide/bgnet/output/html/multipage/advanced.html#sendall

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

1 голос
/ 28 сентября 2009

Если вы все еще заинтересованы, шаблоны, как это:

# 4 bytes are needed here ......
# read remainder of datagram...

может создать глупую оконную вещь.

Проверьте это из

1 голос
/ 09 августа 2009

Если отправитель отправляет 515 байтов, а ваш BUFSIZE равен 512, то первый recv вернет 512 байтов, а следующий вернет 3 байта ... Может ли это быть тем, что происходит?

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

1 голос
/ 09 августа 2009

Из справочной страницы Linux по recv http://linux.about.com/library/cmd/blcmdl2_recv.htm:

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

Итак, если ваш отправитель все еще передает байты, вызов выдаст только то, что было передано до сих пор.

0 голосов
/ 01 апреля 2019

Используйте метод recv_into(...) из модуля socket.

Роберт С. Барнс написал пример на C.

Но вы можете использовать Python 2.x со стандартными библиотеками python:

def readReliably(s,n):
    buf = bytearray(n)
    view = memoryview(buf)
    sz = s.recv_into(view,n)
    return sz,buf

while True:
  sk,skfrom = s.accept()
  sz,buf = io.readReliably(sk,4)
  a = struct.unpack("4B",buf)
  print repr(a)
  ...

Обратите внимание, что sz, возвращаемое функцией readReliably(), может быть больше, чем n.

...