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

Unity运行TCP代码无法连接Raspberry Pi问题求助

解决Unity中异步TCP Socket无法与树莓派Python客户端建立连接的问题

我仔细看过你的异步Socket代码了——在Visual Studio里能正常跑,但移植到Unity就失效,这种情况我遇到过好多次,基本都是Unity的线程模型、环境限制或者配置问题导致的,下面给你拆解问题点和适配Unity的修复方案:


核心问题分析

  1. Unity的线程生命周期管理缺失
    你原来的代码直接用独立线程启动监听,但没考虑Unity的对象生命周期——如果挂载脚本的GameObject被销毁或者场景切换,线程会变成野线程,而且Unity对后台线程的兼容性有要求,必须设置为后台线程才能随程序退出自动销毁。

  2. 控制台输出与Unity日志的差异
    你用的Console.WriteLine在Unity编辑器里虽然能看到,但打包后可能失效,换成Unity的Debug.Log/Debug.LogError才是正确的做法。

  3. 网络权限与防火墙拦截
    Unity默认可能限制网络访问,而且Windows防火墙大概率会拦截你的6670端口连接,这也是常见的“代码没问题但连不上”的原因。


适配Unity的完整代码

把代码改成MonoBehaviour脚本,挂载到场景中的任意GameObject上,就能正常运行了:

using System;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

// State object for reading client data asynchronously
public class StateObject
{
    // Client socket.
    public Socket workSocket = null;
    // Size of receive buffer.
    public const int BufferSize = 1024;
    // Receive buffer.
    public byte[] buffer = new byte[BufferSize];
    // Received data string.
    public StringBuilder sb = new StringBuilder();
}

public class UnityAsyncSocketListener : MonoBehaviour
{
    // Thread signal.
    private static ManualResetEvent allDone = new ManualResetEvent(false);
    private Socket listener;
    private Thread listenThread;
    private bool isListening = true; // 优雅停止线程的标志位

    [Header("Socket Settings")]
    public int port = 6670;

    void Start()
    {
        // 启动后台监听线程
        listenThread = new Thread(StartListening);
        listenThread.IsBackground = true;
        listenThread.Start();
        Debug.Log("Socket listener thread started");
    }

    void OnDestroy()
    {
        // 优雅停止监听
        isListening = false;
        allDone.Set(); // 唤醒等待的线程,让它退出循环

        // 释放Socket资源
        if (listener != null)
        {
            listener.Close();
            listener.Dispose();
        }

        // 释放事件资源
        allDone.Dispose();
        Debug.Log("Socket listener cleaned up properly");
    }

    private void StartListening()
    {
        IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
        Debug.Log($"Listening on {localEndPoint}");

        listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        try
        {
            listener.Bind(localEndPoint);
            listener.Listen(100);

            while (isListening)
            {
                allDone.Reset();
                Debug.Log("Waiting for a connection from Raspberry Pi...");
                listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
                allDone.WaitOne(); // 等待连接建立
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"Socket initialization error: {e.Message}");
        }
    }

    private void AcceptCallback(IAsyncResult ar)
    {
        allDone.Set(); // 通知主线程继续等待下一个连接

        Socket listenerSocket = (Socket)ar.AsyncState;
        Socket handler = listenerSocket.EndAccept(ar);

        StateObject state = new StateObject();
        state.workSocket = handler;
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
        Debug.Log("New client connected (Raspberry Pi)");
    }

    private void ReadCallback(IAsyncResult ar)
    {
        StateObject state = (StateObject)ar.AsyncState;
        Socket handler = state.workSocket;

        int bytesRead = handler.EndReceive(ar);
        if (bytesRead <= 0) return;

        string stringRead = Encoding.ASCII.GetString(state.buffer, 0, bytesRead);
        state.sb.Append(stringRead);

        string content = state.sb.ToString();
        if (content.Contains("<EOF>"))
        {
            Debug.Log($"Full data received: {content.TrimEnd("<EOF>".ToCharArray())}");
            Send(handler, "Data received: " + content);
            state.sb.Clear(); // 重置缓冲区,准备接收下一次数据
        }
        else
        {
            string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
            Debug.Log($"{timestamp} - Partial data: {stringRead}");
            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
            Send(handler, "boogieboogie");
        }
    }

    private void Send(Socket handler, string data)
    {
        byte[] byteData = Encoding.ASCII.GetBytes(data);
        handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
    }

    private void SendCallback(IAsyncResult ar)
    {
        try
        {
            Socket handler = (Socket)ar.AsyncState;
            int bytesSent = handler.EndSend(ar);
            Debug.Log($"Sent {bytesSent} bytes to client");
        }
        catch (Exception e)
        {
            Debug.LogError($"Send error: {e.Message}");
        }
    }
}

额外排查与配置步骤

  1. 树莓派连接IP确认
    在Unity编辑器的Console里找到打印的Listening on xxx.xxx.xxx.xxx:6670,树莓派的Python代码必须用这个局域网IP连接,而不是127.0.0.1(那是本地回环,只能连本机)。

  2. 防火墙设置

    • 关闭Unity运行机器的Windows防火墙,或者添加入站规则:允许TCP端口6670的连接。
    • 树莓派端也要确保没有防火墙拦截出站连接到这个端口。
  3. Unity Player Settings配置
    打开Edit > Project Settings > Player,找到Other Settings > Configuration > Internet Access,设置为Require——这一步很重要,打包后如果没开这个权限,网络连接会被完全拦截。

  4. 线程停止优化
    我在代码里加了isListening标志位,替代了粗暴的Thread.Abort(),这样线程会自然退出,避免资源泄漏。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:18:01