2023.8.20 更新
更加完整的代码已经上传到github,
EasyTcpSocket
,包含socket框架和完整的客户端服务端示例,框架用的.net 6.0,不过和Framework的写法也没什么区别。
还是那句话,边学边做的,仅供参考,切勿用于生产环境。
源码下载,学习的时候做的,现在可以做到一个服务端对应多个客户端同时接受消息,也解决了分包和粘包的问题,欢迎下载
(16条消息) 网络通信编程学习.7z-C#文档类资源-CSDN文库
https://download.csdn.net/download/Trinity_Force/44900216
什么是粘包分包
-
TCP是
面向连接
的协议
-
TCP是点到点的通信
-
TCP提供可靠的传输服务
-
TCP协议提供全双工的通信
-
TCP协议面向
字节流
进行传输的,可以对用户的数据进行拆分或合并
TCP协议是面向字节流传输的,TCP协议会保证字节流传输时顺序不会改变,不会丢失内容,但是TCP协议会灵活的拆分或者合并用户Socket.Send(buffer)出来的内容,将小的数据整合发送或者是将大的数据拆开发送。
所以在实际的编程中就会出现服务端一次Receive就收到了客户端多次Send的数据(“粘包”),或者是客户端只Send了一次,服务端却要多次Receive才能完整接收。
粘包示例
:客户端发送了一万条“Hello”到服务端,结果服务端收到的是这样的
分包示例
:客户端发送了一大串“a”到服务端,结果服务端是分三次收到的。
自己自定义报文格式,发送时根据固定的格式封包,接收时再按照这个格式解包
1 数据包首部添加数据包长度
接收到数据时,先解析首部的“数据包长度”,再解析数据包内容,如果数据包内容的长度不足数据包首部规定的长度,则认为出现了“分包”,需要等待接收下一个数据包,直到传输完整。如果数据包内容的长度大于数据包首部规定的长度,则出现了“粘包”需要认为将粘包分开。
2 数据包结尾添加固定的分隔符
接收到数据后,如果出现结尾标识,则人为将粘包分开,如果一个包中没有结尾标识,则认为出现了“分包”,需要等待下一个数据包,直到出现结尾标识
客户端发送时的封包方法
private void BtnSend_Click(object sender, EventArgs e)
byte[] dataToBeSend = GetSendData(TextSendData.Text.Trim());
if (int.TryParse(textRepeatTimes.SelectedItem.ToString(), out int times))
int dataSize = 0;
for (int i = 0; i < times; i++)
dataSize += ClientSocket.Send(dataToBeSend);
ShowReceiveDataWithDelegate($"共发送{dataSize}字节的数据");
private byte[] GetSendData(string text)
//数据包内容
byte[] content = Encoding.Default.GetBytes(text);
//数据包头部
byte[] header = new byte[4];
ConvertIntToByteArray(content.Length, ref header);
//最终封装好的数据包,数据包首位 0 消息 1 文件,2-5位 数据长度
byte[] dataToBeSend = new byte[content.Length + 5];
dataToBeSend[0] = 0;
Array.Copy(header, 0, dataToBeSend, 1, header.Length);
Array.Copy(content, 0, dataToBeSend, 5, content.Length);
return dataToBeSend;
/// <summary>
/// 把int32类型的数据转存到4个字节的byte数组中
/// </summary>
/// <param name="m">int32类型的数据
/// <param name="arry">4个字节大小的byte数组
/// <returns></returns>
private bool ConvertIntToByteArray(Int32 m, ref byte[] arry)
if (arry == null) return false;
if (arry.Length < 4) return false;
arry[0] = (byte)(m & 0xFF);
arry[1] = (byte)((m & 0xFF00) >> 8);
arry[2] = (byte)((m & 0xFF0000) >> 16);
arry[3] = (byte)((m >> 24) & 0xFF);
return true;
服务端解包类
using System;
using System.Collections.Generic;
namespace AsyncSocketServer
/// <summary>
/// SocketTCP通信解包类,包格式为:内容类型(1位)内容长度(4位)剩余。。
/// 读取完DataList的数据后请务必执行Clear方法();
/// </summary>
public class SocketTcpPack
/// <summary>
/// 接收是否完成了
/// </summary>
public bool IsComplete = false;
/// <summary>
/// 接收缓存
/// </summary>
public byte[] Buffer;
/// <summary>
/// 下次接收从Buffer的哪里开始写入
/// </summary>
public int Offset = 0;
/// <summary>
/// 下次写入Buffer的长度
/// </summary>
public int Size;
/// <summary>
/// 接收到的数据
/// </summary>
public List<ReceiveDataModel> DataList = new List<ReceiveDataModel>();
/// <summary>
/// 缓存长度
/// </summary>
private readonly int BufferLength;
public SocketTcpPack(int bufferLength = 1024)
BufferLength = bufferLength;
Buffer = new byte[BufferLength];
Size = BufferLength;
/// <summary>
/// 处理接收到的数据
/// </summary>
/// <param name="currentDataSize">接收到的数据长度,Socket.Receive()方法返回的数值</param>
public void UntiePack(int currentDataSize)
//Size != BufferLength说明Buffer中保留了一些上次接收的数据,要把这部分数据长度加上
int dataSize = currentDataSize;
if (Size != BufferLength)
dataSize += Offset;
if (DataList.Count == 0)
SplitData(Buffer, dataSize);
//取出DataList中的最后一个元素,通过判断这个元素是否完整来判断是有分包需要补充完整
ReceiveDataModel LastReceiveData = DataList[DataList.Count - 1];
if (LastReceiveData.IsComplete)
SplitData(Buffer, dataSize);
//最后一个包的剩余长度
int remainingDataLength = LastReceiveData.DataLength - LastReceiveData.Content.Length;
//剩余长度 < 本次接收的数据长度,说明这一次接收就可以把上一个分包补充完整
if (remainingDataLength < dataSize)
int realLength = LastReceiveData.Content.Length;
byte[] b = new byte[LastReceiveData.DataLength];
Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
LastReceiveData.Content = b;
Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, remainingDataLength);
//继续处理剩下的数据
byte[] last = new byte[dataSize - remainingDataLength];
Array.Copy(Buffer, remainingDataLength, last, 0, last.Length);
SplitData(last, last.Length);
//剩余长度 > 本次接收的数据长度,说明这一次接收还不能把上一个分包补充完整,还需要继续等待接收
else if (remainingDataLength > dataSize)
int realLength = LastReceiveData.Content.Length;
byte[] b = new byte[LastReceiveData.Content.Length + dataSize];
Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
LastReceiveData.Content = b;
Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, dataSize);
Offset = 0;
Size = BufferLength;
Buffer = new byte[BufferLength];
int realLength = LastReceiveData.Content.Length;
byte[] b = new byte[LastReceiveData.DataLength];
Array.Copy(LastReceiveData.Content, 0, b, 0, LastReceiveData.Content.Length);
LastReceiveData.Content = b;
Array.Copy(Buffer, 0, LastReceiveData.Content, realLength, remainingDataLength);
Offset = 0;
Size = BufferLength;
Buffer = new byte[BufferLength];
IsComplete = true;
/// <summary>
/// 处理byte[]前5位就是包首部的这种数据
/// </summary>
/// <param name="data">byte[]</param>
/// <param name="dataSize">内容的实际长度</param>
private void SplitData(byte[] data, int dataSize)
//长度 <= 5 说明包首部还没有接收完成,需要继续接收
if (dataSize <= 5)
byte[] temp = new byte[BufferLength];
Array.Copy(data, 0, temp, 0, dataSize);
Buffer = temp;
Offset = dataSize;
Size = BufferLength - dataSize;
IsComplete = true;
return;
//包首部
byte[] header = new byte[5];
//包内容
byte[] content = new byte[dataSize - 5];
Array.Copy(data, 0, header, 0, 5);
Array.Copy(data, 5, content, 0, dataSize - 5);
//包内容长度
int dataLength = BitConverter.ToInt32(header, 1);
//dataLength < content.Length 说明本次接收的数据中已经包含一个完整的包,将这个完整的包取出后继续处理剩下的数据
if (dataLength < content.Length)
//发生了粘包
byte[] b = new byte[dataLength];
Array.Copy(content, 0, b, 0, dataLength);
ReceiveDataModel receiveData = new ReceiveDataModel()
DataType = header[0],
DataLength = dataLength,
Content = b
DataList.Add(receiveData);
byte[] last = new byte[content.Length - dataLength];
Array.Copy(content, dataLength, last, 0, last.Length);
SplitData(last, last.Length);
//dataLength >= content.Length 说明本次接收的数据不完整,保存后继续接收
else if (dataLength >= content.Length)
//发生了分包或者什么都没发生
ReceiveDataModel receiveData = new ReceiveDataModel()
DataType = header[0],
DataLength = dataLength,
Content = content
DataList.Add(receiveData);
Offset = 0;
Size = BufferLength;
Buffer = new byte[BufferLength];
if (dataLength == content.Length) IsComplete = true;
public void Clear()
if (DataList.Count > 0)
DataList.Clear();
IsComplete = false;
发送时调用这个类
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
namespace AsyncSocketServer
public partial class Server : Form
public Server()
InitializeComponent();
/// <summary>
/// 存储客户端连接
/// </summary>
private Dictionary<string, Socket> ClientSocketList = new Dictionary<string, Socket>();
/// <summary>
/// 接收数据缓冲区
/// </summary>
private Dictionary<string, SocketTcpPack> ReceiveBufferDic = new Dictionary<string, SocketTcpPack>();
/// <summary>
/// 开始监听
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnStartListen_Click(object sender, EventArgs e)
if (!IPAddress.TryParse(TextIP.Text.Trim(), out IPAddress ip))
MessageBox.Show("不正确的IP地址");
return;
if (!int.TryParse(TextPort.Text.Trim(), out int port))
MessageBox.Show("不正确的端口号");
return;
//创建Socket
Socket socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint pEndPoint = new IPEndPoint(ip, port);
//绑定IP和端口
socketServer.Bind(pEndPoint);
//开始监听
socketServer.Listen(10);
ShowReceiveDataWithDelegate("监听成功");
//接收连接
socketServer.BeginAccept(Accept, socketServer);
/// <summary>
/// BeginAccept的回调
/// </summary>
/// <param name="result"></param>
private void Accept(IAsyncResult result)
Socket socket = (Socket)result.AsyncState;
Socket clientSocket = socket.EndAccept(result);
string clientIP = clientSocket.RemoteEndPoint.ToString();
ClientSocketList.Add(clientIP, clientSocket);
CmbSocket.BeginInvoke(new EventHandler(delegate
CmbSocket.Items.Add(clientIP);
ShowReceiveDataWithDelegate("连接成功");
SocketTcpPack tcpPack = new SocketTcpPack(1024);
ReceiveBufferDic.Add(clientIP, tcpPack);
//开始接受客户端消息
clientSocket.BeginReceive(ReceiveBufferDic[clientIP].Buffer, ReceiveBufferDic[clientIP].Offset, ReceiveBufferDic[clientIP].Size, SocketFlags.None, Receive, clientSocket);
//接受下一个连接
socket.BeginAccept(Accept, socket);
/// <summary>
/// BeginReceive的回调
/// </summary>
/// <param name="result"></param>
private void Receive(IAsyncResult result)
Socket socket = (Socket)result.AsyncState;
string clientIP = socket.RemoteEndPoint.ToString();
int dataSize = socket.EndReceive(result);
if (dataSize > 0)
//对接收到的消息进行解包
ReceiveBufferDic[clientIP].UntiePack(dataSize);
if (ReceiveBufferDic[clientIP].IsComplete)
foreach (var item in ReceiveBufferDic[clientIP].DataList)
string str = Encoding.Default.GetString(item.Content, 0, item.DataLength);
ShowReceiveDataWithDelegate($"{socket.RemoteEndPoint}发来消息:{str}");
ReceiveBufferDic[clientIP].Clear();
//接收下一条消息
socket.BeginReceive(ReceiveBufferDic[clientIP].Buffer, ReceiveBufferDic[clientIP].Offset, ReceiveBufferDic[clientIP].Size, SocketFlags.None, Receive, socket);
catch (SocketException)
string ip = socket.RemoteEndPoint.ToString();
Close(ip);
private void BtnSend_Click(object sender, EventArgs e)
if (CmbSocket.SelectedIndex < 0)
MessageBox.Show("请选择客户端IP");
return;
string clientIP = CmbSocket.SelectedItem.ToString();
Socket clientSocket = ClientSocketList[clientIP];
byte[] data = Encoding.Default.GetBytes(TextSendData.Text.Trim());
clientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, (result) =>
Socket socket = (Socket)result.AsyncState;
socket.EndSend(result);
}, clientSocket);
TextSendData.Clear();
private void ShowReceiveDataWithDelegate(string msgContent)
IAsyncResult result = ListReveiveData.BeginInvoke(new EventHandler(delegate
ListReveiveData.Items.Add($"{ListReveiveData.Items.Count} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} :{msgContent}");
ListReveiveData.SelectedIndex = ListReveiveData.Items.Count - 1;
ListReveiveData.EndInvoke(result);
private void Close(string ip)
ShowReceiveDataWithDelegate($"客户端{ip}断开连接");
ClientSocketList.Remove(ip);
ReceiveBufferDic.Remove(ip);
CmbSocket.BeginInvoke(new EventHandler(delegate
CmbSocket.Items.Remove(ip);
namespace AsyncSocketServer
/// <summary>
/// Socket接收 数据类型
/// </summary>
public class ReceiveDataModel
/// <summary>
/// 数据类型 0 文本,1 文件
/// </summary>
public byte DataType { get; set; }
/// <summary>
/// 数据长度
/// </summary>
public int DataLength { get; set; }
/// <summary>
/// 数据
/// </summary>
public byte[] Content { get; set; }
public bool IsComplete
if (DataLength == 0) return false;
return DataLength == Content.Length;
源码下载,学习的时候做的,现在可以做到一个服务端对应多个客户端同时接受消息,也解决了分包和粘包的问题,欢迎下载(16条消息) 网络通信编程学习.7z-C#文档类资源-CSDN文库https://download.csdn.net/download/Trinity_Force/44900216什么是粘包分包TCP是面向连接的协议 TCP是点到点的通信 TCP提供可靠的传输服务 TCP协议提供全双工的通信 TCP协议面向字节流进行传输的,可以对用户的数据进行拆分或合并TCP...
最近在做Unity局域网时,用到了Socket通信基于TCP协议,然后使用异步方式,主要用到了BeginAccept和BeginReceive方法
然而就可以实现异步通信,然而还是要解决粘包和分包问题
这里我先说明一下什么是分包和粘包,TCP提供面向连接的、可靠的数据流传输,所以当我们发送数据在短时间内比较频繁并且数据量比较小时,TCP为了优化内存资源,会将多条数据粘成几个包来进行处理,相比发...
我们知道如果Socket传输数据太频繁并且数据量级比较大,就很容易出现分包(一个包的内容分成了两份)、粘包(前一个包的内容分成了两份,其中一份连着下一个包的内容)的情况。
粘包的处理方式有很多种,常见的三种是:
每个包都在头部增加一个当前传输包的int4字节大小作为包头。每次接收到数据先读取的包头,确定这一包的实际长度n,当接收够n+4长度的数据就是一个完整的包,再重复读取下一包的包头。(相对...
下面对上面的图进行解释:
1.正常情况:如果Socket Client 发送的数据包,在Socket Server端也是一个一个完整接收的,那个就不会出现粘包和分包情况,数据正常读取。
2.粘包情况:Socket Client发送的数据包,在客户端发送和服务器接收的情况下都有可能发送,因为客户端发送的数据都是发送的一个缓冲buffer,然后由缓冲buffer最后刷到数据链路层的,那么就有可能把数据包2的一部分数据结合数据包1的全部被一起...
使用TCP长连接就会引入粘包的问题,粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。粘包可能由发送方造成,也可能由接收方造成。TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据,造成多个数据包的粘连。如果接收进程不及时接收数据,已收到的数据就放在系统接收缓冲区,用户进程读取数据时就可能同时读到多个数据包。
粘包一般的解决办法是制定通讯协议,由协议来规定如何分包解包,因此在Scoket编程中需要定义分包、解包的逻辑。
最近在工作中遇到了要自己写Socket服务器和客户端的问题,我解决了关于TCP的粘包问题。那么为什么会引起TCP粘包呢原因有以下两点
1、TCP是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;
2、在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能
假如没有做处理的话,发过来的数据包会有三种情况
1.没有粘包(这个问题不讨论,是正常情况)
2.两个或多个整包粘在一起
private Socket listener;
private const int BUFFER_SIZE = 1024;
private byte[] buffer = new byte[BUFFER_SIZE];
public void Start()
listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Any, 8080));
listener.Listen(10);
Console.WriteLine("Server started on port 8080.");
while (true)
Socket client = listener.Accept();
Console.WriteLine("Client connected: " + client.RemoteEndPoint.ToString());
// Start receiving data from client
client.BeginReceive(buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(OnReceive), client);
private void OnReceive(IAsyncResult ar)
Socket client = (Socket)ar.AsyncState;
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0)
// Convert received bytes to string
string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
// Process received data
Console.WriteLine("Received data: " + receivedData);
// Continue receiving data from client
client.BeginReceive(buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(OnReceive), client);
// Client has disconnected
Console.WriteLine("Client disconnected: " + client.RemoteEndPoint.ToString());
client.Close();
public class SocketClient
private Socket client;
private const int BUFFER_SIZE = 1024;
private byte[] buffer = new byte[BUFFER_SIZE];
public void Connect()
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.BeginConnect(new IPEndPoint(IPAddress.Loopback, 8080), new AsyncCallback(OnConnect), null);
private void OnConnect(IAsyncResult ar)
Console.WriteLine("Connected to server.");
client.EndConnect(ar);
// Start receiving data from server
client.BeginReceive(buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(OnReceive), client);
private void OnReceive(IAsyncResult ar)
Socket client = (Socket)ar.AsyncState;
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0)
// Convert received bytes to string
string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
// Process received data
Console.WriteLine("Received data: " + receivedData);
// Continue receiving data from server
client.BeginReceive(buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(OnReceive), client);
// Server has disconnected
Console.WriteLine("Server disconnected.");
client.Close();
public void Send(string data)
byte[] sendData = Encoding.UTF8.GetBytes(data);
client.BeginSend(sendData, 0, sendData.Length, 0, new AsyncCallback(OnSend), null);
private void OnSend(IAsyncResult ar)
client.EndSend(ar);
class Program
static void Main(string[] args)
// Start server
SocketServer server = new SocketServer();
server.Start();
// Connect client to server
SocketClient client = new SocketClient();
client.Connect();
// Send data to server
client.Send("Hello, server!");
Console.ReadLine();
这个示例中,我们使用了异步的 BeginReceive 和 BeginSend 方法来在接收和发送数据时不阻塞主线程。在接收数据时,我们先接收到一部分数据,然后判断是否收到了完整的数据,如果没有就继续接收。在发送数据时,我们将要发送的数据转换成字节数组后,使用 BeginSend 方法发送,然后在回调函数中调用 EndSend 方法结束发送。