C# 设计TCP Server

C# 完整 TCP Server 详细实现(多线程/异步两种方案)

方案1:Async 异步高性能TCP服务(推荐,.NET Core/.NET 5+/Framework通用)

基于 Socket 异步API,支持多客户端并发、消息分包处理、心跳检测、优雅断开,工业/服务端标准写法。

完整服务端代码

csharp 复制代码
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TcpAsyncServer
{
    /// <summary>
    /// TCP客户端会话封装
    /// </summary>
    public class TcpClientSession
    {
        #region 基础属性
        public Socket ClientSocket { get; }
        public string ClientIp { get; }
        public int ClientPort { get; }
        public DateTime ConnectTime { get; }
        private readonly byte[] _buffer;
        private const int BufferSize = 4096; // 缓冲区4K,可按需调整
        private readonly TcpAsyncServer _server;
        #endregion

        public TcpClientSession(Socket clientSocket, TcpAsyncServer server)
        {
            ClientSocket = clientSocket;
            _server = server;
            var ep = (IPEndPoint)clientSocket.RemoteEndPoint;
            ClientIp = ep.Address.ToString();
            ClientPort = ep.Port;
            ConnectTime = DateTime.Now;
            _buffer = new byte[BufferSize];
            Console.WriteLine($"【新客户端接入】{ClientIp}:{ClientPort}");
        }

        #region 异步接收消息循环
        public async Task StartRecvLoopAsync()
        {
            try
            {
                while (ClientSocket.Connected)
                {
                    // 异步读取数据
                    int readLen = await ClientSocket.ReceiveAsync(new ArraySegment<byte>(_buffer), SocketFlags.None);
                    if (readLen <= 0)
                    {
                        // 客户端主动关闭连接
                        break;
                    }

                    // 截取有效数据
                    byte[] recvData = new byte[readLen];
                    Array.Copy(_buffer, recvData, readLen);
                    // 处理收到的消息(业务逻辑入口)
                    await ProcessRecvDataAsync(recvData);
                }
            }
            catch (SocketException ex)
            {
                Console.WriteLine($"【客户端异常断开】{ClientIp}:{ClientPort} 错误:{ex.Message}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"【接收数据异常】{ClientIp}:{ClientPort} {ex}");
            }
            finally
            {
                // 释放资源,从服务端会话池移除
                Disconnect();
            }
        }
        #endregion

        #region 业务数据处理逻辑
        private async Task ProcessRecvDataAsync(byte[] data)
        {
            // 示例:UTF8字符串解析,可替换为自定义协议(Modbus/私有包头长度协议)
            string recvStr = Encoding.UTF8.GetString(data);
            Console.WriteLine($"【收到{ClientIp}:{ClientPort}】{recvStr}");

            // 回显测试:原样返回客户端
            byte[] response = Encoding.UTF8.GetBytes($"服务端已收到:{recvStr}");
            await SendDataAsync(response);
        }
        #endregion

        #region 异步发送数据
        public async Task SendDataAsync(byte[] data)
        {
            if (!ClientSocket.Connected || data == null || data.Length == 0)
                return;
            try
            {
                await ClientSocket.SendAsync(new ArraySegment<byte>(data), SocketFlags.None);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"【发送数据失败】{ClientIp}:{ClientPort} {ex.Message}");
                Disconnect();
            }
        }
        #endregion

        #region 断开连接释放资源
        public void Disconnect()
        {
            if (!ClientSocket.Connected) return;
            try
            {
                ClientSocket.Shutdown(SocketShutdown.Both);
            }
            catch { }
            ClientSocket.Close();
            _server.RemoveSession(this);
            Console.WriteLine($"【客户端断开】{ClientIp}:{ClientPort} 在线时长:{(DateTime.Now - ConnectTime).TotalSeconds:F2}秒");
        }
    }

    /// <summary>
    /// TCP服务端主类
    /// </summary>
    public class TcpAsyncServer
    {
        #region 配置与字段
        private Socket _listenSocket;
        private readonly int _port;
        private readonly IPAddress _listenIp;
        // 线程安全存储所有在线客户端会话
        private readonly ConcurrentDictionary<TcpClientSession, bool> _clientSessions = new ConcurrentDictionary<TcpClientSession, bool>();
        private CancellationTokenSource _cts;
        public bool IsRunning => _listenSocket != null && _listenSocket.IsBound;
        #endregion

        /// <summary>
        /// 构造TCP服务
        /// </summary>
        /// <param name="listenIp">监听IP,IPAddress.Any监听所有网卡</param>
        /// <param name="port">监听端口</param>
        public TcpAsyncServer(IPAddress listenIp, int port)
        {
            _listenIp = listenIp;
            _port = port;
        }

        #region 启动服务
        public async Task StartServerAsync()
        {
            if (IsRunning)
            {
                Console.WriteLine("服务已在运行,无需重复启动");
                return;
            }

            _cts = new CancellationTokenSource();
            // 创建监听Socket,IPv4,流式TCP
            _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            // 端口复用,快速重启服务不占用端口
            _listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _listenSocket.Bind(new IPEndPoint(_listenIp, _port));
            // 最大挂起连接数100,可调整
            _listenSocket.Listen(100);

            Console.WriteLine($"=====================================");
            Console.WriteLine($"TCP异步服务启动成功");
            Console.WriteLine($"监听地址:{_listenIp}:{_port}");
            Console.WriteLine($"=====================================");

            // 持续异步接受客户端连接
            _ = AcceptClientLoopAsync(_cts.Token);
            await Task.CompletedTask;
        }
        #endregion

        #region 循环接受客户端连接
        private async Task AcceptClientLoopAsync(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                try
                {
                    // 异步等待客户端连接
                    Socket clientSocket = await _listenSocket.AcceptAsync(token);
                    // 创建会话对象
                    TcpClientSession session = new TcpClientSession(clientSocket, this);
                    // 加入会话池
                    _clientSessions.TryAdd(session, true);
                    // 启动该客户端的接收循环(异步后台运行)
                    _ = session.StartRecvLoopAsync();
                }
                catch (OperationCanceledException)
                {
                    // 服务停止,正常退出循环
                    break;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"【接受客户端异常】{ex.Message}");
                    await Task.Delay(100); // 异常短暂休眠防止死循环
                }
            }
        }
        #endregion

        #region 会话池管理
        public void RemoveSession(TcpClientSession session)
        {
            _clientSessions.TryRemove(session, out _);
            Console.WriteLine($"当前在线客户端总数:{_clientSessions.Count}");
        }

        /// <summary>
        /// 广播消息给所有在线客户端
        /// </summary>
        public async Task BroadcastAllAsync(byte[] data)
        {
            foreach (var session in _clientSessions.Keys)
            {
                await session.SendDataAsync(data);
            }
        }
        #endregion

        #region 停止服务,释放全部资源
        public void StopServer()
        {
            if (!IsRunning) return;
            // 取消监听循环
            _cts?.Cancel();
            // 关闭监听Socket
            _listenSocket?.Close();
            _listenSocket = null;
            // 断开所有在线客户端
            foreach (var session in _clientSessions.Keys)
            {
                session.Disconnect();
            }
            _clientSessions.Clear();
            _cts?.Dispose();
            Console.WriteLine("TCP服务已完全停止");
        }
        #endregion
    }

    // 程序入口测试
    class Program
    {
        static async Task Main(string[] args)
        {
            // 监听本机所有网卡,端口8899
            var tcpServer = new TcpAsyncServer(IPAddress.Any, 8899);
            await tcpServer.StartServerAsync();

            Console.WriteLine("输入exit关闭服务");
            while (true)
            {
                string input = Console.ReadLine();
                if (input.Equals("exit", StringComparison.OrdinalIgnoreCase))
                {
                    tcpServer.StopServer();
                    break;
                }
                // 输入其他内容可作为广播消息
                byte[] broadcastData = Encoding.UTF8.GetBytes($"服务端广播:{input}");
                _ = tcpServer.BroadcastAllAsync(broadcastData);
            }
        }
    }
}

