Socket传输数据包序号错误排查:格式正确但接收乱序
问题:数据包格式正确但接收序号错误
我在完成作业时耗了好几个小时,还是搞不懂问题出在哪:
我的需求是实现以下步骤:
- 从文件读取500字节数据
- 将包序号与读取的500字节数据打包
- 将格式化后的字节串通过Socket发送
我的代码如下:
sender.py
def sending(host, port, path_to_file, package_size): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) fp = open(path_to_file, 'rb') start_time = time.time() package_number = 1 while True: msg = fp.read(package_size) if len(msg) < package_size: sock.send(pack(f'I{len(msg)}s', package_number, msg)) break final_result = pack(f'I{package_size}s', package_number, msg) sock.send(final_result) package_number += 1 end_time = time.time() print(f"Time elapsed using {package_size} bytes: {end_time - start_time}")
receiver.py
def receive(host, port, extension, package_size): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((host, port)) sock.listen(1) conn, addr = sock.accept() print("Connection accepted with " + str(addr)) fp = open(f'received_file_{package_size}.{extension}', 'wb') start_time = time.time() while True: received_package = conn.recv(package_size) if len(received_package) < package_size: package_number, msg = unpack(f'I{len(received_package)-4}s', received_package) print(f"Package number {package_number} received with size {len(received_package)}") fp.write(msg) break package_number, msg = unpack(f'I{package_size-4}s', received_package) print(f"Package number {package_number} received with size {len(received_package)}") fp.write(msg) if not received_package: break fp.close() end_time = time.time() print(f"Time elapsed using {package_size} bytes: {end_time - start_time}")
但接收端收到的数据出现异常:
Package number 1 received with size 500: (正确的第一个包序号) b'\x01\x00\x00\x00\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x0f ... Package number 2468486165 received with size 500: (应为第二个包) b'\x15\x1c"\x93\x02\x00\x00\x00\xd6\x05\x1ei\x1fv\xb1\xdf\xbb\x1a
可以看到代表序号的字节向右偏移(\x02\x00\x00\x00),导致包序号错误(显示2468486165而非2)。我要传输的是7MB的PNG文件,且必须不能使用FTP库,请问哪里出错了?
问题根源
TCP是流式协议,它没有数据包边界的概念,你不能假设conn.recv(package_size)会正好接收到一个完整的打包后的数据包。
你的发送端每个完整包的结构是4字节整数序号 + 500字节数据,总大小为504字节,但接收端每次只调用recv(500),这会导致:
- 第一次recv只拿到前500字节(4字节序号 + 496字节数据)
- 剩下的4字节数据会和第二个包的序号、数据混在一起,被下一次recv读取
- 解析时错误地把数据字节当成了序号,自然出现序号偏移、数值错误的问题
修复方案
核心是解决TCP的粘包/拆包问题,确保接收端能完整接收每个打包单元。以下是修改后的代码:
修改后的sender.py
def sending(host, port, path_to_file, package_size): import socket import time from struct import pack sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) with open(path_to_file, 'rb') as fp: start_time = time.time() package_number = 1 while True: msg = fp.read(package_size) if not msg: break data_len = len(msg) # 打包格式:序号(4字节) + 数据长度(4字节) + 数据内容 final_result = pack(f'II{data_len}s', package_number, data_len, msg) sock.sendall(final_result) # 用sendall确保所有数据发送完成 package_number += 1 # 发送结束标记:序号0 + 数据长度0,告知接收端传输完成 sock.sendall(pack('II', 0, 0)) end_time = time.time() print(f"使用{package_size}字节包大小耗时:{end_time - start_time}")
修改后的receiver.py
def receive(host, port, extension, package_size): import socket import time from struct import unpack sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((host, port)) sock.listen(1) conn, addr = sock.accept() print("已与" + str(addr) + "建立连接") with open(f'received_file_{package_size}.{extension}', 'wb') as fp: start_time = time.time() def recv_all(conn, length): # 确保接收指定长度的字节,处理TCP拆包情况 data = b'' while len(data) < length: chunk = conn.recv(length - len(data)) if not chunk: return None data += chunk return data while True: # 先接收8字节的头部:序号(4字节) + 数据长度(4字节) header = recv_all(conn, 8) if not header: break package_number, data_len = unpack('II', header) # 收到结束标记则退出循环 if package_number == 0 and data_len == 0: break # 接收对应长度的数据内容 msg = recv_all(conn, data_len) if not msg: break print(f"收到包序号{package_number},总大小{8+data_len}字节") fp.write(msg) end_time = time.time() print(f"使用{package_size}字节包大小耗时:{end_time - start_time}")
关键修改点
- 使用
sendall()替代send():确保所有数据都被发送,避免因网络原因导致的部分发送。 - 增加数据长度字段:每个数据包包含数据长度信息,接收端可以明确知道需要接收多少字节的数据,彻底解决粘包/拆包问题。
- 实现
recv_all()函数:确保能接收到指定长度的字节,不会因TCP拆包导致接收不完整。 - 增加结束标记:通过发送序号0+数据长度0的包,让接收端可以明确判断传输结束,避免依赖数据长度的模糊判断。
内容的提问来源于stack exchange,提问作者Kresnik
相关产品推荐
相关产品推荐

