Read exactly n bytes from a socket.
27def recvall(sock, n, fragment_timeout=_RECVALL_FRAGMENT_TIMEOUT):
28 r"""!Read exactly n bytes from a socket.
29
30 Unlike sock.recv(n), this function guarantees that exactly n bytes
31 are read, even if the underlying TCP stream delivers them in multiple
32 fragments (e.g. over VPN with reduced MTU).
33
34 Uses an internal per-fragment timeout for blocking sockets to prevent
35 threads from hanging indefinitely on half-open connections (e.g. VPN dropout).
36 Respects existing socket timeouts if already set.
37
38 @param sock: A connected socket object
39 @param n: Number of bytes to read
40 @param fragment_timeout: Max seconds to wait for next fragment (default: 10.0)
41 @return Decoded string of exactly n bytes, or None if connection broke or timeout was exceeded"""
42 data = bytearray()
43 while len(data) < n:
44
45 if sock.gettimeout() is None:
46
47 ready, _, _ = select.select([sock], [], [], fragment_timeout)
48 if not ready:
49 logging.warning("recvall: fragment timeout after %.1fs "
50 "(%d/%d bytes received) - possible half-open connection",
51 fragment_timeout, len(data), n)
52 return None
53
54 try:
55 packet = sock.recv(n - len(data))
56 except OSError as e:
57 logging.warning("recvall: socket error after %d/%d bytes: %s", len(data), n, e)
58 return None
59
60 if not packet:
61 logging.debug("recvall: connection closed after %d/%d bytes", len(data), n)
62 return None
63
64 data.extend(packet)
65
66 try:
67 return data.decode("utf-8")
68 except UnicodeDecodeError as e:
69 logging.warning("recvall: decode error: %s | raw: %s", e, data.hex())
70 return None