C# TCP服务端如何存储多TcpClient连接并选取指定连接
多TcpClient连接管理实现方案
核心架构调整说明
首先放弃普通List<TcpClient>或非线程安全的Dictionary,直接使用线程安全的ConcurrentDictionary<TKey, TValue>存储客户端连接,避免多线程读写时的并发异常。你需要先做几个基础调整:
- 定义自定义客户端信息类,统一存储连接相关的所有数据,避免分散管理
- 修复现有代码的闭包陷阱、共用缓冲区的bug
- 增加连接断开后的自动清理逻辑,避免内存泄漏
具体代码实现
1. 定义客户端信息类
public class ClientInfo { // 客户端唯一ID,也可以用远程终结点字符串当ID方便识别 public string Id { get; set; } public TcpClient Client { get; set; } public DateTime ConnectTime { get; set; } // 每个客户端独立的缓冲区,避免多线程共用冲突 public byte[] Buffer { get; set; } = new byte[256]; }
2. 服务端全局存储声明
// 线程安全字典,键为客户端ID,值为客户端信息 private static ConcurrentDictionary<string, ClientInfo> _clientDict = new ConcurrentDictionary<string, ClientInfo>();
3. 修改服务端监听逻辑
while(true) { Log.Write("Waiting for connection..."); TcpClient client = server.AcceptTcpClient(); // 生成唯一客户端ID,也可以替换为 client.Client.RemoteEndPoint.ToString() string clientId = Guid.NewGuid().ToString("N"); var clientInfo = new ClientInfo { Id = clientId, Client = client, ConnectTime = DateTime.Now }; // 存入字典 _clientDict.TryAdd(clientId, clientInfo); Log.Success($"客户端 {clientId} 已接入,当前在线客户端数:{_clientDict.Count}"); // 修复闭包陷阱:把clientInfo作为参数传入线程,不要直接引用循环变量 Thread t = new Thread(() => ConnectClient(clientInfo)); t.Start(); }
4. 修改客户端处理逻辑
public static void ConnectClient(ClientInfo clientInfo) { string _data = null; TcpClient _client = clientInfo.Client; NetworkStream stream = _client.GetStream(); int i; try { while((i = stream.Read(clientInfo.Buffer, 0, clientInfo.Buffer.Length))!=0) { _data = Encoding.ASCII.GetString(clientInfo.Buffer,0,i); Log.Write($"客户端 {clientInfo.Id} 发送:{_data}", ConsoleColor.Cyan); // 原逻辑:转大写返回给客户端 _data = _data.ToUpper(); byte[] msg = Encoding.ASCII.GetBytes(_data); stream.Write(msg,0,msg.Length); Log.Write($"已回复客户端 {clientInfo.Id}:{_data}"); } } catch (Exception ex) { Log.Warn($"客户端 {clientInfo.Id} 连接异常:{ex.Message}"); } finally { // 连接断开后自动从字典移除 _clientDict.TryRemove(clientInfo.Id, out _); _client.Close(); Log.Warn($"客户端 {clientInfo.Id} 已断开,当前在线客户端数:{_clientDict.Count}"); } }
5. 实现指定客户端操作功能
如果要给指定ID的客户端发消息,只需要从字典中取出对应的ClientInfo即可,示例服务端控制台操作逻辑:
// 示例:服务端控制台输入 send 客户端ID 内容 即可定向发消息 while (true) { string cmd = Console.ReadLine(); if (cmd.StartsWith("send ")) { var parts = cmd.Split(' ', 3); if (parts.Length == 3) { string targetId = parts[1]; string content = parts[2]; if (_clientDict.TryGetValue(targetId, out var targetClient)) { var msg = Encoding.ASCII.GetBytes(content); targetClient.Client.GetStream().Write(msg, 0, msg.Length); Log.Success($"已向客户端 {targetId} 发送消息:{content}"); } else { Log.Warn("指定客户端不存在"); } } } }
注意事项
- 不要在多线程环境下使用非线程安全的集合类,否则会出现随机的读写异常、数据错乱
- 每个连接的缓冲区要独立,不要共用全局的byte数组,避免多个连接的数据互相覆盖
- 所有IO操作都要加异常捕获,避免单个客户端断开导致整个服务端崩溃
- 如果需要更高性能,可以把Thread替换成Task,或者使用异步IO(
ReadAsync/WriteAsync)减少线程开销
内容的提问来源于stack exchange,提问作者NotVerySmart
相关产品推荐
相关产品推荐

