源码下载,学习的时候做的,现在可以做到一个服务端对应多个客户端同时接受消息,也解决了分包和粘包的问题,欢迎下载
(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);
}
else
{
//取出DataList中的最后一个元素,通过判断这个元素是否完整来判断是有分包需要补充完整
ReceiveDataModel LastReceiveData = DataList[DataList.Count - 1];
if (LastReceiveData.IsComplete)
{
SplitData(Buffer, dataSize);
}
else
{
//最后一个包的剩余长度
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];
}
else
{
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;
try
{
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
{
get
{
if (DataLength == 0) return false;
return DataLength == Content.Length;
}
}
}
}
|