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;
|
|
namespace WIDESEAWCS_Tasks.SocketServer
|
{
|
public partial class TcpSocketServer
|
{
|
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 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 { _ = MessageReceived.Invoke(message, false, client, robotCrane); } catch { }
|
}
|
}
|
}
|
finally
|
{
|
try { localCts?.Cancel(); localCts?.Dispose(); } catch { }
|
RemoveClient(clientId);
|
try { _ = RobotReceived.Invoke(clientId); } catch { }
|
}
|
}
|
}
|
|
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 data = enc.GetBytes(message + "\n");
|
await networkStream.WriteAsync(data, 0, data.Length, cancellationToken);
|
}
|
finally
|
{
|
if (sem != null) sem.Release();
|
}
|
}
|
|
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; }
|
}
|
|
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;
|
}
|
}
|
}
|