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