C#中使用Socket实现TCP服务端的方法与步骤

 更新时间:2026年06月28日 14:03:03   作者:十贺  
在C#中,Socket TCP通信是一种面向连接的、可靠的网络通信协议,常用于在不同计算机的应用程序之间建立稳定的数据传输通道,本文将详细介绍C#中TCP服务端的实现方法与步骤,需要的朋友可以参考下

概要

在C#中,Socket TCP通信是一种面向连接的、可靠的网络通信协议,常用于在不同计算机的应用程序之间建立稳定的数据传输通道。本文将详细介绍C#中TCP服务端的实现方法与步骤。
我会先分段讲解代码, 然后最后发出完整代码

实现服务端的准备

首先我们需要一个客户端类(ClientSocket)

  • ClientSocket 类封装了与服务端建立连接的远程客户端套接字。
  • 每个实例在构造时被分配一个自增的唯一 clientID,并持有对应的 Socket 对象。
  • 它对外提供了连接状态检查、
    消息发送到客户端
    从客户端接收消息
    连接关闭
    等基本操作
  • 简而言之,它是服务端用来管理和操作单一客户端连接与通信的抽象。

服务端变量

  • 创建服务端Socket socket
  • 因为要接受多个客户端连接, 用字典保存连接到的客户端 <int, ClientSocket>clientDic
  • 定期清理已断链客户端, 用列表保存, 最后处理 delList
  • 是否关闭服务端, 默认为true 关闭
  • 准备一个待清理的客户端字典 , 在最后进行处理
  • 定期进行连接检查
  • 我们正是通过这些实现多个客户端连接

客户端类的准备 (注意是客户端类 ,并不是客户端代码)

