Socket 如何处理粘包
Socket 如何处理粘包
什么是粘包什么是半包?
粘包:
比如发送了AA BB 两条消息,但是另一方接收到的消息却是AAB,像这种一次性读取了俩条数据的情况就是粘包
半包:
比如发送的消息是ABC时,另一方收到的是AB和C俩条消息,这就是半包
为什么会有这种问题呢?
这是因为 TCP 是面向连接的传输协议,TCP 传输的数据是以流的形式,而流数据是没有明确的开始结尾边界,所以 TCP 也没办法判断哪一段流属于一个消息。
粘包的主要原因:
发送方每次写入数据 < 套接字(Socket)缓冲区大小;
接收方读取套接字(Socket)缓冲区数据不够及时。
半包的主要原因:
发送方每次写入数据 > 套接字(Socket)缓冲区大小;
发送的数据大于协议的 MTU (Maximum Transmission Unit,最大传输单元),因此必须拆包。
知识点:什么是缓冲区?
缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。
缓冲区的优势以文件流的写入为例,如果我们不使用缓冲区,那么每次写操作 CPU 都会和低速存储设备也就是磁盘进行交互,那么整个写入文件的速度就会受制于低速的存储设备(磁盘)。但如果使用缓冲区的话,每次写操作会先将数据保存在高速缓冲区内存上,当缓冲区的数据到达某个阈值之后,再将文件一次性写入到磁盘上。因为内存的写入速度远远大于磁盘的写入速度,所以当有了缓冲区之后,文件的写入速度就被大大提升了。
如何解决它呢?
下面我们将用.Net7做示例
1.实现项目创建 我们需要创建三个项目
分别是 Share
SocketClient
SocketServer
如图下图所示
2.实现SocketServer项目
创建一个SocketServer的Class 然后写入以下代码
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
namespace SocketServer;
public class SocketServer : IDisposable
{
/// <summary>
/// 是否被释放
/// </summary>
private bool _disposable;
/// <summary>
/// 当前的server的Socket对象
/// </summary>
private readonly Socket _socket;
/// <summary>
/// 所有的SocketManager的管理集合
/// 所有客户端都在这里进行管理
/// </summary>
private ConcurrentBag<SocketManager> SocketManagers = new ConcurrentBag<SocketManager>();
/// <summary>
/// 创建SocketServer对象
/// </summary>
/// <param name="ip">监听的ip</param>
/// <param name="port">监听的端口</param>
public SocketServer(string ip, int port = 1314)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var ipAddress = IPAddress.Parse(ip);
_socket.Bind(new IPEndPoint(ipAddress, port));
_socket.Listen();
}
public void Dispose()
{
_disposable = true;
}
/// <summary>
/// 开始等待客户端链接
/// </summary>
public void Start()
{
_ = Task.Factory.StartNew(async () =>
{
while (!_disposable)
{
var socket = await _socket.AcceptAsync();
SocketManagers.Add(new SocketManager(socket));
}
});
}
}
我们看到创建了一个Socket并且监听了指定ip和端口
然后Start()方法是将链接的客户端添加到SocketManager集合管理
在看看SocketManager的方法
using Share;
using System.Net.Sockets;
using System.Text;
namespace SocketServer;
public class SocketManager : IDisposable
{
/// <summary>
/// 是否被释放
/// </summary>
private bool _disposable = false;
/// <summary>
/// 客户端的Socket链接对象
/// </summary>
private readonly Socket ClientSocket;
/// <summary>
/// 接收缓冲区大小
/// 默认8kb
/// </summary>
private int MessageBufferSize;
public SocketManager(Socket clientSocket, int? messageBufferSize = null)
{
ClientSocket = clientSocket;
MessageBufferSize = messageBufferSize ?? 1024 * 8;
Start();
}
public void Dispose()
{
_disposable = true;
}
public void Start()
{
_ = Task.Factory.StartNew(async () =>
{
while (!_disposable)
{
//设置了缓冲区
var buffer = new byte[MessageBufferSize];
// 接送的数据不一定有缓冲区那么大
var len = await ClientSocket.ReceiveAsync(buffer);
// 获取协议定义的长度
var length = ByteUtil.BytesToInt(buffer.AsSpan(0, 4).ToArray(), 0);
// 注:请自行修改地址
var file = File.Create("E:\\迅雷下载\\cs.zip");
// 删掉协议的长度的四个字节
await file.WriteAsync(buffer.AsSpan(4).ToArray());
length -= buffer.AsSpan(4).ToArray().Length;
while ((len = await ClientSocket.ReceiveAsync(buffer))>0)
{
await file.WriteAsync(buffer);
length -= len;
if(length <= 0)
{
Console.WriteLine("获取完成");
file.Close();
break;
}
}
}
});
}
}
当前SocketManager是接收Socket客户端发送的文件并且进行写入到文件中 我们看到ByteUtil.BytesToInt的方法
这个方法可能有些人有疑惑了这是个什么了
首先我们需要解决粘包问题 我们就一开始在发送和接收的时候就定义好了整个发送的消息体,其实可以理解为协议
为了解决掉socket粘包 需要自定义一个通讯协议处理粘包
首先定义消息结构
消息结构由俩部分组成
第一部分由四个字节的消息长度组成 第二部分由消息的整体数据组成这个消息的长度一定是第一部分的所表示的长度
长度 具体消息
xxxx xxxxxxxxx01xxx
这个四个字节就是这条消息的长度(不包括当前四个字节)一般来说四个字节是够用的,这样整个消息体的组成结构就是 0000 0000000000 这样的,前面四个字节一定是整个消息的长度 后面的就是数据
然后需要定义字节转换int和int转换byte的一个方法如下代码
ByteUtil:当前方法是为了协议的长度转换
namespace Share
{
/// <summary>
/// 转换协议字节帮助类
/// </summary>
public class ByteUtil
{
/// <summary>
/// byte数组转换int
/// </summary>
/// <param name="src"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static int BytesToInt(byte[] src, int offset)
{
int value;
value = (int)((src[offset] & 0xFF)
| ((src[offset + 1] & 0xFF) << 8)
| ((src[offset + 2] & 0xFF) << 16)
| ((src[offset + 3] & 0xFF) << 24));
return value;
}
/// <summary>
/// int转byte数组
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static byte[] IntToBytes(int value)
{
byte[] src = new byte[4];
src[3] = (byte)((value >> 24) & 0xFF);
src[2] = (byte)((value >> 16) & 0xFF);
src[1] = (byte)((value >> 8) & 0xFF);
src[0] = (byte)(value & 0xFF);
return src;
}
}
}
好我们在看看SocketClient:
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Net;
using System.Text;
using Share;
namespace SocketClient;
public class SocketClient : IDisposable
{
/// <summary>
/// 是否释放
/// </summary>
private bool _disposable;
/// <summary>
/// 当前链接socket对象
/// </summary>
private readonly Socket _socket;
/// <summary>
/// 创建SocketClient对象
/// </summary>
/// <param name="ip">需要连接的ip</param>
/// <param name="port">需要连接的端口</param>
public SocketClient(string ip, int port = 1314)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.Connect(new IPEndPoint(IPAddress.Parse(ip), port));
}
public void Dispose()
{
_disposable = true;
}
/// <summary>
/// 发送信息到服务器
/// </summary>
/// <param name="value"></param>
public async Task SendByteAsync(byte[] buffer)
{
await SocketExtension.SendByteAsync(_socket, buffer);
}
}
然后在看SocketClient的Program的方法就是我们的Client的启动类
// 创建SocketClient对象 连接到127.0.0.1的SocketServer服务
var socketClient = new SocketClient.SocketClient("127.0.0.1");
// 读取一个zip文件
var fileStream = File.ReadAllBytes("C:\\Users\\Administrator\\Downloads\\Files.zip");
// 然后调用SendByteAsync
await socketClient.SendByteAsync(fileStream);
// 暂停
Console.ReadKey();
SocketExtension.SendByteAsync内部方法:
using System;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace Share
{
public static class SocketExtension
{
/// <summary>
/// 发送定义的协议数据结构
/// </summary>
/// <param name="socket"></param>
/// <param name="value"></param>
/// <returns></returns>
public static async Task<int> SendByteAsync(this Socket socket, byte[] value)
{
// 定义消息和消息长度的总和的字节数组
// 由于定义的消息长度需要占用四个字节所以这个加4
var bytes = new byte[4 + value.Length];
Array.ConstrainedCopy(ByteUtil.IntToBytes(value.Length), 0, bytes, 0, 4);
Array.ConstrainedCopy(value, 0, bytes, 4, value.Length);
return await socket.SendAsync(bytes, SocketFlags.None);
}
}
}
其实这个方法就是将发送的数据组成我们的协议体 前四个字节就是我们的数据长度 后面就是我们的数据了
SocketServer的Program的类:
Console.WriteLine("启动SocketServer中");
// 监听127.0.0.1的端口
var socketServer = new SocketServer.SocketServer("127.0.0.1");
// 启动客户端接收发送接收的服务
socketServer.Start();
Console.ForegroundColor = ConsoleColor.DarkBlue;
Console.WriteLine("启动SocketServer完成");
Console.ReadKey();
然后我们依次启动SocketServer控制台
在启动SocketClient控制台
这个时候我们就可以观察到SocketClient往SocketServer发送的消息了
我们可以将断点打到这个结束位置
等数据传输完成我们就可以发现进入断点
//设置了缓冲区
var buffer = new byte[MessageBufferSize];
// 接送的数据不一定有缓冲区那么大
var len = await ClientSocket.ReceiveAsync(buffer);
// 获取协议定义的长度
var length = ByteUtil.BytesToInt(buffer.AsSpan(0, 4).ToArray(), 0);
var file = File.Create("E:\\迅雷下载\\cs.zip");
// 删掉协议的长度的四个字节
await file.WriteAsync(buffer.AsSpan(4).ToArray());
length -= buffer.AsSpan(4).ToArray().Length;
while ((len = await ClientSocket.ReceiveAsync(buffer))>0)
{
await file.WriteAsync(buffer);
length -= len;
if(length <= 0)
{
Console.WriteLine("获取完成");
file.Close();
break;
}
}
这一块代码主要是做的角色接收第一次的消息体然后解析前面四个字节的协议得到后面需要处理的数据的长度
只要找到数据的长度就可以做到出来粘包的问题因为我们可以吧数据拆分出去,将其不粘包,
这就做到了拆包
好的本文讲完了!源码在群里有兴趣的朋友可以加技术交流群:737776595
微信交流群:
来自token的分享!
Socket 如何处理粘包的更多相关文章
- Socket的粘包处理
Socket的粘包处理 当socket接收到数据后,会根据buffer的大小一点一点的接收数据,比如: 对方发来了1M的数据量过来,但是,本地的buffer只有1024字节,那就代表socket需要重 ...
- 【Python】TCP Socket的粘包和分包的处理
Reference: http://blog.csdn.net/yannanxiu/article/details/52096465 概述 在进行TCP Socket开发时,都需要处理数据包粘包和分包 ...
- c# socket 解决粘包,半包
处理原理: 半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码 粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断 ...
- Socket解决粘包问题2
在AsynServer中对接收函数增加接收判断,如果收到客户端发送的请求信息,则发送10个测试包给发送端,否则继续接收,修改后的接收代码如下: private void AsynReceive() { ...
- Socket解决粘包问题1
粘包是指发送端发送的包速度过快,到接收端那边多包并成一个包的现象,比如发送端连续10次发送1个字符'a',因为发送的速度很快,接收端可能一次就收到了10个字符'aaaaaaaaaa',这就是接收端的粘 ...
- python之socket编程------粘包
一.粘包 什么是粘包 只有TCP只有粘包现象,UDP永远不会粘包 所谓粘包问题主要还是因为接收方不知道之间的界限,不知道一次性提取多少字节的数据所造成的 两种情况发生粘包: 1.发送端需要等缓冲区满才 ...
- socket之粘包发生问题
粘包 注意注意注意: res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdout=subproc ...
- UNIX网络编程——Socket/TCP粘包、多包和少包, 断包
为什么TCP 会粘包 前几天,调试mina的TCP通信, 第一个协议包解析正常,第二个数据包不完整.为什么会这样吗,我们用mina这样通信框架,还会出现这种问题? TCP(transport cont ...
- 网络编程基础【day09】:socket解决粘包问题之MD5(八)
本节内容 1.概述 2.代码实现 一.概述 上一篇博客讲到的用MD5来校验还是用的之前解决粘包的方法,就是客户端发送一个请求,等待服务端的确认的这样的一个笨方法.下面我们用另外一种方法:就是客户端已经 ...
- 网络 --- 3 socket模块 粘包
一 .socket 模块参数及方法 二.缓冲区 三.粘包 1.两种粘包现象 ①连续的小包可能会被优化算法给组合到一起进行发送 ②第一次如果发送的数据大小2000B接收端一次性接受大小为1024, 这就 ...
随机推荐
- MySQL运维7-Mycat水平分表
一.水平分表场景 在业务系统中,有一张日志表,业务系统每天都会产生大量的日志数据,单台服务器的数据存储即处理能力是有限的,可以对数据库表进行拆分,这时候就可以使用水平分表的策略 说明1:水平分表,每个 ...
- pytest框架学习-标签@pytest.mark.
标签 自定义标签(区分大小写) 可以标记测试用例,对测试用例进行分组,有利于对测试用例进行筛选. 比如:给用例打标为API,代表接口自动化的用例,打标方法为@pytest.mark.API,打标后,需 ...
- 为什么在使用onnxruntime-gpu下却没有成功调用GPU?
20240105,记. 最近在使用GPU对onnx模型进行加速过程中(仅针对N卡,毕竟也没有别的显卡了..),遇到了点问题:就是明明在安装了合适版本的显卡驱动和CUDA后,onnx还是不能够成功调用G ...
- B 树和 B+ 树及其实现
B 树 B 树和一般的二叉树有许多相似的地方,二者都是为了加快查找的速度,不同之处在于 B 树是为了解决大量的数据而产生的,更加适合读取相对大的数据块的存储系统.B 树的每个节点一般不会存储实际的数据 ...
- Zookeeper 的基本使用
维基百科对 Zookeeper 的介绍如下所示: Apache ZooKeeper是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务.同步服务和命名注册 ZooKe ...
- CSS3学习笔记-动画
CSS3中提供了许多有趣和实用的动画效果,可以使页面更加生动有趣,下面介绍一些常用的动画效果. @keyframes规则 使用@keyframes规则可以创建一系列动画帧,并定义它们的状态和样式,在页 ...
- Java日期时间处理详解
Java中SimpleDateFormat.LocalDateTime和DateTimeFormatter的区别及使用 在Java的世界里,处理日期和时间是常见的任务.尤其在Java 8之前,Simp ...
- 文心一言 VS 讯飞星火 VS chatgpt (31)-- 算法导论5.2 3题
三.利用指示器随机变量来计算掷n 个骰子之和的期望值. 文心一言: 为了计算掷n个骰子之和的期望值,我们需要先了解一个重要的概念:指示器随机变量. 指示器随机变量是一种特殊的随机变量,它只有两个取值: ...
- 云小课|使用SpringBoot快速构建FunctionGraph HTTP函数
阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:本篇云小课主要指导 ...
- Gartner 权威预测未来4年网络安全的8大发展趋势
翻译:SEAL安全 原文标题: Gartner Unveils the Top Eight Cybersecurity Predictions for 2022-23 原文链接: https://ww ...