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,