客户端的四个参数

  • 全局的ID
  • 客户端的ID
  • 客户端的套接字
  • 当前的连接状态
    实例化新的客户端类,全局ID会自增, 赋值给客户端
 internal class ClientSocket
 {
     private static int CLIENT_BEGIN_ID = 1;
     public int clientID;
     public Socket socket;
     public bool Connected => socket.Connected;
  //用于处理分包时 缓存的 字节数组 和 字节数组长度
  private byte[] cacheBytes = new byte[1024 * 1024];
  private int cacheNum = 0;

  //上一次收到消息的时间
  private long frontTime = -1;
  //超时时间
  private static int TIME_OUT_TIME = 10;
     public ClientSocket(Socket socket) { 
         this.clientID = CLIENT_BEGIN_ID;
         this.socket = socket;
         ++CLIENT_BEGIN_ID;
     }

关闭连接

 public void Close()
 {
     if(socket != null)
     {
         socket.Shutdown(SocketShutdown.Both);
         socket.Close();
         socket = null;
     }
 }

心跳消息判断客户端是否处于连接

  • 检查 frontTime 是否为 -1,若是则跳过(未收到过消息)
  • 获取当前时间(秒)
  • 计算距上次收到消息的时间差
  • 判断差值是否达到超时阈值 TIME_OUT_TIME
  • 超时则调用 Program.socket.AddDelSocket(this) 标记断开
    未超时则不做处理

DateTime.Now.Ticks 获取当前时间的刻度数
TimeSpan.TicksPerSecond 常量 用于将 Tick 转为秒
Program.socket.AddDelSocket(this) 标记本客户端为待断开,加入清理队列

       /// <summary>
       /// 间隔一段时间 检测一次超时 如果超时 就会主动断开该客户端的连接
       /// </summary>
       /// <param name="obj"></param>
       private void CheckTimeOut(/*object obj*/)
       {
               if (frontTime != -1 &&
               DateTime.Now.Ticks / TimeSpan.TicksPerSecond - frontTime >= TIME_OUT_TIME)
               {
                   Program.socket.AddDelSocket(this);
               }
       }

发送信息到客户端

将消息序列化后,通过该客户端的 Socket 发送给客户端。
BaseMsg 是作者自定义类型, 是一个包含了很多序列化方法的类,
其中 Writing() 方法, 是类中自定义的将类序列化为字节数组的方法.
Send 流程

  • 检查 Connected 状态
  • 调用 info.Writing() 将消息对象转为字节数组
  • 执行 socket.Send(…) 发送数据
  • 若出现异常或连接已断开,则将该客户端加入待删除列表
 public void Send(BaseMsg info)
 {
     if (Connected)
     {
         try
         {
             socket.Send(info.Writing());
         }
         catch (Exception e)
         {
             Console.WriteLine("发消息出错" + e.Message);
             Program.socket.AddDelSocket(this);
         }
     }
     else
         Program.socket.AddDelSocket(this);
 }

接收客户端发来消息

从该客户端的 Socket 接收数据,解析出消息并交给处理函数。
Receive 流程

  • 检查 Connected,若断开则加入待删除列表
  • 若 socket.Available > 0,读取数据到缓冲区
  • 根据消息头中的 msgID 反序列化为具体消息
  • 通过 ThreadPool.QueueUserWorkItem(MsgHandle, msg) 异步处理消息
  • 若解析出错,同样加入待删除列表
 public void Receive()
 {
     if (!Connected)
     {
         Program.socket.AddDelSocket(this);
         return;
     }
     try
     {
         if(socket.Available > 0)
         {
             byte[] result = new byte[1024 * 5];
             int receiveNum = socket.Receive(result);
             HandleReceiveMsg(result, receiveNum);
         }
         //检测 是否超时 
         CheckTimeOut();
     }
     catch (Exception e)
     {
         Console.WriteLine("收消息出错" + e.Message);
         //解析消息出错 也认为 要把socket断开了
         Program.socket.AddDelSocket(this);
     }
 }

处理消息

这段代码主要是处理分包黏包, 和解析数据

  //处理接受消息 分包、黏包问题的方法
  private void HandleReceiveMsg(byte[] receiveBytes, int receiveNum)
  {
      int msgID = 0;
      int msgLength = 0;
      int nowIndex = 0;

      //收到消息时 应该看看 之前有没有缓存的 如果有的话 我们直接拼接到后面
      receiveBytes.CopyTo(cacheBytes, cacheNum);
      cacheNum += receiveNum;

      while (true)
      {
          //每次将长度设置为-1 是避免上一次解析的数据 影响这一次的判断
          msgLength = -1;
          //处理解析一条消息
          if (cacheNum - nowIndex >= 8)
          {
              //解析ID
              msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
              nowIndex += 4;
              //解析长度
              msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
              nowIndex += 4;
          }

          if (cacheNum - nowIndex >= msgLength && msgLength != -1)
          {
              //解析消息体
              BaseMsg baseMsg = null;
              switch (msgID)
              {
                  case 1001:
                      baseMsg = new PlayerMsg();
                      baseMsg.Reading(cacheBytes, nowIndex);
                      break;
                  case 1003:
                      baseMsg = new QuitMsg();
                      //由于该消息没有消息体 所以都不用反序列化
                      break;
                  case 999:
                      baseMsg = new HeartMsg();
                      //由于该消息没有消息体 所以都不用反序列化
                      break;
              }
              if (baseMsg != null)
                  ThreadPool.QueueUserWorkItem(MsgHandle, baseMsg);
              nowIndex += msgLength;
              if (nowIndex == cacheNum)
              {
                  cacheNum = 0;
                  break;
              }
          }
          else
          {
              //如果不满足 证明有分包 
              //那么我们需要把当前收到的内容 记录下来
              //有待下次接受到消息后 再做处理
              //receiveBytes.CopyTo(cacheBytes, 0);
              //cacheNum = receiveNum;
              //如果进行了 id和长度的解析 但是 没有成功解析消息体 那么我们需要减去nowIndex移动的位置
              if (msgLength != -1)
                  nowIndex -= 8;
              //就是把剩余没有解析的字节数组内容 移到前面来 用于缓存下次继续解析
              Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheNum - nowIndex);
              cacheNum = cacheNum - nowIndex;
              break;
          }
      }

  }

  private void MsgHandle(object obj)
  {
      BaseMsg msg = obj as BaseMsg;
      if(msg is PlayerMsg)
      {
          PlayerMsg playerMsg = msg as PlayerMsg;
          Console.WriteLine(playerMsg.playerID);
          Console.WriteLine(playerMsg.playerData.name);
          Console.WriteLine(playerMsg.playerData.lev);
          Console.WriteLine(playerMsg.playerData.atk);
      }
      else if(msg is QuitMsg)
      {
          //收到断开连接消息 把自己添加到待移除的列表当中
          Program.socket.AddDelSocket(this);
      }
      else if(msg is HeartMsg)
      {
          //收到心跳消息 记录收到消息的时间
          frontTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
          Console.WriteLine("收到心跳消息");
      }
  }

