using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks.SocketServer
{
///
/// TCP Socket 服务器 - 核心类
///
///
/// 核心职责:
/// 1. 接受客户端 TCP 连接
/// 2. 管理客户端连接状态
/// 3. 接收和发送消息
/// 4. 处理设备注册
/// 5. 消息帧解析(支持头尾标识)
///
/// 服务器使用以下数据结构管理客户端:
/// - _clients: 客户端 ID 到 TcpClient 的映射
/// - _clientLocks: 客户端 ID 到信号量的映射(保证每个客户端的发送互斥)
/// - _deviceBindings: 设备 ID 到客户端 ID 的映射
/// - _clientEncodings: 客户端 ID 到编码的映射(支持自动编码检测)
/// - _clientLastActive: 客户端 ID 到最后活跃时间的映射
///
public partial class TcpSocketServer : IDisposable
{
///
/// 服务器配置选项
///
private readonly SocketServerOptions _options;
///
/// 同步根对象,用于线程同步
///
///
/// 在多线程访问共享数据结构时使用此对象进行同步。
/// 采用保守策略,确保线程安全。
///
public readonly object _syncRoot = new object();
///
/// TCP 监听器
///
private TcpListener? _listener;
///
/// 取消令牌源
///
///
/// 用于请求停止服务器的运行。
///
public CancellationTokenSource? _cts;
///
/// 客户端任务列表
///
///
/// 记录所有活跃客户端的处理任务。
///
public readonly List _clientTasks = new();
///
/// 客户端连接字典
///
///
/// Key: 客户端 ID(通常是 IP:Port)
/// Value: TcpClient 连接对象
///
public readonly Dictionary _clients = new();
///
/// 设备绑定字典
///
///
/// Key: 设备 ID
/// Value: 客户端 ID
/// 用于通过设备 ID 找到对应的客户端连接。
///
public readonly Dictionary _deviceBindings = new();
///
/// 客户端锁字典
///
///
/// 每个客户端一个 SemaphoreSlim,确保同一客户端的发送操作互斥。
///
public readonly Dictionary _clientLocks = new();
///
/// 客户端编码字典
///
///
/// 记录每个客户端使用的字符编码。
/// 支持自动检测:UTF-8 或 GBK。
///
public readonly Dictionary _clientEncodings = new();
///
/// 客户端最后活跃时间字典
///
///
/// 记录每个客户端最后一次活动的时间。
/// 用于空闲超时检测。
///
public readonly Dictionary _clientLastActive = new();
///
/// 默认文本编码
///
public readonly Encoding _textEncoding;
///
/// 自动检测的 GBK 编码
///
public readonly Encoding? _autoDetectedGb2312;
///
/// 日志文件路径
///
private readonly string _logFile;
///
/// 客户端监控任务
///
private Task? _monitorTask;
///
/// 服务器是否正在运行
///
public bool IsRunning { get; private set; }
///
/// 消息接收事件
///
///
/// 当服务器接收到消息时触发。
/// 参数:消息内容、是否 JSON 格式、TCP 客户端、机器人状态
///
public event Func>? MessageReceived;
///
/// 机器人连接断开事件
///
///
/// 当机器人客户端断开连接时触发。
/// 参数:客户端 ID
///
public event Func>? RobotReceived;
///
/// 构造函数
///
///
/// 使用指定的配置选项初始化 TcpSocketServer 实例。
/// 配置项包括:端口、字符编码、自动编码检测、日志文件路径等。
///
/// Socket 服务器配置选项
public TcpSocketServer(IOptions options)
{
_options = options.Value;
// 配置字符编码
if (_options.AutoDetectEncoding)
{
// 自动检测编码模式:默认 UTF-8,也支持 GBK
_textEncoding = Encoding.UTF8;
try { _autoDetectedGb2312 = Encoding.GetEncoding("GBK"); } catch { _autoDetectedGb2312 = null; }
}
else
{
// 指定编码模式
try { _textEncoding = Encoding.GetEncoding(_options.EncodingName ?? "utf-8"); }
catch { _textEncoding = Encoding.UTF8; }
_autoDetectedGb2312 = null;
}
// 配置日志文件路径
_logFile = Path.Combine(AppContext.BaseDirectory ?? ".", _options.LogFilePath ?? "socketserver.log");
Log($"[{DateTime.Now}] TcpSocketServer starting");
}
///
/// 记录日志
///
///
/// 将消息输出到控制台并写入日志文件。
///
/// 日志消息
private void Log(string message)
{
Console.WriteLine(message);
try { File.AppendAllText(_logFile, message + Environment.NewLine); } catch { }
}
}
}