| | |
| | | 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) |
| | |
| | | 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) |
| | | { |
| | |
| | | |
| | | if (MessageReceived != null) |
| | | { |
| | | try { _ = MessageReceived.Invoke(message, false, client, robotCrane); } catch { } |
| | | try |
| | | { |
| | | // 判断是否为 JSON 格式 |
| | | bool isJsonFormat = TryParseJsonSilent(message); |
| | | _ = MessageReceived.Invoke(message, isJsonFormat, client, robotCrane); |
| | | } |
| | | catch { } |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /// <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,")) |
| | |
| | | return true; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 更新客户端状态 |
| | | /// </summary> |
| | | /// <param name="clientId"></param> |
| | | /// <param name="message"></param> |
| | | private void UpdateClientStatus(string clientId, string message) |
| | | { |
| | | lock (_syncRoot) |
| | |
| | | } |
| | | } |
| | | |
| | | /// <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; |
| | |
| | | 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 |
| | |
| | | } |
| | | } |
| | | |
| | | /// <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; |
| | |
| | | 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; |
| | |
| | | } |
| | | 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(); |
| | | } |
| | | } |
| | | } |