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 个回答
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 = []
当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。
如果你很在意性能,并且完全控制这个套接字(比如说,你没有把它传给某个库),那么可以尝试在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)相比,效果如何。
recv()
这个函数是直接通过调用C语言的库函数来处理的。
它会阻塞,意思是它会等待直到套接字有数据可用。实际上,它只是让recv()
这个系统调用处于阻塞状态。
file.readline()
是一个高效的缓冲实现。它不是线程安全的,因为它假设自己是唯一在读取这个文件的程序。(比如它会缓存即将到来的输入。)
如果你在使用文件对象,每次调用read()
并传入一个正数时,底层的代码只会recv()
请求的数据量,除非这些数据已经被缓存了。
数据会被缓存的情况有:
你调用了readline(),它会读取整个缓冲区
行的结束位置在缓冲区的结束位置之前
这样就会在缓冲区里留下数据。否则,缓冲区通常不会被填满。
这个问题的目的不太明确。如果你需要在读取之前检查数据是否可用,可以使用select()
,或者将套接字设置为非阻塞模式,使用s.setblocking(False)
。这样,如果没有等待的数据,读取操作会返回空,而不是阻塞。
你是在用多个线程读取一个文件或套接字吗?我建议让一个线程专门负责读取套接字,并把接收到的内容放入一个队列,供其他线程处理。