Unity(C#)与Python TCP Socket双向通信:单次接收完整数据问题
解决Unity与Python TCP通信的粘包问题
为啥会出现重复聚合的情况?
TCP是流式传输协议,它不会区分你发送的是单条还是多条消息,只会把数据当成连续的字节流传递。所以多次调用receive()时,可能会把好几次发送的JSON包混在一起返回(也就是常说的“粘包”),或者单个JSON包被拆成好几次返回,这就是你看到重复数据的原因。StreamWriter.Flush()只是把Unity端缓冲区里的数据强制发出去,但解决不了TCP本身没有消息边界的问题。
最靠谱的解决办法:给消息加长度前缀
这是TCP通信里通用的粘包解决方案,核心逻辑是先告诉对方“接下来要发的消息有多长”,再发送实际内容,步骤如下:
- Unity端:先把JSON数据的字节长度转成固定4字节的整数发送,再发送JSON本身的字节数据
- Python端:先读取这4字节的长度值,再根据长度读取对应字节数的内容,确保每次拿到的都是完整的一条消息
Unity端代码修改示例(C#)
using System; using System.Net.Sockets; using System.Text; using UnityEngine; public class TcpSender : MonoBehaviour { private TcpClient _client; private NetworkStream _stream; void Start() { _client = new TcpClient("127.0.0.1", 8888); _stream = _client.GetStream(); } void Update() { // 模拟获取机器人位置和额外浮点数据 Vector3 robotPos = transform.position; float extraParam = 0.7f; // 把数据序列化为JSON var sendData = new { X = robotPos.x, Y = robotPos.y, Z = robotPos.z, Extra = extraParam }; string jsonStr = JsonUtility.ToJson(sendData); byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonStr); // 先发送消息长度(4字节,统一用大端字节序,和Python对齐) byte[] lengthBytes = BitConverter.GetBytes(jsonBytes.Length); if (BitConverter.IsLittleEndian) Array.Reverse(lengthBytes); // 把小端字节序转为大端 _stream.Write(lengthBytes, 0, 4); // 发送JSON数据 _stream.Write(jsonBytes, 0, jsonBytes.Length); _stream.Flush(); // 确保缓冲区数据即时发送 } void OnDestroy() { _stream?.Close(); _client?.Close(); } }
Python端代码修改示例
import socket import json import struct # 启动TCP服务器 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen(1) print("等待Unity连接...") client_sock, addr = server.accept() print(f"已连接: {addr}") def get_full_message(): # 先读取4字节的消息长度(按大端解析) len_data = client_sock.recv(4) if not len_data: return None msg_length = struct.unpack('!I', len_data)[0] # 按长度读取完整消息内容 msg_bytes = b'' while len(msg_bytes) < msg_length: chunk = client_sock.recv(msg_length - len(msg_bytes)) if not chunk: return None msg_bytes += chunk # 解析为JSON对象返回 return json.loads(msg_bytes.decode('utf-8')) # 循环接收消息 try: while True: data = get_full_message() if not data: break print("收到完整数据:", data) # 处理数据后,向Unity发送控制动作(同样用长度前缀法) control_data = {"move_speed": 1.5, "rotate_angle": 0.3} control_json = json.dumps(control_data) control_bytes = control_json.encode('utf-8') # 先发送长度 client_sock.send(struct.pack('!I', len(control_bytes))) # 发送控制数据 client_sock.send(control_bytes) finally: client_sock.close() server.close()
简单场景的替代方案:用分隔符
如果你的JSON数据里绝对不会出现换行符,也可以用\n作为消息结尾:
- Unity端:在JSON字符串末尾添加
\n,再转成字节发送 - Python端:用
client_sock.makefile().readline()按行读取,每次读取一行就是一条完整的JSON消息
但这个方法有局限性:如果JSON的字符串字段包含换行符,会导致消息被错误拆分,所以还是长度前缀法最稳妥。
额外注意事项
- 字节序统一:Unity和Python解析长度时必须使用相同的字节序(示例中用大端
!I,跨平台兼容性更好) - Unity避免主线程阻塞:不要在Update里直接处理Socket操作,最好开单独线程或用
Task异步处理,防止卡帧 - 增加异常处理:实际使用时要处理连接断开、读取失败等异常情况,避免程序崩溃
内容的提问来源于stack exchange,提问作者Rohit
相关产品推荐
相关产品推荐