解析出的类(不重要看一眼就行)

PlayerMsg

using System.Collections;
using System.Collections.Generic;

public class PlayerMsg : BaseMsg
{
    public int playerID;
    public PlayerData playerData;
    public override byte[] Writing()
    {
        int index = 0;
        int bytesNum = GetBytesNum();
        byte[] bytes = new byte[bytesNum];
        //先写消息ID
        WriteInt(bytes, GetID(), ref index);
        //写如消息体的长度 我们-8的目的 是只存储 消息体的长度 前面8个字节 是我们自己定的规则 解析时按照这个规则处理就行了
        WriteInt(bytes, bytesNum - 8, ref index);
        //写这个消息的成员变量
        WriteInt(bytes, playerID, ref index);
        WriteData(bytes, playerData, ref index);
        return bytes;
    }

    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        //反序列化不需要去解析ID 因为在这一步之前 就应该把ID反序列化出来
        //用来判断到底使用哪一个自定义类来反序化
        int index = beginIndex;
        playerID = ReadInt(bytes, ref index);
        playerData = ReadData<PlayerData>(bytes, ref index);
        return index - beginIndex;
    }

    public override int GetBytesNum()
    {
        return 4 + //消息ID的长度
             4 + //消息体的长度
             4 + //playerID的字节数组长度
             playerData.GetBytesNum();//playerData的字节数组长度
    }

    /// <summary>
    /// 自定义的消息ID 主要用于区分是哪一个消息类
    /// </summary>
    /// <returns></returns>
    public override int GetID()
    {
        return 1001;
    }
}

QuitMsg

public class QuitMsg : BaseMsg
{
    public override int GetBytesNum()
    {
        return 8;
    }

    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        return 0;
    }

    public override byte[] Writing()
    {
        int index = 0;
        byte[] bytes = new byte[GetBytesNum()];
        WriteInt(bytes, GetID(), ref index);
        WriteInt(bytes, 0, ref index);
        return bytes;
    }

    public override int GetID()
    {
        return 1003;
    }
}

HeartMsg

public class HeartMsg : BaseMsg
{
    public override int GetBytesNum()
    {
        return 8;
    }

    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        return 0;
    }

    public override byte[] Writing()
    {
        int index = 0;
        byte[] bytes = new byte[GetBytesNum()];
        WriteInt(bytes, GetID(), ref index);
        WriteInt(bytes, 0, ref index);
        return bytes;
    }

    public override int GetID()
    {
        return 999;
    }
}

