В Python сокеты буферизации

предположим, я хочу прочитать строку из сокета, используя стандартный socket модуль:

def read_line(s):
    ret = ''

    while True:
        c = s.recv(1)

        if c == 'n' or c == '':
            break
        else:
            ret += c

    return ret

что именно происходит в s.recv(1)? Будет ли он выдавать системный вызов каждый раз? Я думаю, я должен добавить некоторую буферизацию, в любом случае:

для лучшего соответствия с аппаратными и сетевыми реалиями значение bufsize должна быть относительно небольшая мощность 2, например, 4096.

http://docs.python.org/library/socket.html#socket.socket.recv

но не кажется легким написать эффективную и потокобезопасную буферизацию. Что делать, если я использую file.readline()?

# does this work well, is it efficiently buffered?
s.makefile().readline()

3 ответов


на recv() звонок осуществляется непосредственно путем вызова функции библиотеки C.

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

file.readline() является эффективной буферизованной реализацией. Это не threadsafe, потому что он предполагает, что это единственный, кто читает файл. (Например, путем буферизации предстоящих входных данных.)

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

он будет буферизован, если:

  • вы вызвали readline (), который считывает полный буфер

  • конец строки был до конца буфера

таким образом, оставляя данные в буфере. В противном случае буфер обычно не переполнен.

цель вопрос нет четкий. если вам нужно увидеть, доступны ли данные перед чтением, вы можете select() или установите сокет в режим nonblocking с s.setblocking(False). Затем reads вернет пустой, а не блокирует, если нет ожидающих данных.

Вы читаете один файл или сокет с несколькими потоками? Я бы поставил одного работника на чтение сокета и подачу полученных элементов в очередь для обработки другими потоками.

предлагаем консультации источник модуля сокета Python и источник C, который делает системные вызовы.


Если вы обеспокоены производительностью и полностью контролируете сокет (вы не передаете его в библиотеку, например) затем попробуйте реализовать ваша собственная буферизация в Python -- Python string.найти и привязать.split и такие могут быть удивительно быстро.

def linesplit(socket):
    buffer = socket.recv(4096)
    buffering = True
    while buffering:
        if "\n" in buffer:
            (line, buffer) = buffer.split("\n", 1)
            yield line + "\n"
        else:
            more = socket.recv(4096)
            if not more:
                buffering = False
            else:
                buffer += more
    if buffer:
        yield buffer

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


def buffered_readlines(pull_next_chunk, buf_size=4096):
  """
  pull_next_chunk is callable that should accept one positional argument max_len,
  i.e. socket.recv or file().read and returns string of up to max_len long or
  empty one when nothing left to read.

  >>> for line in buffered_readlines(socket.recv, 16384):
  ...   print line
    ...
  >>> # the following code won't read whole file into memory
  ... # before splitting it into lines like .readlines method
  ... # of file does. Also it won't block until FIFO-file is closed
  ...
  >>> for line in buffered_readlines(open('huge_file').read):
  ...   # process it on per-line basis
        ...
  >>>
  """
  chunks = []
  while True:
    chunk = pull_next_chunk(buf_size)
    if not chunk:
      if chunks:
        yield ''.join(chunks)
      break
    if not '\n' in chunk:
      chunks.append(chunk)
      continue
    chunk = chunk.split('\n')
    if chunks:
      yield ''.join(chunks + [chunk[0]])
    else:
      yield chunk[0]
    for line in chunk[1:-1]:
      yield line
    if chunk[-1]:
      chunks = [chunk[-1]]
    else:
      chunks = []