方案2:传统TcpListener同步多线程(简单入门,适合小型工具)

封装简单,逻辑直观,适合新手学习基础TCP流程

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace TcpSyncServerDemo
{
    class SyncTcpServer
    {
        private readonly TcpListener _listener;
        private bool _isRun = false;
        private readonly List<TcpClient> _clientList = new List<TcpClient>();
        private readonly object _lockObj = new object();

        public SyncTcpServer(int port)
        {
            _listener = new TcpListener(IPAddress.Any, port);
        }

        public void Start()
        {
            if (_isRun) return;
            _isRun = true;
            _listener.Start();
            Console.WriteLine($"同步TCP服务启动,端口:{((IPEndPoint)_listener.LocalEndpoint).Port}");
            // 开启新线程循环接收客户端
            Thread acceptThread = new Thread(AcceptClientLoop);
            acceptThread.IsBackground = true;
            acceptThread.Start();
        }

        private void AcceptClientLoop()
        {
            while (_isRun)
            {
                try
                {
                    TcpClient client = _listener.AcceptTcpClient();
                    lock (_lockObj)
                    {
                        _clientList.Add(client);
                    }
                    IPEndPoint clientEp = (IPEndPoint)client.Client.RemoteEndPoint;
                    Console.WriteLine($"客户端接入 {clientEp.Address}:{clientEp.Port}");
                    // 每个客户端单独线程处理收发
                    Thread clientThread = new Thread(ClientDataHandle);
                    clientThread.IsBackground = true;
                    clientThread.Start(client);
                }
                catch
                {
                    if (!_isRun) break;
                }
            }
        }

        private void ClientDataHandle(object obj)
        {
            TcpClient client = obj as TcpClient;
            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[4096];
            int readLen;
            try
            {
                while ((readLen = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    string msg = Encoding.UTF8.GetString(buffer, 0, readLen);
                    Console.WriteLine($"收到消息:{msg}");
                    // 回发数据
                    byte[] resp = Encoding.UTF8.GetBytes($"服务端应答:{msg}");
                    stream.Write(resp, 0, resp.Length);
                }
            }
            catch
            {
                IPEndPoint ep = (IPEndPoint)client.Client.RemoteEndPoint;
                Console.WriteLine($"客户端断开 {ep.Address}:{ep.Port}");
            }
            finally
            {
                lock (_lockObj)
                {
                    _clientList.Remove(client);
                }
                client.Close();
            }
        }

        public void Stop()
        {
            _isRun = false;
            _listener.Stop();
            lock (_lockObj)
            {
                foreach (var client in _clientList)
                {
                    client.Close();
                }
                _clientList.Clear();
            }
            Console.WriteLine("服务已停止");
        }

        static void Main(string[] args)
        {
            SyncTcpServer server = new SyncTcpServer(8899);
            server.Start();
            Console.WriteLine("输入stop关闭服务");
            while (Console.ReadLine() != "stop") { }
            server.Stop();
        }
    }
}

关键功能说明

1. 异步方案优势(生产推荐)

  1. IO多路复用:异步Socket无阻塞等待,上万客户端连接仅消耗少量线程
  2. 线程安全会话池ConcurrentDictionary 管理在线客户端,支持广播群发
  3. 资源自动释放:客户端异常/主动断开自动回收Socket,无句柄泄漏
  4. 可扩展私有协议ProcessRecvDataAsync 可修改为 包头+长度+数据 工业标准协议

2. 核心扩展改造点

(1)自定义分包协议(解决TCP粘包)

TCP是流式协议,多条消息会粘连,推荐固定4字节包头标识数据长度

csharp 复制代码
// 发送时封装包头
byte[] body = Encoding.UTF8.GetBytes("测试消息");
byte[] header = BitConverter.GetBytes(body.Length);
byte[] sendAll = header.Concat(body).ToArray();
await session.SendDataAsync(sendAll);

// 接收时解析长度,完整读取一条消息

(2)心跳检测

StartRecvLoopAsync 增加超时计时,长时间无数据主动断开无效连接。

(3)日志替换

代码中Console.WriteLine可替换为log4net/NLog日志组件,落地日志文件。

3. 运行测试

  1. 编译运行程序,监听端口8899
  2. 测试工具:NetAssist、Telnet、自己写C# TcpClient客户端
  3. 发送字符串,服务端自动回显,输入exit关闭服务

4. 跨版本兼容

  • .NET Framework 4.5+:完全支持异步Socket API
  • .NET Core 3.1 / .NET5/6/7/8:原生支持,无兼容性问题
  • 可部署Windows服务、Linux(.NET跨平台)
相关推荐
程序喵大人2 小时前
【C++进阶】STL容器与迭代器 - 05 map 和 set 为什么按键保持有序
开发语言·c++·容器·迭代器·stl
完美火龙篇 四月的友2 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
开发语言·php
冻柠檬飞冰走茶2 小时前
PTA基础编程题目集 7-15 计算圆周率(C语言实现)
c语言·开发语言·数据结构·算法
用户298698530142 小时前
在 PC 上将 HTML 转换为 PDF 的3种有效方法
人工智能·c#·html
前端炒粉2 小时前
简易实现ssr
开发语言·前端·javascript
库克克3 小时前
【C++】 unordered_map 与unordered_set
开发语言·c++
huainingning3 小时前
华三瘦AP切换为胖AP并配置无线功能
运维·网络·网络协议·tcp/ip
雪的季节3 小时前
人工智能 AI 5问
开发语言
李迟4 小时前
一种轻量级C++ CSV文件读写库的实现方案
开发语言·c++
小园子的小菜4 小时前
Java 并发编程:线程安全队列全解 —— 阻塞与非阻塞实现原理及源码深度剖析
java·开发语言