数据包有时发送完整有时不发送完整

packet is sent completely somtimes and somtimes is not sent completely

@Grismar 建议我为以下问题创建新主题:

我用 socket module.For 多连接写了一个服务器和客户端 我用 selectors module 而不是 threadfork().

场景:我必须生成大量字符串并根据客户端生成的字符串发送到 client.Of 课程。实际上,客户端发送查询,服务器生成结果并发送给客户端。我没有向服务器发送查询的问题。

因为我有大量的字符串,所以我决定将我的字符串分成块,例如:

if sys.getsizeof(search_result_string) > 1024: #131072:
    if sys.getsizeof(search_result_string) % 1024 == 0:
        chunks = int(sys.getsizeof(search_result_string) / 1024 )
    else:
        chunks = int(sys.getsizeof(search_result_string) / 1024) + 1
for chunk in range(chunks):
    packets.append(search_result_string[:1024])
    search_result_string = search_result_string[1024:]

所以,我有数据包列表。 那么:

conn.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1000000)
for chunk in packets:
    conn.sendall(bytes(chunk,'utf-8'))

有时我在客户端没有任何问题,有时我收到以下错误:

Traceback (most recent call last):
  File "./multiconn-client.py", line 116, in <module>
    service_connection(key, mask)
  File "./multiconn-client.py", line 89, in service_connection
    target_string += recv_data.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd9 in position 42242: unexpected end of data

在我的客户端,我使用了以下回调:

def service_connection(key, mask):
    buff = 10000
    sock = key.fileobj
    data = key.data
    target_string = str()
    if mask & selectors.EVENT_READ:
        buff = sock.getsockopt(SOL_SOCKET,SO_RCVBUF)
        recv_data = sock.recv( 128*1024 |buff)
        if recv_data:
            buff = sock.getsockopt(SOL_SOCKET,SO_RCVBUF)
            data.recv_total += len(recv_data)
        target_string += recv_data.decode('utf-8')
        print(target_string)
        if not recv_data: #or data.recv_total == data.msg_total:
            print("closing connection", data.connid)
            sel.unregister(sock)
            sock.close()
    if mask & selectors.EVENT_WRITE:
        if not data.outb and data.messages:
            data.outb = data.messages.pop(0)
        if data.outb:
            print("sending", repr(data.outb), "to connection", data.connid)
            sent = sock.send(data.outb)  # Should be ready to write
            data.outb = data.outb[sent:]

顺便说一句,我在本地主机都使用 TCP socket.And 测试。
我对每个 运行 使用相同的字符串。

问题是,为什么有时一切正常,有时字符串发送不完整。

发生的事情是你的数据被操作系统分块(除了你正在做的事情)。当操作系统执行此操作时,它可能会在 UTF-8 编码序列的中间拆分您的数据。换句话说,考虑这个代码块:

foo = '\xce\xdd\xff'       # three non-ascii characters
print(len(foo))            # => 3
bar = foo.encode('utf-8')
print(bar)                 # => b'\xc3\x8e\xc3\x9d\xc3\xbf'
bar[:3].decode()           # =>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc3 in position 2: unexpected end of data

发生了什么:0x7f 以上的字符被编码为两个 UTF8 字节。但是,如果两字节序列在中间被截断,则无法解码字符。

因此,要轻松解决您的问题,请先接收所有数据(作为字节串),然后将整个字节串作为一个单元进行解码。

这带来了另一个相关问题:您不需要创建自己的数据块。 TCP 会为你做到这一点。正如您所见,TCP 无论如何都不会 保留 您的消息边界。所以你最好的选择是正确地 "frame" 你的数据。

也就是说,提取字符串的一部分(如果不是数百兆字节,则提取全部字符串),然后将其编码为 UTF-8。获取生成的字节缓冲区的长度。作为二进制数据发送包含该长度的固定长度大小字段(使用 struct 模块创建)。在接收端,首先接收固定长度的大小字段。这让您知道实际需要接收多少字节的字符串数据。接收所有这些字节,然后立即解码整个缓冲区。

也就是说,忽略错误处理,发送端:

import struct
import socket
...
str_to_send = "blah blah\xce"
bytes_to_send = str_to_send.encode('utf-8')
len_bytes = len(bytes_to_send)
sock.send(struct.pack("!I", len_bytes)         # Send 4-byte size header
sock.send(bytes_to_send)                       # Let TCP handle chunking bytes

接收方:

len_bytes = sock.recv(4)                       # Receive 4-byte size header
len_bytes = struct.unpack("!I")[0]             # Convert to number (unpack returns a list)

bytes_sent = b''
while len(bytes_sent) < len_bytes:
    buf = sock.recv(1024)          # Note, may not always receive 1024 (but typically will)
    if not buf:
        print("Unexpected EOF!")
        sys.exit(1)
    bytes_sent += buf
str_sent = bytes_sent.decode('utf-8')

结语:socket.send 保证发送整个缓冲区(尽管它通常会发送)。并且 socket.recv 不保证接收您在参数中指定的字节数。因此,健壮的 TCP sending/receiving 代码需要适应这些警告。