Подтвердить что ты не робот

Буферизация сокетов 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()
4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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 = []