客户端类完整代码

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace TeachTcpServerExercises2
{
    class ClientSocket
    {
        private static int CLIENT_BEGIN_ID = 1;
        public int clientID;
        public Socket socket;

        //用于处理分包时 缓存的 字节数组 和 字节数组长度
        private byte[] cacheBytes = new byte[1024 * 1024];
        private int cacheNum = 0;

        //上一次收到消息的时间
        private long frontTime = -1;
        //超时时间
        private static int TIME_OUT_TIME = 10;

        public ClientSocket(Socket socket)
        {
            this.clientID = CLIENT_BEGIN_ID;
            this.socket = socket;
            ++CLIENT_BEGIN_ID;
            //我们现在为了方便大家理解 所以开了一个线程专门计时 但是这种方式比较消耗性能 不建议这样使用
            //ThreadPool.QueueUserWorkItem(CheckTimeOut);
        }

        /// <summary>
        /// 间隔一段时间 检测一次超时 如果超时 就会主动断开该客户端的连接
        /// </summary>
        /// <param name="obj"></param>
        private void CheckTimeOut(/*object obj*/)
        {
            //while (Connected)
            //{
                if (frontTime != -1 &&
                DateTime.Now.Ticks / TimeSpan.TicksPerSecond - frontTime >= TIME_OUT_TIME)
                {
                    Program.socket.AddDelSocket(this);
                    //break;
                }
                //Thread.Sleep(5000);
            //}
        }

        /// <summary>
        /// 是否是连接状态
        /// </summary>
        public bool Connected => socket.Connected;


        //我们应该封装一些方法
        //关闭
        public void Close()
        {
            if(socket != null)
            {
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
                socket = null;
            }
        }
        //发送
        public void Send(BaseMsg info)
        {
            if (Connected)
            {
                try
                {
                    socket.Send(info.Writing());
                }
                catch (Exception e)
                {
                    Console.WriteLine("发消息出错" + e.Message);
                    Program.socket.AddDelSocket(this);
                }
            }
            else
                Program.socket.AddDelSocket(this);
        }
        //接收
        public void Receive()
        {
            if (!Connected)
            {
                Program.socket.AddDelSocket(this);
                return;
            }
            try
            {
                if(socket.Available > 0)
                {
                    byte[] result = new byte[1024 * 5];
                    int receiveNum = socket.Receive(result);
                    HandleReceiveMsg(result, receiveNum);
                    ////收到数据后 先读取4个字节 转为ID 才知道用哪一个类型去处理反序列化
                    //int msgID = BitConverter.ToInt32(result, 0);
                    //BaseMsg msg = null;
                    //switch (msgID)
                    //{
                    //    case 1001:
                    //        msg = new PlayerMsg();
                    //        msg.Reading(result, 4);
                    //        break;
                    //}
                    //if (msg == null)
                    //    return;
                    //ThreadPool.QueueUserWorkItem(MsgHandle, msg);
                }

                //检测 是否超时 
                CheckTimeOut();
            }
            catch (Exception e)
            {
                Console.WriteLine("收消息出错" + e.Message);
                //解析消息出错 也认为 要把socket断开了
                Program.socket.AddDelSocket(this);
            }
        }

        //处理接受消息 分包、黏包问题的方法
        private void HandleReceiveMsg(byte[] receiveBytes, int receiveNum)
        {
            int msgID = 0;
            int msgLength = 0;
            int nowIndex = 0;

            //收到消息时 应该看看 之前有没有缓存的 如果有的话 我们直接拼接到后面
            receiveBytes.CopyTo(cacheBytes, cacheNum);
            cacheNum += receiveNum;

            while (true)
            {
                //每次将长度设置为-1 是避免上一次解析的数据 影响这一次的判断
                msgLength = -1;
                //处理解析一条消息
                if (cacheNum - nowIndex >= 8)
                {
                    //解析ID
                    msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
                    nowIndex += 4;
                    //解析长度
                    msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
                    nowIndex += 4;
                }

                if (cacheNum - nowIndex >= msgLength && msgLength != -1)
                {
                    //解析消息体
                    BaseMsg baseMsg = null;
                    switch (msgID)
                    {
                        case 1001:
                            baseMsg = new PlayerMsg();
                            baseMsg.Reading(cacheBytes, nowIndex);
                            break;
                        case 1003:
                            baseMsg = new QuitMsg();
                            //由于该消息没有消息体 所以都不用反序列化
                            break;
                        case 999:
                            baseMsg = new HeartMsg();
                            //由于该消息没有消息体 所以都不用反序列化
                            break;
                    }
                    if (baseMsg != null)
                        ThreadPool.QueueUserWorkItem(MsgHandle, baseMsg);
                    nowIndex += msgLength;
                    if (nowIndex == cacheNum)
                    {
                        cacheNum = 0;
                        break;
                    }
                }
                else
                {
                    //如果不满足 证明有分包 
                    //那么我们需要把当前收到的内容 记录下来
                    //有待下次接受到消息后 再做处理
                    //receiveBytes.CopyTo(cacheBytes, 0);
                    //cacheNum = receiveNum;
                    //如果进行了 id和长度的解析 但是 没有成功解析消息体 那么我们需要减去nowIndex移动的位置
                    if (msgLength != -1)
                        nowIndex -= 8;
                    //就是把剩余没有解析的字节数组内容 移到前面来 用于缓存下次继续解析
                    Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheNum - nowIndex);
                    cacheNum = cacheNum - nowIndex;
                    break;
                }
            }

        }

        private void MsgHandle(object obj)
        {
            BaseMsg msg = obj as BaseMsg;
            if(msg is PlayerMsg)
            {
                PlayerMsg playerMsg = msg as PlayerMsg;
                Console.WriteLine(playerMsg.playerID);
                Console.WriteLine(playerMsg.playerData.name);
                Console.WriteLine(playerMsg.playerData.lev);
                Console.WriteLine(playerMsg.playerData.atk);
            }
            else if(msg is QuitMsg)
            {
                //收到断开连接消息 把自己添加到待移除的列表当中
                Program.socket.AddDelSocket(this);
            }
            else if(msg is HeartMsg)
            {
                //收到心跳消息 记录收到消息的时间
                frontTime = DateTime.Now.Ticks / TimeSpan.TicksPerSecond;
                Console.WriteLine("收到心跳消息");
            }
        }

    }
}

