Как я могу надежно прочитать ровно n байтов из сокета TCP? - PullRequest
0 голосов
/ 24 апреля 2019

Контекст:

Обычно двоичный протокол определяет кадров заданного размера. Модуль struct хорошо разбирает это, при условии, что все было получено в одном буфере.

Проблема:

TCP-сокеты являются потоками. Чтение из сокета не может дать больше байтов, чем запрошено, но может вернуть меньше. Так что этот код не является надежным:

def readnbytes(sock, n):
    return sock.recv(n)   # can return less than n bytes

Наивный обходной путь:

def readnbytes(sock, n):
    buff = b''
    while n > 0:
        b = sock.recv(n)
        buff += b
        if len(b) == 0:
            raise EOFError          # peer socket has received a SH_WR shutdown
        n -= len(b)
    return buff

может быть неэффективным, потому что если мы запрашиваем большое количество байтов, а данные, если они очень фрагментированы, мы будем многократно перераспределять новый буфер байтов.

Вопрос:

Как можно надежно получить ровно n байтов из сокета потока без риска перераспределения?

Ссылки:

Эти другие вопросы связаны и дают подсказки, но ни один не дает простого и ясного ответа:

Ответы [ 2 ]

1 голос
/ 25 апреля 2019

Вы можете использовать socket.makefile () , чтобы обернуть сокет в файлоподобный объект.Тогда чтение вернет точно запрошенную сумму, если сокет не будет закрыт, где он может вернуть остаток.Вот пример:

server.py

from socket import *

sock = socket()
sock.bind(('',5000))
sock.listen(1)
with sock:
    client,addr = sock.accept()
    with client, client.makefile() as clientfile:
        while True:
            data = clientfile.read(5)
            if not data: break
            print(data)

client.py

from socket import *
import time

sock = socket()
sock.connect(('localhost',5000))
with sock:
    sock.sendall(b'123')
    time.sleep(.5)
    sock.sendall(b'451234')
    time.sleep(.5)
    sock.sendall(b'51234')

СерверВыход

12345
12345
1234
0 голосов
/ 24 апреля 2019

Решение состоит в том, чтобы использовать recv_into и memoryview.Python позволяет предварительно выделить модифицируемую bytearray, которая может быть передана в recv_into.Но вы не можете получить данные в срез байтового массива, потому что срез будет копией.Но memoryview позволяет получать несколько фрагментов в один bytearray:

def readnbyte(sock, n):
    buff = bytearray(n)
    pos = 0
    while pos < n:
        cr = sock.recv_into(memoryview(buff)[pos:])
        if cr == 0:
            raise EOFError
        pos += cr
    return buff
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...