Disons que je veux lire une ligne à partir d'un socket, en utilisant le module standard socket
:
def read_line(s):
ret = ''
while True:
c = s.recv(1)
if c == '\n' or c == '':
break
else:
ret += c
return ret
Que se passe-t-il exactement dans s.recv(1)
? Émettra-t-il un appel système à chaque fois? Je suppose que je devrais ajouter un tampon, de toute façon:
Pour une meilleure correspondance avec les réalités matérielles et réseau, la valeur de bufsize doit être une puissance relativement faible de 2, par exemple 4096.
http://docs.python.org/library/socket.html#socket.socket.recv
Mais il ne semble pas facile d'écrire un tampon efficace et thread-safe. Et si j'utilise file.readline()
?
# does this work well, is it efficiently buffered?
s.makefile().readline()
L'appel recv()
est géré directement en appelant la fonction de bibliothèque C.
Il bloquera l'attente des données du socket. En réalité, il laissera simplement le bloc d'appel système recv()
.
file.readline()
est une implémentation efficace tamponnée. Ce n'est pas threadsafe, car il suppose qu'il est le seul à lire le fichier. (Par exemple, en mettant en mémoire tampon les entrées à venir.)
Si vous utilisez l'objet fichier, chaque fois que read()
est appelée avec un argument positif, le code sous-jacent ne recv()
que la quantité de données demandée, sauf si elle est déjà mise en mémoire tampon.
Il serait mis en mémoire tampon si:
vous aviez appelé readline (), qui lit un tampon complet
la fin de la ligne était avant la fin de la mémoire tampon
Laissant ainsi les données dans le tampon. Sinon, le tampon n'est généralement pas trop rempli.
Le but de la question n'est pas clair. si vous avez besoin de voir si des données sont disponibles avant la lecture, vous pouvez select()
ou régler le socket en mode non bloquant avec s.setblocking(False)
. Ensuite, les lectures retourneront vides, plutôt que bloquantes, s'il n'y a pas de données en attente.
Lisez-vous un fichier ou un socket avec plusieurs threads? Je mettrais un seul travailleur à lire le socket et à alimenter les éléments reçus dans une file d'attente pour les gérer par d'autres threads.
Suggérez de consulter source du module Socket Python et source C qui effectue les appels système .
Si vous êtes préoccupé par les performances et contrôlez complètement le socket (vous ne le passez pas dans une bibliothèque par exemple), essayez d'implémenter votre propre mise en mémoire tampon dans Python - Python string.find et string.split et cela peut être incroyablement rapide.
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
Si vous vous attendez à ce que la charge utile soit constituée de lignes qui ne sont pas trop volumineuses, cela devrait fonctionner assez rapidement et éviter de sauter inutilement trop de couches d'appels de fonction. Je serais intéressant de savoir comment cela se compare à file.readline () ou d'utiliser 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 = []