Буферизация сокетов Python - PullRequest
19 голосов
/ 05 мая 2009

Допустим, я хочу прочитать строку из сокета, используя стандартный модуль 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 ]

26 голосов
/ 05 мая 2009

Если вы беспокоитесь о производительности и полностью контролируете розетку (вы не передаете его в библиотеку, например), затем попробуйте реализовать ваша собственная буферизация в Python - Python string.find и 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

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

19 голосов
/ 05 мая 2009

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

Он заблокирует ожидание сокета данных. На самом деле он просто разрешит системному вызову recv().

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

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

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

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

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

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

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

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

Предложить консультацию Источник Python Socket Module и C Источник, который делает системные вызовы .

6 голосов
/ 05 июля 2012
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 = []
...