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 { } } } }