Unity C#客户端与树莓派Python服务器TCP重连异常求助
解决TCP重连后发送指令导致连接断开的问题
看起来你遇到的是典型的TCP连接生命周期处理不当的问题,我们分Unity客户端和Python服务器两部分来逐一修复:
一、Unity客户端的问题与修复
你的客户端代码存在几个关键隐患:
- 同步
Receive会阻塞Unity主线程,导致UI冻结,连接断开时还会触发未捕获的异常 - 断开连接后没有彻底清理旧的
Socket实例,重新连接时可能复用失效对象 - 发送指令前缺少连接状态检查,也没有异常捕获逻辑
修正后的Unity代码
using System.Collections.Generic; using UnityEngine; using System.Net; using System.Net.Sockets; using UnityEngine.UI; using System.Threading; public class Control : MonoBehaviour { public string IP = "192.168.8.100"; public int Port = 6704; public Socket client; public Text display; private Thread receiveThread; // 子线程处理接收,避免阻塞主线程 private bool isConnected = false; // 统一封装指令发送逻辑,减少重复代码 private void SendCommand(string cmd) { if (!isConnected || client == null || !client.Connected) { Debug.LogError("未连接到服务器,无法发送指令"); display.text = "未连接,无法发送"; return; } try { byte[] data = System.Text.Encoding.ASCII.GetBytes(cmd); client.Send(data); } catch (SocketException ex) { Debug.LogError($"发送指令失败:{ex.Message}"); HandleDisconnect(); } } // 按钮点击方法简化 public void Forward_Click() => SendCommand("1"); public void Back_Click() => SendCommand("2"); public void Right_Click() => SendCommand("3"); public void Left_Click() => SendCommand("4"); public void R_Turn_Click() => SendCommand("5"); public void L_Turn_Click() => SendCommand("6"); public void FR_Click() => SendCommand("7"); public void FL_Click() => SendCommand("8"); public void OnRelease() => SendCommand("0"); public void Connecting() { // 先断开旧连接,避免资源冲突 if (isConnected) { HandleDisconnect(); } try { client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.Connect(IP, Port); isConnected = true; display.text = "已连接到服务器"; // 启动子线程处理数据接收 receiveThread = new Thread(ReceiveDataLoop); receiveThread.IsBackground = true; receiveThread.Start(); } catch (SocketException ex) { Debug.LogError($"连接失败:{ex.Message}"); display.text = "连接失败"; HandleDisconnect(); } } // 子线程循环接收数据 private void ReceiveDataLoop() { byte[] buffer = new byte[1024]; while (isConnected && client != null && client.Connected) { try { int receivedBytes = client.Receive(buffer); if (receivedBytes <= 0) { Debug.Log("服务器断开连接"); HandleDisconnect(); break; } string receivedData = System.Text.Encoding.ASCII.GetString(buffer, 0, receivedBytes); Debug.Log($"从Python收到数据:{receivedData}"); // Unity主线程才能更新UI,用Invoke切回主线程 Invoke(nameof(UpdateDisplayWithData), 0, receivedData); } catch (SocketException ex) { Debug.LogError($"接收数据异常:{ex.Message}"); HandleDisconnect(); break; } } } private void UpdateDisplayWithData(string data) { display.text = $"收到:{data}"; } public void Disconnect() { HandleDisconnect(); } // 统一处理断开连接的资源清理 private void HandleDisconnect() { isConnected = false; if (client != null) { try { client.Shutdown(SocketShutdown.Both); } catch { } client.Close(); client = null; } if (receiveThread != null && receiveThread.IsAlive) { receiveThread.Abort(); receiveThread = null; } display.text = "已断开连接"; } // 场景销毁时确保清理所有资源 private void OnDestroy() { HandleDisconnect(); } }
Unity代码修改说明
- 用子线程处理接收:避免同步
Receive阻塞主线程,解决UI冻结和异常问题 - 统一
SendCommand方法:增加连接状态校验,发送时捕获异常,出现问题自动触发断开逻辑 - 新增
HandleDisconnect:集中处理Socket、线程的资源清理,避免残留无效对象 - 主线程更新UI:通过
Invoke将UI操作切回主线程,符合Unity的线程安全要求
二、Python服务器的核心问题与修复
你的问题根源大概率在服务器端:新手常写的TCP服务器只接受一次连接,客户端断开后就退出监听,无法处理新连接。正确的TCP服务器需要循环监听新连接,并为每个客户端分配独立的处理线程。
正确的Python服务器示例代码
import socket import threading import RPi.GPIO as GPIO # 继电器GPIO配置(根据你的硬件调整引脚) RELAY_PINS = { "1": 17, "2": 18, "3": 27, "4": 22, "5": 23, "6": 24, "7": 25, "8": 4 } def setup_relays(): GPIO.setmode(GPIO.BCM) for pin in RELAY_PINS.values(): GPIO.setup(pin, GPIO.OUT) GPIO.output(pin, GPIO.LOW) # 默认关闭继电器 def handle_client(client_socket, client_addr): print(f"新客户端连接:{client_addr}") try: while True: # 接收客户端指令 data = client_socket.recv(1024).decode('ascii').strip() if not data: # 收到空数据表示客户端断开 print(f"客户端 {client_addr} 断开连接") break print(f"收到指令:{data}") # 处理继电器逻辑 if data in RELAY_PINS: # 切换继电器状态(可根据需求改为固定高低电平) current_state = GPIO.input(RELAY_PINS[data]) GPIO.output(RELAY_PINS[data], GPIO.HIGH if current_state == GPIO.LOW else GPIO.LOW) client_socket.send(f"指令 {data} 已执行".encode('ascii')) elif data == "0": # 关闭所有继电器 for pin in RELAY_PINS.values(): GPIO.output(pin, GPIO.LOW) client_socket.send("所有继电器已关闭".encode('ascii')) else: client_socket.send("无效指令".encode('ascii')) except Exception as e: print(f"处理客户端 {client_addr} 时出错:{e}") finally: client_socket.close() print(f"客户端 {client_addr} 连接已关闭") def main(): setup_relays() HOST = '0.0.0.0' # 监听所有网卡 PORT = 6704 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 允许复用端口,避免重启时端口占用 server_socket.bind((HOST, PORT)) server_socket.listen(5) # 最大等待连接数 print(f"服务器启动,监听端口 {PORT}...") try: while True: # 循环接受新连接 client_socket, client_addr = server_socket.accept() # 为每个客户端启动独立线程 client_thread = threading.Thread(target=handle_client, args=(client_socket, client_addr)) client_thread.daemon = True # 主线程退出时自动关闭子线程 client_thread.start() except KeyboardInterrupt: print("服务器正在关闭...") finally: GPIO.cleanup() server_socket.close() if __name__ == "__main__": main()
Python服务器修改说明
- 循环监听新连接:
while True持续调用accept(),确保客户端断开后服务器能继续接受新连接 - 独立线程处理客户端:单个客户端的断开不会影响整个服务器的运行
- 设置
SO_REUSEADDR:允许服务器重启时立刻复用端口,避免"地址已在使用"的错误 - 完善资源清理:客户端断开时关闭Socket,程序退出时清理GPIO资源
三、测试步骤
- 先启动Python服务器
- 在Unity中点击连接按钮,测试发送指令
- 断开Unity连接,重新连接后再次发送指令,此时不会再出现连接被终止的问题
按照这个方案修改后,你的TCP连接就能正常支持多次断开重连,指令发送也能稳定工作了。
内容的提问来源于stack exchange,提问作者Ghada
相关产品推荐
相关产品推荐

