树莓派4 TCP客户端停止接收.NET MAUI服务器数据问题排查
核心问题定位
你的通信故障和后续无法重连,主要由Python客户端的连接逻辑错误和C#服务器的事件处理与数据发送逻辑缺失共同导致,具体如下:
一、Python客户端的错误
频繁创建/销毁TCP连接
代码在while True循环内每次都通过with socket.socket(...)新建连接,接收1字节数据后立即关闭连接。TCP连接的建立/关闭需要三次握手/四次挥手,频繁操作会:- 消耗树莓派和手机的端口资源,短时间内会出现端口耗尽,无法创建新连接;
- 服务器端堆积大量处于
TIME_WAIT状态的连接,最终无法接受新的连接请求。
无超时与异常处理
s.recv(1)是阻塞式调用,当服务器没有数据发送时,客户端会一直卡在这个调用上;如果连接中途断开(比如手机网络波动),connect或recv会抛出异常,直接中断程序循环,无法尝试重新连接。GPIO初始化冗余
部分GPIO引脚重复设置(比如leftForwardPin=21,后面又GPIO.setup(21, GPIO.OUT)),虽然不影响功能,但属于不必要的冗余代码。
二、C# MAUI服务器的错误
仅在客户端连接时发送一次数据
只有ClientConnected事件触发时才发送当前command值,后续按钮修改command后,没有主动将新指令发送给已连接的客户端。这会导致客户端每次连接后只能收到一次初始指令,后续服务器没有数据发送,客户端recv阻塞,表现为"停止接收数据包"。未实现关键事件处理方法
ClientDisconnected和DataReceived方法直接抛出NotImplementedException,一旦客户端断开连接或有数据发送到服务器,服务器会因未捕获的异常崩溃,停止监听端口,客户端自然无法重新连接。服务器IP绑定局限性
硬编码绑定192.168.99.146,如果手机的IP地址发生变化(比如重启路由器后),服务器将无法被客户端连接;另外,启动时仅检查网络访问权限,未处理后续网络变化的情况。
连接停止且无法重置的根本原因
- 服务器因未捕获的异常崩溃,停止监听11000端口,客户端后续的
connect请求会直接失败; - 客户端频繁创建连接导致两端端口资源耗尽,无法建立新的TCP连接;
- 客户端无异常处理,一旦网络故障导致
recv阻塞或连接失败,程序无法进入下一次循环尝试重连。
修复建议
Python客户端修复示例
import socket from time import sleep import RPi.GPIO as GPIO host = "192.168.99.146" port = 11000 # GPIO配置 leftBackwardPin = 26 leftForwardPin = 21 rightBackwardPin = 13 rightForwardPin = 19 GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(20, GPIO.IN) # 统一初始化GPIO输出引脚 GPIO.setup([leftBackwardPin, leftForwardPin, rightBackwardPin, rightForwardPin], GPIO.OUT) def reset_motors(): # 封装电机停止逻辑,避免重复代码 GPIO.output(leftForwardPin, GPIO.LOW) GPIO.output(rightForwardPin, GPIO.LOW) GPIO.output(leftBackwardPin, GPIO.LOW) GPIO.output(rightBackwardPin, GPIO.LOW) while True: try: # 建立长连接,而不是每次循环新建 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(5) # 设置超时,避免无限阻塞 s.connect((host, port)) print("已连接到服务器") while True: try: data = s.recv(1) if not data: print("服务器断开连接") break print(f"收到指令: {data}") reset_motors() if data == b'1': print("前进") GPIO.output(leftForwardPin, GPIO.HIGH) GPIO.output(rightForwardPin, GPIO.HIGH) elif data == b'2': print("后退") GPIO.output(leftBackwardPin, GPIO.HIGH) GPIO.output(rightBackwardPin, GPIO.HIGH) elif data == b'3': print("左转") GPIO.output(leftForwardPin, GPIO.HIGH) GPIO.output(rightBackwardPin, GPIO.HIGH) elif data == b'4': print("右转") GPIO.output(leftBackwardPin, GPIO.HIGH) GPIO.output(rightForwardPin, GPIO.HIGH) # 其他情况执行reset_motors停止电机 sleep(0.1) except socket.timeout: print("接收超时,保持连接") continue except Exception as e: print(f"通信错误: {e}") break except Exception as e: print(f"连接失败: {e}") print("5秒后尝试重新连接...") reset_motors() sleep(5)
C# MAUI服务器修复示例
using Microsoft.Maui.Controls; using SuperSimpleTcp; using System.Diagnostics; using System.Text; namespace MauiApp1; public partial class MainPage : ContentPage { private int _command = 0; private SimpleTcpServer? _server; private string _clientIpPort = string.Empty; // 存储已连接客户端的标识 public MainPage() { InitializeComponent(); StartTcpServer(); } private void StartTcpServer() { // 绑定到所有网卡的11000端口,避免IP硬编码问题 _server = new SimpleTcpServer("0.0.0.0:11000"); _server.Events.ClientConnected += OnClientConnected; _server.Events.ClientDisconnected += OnClientDisconnected; _server.Events.DataReceived += OnDataReceived; try { _server.Start(); Debug.WriteLine("TCP服务器已启动"); } catch (Exception ex) { Debug.WriteLine($"启动服务器失败: {ex.Message}"); } } private void OnDataReceived(object sender, DataReceivedEventArgs e) { // 实现数据接收逻辑,避免抛异常 Debug.WriteLine($"收到客户端数据: {Encoding.UTF8.GetString(e.Data)}"); } private void OnClientDisconnected(object sender, ConnectionEventArgs e) { Debug.WriteLine($"客户端断开连接: {e.IpPort}"); _clientIpPort = string.Empty; } private void OnClientConnected(object sender, ConnectionEventArgs e) { Debug.WriteLine($"客户端已连接: {e.IpPort}"); _clientIpPort = e.IpPort; // 发送初始指令 SendCommandToClient(); } private void SendCommandToClient() { if (_server != null && !string.IsNullOrEmpty(_clientIpPort) && _server.IsListening) { try { _server.Send(_clientIpPort, _command.ToString()); Debug.WriteLine($"发送指令: {_command}"); } catch (Exception ex) { Debug.WriteLine($"发送指令失败: {ex.Message}"); } } } private void Forward(object sender, EventArgs e) { _command = 1; SendCommandToClient(); } private void Backward(object sender, EventArgs e) { _command = 2; SendCommandToClient(); } private void Left(object sender, EventArgs e) { _command = 3; SendCommandToClient(); } private void Right(object sender, EventArgs e) { _command = 4; SendCommandToClient(); } private void Released(object sender, EventArgs e) { _command = 0; SendCommandToClient(); } }
内容的提问来源于stack exchange,提问作者Михаил Димитров