服务端

开启服务器

开启服务器 , 需要

  1. 创建服务端Socket 对象
  2. 将Socket 绑定到指定的IP地址和端口
  3. 开启对客户端连接请求的监听
  4. 使用线程不停的检测客户端连入, 和客户端发消息
    以下是具体代码
  //开启服务器端
  public void Start(string ip, int port, int num)
  {
      isClose = false;
      socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
      socket.Bind(ipPoint);
      socket.Listen(num);
      ThreadPool.QueueUserWorkItem(Accept);
      ThreadPool.QueueUserWorkItem(Receive);
  }

代码最后的
ThreadPool.QueueUserWorkItem(Accept);
ThreadPool.QueueUserWorkItem(Receive);
是开启接受连接, 和接收消息的线程, 相关代码会在下面. 这样我们就开启了一个可连接的服务端

接受客户端连入

接受客户端连入API : Socket.Accept();
Accept() 会从 listen() 维护的队列中 ,提取第一个等待的连接, 并且返回一个服务端到客户端的Socket 对象.
要注意, 如果列表为空, 也就是无人链接, Accept() 会阻塞当前线程, 直到有新的链接 , 这也是为什么我们单开了一个线程来处理接受链接.
下面是代码

 private void Accept(object obj)
 {
     while (!isClose) {
         try{
             //连入一个客户端
             Socket clientSocket = socket.Accept();
             ClientSocket client = new ClientSocket(clientSocket);
             lock(clientDic)
                 clientDic.Add(client.clientID, client);
         }
         catch (Exception e) {
             Console.WriteLine("客户端连入报错" + e.Message);
         }
     }
 }
  1. 如果服务端处于打开状态, 一直循环等待接受客户端连接
  2. 用Accept() 接收到的Socket 新实例化一个客户端Socket
  3. 然后将连入的客户端放入字典 , 需要给字典上锁, 因为会频繁访问字典, 防止出错

接受客户端发送消息

接受客户端连入API : Socket.Receive(byte[]result);
从远端发送过来的数据流中读取字节,存储在本地缓冲区。它工作在已建立连接的套接字上(客户端 Connect 之后,或服务端 Accept 得到的 Socket)。
注意默认也是阻塞模式, 如果接收缓冲区中没有数据可用,Receive 会阻塞当前线程,直到有数据到达或连接断开。
所以我们也会单开线程, 不阻塞主线程的运行
以下是代码

    if (!Connected)
    {
    //如果没链接, 把当前套接字丢入待删除字典
        Program.socket.AddDelSocket(this);
        return;
    }
    try
    {
        if(socket.Available > 0)
        {
            byte[] result = new byte[1024 * 5];//接收到的字节数组
            int receiveNum = socket.Receive(result);//获取字节长度, 保存读取到的信息到字节数组
            HandleReceiveMsg(result, receiveNum);//处理消息的方法
        }

        //检测 是否超时 
        CheckTimeOut();//后续会讲
    }
    catch (Exception e)
    {
        Console.WriteLine("收消息出错" + e.Message);
        //解析消息出错 也认为 要把socket断开了
        Program.socket.AddDelSocket(this);
    }
}

广播消息给客户端

遍历所有套接字, 把消息发给所有客户端

        public void Broadcast(BaseMsg msg) {
            lock (clientDic) {
                foreach (ClientSocket client in clientDic.Values)
                {
                    client.Send(msg);
                }
            }

        }

