【问题标题】:Python sockets bufferingPython套接字缓冲
【发布时间】:2010-10-23 17:56:53
【问题描述】:

假设我想使用标准的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()

【问题讨论】:

  • "每次都会发出系统调用吗?"为什么这很重要?
  • 因为系统调用很慢。最好先获取一大块数据(如果可用),然后对其进行处理。现在我知道 Python 并不是特别快,也许这并不重要。但是文档说最好还是按大块阅读。
  • 请注意,使用+= 构建字符串是一个禁忌,因为它可能是二次的,而使用追加使用str.join 构建列表始终是线性的。
  • @MikeGraham 吹毛求疵,我知道,但是增加一个列表将是平均线性的,除了当后备数组填满并且新数组被分配和复制时。所以是的,线性时间,除了偶尔的打嗝
  • @Miquel,我描述的操作将是线性的。附加项目通常是恒定的(并且平均而言是恒定的)。偶尔追加一个项目是线性的,但这些以这样一种方式展开,以至于添加 n 个项目是线性的。

标签: python sockets buffering


【解决方案1】:

如果您关心性能并完全控制套接字 (例如,您没有将其传递到库中)然后尝试实现 你自己在 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) 相比如何。

【讨论】:

  • buffer.split("\n", 1) 如果由于元组中的缓冲区部分导致缓冲区很大,则速度不是很快,最好使用for line in buffer.split("\n"): yield line + "\n"
【解决方案2】:

recv() 调用直接通过调用 C 库函数来处理。

它将阻塞等待套接字有数据。实际上它只会让recv() 系统调用阻塞。

file.readline() 是一种高效的缓冲实现。它不是线程安全的,因为它假定它是唯一读取文件的人。 (例如通过缓冲即将到来的输入。)

如果您使用的是文件对象,则每次使用正参数调用 read() 时,底层代码将 recv() 仅请求的数据量,除非它已经被缓冲。

如果满足以下条件,它将被缓冲:

  • 您调用了 readline(),它读取了一个完整的缓冲区

  • 行尾在缓冲区末尾之前

因此将数据留在缓冲区中。否则缓冲区一般不会溢出。

问题的目标不明确。如果您需要在读取之前查看数据是否可用,您可以select() 或使用s.setblocking(False) 将套接字设置为非阻塞模式。然后,如果没有等待数据,读取将返回空,而不是阻塞。

您是否正在读取一个文件或具有多个线程的套接字?我会让一个工作人员读取套接字并将接收到的项目送入队列以供其他线程处理。

建议咨询Python Socket Module sourceC Source that makes the system calls

【讨论】:

  • 我真的不知道我为什么要问线程安全,我现在的项目中不需要它。事实上,我想用 Python 重写一个 Java 程序。在 Java 中很容易获得缓冲读取,我想知道 Python 的 socket 模块是否提供相同的缓冲(实际上,我想知道为什么有人不想要缓冲而直接调用系统调用)。
  • reallines() 不是实时的。所以它对于像 SMTP 这样的交互式 TCP 服务没用,不过 readline 似乎可以工作。
【解决方案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 = []

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-25
    • 1970-01-01
    • 2012-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多