You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.08 11:09:04