处理断开连接的客户端

        
 //添加待移除的 socket内容
 public void AddDelSocket(ClientSocket socket)
 {
     if (!delList.Contains(socket))
         delList.Add(socket);
 }

 //判断有没有 断开连接 把其移除
 public void CloseDelListSocket()
 {
     //判断有没有 断开连接的 把其 移除
     for (int i = 0; i < delList.Count; i++)
         CloseClientSocket(delList[i]);
     delList.Clear();
 }

 //关闭客户端连接的 从字典中移除
 public void CloseClientSocket(ClientSocket socket)
 {
     lock (clientDic)
     {
         socket.Close();
         if (clientDic.ContainsKey(socket.clientID))
         {
             clientDic.Remove(socket.clientID);
             Console.WriteLine("客户端{0}主动断开连接了", socket.clientID);
         }
     }
 }

服务器完整代码

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace TeachTcpServerExercises2
{
    class ServerSocket
    {
        //服务端Socket
        public Socket socket;
        //客户端连接的所有Socket
        public Dictionary<int, ClientSocket> clientDic = new Dictionary<int, ClientSocket>();

        //有待移除的客户端socket 避免 在foreach时直接从字典中移除 出现问题
        private List<ClientSocket> delList = new List<ClientSocket>();

        private bool isClose;

        //开启服务器端
        public void Start(string ip, int port, int num)
        {
            isClose = false;
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
            socket.Bind(ipPoint);
            socket.Listen(num);
            ThreadPool.QueueUserWorkItem(Accept);
            ThreadPool.QueueUserWorkItem(Receive);
        }

        //关闭服务器端
        public void Close()
        {
            isClose = true;
            foreach (ClientSocket client in clientDic.Values)
            {
                client.Close();
            }
            clientDic.Clear();

            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
            socket = null;
        }

        //接受客户端连入
        private void Accept(object obj)
        {
            while (!isClose)
            {
                try
                {
                    //连入一个客户端
                    Socket clientSocket = socket.Accept();
                    ClientSocket client = new ClientSocket(clientSocket);
                    lock(clientDic)
                        clientDic.Add(client.clientID, client);
                }
                catch (Exception e)
                {
                    Console.WriteLine("客户端连入报错" + e.Message);
                }
            }
        }
        //接收客户端消息
        private void Receive(object obj)
        {
            while (!isClose)
            {
                if(clientDic.Count > 0)
                {
                    lock (clientDic)
                    {
                        foreach (ClientSocket client in clientDic.Values)
                        {
                            client.Receive();
                        }

                        CloseDelListSocket();
                    }
                    
                }
            }
        }

        public void Broadcast(BaseMsg info)
        {
            lock (clientDic)
            {
                foreach (ClientSocket client in clientDic.Values)
                {
                    client.Send(info);
                }
            }
                
        }

        //添加待移除的 socket内容
        public void AddDelSocket(ClientSocket socket)
        {
            if (!delList.Contains(socket))
                delList.Add(socket);
        }

        ////判断有没有 断开连接的 把其 移除
        public void CloseDelListSocket()
        {
            //判断有没有 断开连接的 把其 移除
            for (int i = 0; i < delList.Count; i++)
                CloseClientSocket(delList[i]);
            delList.Clear();
        }

        //关闭客户端连接的 从字典中移除
        public void CloseClientSocket(ClientSocket socket)
        {
            lock (clientDic)
            {
                socket.Close();
                if (clientDic.ContainsKey(socket.clientID))
                {
                    clientDic.Remove(socket.clientID);
                    Console.WriteLine("客户端{0}主动断开连接了", socket.clientID);
                }
            }
        }
    }
}

使用示例

  • 开启服务器
  • 循环检测输入
  • 处理输入消息
 class Program
 {
     public static ServerSocket socket;
     static void Main(string[] args)
     {
         socket = new ServerSocket();
         socket.Start("127.0.0.1", 8080, 1024);
         Console.WriteLine("服务器开启成功");
         while (true)
         {
             string input = Console.ReadLine();
             if(input == "Quit")
             {
                 socket.Close();
             }
             else if( input.Substring(0,2) == "B:" )
             {
                 if(input.Substring(2) == "1001")
                 {
                     PlayerMsg msg = new PlayerMsg();
                     msg.playerID = 9876;
                     msg.playerData = new PlayerData();
                     msg.playerData.name = "服务器端发来的消息";
                     msg.playerData.lev = 99;
                     msg.playerData.atk = 80;
                     socket.Broadcast(msg);
                 }
             }
         }
     }
 }

