Python Socket+Selectors实现多独立消息收发的问题求助
兄弟我太懂你这糟心的情况了!你遇到的是TCP粘包问题——TCP本身是流式协议,它完全不关心你调用了几次send,操作系统的TCP栈为了传输效率,会把连续的小数据包合并成一个发送(这就是Nagle算法搞的鬼),所以服务器那边自然就把两次发送的内容当成一坨接收到了。
要解决这个问题,核心思路是自己给消息加上边界标识,让服务器能准确区分开不同的消息。给你几个实用的方案,结合你的代码来改:
方案一:前缀消息长度(最靠谱、通用性最强)
这种方法是行业通用的标准做法:发送每条消息前,先把消息的长度用固定字节数(比如4字节的无符号整数)打包发送,服务器先读取这个长度,再精准读取对应长度的内容,完美避免粘包。
客户端修改示例
先写个辅助函数封装发送逻辑:
import struct from datetime import datetime def send_prefixed_message(sock, msg_bytes): # 把消息长度打包成4字节大端模式的整数(!I表示网络字节序的无符号int) len_prefix = struct.pack('!I', len(msg_bytes)) # 用sendall确保数据全部写入操作系统缓冲区 sock.sendall(len_prefix) sock.sendall(msg_bytes) # 替换你原来的发送逻辑 if not data.outb and gvl.global_Counter == 1: # 发送global_Status status_bytes = bytes(gvl.global_Status, 'utf-8') send_prefixed_message(sock, status_bytes) # 发送时间戳(顺便纠正你原来的strftime格式错误!%M是分钟,小写%m才是月份) now = datetime.now() mytime_show = now.strftime('%Y-%m-%d %H:%M:%S') time_bytes = bytes(mytime_show, 'utf-8') send_prefixed_message(sock, time_bytes) # 清空outb(你的原逻辑里这部分有点绕,直接处理完就清掉就行) data.outb = b''
服务器端修改示例
同样写个辅助函数处理接收:
import struct def recv_prefixed_message(sock): # 先读取4字节的长度前缀 len_data = sock.recv(4) if not len_data: return None # 连接已关闭 # 解析出消息长度 msg_len = struct.unpack('!I', len_data)[0] # 循环读取直到拿到完整的消息 msg_content = b'' while len(msg_content) < msg_len: chunk = sock.recv(msg_len - len(msg_content)) if not chunk: return None # 连接异常断开 msg_content += chunk return msg_content.decode('utf-8') # 在你的selector读事件里替换原来的接收逻辑 readyData = recv_prefixed_message(sock) if readyData: print(f"收到独立消息:{readyData}") # 这里就可以分别处理状态消息和时间戳消息了
方案二:用特殊分隔符(适合简单场景)
如果你的消息内容里不会出现某个特殊字符/字符串(比如\r\n或者自定义的###END###),可以在每条消息末尾加上这个分隔符,服务器接收数据时不断累加,直到遇到分隔符就拆分出一条完整消息。
比如客户端发送时:
# 发送状态 sock.sendall(bytes(gvl.global_Status + '\r\n', 'utf-8')) # 发送时间戳 sock.sendall(bytes(mytime_show + '\r\n', 'utf-8'))
服务器端需要维护一个缓冲区,每次接收数据都追加进去,然后按分隔符拆分:
# 服务器端每个连接维护一个缓冲区 data.recv_buffer += sock.recv(1024) # 按分隔符拆分 while b'\r\n' in data.recv_buffer: msg_bytes, _, data.recv_buffer = data.recv_buffer.partition(b'\r\n') readyData = msg_bytes.decode('utf-8') print(f"收到独立消息:{readyData}")
这种方法实现简单,但如果消息里不小心出现了分隔符,就会导致消息拆分错误,所以只适合消息内容可控的场景。
方案三:固定消息长度(灵活性差,不推荐)
如果你的每条消息长度完全固定,服务器可以每次固定读取指定字节数,但你的场景里状态和时间戳长度显然不一样,所以这个方案对你不适用,就不多说了。
最后提一句:你原来代码里的strftime格式写错了!%M是分钟,%D是月/日/年,正确的日期时间格式应该是%Y-%m-%d %H:%M:%S(小写m是月份,小写d是日期),别让时间戳格式也出问题哦~
备注:内容来源于stack exchange,提问作者stopbanningmelmao123

