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

Unity与Python双向TCP通信实现咨询:双套接字可行性?

Unity与Python实现TCP双向通信方案

核心结论

完全可以使用两个独立的Socket分别处理收发,也能同时创建并连接不同端口的套接字。这种方案逻辑清晰,能有效避免单端口通信的粘包、同步阻塞问题,是实现双向实时通信的可靠选择。

具体实现方案

1. 端口规划

给收发逻辑分配不同端口,避免冲突:

  • Unity → Python(位置数据发送):端口 55000
  • Python → Unity(计算数据发送):端口 55001

2. Unity端修改

优化后的发送脚本(TCPSendPipe.cs)

修复原脚本重复创建流对象的问题,使用二进制传输提升效率:

using System;
using System.IO;
using System.Net.Sockets;
using UnityEngine;

public class TCPSendPipe : MonoBehaviour
{
    public string Host = "localhost";
    public int Port = 55000;

    private TcpClient _tcpClient;
    private NetworkStream _networkStream;
    private BinaryWriter _binaryWriter;
    public GameObject robot;

    void Start()
    {
        robot = GameObject.Find("robot");
        _tcpClient = new TcpClient();
        SetupConnection();
    }

    void Update()
    {
        if (!_tcpClient.Connected)
        {
            SetupConnection();
            return;
        }
        SendRobotPosition();
    }

    private bool SetupConnection()
    {
        try
        {
            _tcpClient.Connect(Host, Port);
            _networkStream = _tcpClient.GetStream();
            _binaryWriter = new BinaryWriter(_networkStream);
            Debug.Log("发送Socket连接成功");
            return true;
        }
        catch (Exception e)
        {
            Debug.Log("发送Socket错误: " + e);
            return false;
        }
    }

    private void SendRobotPosition()
    {
        Vector3 robPos = robot.transform.position;
        byte[] sendBytes = new byte[12];
        Buffer.BlockCopy(BitConverter.GetBytes(robPos.x), 0, sendBytes, 0, 4);
        Buffer.BlockCopy(BitConverter.GetBytes(robPos.y), 0, sendBytes, 4, 4);
        Buffer.BlockCopy(BitConverter.GetBytes(robPos.z), 0, sendBytes, 8, 4);
        _binaryWriter.Write(sendBytes);
        _binaryWriter.Flush();
    }

    private void OnApplicationQuit()
    {
        _binaryWriter?.Close();
        _networkStream?.Close();
        if (_tcpClient != null && _tcpClient.Connected)
            _tcpClient.Close();
    }
}

优化后的接收脚本(TCPListenPipe.cs)

修改端口为55001,使用二进制读取替代字符串解析,避免阻塞:

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using UnityEngine;

public class TCPListenPipe : MonoBehaviour
{
    public int Port = 55001;
    private TcpListener _tcpListener;
    private TcpClient _connectedClient;
    private NetworkStream _networkStream;
    private BinaryReader _binaryReader;

    void Awake()
    {
        _tcpListener = new TcpListener(IPAddress.Loopback, Port);
        _tcpListener.Start();
        Debug.Log("监听Socket已启动,端口: " + Port);
    }

    void Update()
    {
        if (_connectedClient == null)
        {
            if (_tcpListener.Pending())
            {
                _connectedClient = _tcpListener.AcceptTcpClient();
                _networkStream = _connectedClient.GetStream();
                _binaryReader = new BinaryReader(_networkStream);
                Debug.Log("接收Socket已连接");
            }
            return;
        }

        if (_networkStream.DataAvailable)
        {
            try
            {
                float receivedData = _binaryReader.ReadSingle();
                Debug.Log("收到Python数据: " + receivedData);
            }
            catch (EndOfStreamException)
            {
                Debug.Log("连接已断开");
                CleanupConnection();
            }
        }
    }

    private void CleanupConnection()
    {
        _binaryReader?.Close();
        _networkStream?.Close();
        _connectedClient?.Close();
        _connectedClient = null;
    }

    private void OnApplicationQuit()
    {
        CleanupConnection();
        _tcpListener.Stop();
    }
}

3. Python端修改

使用多线程分离收发逻辑,同时处理两个端口的通信:

import socket
import struct
import numpy as np
import threading
import time

class DualComm:
    def __init__(self):
        self.RECV_PORT = 55000  # 接收Unity数据的端口
        self.SEND_PORT = 55001  # 发送数据到Unity的端口
        self.TCP_IP = '127.0.0.1'
        self.recv_conn = None
        self.send_socket = None
        self.running = True

    def _recv_handler(self):
        recv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        recv_sock.bind((self.TCP_IP, self.RECV_PORT))
        recv_sock.listen(1)
        print(f"等待Unity连接到端口 {self.RECV_PORT}...")
        self.recv_conn, addr = recv_sock.accept()
        print(f"Unity已连接,地址: {addr}")

        while self.running:
            try:
                byte_data = self.recv_conn.recv(12)
                if not byte_data:
                    break
                pos_data = np.frombuffer(byte_data, dtype=np.float32)
                print(f"收到Unity位置数据: {pos_data}")
                # 此处可添加数据处理逻辑
            except Exception as e:
                print(f"接收错误: {e}")
                break
        self.recv_conn.close()
        recv_sock.close()

    def _send_handler(self):
        time.sleep(2)  # 等待Unity启动监听
        while self.running:
            try:
                if not self.send_socket or self.send_socket._closed:
                    self.send_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    self.send_socket.connect((self.TCP_IP, self.SEND_PORT))
                    print(f"已连接到Unity端口 {self.SEND_PORT}")

                # 示例:发送动态变化的浮点数据
                cable_length = np.sin(time.time()) * 10 + 20
                send_data = struct.pack('f', cable_length)
                self.send_socket.sendall(send_data)
                print(f"发送数据到Unity: {cable_length:.3f}")
                time.sleep(0.1)
            except ConnectionRefusedError:
                print("Unity监听未启动,重试连接...")
                time.sleep(1)
            except Exception as e:
                print(f"发送错误: {e}")
                self.send_socket = None
                time.sleep(1)
        self.send_socket.close()

    def start(self):
        recv_thread = threading.Thread(target=self._recv_handler)
        send_thread = threading.Thread(target=self._send_handler)
        recv_thread.start()
        send_thread.start()
        recv_thread.join()
        send_thread.join()

    def stop(self):
        self.running = False

if __name__ == "__main__":
    comm = DualComm()
    try:
        comm.start()
    except KeyboardInterrupt:
        print("程序终止")
        comm.stop()

使用步骤

  1. 在Unity场景中同时挂载TCPSendPipe和TCPListenPipe脚本
  2. 启动Unity游戏
  3. 运行Python脚本,自动建立双向连接并开始实时数据收发

关键优化点

  • 采用二进制传输替代字符串,避免解析错误,提升传输效率
  • 缓存流对象,避免每帧重复创建资源
  • Python端用多线程分离收发逻辑,避免单线程阻塞
  • 添加异常处理与重连逻辑,提升通信稳定性

内容的提问来源于stack exchange,提问作者Rohit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 04:03:19