wanshenmean
2026-03-26 8e42d0c1b7ae36cff2e7c69999117911a4b6f300
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
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();
        }
    }
}