Buffering dei socket Python
Domanda
Diciamo che voglio leggere una riga da un socket, usando il modulo socket
standard:
def read_line(s):
ret = ''
while True:
c = s.recv(1)
if c == '\n' or c == '':
break
else:
ret += c
return ret
Cosa succede esattamente in s.recv (1)
? Emetterà una chiamata di sistema ogni volta? Immagino che dovrei aggiungere un po 'di buffering, comunque:
Per una migliore corrispondenza con le realtà hardware e di rete, il valore di bufsize dovrebbe essere una potenza relativamente piccola di 2, ad esempio 4096.
http://docs.python.org/library/socket. html # socket.socket.recv
Ma non sembra facile scrivere buffering efficiente e thread-safe. Cosa succede se utilizzo file.readline ()
?
# does this work well, is it efficiently buffered?
s.makefile().readline()
Soluzione
La chiamata recv ()
viene gestita direttamente chiamando la funzione di libreria C.
Bloccherà in attesa che il socket contenga dati. In realtà, consentirà solo il blocco delle chiamate di sistema recv ()
.
file.readline ()
è un'implementazione bufferizzata efficiente. Non è thread-safe, perché presume che sia l'unico a leggere il file. (Ad esempio bufferando l'immissione imminente.)
Se si utilizza l'oggetto file, ogni volta che read ()
viene chiamato con un argomento positivo, il codice sottostante recv ()
solo la quantità di dati richiesti , a meno che non sia già bufferizzato.
Sarebbe bufferizzato se:
-
che avevi chiamato readline (), che legge un buffer completo
-
la fine della riga era prima della fine del buffer
In questo modo lasciando i dati nel buffer. In caso contrario, il buffer non viene generalmente riempito eccessivamente.
L'obiettivo della domanda non è chiaro. se hai bisogno di vedere se i dati sono disponibili prima di leggere, puoi selezionare ()
o impostare il socket in modalità non bloccante con s.setblocking (False)
. Quindi, le letture torneranno vuote, anziché bloccate, se non ci sono dati di attesa.
Stai leggendo un file o socket con più thread? Metterei un solo lavoratore a leggere il socket e ad alimentare gli articoli ricevuti in una coda per gestirli con altri thread.
Suggerisci consulenza Python Socket Fonte del modulo e Fonte C che effettua le chiamate di sistema .
Altri suggerimenti
Se ti preoccupi delle prestazioni e controlli completamente la presa (ad esempio non lo passi in una libreria) quindi prova a implementarlo il tuo buffering in Python - Python string.find e string.split e simili essere incredibilmente veloce.
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
Se si prevede che il payload sia costituito da righe che non sono troppo grandi, che dovrebbero funzionare abbastanza velocemente, ed evitare di saltare attraverso troppi livelli di funzione chiama inutilmente. Sarei interessante nel saperlo come questo si confronta con file.readline () o usando socket.recv (1).
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 = []