首页 > 编程语言 >C# 使用TouchSocket实现Tcp协议通讯,并且解决分包、粘包的问题

C# 使用TouchSocket实现Tcp协议通讯,并且解决分包、粘包的问题

时间:2023-08-09 11:56:55浏览次数:40  
标签:字节 C# Tcp 粘包 Length var new byte data

我们知道如果Socket传输数据太频繁并且数据量级比较大,就很容易出现分包(一个包的内容分成了两份)、粘包(前一个包的内容分成了两份,其中一份连着下一个包的内容)的情况。

粘包的处理方式有很多种,常见的三种是:

  1. 每个包都在头部增加一个当前传输包的int4字节大小作为包头。每次接收到数据先读取的包头,确定这一包的实际长度n,当接收够n+4长度的数据就是一个完整的包,再重复读取下一包的包头。(相对其它方式,编码复杂
  2. 使用固定分隔符对每包进行分割。(轮询查询每个分隔符的位置,并且有可能分隔符与实际数据出现相同的情况
  3. 发送方和接收方规定固定大小的缓冲区,也就是发送和接收都使用固定大小的 byte[] 数组长度,当字符长度不够时使用空字符弥补。(字符长度不够增加空字符使得传输数据增长

推荐使用第一种方式。但是第一种方式编码相对复杂,所以下面结合第三方组件简单快速的实现第一种方式。

下面演示使用TouchSocket组件进行Socket传输。

客户端:

           TouchSocket.Sockets.TcpClient tcpClient = new TouchSocket.Sockets.TcpClient();

            tcpClient.Connecting = (client, e) =>
            {
                // 这里是指增加Int大小的包头
                client.SetDataHandlingAdapter(new FixedHeaderPackageAdapter() { FixedHeaderType = FixedHeaderType.Int });
            };

            tcpClient.Received = (client, byteBlock, obj) =>
            {
                // 前4字节为包头,包含了包大小信息
                var data = new byte[byteBlock.Buffer.Length - 4];
                Array.Copy(byteBlock.Buffer, 4, data, 0, data.Length);

                //从服务器收到信息
                string mes = Encoding.UTF8.GetString(data, 0, data.Length);
                var text = $"已从服务器接收到信息:{mes}\r\n";
                Console.WriteLine(text);
                Application.Current.Dispatcher.Invoke(() =>
                {
                    textBlock.Text += text;
                });
            };
            tcpClient.Setup("127.0.0.1:21345");
            tcpClient.Connect();

            var str = "物服务器服务器服务器发起非亲非故";
            var bytes = Encoding.UTF8.GetBytes(str);
            var addLen = AddLen(bytes);
            tcpClient.Send(addLen);
            tcpClient.Close();
        /// <summary>
        /// 增加包头
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        internal byte[] AddLen(byte[] bytes)
        {
            int dataLength = bytes.Length;

            // 不要=先将int转字符串,再通过Encoding.UTF8.GetBytes转byte[],那样子byte[]的长度大小不固定,而BitConverter.GetBytes(int)固定为4个字节,BitConverter.GetBytes(long)固定为8个字节
            var dataLengthBytes = BitConverter.GetBytes(dataLength);

            byte[] sendBuffer = new byte[dataLengthBytes.Length + bytes.Length]; // 为数据包申请缓存,包括4个字节的分隔符和实际数据

            Buffer.BlockCopy(dataLengthBytes, 0, sendBuffer, 0, dataLengthBytes.Length); // 将长度信息写入数据包缓存的前面4个字节
            Buffer.BlockCopy(bytes, 0, sendBuffer, dataLengthBytes.Length, bytes.Length); // 将实际数据写入数据包缓存的后面

            return sendBuffer;
        }

服务端:

           TcpService service = new TcpService();
            service.Connecting = (client, ex) =>
            {
                client.SetDataHandlingAdapter(new FixedHeaderPackageAdapter() { FixedHeaderType = FixedHeaderType.Int });
            };

            service.Connected += (client, args) =>
            {
                _socketClient = client;
            };

            service.Received = (client, byteBlock, requestInfo) =>
            {
                var array = byteBlock.ToArray();

                // 前4字节为包头,包含了包大小信息
                var data = new byte[byteBlock.Buffer.Length - 4];
                Array.Copy(byteBlock.Buffer, 4, data, 0, data.Length);

                var text = $"已从{client.IP}:{client.Port}接收到信息:{Encoding.UTF8.GetString(data)}\r\n";
                Console.WriteLine(text);//Name即IP+Port
                Application.Current.Dispatcher.Invoke(() =>
                {
                    textBlock.Text += text;
                });

                var sendBuffer = AddLen(data);

                for (int i = 0; i < 100; i++)
                {
                    _socketClient.Send(sendBuffer); // 发送整个数据包
                    Thread.Sleep(1000);
                }
            };

            var config = new TouchSocketConfig();
            config.SetListenIPHosts(new IPHost[] { new IPHost("127.0.0.1:21345"), new IPHost(7790) });//同时监听两个地址
            service.Setup(config);
            service.Start();

其中增加Int包头的设置必须在Connecting回调中。

注意包的大小为int类型,不要先把int类型转成字符串再转byte[],那样子会导致包头不是固定的4字节(因为int类型本身只占4字节),所以应该使用BitConverter.GetBytes(dataLength)进行转换。

由于增加了包头,所以接收到的数据都必须先把包头前的4字节去掉。

【FixedHeaderPackageAdapter包模式】

该适配器主要解决TCP粘分包问题,数据格式采用简单而高效的“包头+数据体”的模式,其中包头支持:

  • Byte模式(1+n),一次性最大接收255字节的数据。
  • Ushort模式(2+n),一次最大接收65535字节。(默认)
  • Int模式(4+n),一次最大接收2G数据。

由于Int模式一次最大接收2G数据,所以不用额外考虑包大小的问题,组件里面自动会进行拆包传输处理。

 

标签:字节,C#,Tcp,粘包,Length,var,new,byte,data
From: https://www.cnblogs.com/log9527blog/p/17587958.html

相关文章

  • Jackson —— json转换工具
    一、介绍1.一个可以实现JSON字符串和Java对象进行转换的工具类2.转换的核心类是ObjectMapper 二、ObjectMapper常用的两个方法方法名称说明publicStringwriteValueAsString(Objectobj)将一个对象转换为json字符串publicTreadValue(Stringjson,Classclaz......
  • kubeadm 更新ca、front-proxy-ca 根证书到100年(基于现有的K8S环境)
    背景:在现有K8S环境中默认情况下编译新kubeadm只能更新组件证书的时间,至于ca,front-proxy-ca集群根证书是无法更新的#更新集群ca、front-proxy-ca根证书1、备份master节点的ca、front-proxy-ca根证书cp-a/etc/kubernetes/pki/ca.crt/etc/kubernetes/pki/ca.crt.oldcp-a......
  • CF1857B Maximum Rounding 题解
    题面题目大意给定\(T\)组数据,每组数据一个自然数\(n\),可以多次选择第\(k\)位数进行四舍五入,求出四舍五入后该数的最大值。分析思路思想:贪心。这里给定了两种操作。四舍和五入。显然我们想要让最终的结果最大,我们的操作只能进行五入而不可以进行四舍。因为如果我们进行了......
  • Excel实现下拉菜单多选
    Excel实现下拉菜单多选注意事项需要用到VBA宏编程WPS需要商业版才能启用VBA编程,office破解版(仅供学习)可以使用本文所有操作均在office2021上进行 一、(已有可忽略)打开office开发者工具"文件“-->"选项"-->"自定义功能区"-->勾上"开发者工具" 二、设......
  • C# byte[]与Bitmap互转
    首先先观察一下本地bmp图像结构(参考:https://blog.csdn.net/qq_37872392/article/details/124710600): 可以看到bmp图像结构除了纯图像像素点位信息,还有一块未用空间(OffSet)。所以如果需要得到图像所有数据进行转换,则可以使用网上提供的大部分方式:bitmap转byte[]:publicbyte[......
  • .NET CORE Worker Service服务访问服务器共享目录提示无权限
    问题:.NETCOREWorkerService中访问服务器共享目录,代码运行时不报错,发布出来后访问共享目录创建文件夹或者文件等所有操作都提示无权限解决方案:先对共享目录进行连接,然后再进行文件夹或文件创建等操作boolstate=connectState(共享目录路径,用户名,密码);......
  • java XSSFWorkbook excel 公式计算
    excel公式计算//创建一个工作薄XSSFWorkbookworkbook=newXSSFWorkbook();//如果是最后一列添加一个求和计算,将结果放到同一列最后一个。dataLists数据列表XSSFSheetsheet=workbook.getSheet(replaceSpecStr(sheetNames.get(0)));Rowrow......
  • MuMu模拟器运行一段时间后Device.Present耗时突然上升
    1)MuMu模拟器运行一段时间后Device.Present耗时突然上升2)​如何在运行过程中获得温度信息3)InputSystem鼠标更换主按键的Bug4)如何禁止Unity向https://config.uca.cloud.unity3d.com发送设备信息这是第347篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子......
  • 微前端框架哪个好?QianKun还是MicroApp
    在当前云原生微服务、业务中台、低代码平台等IT架构下,不再是传统的烟囱式应用系统建设,而是打破企业业务部门竖井,建立企业级的信息化平台(数据中台、业务中台),那么对业务开发的解耦和聚合将成为关键技术,目前对于系统后端已有成熟的微服务架构,基于SpringBoot开发微服务,通过SpringCloud......
  • docker部署php7.3+nginx
    1.拉取php+nginx镜像dockerpullphp:7.3.24-fpm-stretchdockerpullnginx:latest 2.启动PHP:dockerrun-d-v/var/www/test:/var/www/html-p9000:9000--namexy_phpfpmphp:7.3.24-fpm-stretch 参数说明-d让容器在后台运行-p添加主机到容器的端口映射-......