using System.Net;
|
using System.Net.Sockets;
|
using System.Text;
|
using System.Text.Json;
|
using System.IO;
|
using WIDESEAWCS_Core.LogHelper;
|
|
namespace WIDESEAWCS_Tasks.SocketServer
|
{
|
public partial class TcpSocketServer
|
{
|
/// <summary>
|
/// 异步启动 TCP Socket 服务器
|
/// </summary>
|
/// <remarks>
|
/// 创建 TCP 监听器并开始接受客户端连接。
|
/// 如果服务器已在运行或被禁用,直接返回。
|
/// 启动后启动接受循环和客户端监控任务。
|
/// </remarks>
|
/// <param name="cancellationToken">取消令牌</param>
|
/// <returns>启动任务</returns>
|
public Task StartAsync(CancellationToken cancellationToken)
|
{
|
if (IsRunning || !_options.Enabled)
|
{
|
return Task.CompletedTask;
|
}
|
|
// 解析监听地址
|
IPAddress ipAddress = IPAddress.Any;
|
if (IPAddress.TryParse(_options.IpAddress, out IPAddress? parsedAddress))
|
{
|
ipAddress = parsedAddress;
|
}
|
|
// 创建监听器
|
_listener = new TcpListener(ipAddress, _options.Port);
|
_listener.Start(_options.Backlog);
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
IsRunning = true;
|
|
// 启动接受客户端连接循环
|
_ = AcceptLoopAsync(_cts.Token);
|
|
// 启动客户端监控任务(检查空闲超时)
|
_monitorTask = Task.Run(() => MonitorClientsAsync(_cts.Token));
|
|
return Task.CompletedTask;
|
}
|
|
/// <summary>
|
/// 异步停止 TCP Socket 服务器
|
/// </summary>
|
/// <remarks>
|
/// 停止接受新连接,等待所有客户端任务完成。
|
/// </remarks>
|
/// <param name="cancellationToken">取消令牌</param>
|
/// <returns>停止任务</returns>
|
public async Task StopAsync(CancellationToken cancellationToken)
|
{
|
if (!IsRunning)
|
{
|
return;
|
}
|
|
// 发送取消信号
|
_cts?.Cancel();
|
|
// 停止监听
|
_listener?.Stop();
|
|
// 等待所有客户端任务完成
|
Task[] tasks;
|
lock (_syncRoot)
|
{
|
tasks = _clientTasks.ToArray();
|
}
|
|
if (tasks.Length > 0)
|
{
|
await Task.WhenAll(tasks);
|
}
|
|
IsRunning = false;
|
}
|
|
/// <summary>
|
/// 异步接受客户端连接的主循环
|
/// </summary>
|
/// <summary>
|
/// 异步接受客户端连接的主循环
|
/// </summary>
|
/// <remarks>
|
/// 在后台线程中持续接受新的客户端连接。
|
/// 当有新连接时,将其添加到客户端字典并启动消息处理任务。
|
/// </remarks>
|
/// <param name="cancellationToken">取消令牌</param>
|
private async Task AcceptLoopAsync(CancellationToken cancellationToken)
|
{
|
while (!cancellationToken.IsCancellationRequested)
|
{
|
TcpClient? client = null;
|
try
|
{
|
// 等待客户端连接
|
client = await _listener!.AcceptTcpClientAsync().WaitAsync(cancellationToken);
|
QuartzLogger.Info($"客户端连接:{client.Client.RemoteEndPoint.ToString()}");
|
}
|
catch (OperationCanceledException) { break; }
|
catch (ObjectDisposedException) { break; }
|
catch
|
{
|
if (cancellationToken.IsCancellationRequested) break;
|
}
|
|
if (client == null) continue;
|
|
// 生成客户端 ID(使用远程端点地址)
|
string clientId = GetClientId(client);
|
|
// 添加到客户端字典
|
lock (_syncRoot)
|
{
|
_clients[clientId] = client;
|
_clientLocks[clientId] = new SemaphoreSlim(1, 1);
|
}
|
}
|
}
|
|
/// <summary>
|
/// 移除客户端连接
|
/// </summary>
|
/// <remarks>
|
/// 关闭客户端连接并清理相关资源:
|
/// - 关闭 TcpClient
|
/// - 释放信号量
|
/// - 移除活跃时间和编码记录
|
/// - 移除设备绑定
|
/// </remarks>
|
/// <param name="clientId">要移除的客户端唯一标识</param>
|
private void RemoveClient(string clientId)
|
{
|
lock (_syncRoot)
|
{
|
// 关闭并移除客户端连接
|
if (_clients.TryGetValue(clientId, out var client))
|
{
|
try { client.Close(); } catch { }
|
_clients.Remove(clientId);
|
}
|
|
// 释放信号量
|
if (_clientLocks.TryGetValue(clientId, out var sem))
|
{
|
_clientLocks.Remove(clientId);
|
sem.Dispose();
|
}
|
|
// 移除活跃时间记录
|
_clientLastActive.Remove(clientId);
|
|
// 移除编码记录
|
_clientEncodings.Remove(clientId);
|
|
// 移除设备绑定
|
var deviceIds = _deviceBindings.Where(kv => kv.Value == clientId).Select(kv => kv.Key).ToList();
|
foreach (var deviceId in deviceIds)
|
{
|
_deviceBindings.Remove(deviceId);
|
}
|
}
|
}
|
|
/// <summary>
|
/// 异步监控客户端空闲超时
|
/// </summary>
|
/// <remarks>
|
/// 定期检查所有客户端的最后活跃时间,
|
/// 如果超过空闲超时时间,断开该客户端连接。
|
/// </remarks>
|
/// <param name="cancellationToken">取消令牌</param>
|
private async Task MonitorClientsAsync(CancellationToken cancellationToken)
|
{
|
while (!cancellationToken.IsCancellationRequested)
|
{
|
try
|
{
|
List<string> toRemove = new();
|
lock (_syncRoot)
|
{
|
foreach (var kv in _clientLastActive)
|
{
|
// 检查是否超过空闲超时
|
if (_options.IdleTimeoutSeconds > 0 && DateTime.Now - kv.Value > TimeSpan.FromSeconds(_options.IdleTimeoutSeconds))
|
{
|
toRemove.Add(kv.Key);
|
}
|
}
|
}
|
|
// 断开超时的客户端
|
foreach (var cid in toRemove)
|
{
|
RemoveClient(cid);
|
Log($"[{DateTime.Now}] TcpSocketServer disconnect idle client {cid}");
|
}
|
}
|
catch { }
|
|
// 每秒检查一次
|
try { await Task.Delay(1000, cancellationToken); } catch { }
|
}
|
}
|
|
/// <summary>
|
/// 获取客户端唯一标识
|
/// </summary>
|
/// <remarks>
|
/// 使用客户端的远程端点地址作为标识。
|
/// 如果远程端点不可用,生成随机 GUID。
|
/// </remarks>
|
/// <param name="client">TCP 客户端</param>
|
/// <returns>客户端标识字符串</returns>
|
public static string GetClientId(TcpClient client)
|
{
|
return client.Client.RemoteEndPoint?.ToString() ?? Guid.NewGuid().ToString();
|
}
|
}
|
}
|