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();
}
}
}