using System.Net.Sockets; using System.Text; using System.Text.Json; using System.IO; namespace WIDESEAWCS_Tasks.SocketServer { public partial class TcpSocketServer { /// /// 处理客户端连接的消息循环 /// /// /// 持续接收客户端消息,直到连接断开或取消。 /// 处理流程: /// 1. 接收消息(帧解析) /// 2. 更新客户端状态(活跃时间、编码) /// 3. 处理设备注册 /// 4. 触发 MessageReceived 事件 /// 连接断开时清理资源并触发 RobotReceived 事件。 /// /// TCP 客户端连接 /// 客户端唯一标识 /// 取消令牌 /// 机器人状态 public async Task HandleClientAsync(TcpClient client, string clientId, CancellationToken cancellationToken, RobotSocketState robotCrane) { using (client) using (NetworkStream networkStream = client.GetStream()) using (StreamReader reader = new(networkStream, _textEncoding, false, 1024, true)) using (StreamWriter writer = new(networkStream, _textEncoding, 1024, true) { AutoFlush = true }) { CancellationTokenSource? localCts = null; if (_options.EnableHeartbeat || _options.IdleTimeoutSeconds > 0) { // 创建链接的取消令牌源 localCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); } try { // 消息接收循环 while (!cancellationToken.IsCancellationRequested && client.Connected) { string? message; try { var ct = localCts?.Token ?? cancellationToken; // 接收完整消息(帧解析) message = await ReceiveFullMessageAsync(networkStream, _textEncoding, ct); } catch (OperationCanceledException) { break; } if (message == null) { break; } // 更新客户端状态 UpdateClientStatus(clientId, message); string messageLower = message.ToLowerInvariant(); // 处理注册消息 if (TryHandleRegister(messageLower, message, clientId, networkStream, cancellationToken)) { continue; } // 触发消息接收事件 if (MessageReceived != null) { try { // 判断是否为 JSON 格式 bool isJsonFormat = TryParseJsonSilent(message); _ = MessageReceived.Invoke(message, isJsonFormat, client, robotCrane); } catch { } } } } finally { // 清理资源 try { localCts?.Cancel(); localCts?.Dispose(); } catch { } RemoveClient(clientId); try { _ = RobotReceived.Invoke(clientId); } catch { } } } } /// /// 处理设备注册消息 /// /// /// 注册消息格式:register,{deviceId} /// 将设备 ID 绑定到当前客户端 ID。 /// /// 消息小写版本 /// 原始消息 /// 客户端 ID /// 网络流 /// 取消令牌 /// 是否处理了注册消息 private bool TryHandleRegister(string messageLower, string message, string clientId, NetworkStream networkStream, CancellationToken cancellationToken) { if (!messageLower.StartsWith("register,")) { return false; } // 提取设备 ID string deviceId = message.Substring("register,".Length).Trim(); if (!string.IsNullOrEmpty(deviceId)) { lock (_syncRoot) { // 绑定设备到客户端 _deviceBindings[deviceId] = clientId; } // 回复注册成功 _ = WriteToClientAsync(clientId, networkStream, $"Registered,{deviceId}", cancellationToken); } return true; } /// /// 更新客户端状态 /// /// /// 更新最后活跃时间和字符编码。 /// 如果开启了自动编码检测,根据消息内容判断是 UTF-8 还是 GBK。 /// /// 客户端 ID /// 最新接收的消息 private void UpdateClientStatus(string clientId, string message) { lock (_syncRoot) { // 更新最后活跃时间 _clientLastActive[clientId] = DateTime.Now; // 如果还没有记录编码 if (!_clientEncodings.ContainsKey(clientId)) { if (_options.AutoDetectEncoding && _autoDetectedGb2312 != null) { // 自动检测编码:JSON 或 UTF-8 字节特征则用 UTF-8,否则用 GBK bool isUtf8 = TryParseJsonSilent(message) || IsLikelyUtf8(_textEncoding.GetBytes(message)); _clientEncodings[clientId] = isUtf8 ? _textEncoding : _autoDetectedGb2312; } else { _clientEncodings[clientId] = _textEncoding; } } } } /// /// 异步发送消息到客户端 /// /// /// 内部方法,不使用帧格式,直接发送原始消息。 /// private async Task WriteToClientAsync(string clientId, NetworkStream networkStream, string message, CancellationToken cancellationToken) { SemaphoreSlim? sem = null; Encoding? enc = null; lock (_syncRoot) { _clientLocks.TryGetValue(clientId, out sem); _clientEncodings.TryGetValue(clientId, out enc); } enc ??= _textEncoding; if (sem != null) await sem.WaitAsync(cancellationToken); try { var framedMessage = BuildFramedMessage(message); var data = enc.GetBytes(framedMessage); await networkStream.WriteAsync(data, 0, data.Length, cancellationToken); } finally { if (sem != null) sem.Release(); } } /// /// 构建帧消息 /// /// /// 在消息前后添加头尾标识。 /// /// 原始消息 /// 带帧标识的消息 private string BuildFramedMessage(string message) { var header = _options.MessageHeader ?? string.Empty; var footer = _options.MessageFooter ?? string.Empty; return header + (message ?? string.Empty) + footer; } /// /// 静默尝试解析 JSON /// /// /// 判断消息是否以 { 或 [ 开头,如果是则尝试解析。 /// 解析失败不抛异常。 /// /// 消息内容 /// 是否是有效的 JSON 格式 private static bool TryParseJsonSilent(string message) { if (string.IsNullOrWhiteSpace(message)) return false; char c = message.TrimStart()[0]; if (c != '{' && c != '[') return false; try { JsonDocument.Parse(message); return true; } catch { return false; } } /// /// 判断字节数组是否为 UTF-8 编码 /// /// /// 通过检查字节序列是否符合 UTF-8 多字节字符的编码规则。 /// /// 字节数组 /// 是否可能是 UTF-8 编码 private static bool IsLikelyUtf8(byte[] data) { int i = 0; while (i < data.Length) { byte b = data[i]; if (b <= 0x7F) { i++; continue; } // ASCII 字符 // 检查多字节字符 if (b >= 0xC2 && b <= 0xDF) // 2字节字符 { if (i + 1 >= data.Length) return false; if ((data[i + 1] & 0xC0) != 0x80) return false; i += 2; continue; } if (b >= 0xE0 && b <= 0xEF) // 3字节字符 { if (i + 2 >= data.Length) return false; if ((data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80) return false; i += 3; continue; } if (b >= 0xF0 && b <= 0xF4) // 4字节字符 { if (i + 3 >= data.Length) return false; if ((data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80) return false; i += 4; continue; } return false; } return true; } /// /// 接收完整消息(帧解析) /// /// /// 根据配置的头尾标识解析消息。 /// 如果未配置头尾,则一直读到数据不可用。 /// /// 网络流 /// 字符编码 /// 取消令牌 /// 接收到的消息 private async Task ReceiveFullMessageAsync(NetworkStream networkStream, Encoding encoding, CancellationToken cancellationToken) { var header = _options.MessageHeader ?? string.Empty; var footer = _options.MessageFooter ?? string.Empty; var buffer = new byte[1024]; var builder = new StringBuilder(); while (true) { // 读取数据 int bytesRead = await networkStream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken); if (bytesRead <= 0) { if (builder.Length == 0) return null; // 无头尾配置时,返回已有数据 return string.IsNullOrEmpty(header) && string.IsNullOrEmpty(footer) ? builder.ToString() : null; } builder.Append(encoding.GetString(buffer, 0, bytesRead)); // 如果没有配置头尾,且数据不可用,返回已有数据 if (string.IsNullOrEmpty(header) && string.IsNullOrEmpty(footer)) { if (!networkStream.DataAvailable) { break; } continue; } // 查找帧头 var data = builder.ToString(); var headerIndex = string.IsNullOrEmpty(header) ? 0 : data.IndexOf(header, StringComparison.Ordinal); if (headerIndex < 0) { continue; } // 提取帧内容 var startIndex = headerIndex + header.Length; var footerIndex = string.IsNullOrEmpty(footer) ? data.Length : data.IndexOf(footer, startIndex, StringComparison.Ordinal); if (footerIndex >= 0) { return data.Substring(startIndex, footerIndex - startIndex); } } return builder.ToString(); } } }