小结

本文实现了一个基于 TCP 的 C# 服务端框架,其核心设计围绕以下两个关键方面展开:

1. 客户端连接管理

  • 连接封装与标识:通过 ClientSocket 类对每个远端连接进行封装,分配唯一 clientID,实现集中化的生命周期管理。
  • 线程安全维护:使用字典 clientDic 维护在线客户端,配合延迟清理列表 delList,避免在遍历时直接修改集合,确保线程安全。
  • 心跳检测机制:基于最后一次收包时间 frontTime 进行超时判断,超时后自动标记断开,有效防止无效连接长期占用资源。

2. 消息的序列化与拆包

  • 消息协议设计:自定义 BaseMsg 消息体系,采用固定 8 字节消息头(4 字节消息 ID + 4 字节消息体长度),消息体由子类实现序列化。
  • 粘包与分包处理:接收端通过 cacheBytes 缓冲区缓存未完整解析的数据,结合长度字段循环解析,可靠还原变长消息。
  • 异步消息处理:解析后的消息通过 ThreadPool.QueueUserWorkItem 异步执行处理逻辑,避免阻塞主接收循环。

整体架构遵循单一职责原则

  • ServerSocket 负责连接的调度、广播与全局管理。
  • ClientSocket 负责单个连接的收发、状态维护与心跳检测。

以上就是C#中使用Socket实现TCP服务端的方法与步骤的详细内容,更多关于C# Socket实现TCP服务端的资料请关注脚本之家其它相关文章!

相关文章

  • Unity使用EzySlice实现模型多边形顺序切割

    Unity使用EzySlice实现模型多边形顺序切割

    这篇文章主要为大家详细介绍了Unity使用EzySlice实现模型多边形顺序切割,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07
  • 在类库或winform项目中打开另一个winform项目窗体的方法

    在类库或winform项目中打开另一个winform项目窗体的方法

    这篇文章主要介绍了在类库或winform项目中打开另一个winform项目窗体的方法,可以实现Winform项目间窗体的调用,在进行Winform项目开发中非常具有实用价值,需要的朋友可以参考下
    2014-11-11
  • Unity实现截图功能

    Unity实现截图功能

    这篇文章主要为大家详细介绍了Unity实现截图功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • C#实现导出List数据到xml文件的方法【附demo源码下载】

    C#实现导出List数据到xml文件的方法【附demo源码下载】

    这篇文章主要介绍了C#实现导出List数据到xml文件的方法,涉及C#针对list类及xml文件的相关操作技巧,并附带完整demo源码供读者下载参考,需要的朋友可以参考下
    2016-08-08
  • Unity实现简单虚拟摇杆

    Unity实现简单虚拟摇杆

    这篇文章主要为大家详细介绍了Unity实现简单虚拟摇杆,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • unity与vue交互(无第三方插件)

    unity与vue交互(无第三方插件)

    这篇文章主要讲述了如何使用vue在通过不是用第三方插件的情况下与Unity进行交互,该篇包含详细的图文讲解,内容比较详细,希望对你有所帮助
    2021-06-06
  • C#实现winform版飞行棋

    C#实现winform版飞行棋

    这篇文章主要为大家详细介绍了C#实现winform版飞行,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • C# SetWindowPos函数实例详解

    C# SetWindowPos函数实例详解

    在C#中,SetWindowPos函数用于设置窗口的位置和大小,这篇文章主要介绍了C# SetWindowPos函数实例详解,本文给大家介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • C#中WinForm程序退出方法技巧总结

    C#中WinForm程序退出方法技巧总结

    这篇文章主要介绍了C#中WinForm程序退出方法,实例总结了技巧退出WinForm程序窗口的各种常用技巧,非常具有实用价值,需要的朋友可以参考下
    2014-12-12
  • C#使用Socket实现本地多人聊天室

    C#使用Socket实现本地多人聊天室

    这篇文章主要为大家详细介绍了C#使用Socket实现本地多人聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02

最新评论