Python 数据包在某些时候被完全发送,而某些时候没有被完全发送

Python 数据包在某些时候被完全发送,而某些时候没有被完全发送,python,sockets,network-programming,Python,Sockets,Network Programming,@Grismar建议我为以下问题创建新主题: 我用socket模块编写了一个服务器和客户端。对于多连接,我使用了线程或fork() 场景:我必须生成一个巨大的字符串并发送给客户端。当然,根据这个字符串是由客户端生成的。实际上,客户机发送一个查询,服务器生成一个结果并发送给客户机。我没有向服务器发送查询的问题 因为我有大量的字符串,所以我决定将字符串拆分为块,例如: if sys.getsizeof(search_result_string) > 1024: #131072: if

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

我用
socket
模块编写了一个服务器和客户端。对于多连接,我使用了
线程
fork()

场景:我必须生成一个巨大的字符串并发送给客户端。当然,根据这个字符串是由客户端生成的。实际上,客户机发送一个查询,服务器生成一个结果并发送给客户机。我没有向服务器发送查询的问题

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

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:]
所以,我有一份清单。 然后:

有时我在客户端没有任何问题,有时我会出现以下错误:

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
顺便说一句,我在本地主机中同时使用TCP套接字和test。
我每次跑步都使用相同的字符串


问题是,为什么有时一切正常,有时字符串没有完全发送

发生的情况是,您的数据被操作系统分块(除了您正在做的事情之外)。当操作系统执行时,它可以在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无论如何都不会保留您的消息边界。因此,最好的选择是正确地“框定”数据

也就是说,取字符串的一部分(如果不是数百兆字节,则取所有字符串),并用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')
len_bytes=sock.recv(4)#接收4字节大小的报头
len_bytes=struct.unpack(“!I”)[0]#转换为数字(unpack返回一个列表)
已发送字节数=b“”
而len(已发送的字节数)

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

@Grismar我创建了一个新主题。
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')