C#中使用Socket实现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("收到心跳消息");
}
}
}
}
服务端
开启服务器
开启服务器 , 需要
- 创建服务端Socket 对象
- 将Socket 绑定到指定的IP地址和端口
- 开启对客户端连接请求的监听
- 使用线程不停的检测客户端连入, 和客户端发消息
以下是具体代码
//开启服务器端
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);
}
}
}
- 如果服务端处于打开状态, 一直循环等待接受客户端连接
- 用Accept() 接收到的Socket 新实例化一个客户端Socket
- 然后将连入的客户端放入字典 , 需要给字典上锁, 因为会频繁访问字典, 防止出错
接受客户端发送消息
接受客户端连入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服务端的资料请关注脚本之家其它相关文章!
相关文章
在类库或winform项目中打开另一个winform项目窗体的方法
这篇文章主要介绍了在类库或winform项目中打开另一个winform项目窗体的方法,可以实现Winform项目间窗体的调用,在进行Winform项目开发中非常具有实用价值,需要的朋友可以参考下2014-11-11
C#实现导出List数据到xml文件的方法【附demo源码下载】
这篇文章主要介绍了C#实现导出List数据到xml文件的方法,涉及C#针对list类及xml文件的相关操作技巧,并附带完整demo源码供读者下载参考,需要的朋友可以参考下2016-08-08


最新评论