using System.Net.Sockets; using System.Text; using System.Text.Json; using System.IO; namespace WIDESEAWCS_Tasks.SocketServer { public partial class TcpSocketServer { /// /// Òì²½´¦ÀíÓëÒÑÁ¬½ÓµÄTCP¿Í»§¶ËµÄͨÐÅ£¬´¦Àí»úÆ÷ÈËÆðÖØ»ú»á»°ÖеĴ«ÈëÏûÏ¢ºÍ¿Í»§¶Ë״̬¸üС£ /// /// ´Ë·½·¨¹ÜÀí¿Í»§¶ËÁ¬½ÓµÄÉúÃüÖÜÆÚ£¬°üÀ¨¶ÁÈ¡ÏûÏ¢¡¢¸üпͻ§¶Ë״̬ºÍµ÷ÓÃÏà¹ØÊ¼þ¡£ /// µ±´¦Àí½áÊøÊ±£¬¿Í»§¶ËºÍÏà¹ØµÄÍøÂç×ÊÔ´½«±»ÊÍ·Å¡£Èç¹ûÆôÓÃÐÄÌø»ò¿ÕÏг¬Ê±Ñ¡Ï /// ½«Ó¦ÓöîÍâµÄÈ¡ÏûÂß¼­¡£Ê¼þµ÷ÓÃÆÚ¼äµÄÒì³£½«±»²¶»ñ²¢ÒÖÖÆ£¬ÒÔÈ·±£»á»°´¦ÀíµÄ³°ôÐÔ¡£ /// ±íʾҪ´¦ÀíµÄÔ¶³ÌÁ¬½ÓµÄ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); //message = await reader.ReadLineAsync().WaitAsync(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 { } } } } /// /// ³¢ÊÔ´¦ÀíÀ´×Ô¿Í»§¶ËµÄÉ豸ע²áÇëÇó¡£·µ»ØÒ»¸öֵָʾ¸ÃÏûÏ¢ÊÇ·ñ±»×÷Ϊע²áÇëÇó´¦Àí¡£ /// /// Èç¹ûÏûÏ¢ÊÇÓÐЧµÄ×¢²áÇëÇóÇÒ°üº¬·Ç¿ÕµÄÉ豸±êʶ·û£¬ /// Ôò½«É豸°ó¶¨µ½¿Í»§¶Ë²¢·¢ËÍÈ·ÈÏÐÅÏ¢¡£´Ë·½·¨²»»áÒòÎÞЧÏûÏ¢¶øÅ׳öÒì³££» /// Ëü½ö·µ»Ø false¡£ /// ¿Í»§¶ËÏûÏ¢µÄСд°æ±¾£¬ÓÃÓÚÅжÏÏûÏ¢ÊÇ·ñΪע²áÇëÇó¡£ /// °üº¬×¢²áÃüÁîºÍÉ豸±êʶ·ûµÄԭʼ¿Í»§¶ËÏûÏ¢¡£ /// ·¢ËÍ×¢²áÇëÇóµÄ¿Í»§¶ËµÄΨһ±êʶ·û¡£ /// Óë¿Í»§¶ËͨÐŵÄTCP¿Í»§¶ËÁ¬½Ó¡£ /// ¿ÉÓÃÓÚÈ¡Ïû×¢²á²Ù×÷µÄÈ¡ÏûÁîÅÆ¡£ /// Èç¹ûÏûÏ¢±»Ê¶±ð²¢×÷Ϊע²áÇëÇó´¦Àí£¬Ôò·µ»Ø true£»·ñÔò·µ»Ø false¡£ private bool TryHandleRegister(string messageLower, string message, string clientId, NetworkStream networkStream, CancellationToken cancellationToken) { if (!messageLower.StartsWith("register,")) { return false; } string deviceId = message.Substring("register,".Length).Trim(); if (!string.IsNullOrEmpty(deviceId)) { lock (_syncRoot) { _deviceBindings[deviceId] = clientId; } _ = WriteToClientAsync(clientId, networkStream, $"Registered,{deviceId}", cancellationToken); } return true; } /// /// ¸üпͻ§¶Ë״̬ /// /// /// private void UpdateClientStatus(string clientId, string message) { lock (_syncRoot) { _clientLastActive[clientId] = DateTime.Now; if (!_clientEncodings.ContainsKey(clientId)) { if (_options.AutoDetectEncoding && _autoDetectedGb2312 != null) { 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¸ñʽ³¢ÊÔ½âÎö£¨¾²Ä¬Ê§°Ü£© /// /// /// 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 ¿ÉÄÜÐÔ¼ì²â /// /// /// private static bool IsLikelyUtf8(byte[] data) { int i = 0; while (i < data.Length) { byte b = data[i]; if (b <= 0x7F) { i++; continue; } if (b >= 0xC2 && b <= 0xDF) { if (i + 1 >= data.Length) return false; if ((data[i + 1] & 0xC0) != 0x80) return false; i += 2; continue; } if (b >= 0xE0 && b <= 0xEF) { 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) { 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(); } } }