Python 套接字缓冲

24 投票
3 回答
70701 浏览
提问于 2025-04-15 11:24

假设我想从一个套接字中读取一行数据,使用标准的 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 个回答

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

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

30

如果你很在意性能,并且完全控制这个套接字(比如说,你没有把它传给某个库),那么可以尝试在Python中自己实现缓冲。Python的字符串查找和分割功能其实可以非常快。

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)相比,效果如何。

21

recv()这个函数是直接通过调用C语言的库函数来处理的。

它会阻塞,意思是它会等待直到套接字有数据可用。实际上,它只是让recv()这个系统调用处于阻塞状态。

file.readline()是一个高效的缓冲实现。它不是线程安全的,因为它假设自己是唯一在读取这个文件的程序。(比如它会缓存即将到来的输入。)

如果你在使用文件对象,每次调用read()并传入一个正数时,底层的代码只会recv()请求的数据量,除非这些数据已经被缓存了。

数据会被缓存的情况有:

  • 你调用了readline(),它会读取整个缓冲区

  • 行的结束位置在缓冲区的结束位置之前

这样就会在缓冲区里留下数据。否则,缓冲区通常不会被填满。

这个问题的目的不太明确。如果你需要在读取之前检查数据是否可用,可以使用select(),或者将套接字设置为非阻塞模式,使用s.setblocking(False)。这样,如果没有等待的数据,读取操作会返回空,而不是阻塞。

你是在用多个线程读取一个文件或套接字吗?我建议让一个线程专门负责读取套接字,并把接收到的内容放入一个队列,供其他线程处理。

建议查看Python Socket模块的源代码进行系统调用的C语言源代码

撰写回答