web-dev-qa-db-fra.com

Python tampon de sockets

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()
23

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 .

20
Joe Koberg

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).

29
Aaron Watters
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 = []
8
alex