You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity(C#)与Python TCP Socket双向通信:单次接收完整数据问题

解决Unity与Python TCP通信的粘包问题

为啥会出现重复聚合的情况?

TCP是流式传输协议,它不会区分你发送的是单条还是多条消息,只会把数据当成连续的字节流传递。所以多次调用receive()时,可能会把好几次发送的JSON包混在一起返回(也就是常说的“粘包”),或者单个JSON包被拆成好几次返回,这就是你看到重复数据的原因。StreamWriter.Flush()只是把Unity端缓冲区里的数据强制发出去,但解决不了TCP本身没有消息边界的问题。

最靠谱的解决办法:给消息加长度前缀

这是TCP通信里通用的粘包解决方案,核心逻辑是先告诉对方“接下来要发的消息有多长”,再发送实际内容,步骤如下:

  1. Unity端:先把JSON数据的字节长度转成固定4字节的整数发送,再发送JSON本身的字节数据
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 09:45:42