From d8a9b76a6bb2824c1e9fb0d17938c926472dd78b Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期二, 03 二月 2026 13:44:27 +0800
Subject: [PATCH] 支持帧消息协议并改进 TCP 服务器

---
 Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs |  131 +++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 123 insertions(+), 8 deletions(-)

diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs
index 9f186e4..a0b8a91 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Messaging.cs
@@ -1,16 +1,23 @@
-using HslCommunication.Core.IMessage;
-using System;
 using System.Net.Sockets;
 using System.Text;
 using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using WIDESEAWCS_QuartzJob;
+using System.IO;
 
 namespace WIDESEAWCS_Tasks.SocketServer
 {
     public partial class TcpSocketServer
     {
+        /// <summary>
+        /// 异步处理与已连接的TCP客户端的通信,处理机器人起重机会话中的传入消息和客户端状态更新。
+        /// </summary>
+        /// <remarks>此方法管理客户端连接的生命周期,包括读取消息、更新客户端状态和调用相关事件。
+        /// 当处理结束时,客户端和相关的网络资源将被释放。如果启用心跳或空闲超时选项,
+        /// 将应用额外的取消逻辑。事件调用期间的异常将被捕获并抑制,以确保会话处理的鲁棒性。</remarks>
+        /// <param name="client">表示要处理的远程连接的TCP客户端。方法完成后将释放此对象。</param>
+        /// <param name="clientId">已连接客户端的唯一标识符。用于在整个会话中跟踪和更新客户端状态。</param>
+        /// <param name="cancellationToken">可用于取消客户端处理操作的取消令牌。如果请求取消,方法将立即终止处理。</param>
+        /// <param name="robotCrane">表示与客户端关联的机器人起重机的当前状态对象。用于为消息处理和事件调用提供上下文。</param>
+        /// <returns>表示处理客户端连接的异步操作的任务。当客户端断开连接或请求取消时任务完成。</returns>
         public async Task HandleClientAsync(TcpClient client, string clientId, CancellationToken cancellationToken, RobotSocketState robotCrane)
         {
             using (client)
@@ -32,7 +39,8 @@
                         try
                         {
                             var ct = localCts?.Token ?? cancellationToken;
-                            message = await reader.ReadLineAsync().WaitAsync(ct);
+                            message = await ReceiveFullMessageAsync(networkStream, _textEncoding, ct);
+                            //message = await reader.ReadLineAsync().WaitAsync(ct);
                         }
                         catch (OperationCanceledException)
                         {
@@ -55,7 +63,13 @@
 
                         if (MessageReceived != null)
                         {
-                            try { _ = MessageReceived.Invoke(message, false, client, robotCrane); } catch { }
+                            try
+                            {
+                                // 判断是否为 JSON 格式
+                                bool isJsonFormat = TryParseJsonSilent(message);
+                                _ = MessageReceived.Invoke(message, isJsonFormat, client, robotCrane);
+                            }
+                            catch { }
                         }
                     }
                 }
@@ -68,6 +82,18 @@
             }
         }
 
+        /// <summary>
+        /// 尝试处理来自客户端的设备注册请求。返回一个值指示该消息是否被作为注册请求处理。
+        /// </summary>
+        /// <remarks>如果消息是有效的注册请求且包含非空的设备标识符,
+        /// 则将设备绑定到客户端并发送确认信息。此方法不会因无效消息而抛出异常;
+        /// 它仅返回 false。</remarks>
+        /// <param name="messageLower">客户端消息的小写版本,用于判断消息是否为注册请求。</param>
+        /// <param name="message">包含注册命令和设备标识符的原始客户端消息。</param>
+        /// <param name="clientId">发送注册请求的客户端的唯一标识符。</param>
+        /// <param name="client">与客户端通信的TCP客户端连接。</param>
+        /// <param name="cancellationToken">可用于取消注册操作的取消令牌。</param>
+        /// <returns>如果消息被识别并作为注册请求处理,则返回 true;否则返回 false。</returns>
         private bool TryHandleRegister(string messageLower, string message, string clientId, NetworkStream networkStream, CancellationToken cancellationToken)
         {
             if (!messageLower.StartsWith("register,"))
@@ -89,6 +115,11 @@
             return true;
         }
 
+        /// <summary>
+        /// 更新客户端状态
+        /// </summary>
+        /// <param name="clientId"></param>
+        /// <param name="message"></param>
         private void UpdateClientStatus(string clientId, string message)
         {
             lock (_syncRoot)
@@ -110,6 +141,14 @@
             }
         }
 
+        /// <summary>
+        /// 写入消息到客户端
+        /// </summary>
+        /// <param name="clientId"></param>
+        /// <param name="networkStream"></param>
+        /// <param name="message"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
         private async Task WriteToClientAsync(string clientId, NetworkStream networkStream, string message, CancellationToken cancellationToken)
         {
             SemaphoreSlim? sem = null;
@@ -125,7 +164,8 @@
             if (sem != null) await sem.WaitAsync(cancellationToken);
             try
             {
-                var data = enc.GetBytes(message + "\n");
+                var framedMessage = BuildFramedMessage(message);
+                var data = enc.GetBytes(framedMessage);
                 await networkStream.WriteAsync(data, 0, data.Length, cancellationToken);
             }
             finally
@@ -134,6 +174,23 @@
             }
         }
 
+        /// <summary>
+        /// 添加消息帧头尾
+        /// </summary>
+        /// <param name="message"></param>
+        /// <returns></returns>
+        private string BuildFramedMessage(string message)
+        {
+            var header = _options.MessageHeader ?? string.Empty;
+            var footer = _options.MessageFooter ?? string.Empty;
+            return header + (message ?? string.Empty) + footer;
+        }
+
+        /// <summary>
+        /// JSON格式尝试解析(静默失败)
+        /// </summary>
+        /// <param name="message"></param>
+        /// <returns></returns>
         private static bool TryParseJsonSilent(string message)
         {
             if (string.IsNullOrWhiteSpace(message)) return false;
@@ -142,6 +199,11 @@
             try { JsonDocument.Parse(message); return true; } catch { return false; }
         }
 
+        /// <summary>
+        /// utf-8 可能性检测
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
         private static bool IsLikelyUtf8(byte[] data)
         {
             int i = 0;
@@ -171,5 +233,58 @@
             }
             return true;
         }
+
+        /// <summary>
+        /// 读取完整消息
+        /// </summary>
+        /// <param name="networkStream">字节流</param>
+        /// <param name="encoding">编码格式</param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        private async Task<string?> 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();
+        }
     }
 }
\ No newline at end of file

--
Gitblit v1.9.3