C#高性能大容量SOCKET并发(五):粘包、分包、解包

C#Socket通信,解决粘包和分包问题 源码下载,学习的时候做的,现在可以做到一个服务端对应多个客户端同时接受消息,也解决了分包和粘包的问题,欢迎下载 (16条消息) 网络通信编程学习.7z-C#文档类资源-优快云文库https://download.youkuaiyun.com/download/Trinity_Force/44900216 什么是粘包分包 TCP是面向连接的协议 TCP是点到点的通信 TCP提供可靠的传输服务 TCP协议提供全双工的通信 TCP协议面向字节流进行传输的,可以对用户的数据进行拆分或合并 TCP... 阅读详情

粘包

使用TCP长连接就会引入粘包的问题,粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。粘包可能由发送方造成,也可能由接收方造成。TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据,造成多个数据包的粘连。如果接收进程不及时接收数据,已收到的数据就放在系统接收缓冲区,用户进程读取数据时就可能同时读到多个数据包。

粘包一般的解决办法是制定通讯协议,由协议来规定如何分包解包。

分包

在NETIOCPDemo例子程序中,我们分包的逻辑是先发一个长度,然后紧接着是数据包内容,这样就可以把每个包分开。

应用层数据包格式如下:

应用层数据包格式 
数据包长度Len:Cardinal(4字节无符号整数)数据包内容,长度为Len
AsyncSocketInvokeElement分包处理主要代码,我们收到的数据都是在ProcessReceive方法中处理,处理的方法是把收到的数据存到缓冲区数组中,然后取前4个字节为长度,如果剩下的字节数大于等于长度,则取到一个完整包,进行后续逻辑处理,如果取到的不够一个包,则不处理,等待后续包接收,具体代码如下:
        public virtual bool ProcessReceive(byte[] buffer, int offset, int count) //接收异步事件返回的数据,用于对数据进行缓存和分包
        {
            m_activeDT = DateTime.UtcNow;
            DynamicBufferManager receiveBuffer = m_asyncSocketUserToken.ReceiveBuffer;

            receiveBuffer.WriteBuffer(buffer, offset, count);
            if (receiveBuffer.DataCount > sizeof(int))
            {
                //按照长度分包
                int packetLength = BitConverter.ToInt32(receiveBuffer.Buffer, 0); //获取包长度
                if (NetByteOrder)
                    packetLength = System.Net.IPAddress.NetworkToHostOrder(packetLength); //把网络字节顺序转为本地字节顺序


                if ((packetLength > 10 * 1024 * 1024) | (receiveBuffer.DataCount > 10 * 1024 * 1024)) //最大Buffer异常保护
                    return false;

                if ((receiveBuffer.DataCount - sizeof(int)) >= packetLength) //收到的数据达到包长度
                {
                    bool result = ProcessPacket(receiveBuffer.Buffer, sizeof(int), packetLength);
                    if (result)
                        receiveBuffer.Clear(packetLength + sizeof(int)); //从缓存中清理
                    return result;
                }
                else
                {
                    return true;
                }
            }
            else
            {
                return true;
            }
        }
解包

由于我们应用层数据包既可以传命令也可以传数据,因而针对每个包我们进行解包,分出命令和数据分别处理,因而每个Socket服务对象都需要解包,我们解包的逻辑是放在ProcessPacket中,命令和数据的包格式为:

命令长度Len:Cardinal(4字节无符号整数)命令数据
        public virtual bool ProcessPacket(byte[] buffer, int offset, int count) //处理分完包后的数据,把命令和数据分开,并对命令进行解析
        {
            if (count < sizeof(int))
                return false;
            int commandLen = BitConverter.ToInt32(buffer, offset); //取出命令长度
            string tmpStr = Encoding.UTF8.GetString(buffer, offset + sizeof(int), commandLen);
            if (!m_incomingDataParser.DecodeProtocolText(tmpStr)) //解析命令
              return false;

            return ProcessCommand(buffer, offset + sizeof(int) + commandLen, count - sizeof(int) - commandLen); //处理命令
        }
每个包中包含多个协议关键字,每个协议关键字用回车换行分开,因此我们需要调用文本分开函数,然后针对每条命令解析出关键字和值,具体代码在IncomingDataParser.DecodeProtocolText如下:
        public bool DecodeProtocolText(string protocolText)
        {
            m_header = "";
            m_names.Clear();
            m_values.Clear();
            int speIndex = protocolText.IndexOf(ProtocolKey.ReturnWrap);
            if (speIndex < 0)
            {
                return false;
            }
            else
            {
                string[] tmpNameValues = protocolText.Split(new string[] { ProtocolKey.ReturnWrap }, StringSplitOptions.RemoveEmptyEntries);
                if (tmpNameValues.Length < 2) //每次命令至少包括两行
                    return false;
                for (int i = 0; i < tmpNameValues.Length; i++)
                {
                    string[] tmpStr = tmpNameValues[i].Split(new string[] { ProtocolKey.EqualSign }, StringSplitOptions.None);
                    if (tmpStr.Length > 1) //存在等号
                    {
                        if (tmpStr.Length > 2) //超过两个等号,返回失败
                            return false;
                        if (tmpStr[0].Equals(ProtocolKey.Command, StringComparison.CurrentCultureIgnoreCase))
                        {
                            m_command = tmpStr[1];
                        }
                        else
                        {
                            m_names.Add(tmpStr[0].ToLower());
                            m_values.Add(tmpStr[1]);
                        }
                    }
                }
                return true;
            }
        }
处理命令

解析出命令后,需要对每个命令进行处理,各个协议实现类从AsyncSocketInvokeElement.ProcessCommand继承,然后编写各自协议处理逻辑,如吞吐量的测试协议逻辑实现代码如下:

namespace SocketAsyncSvr
{
    class ThroughputSocketProtocol : BaseSocketProtocol
    {
        public ThroughputSocketProtocol(AsyncSocketServer asyncSocketServer, AsyncSocketUserToken asyncSocketUserToken)
            : base(asyncSocketServer, asyncSocketUserToken)
        {
            m_socketFlag = "Throughput";
        }

        public override void Close()
        {
            base.Close();
        }

        public override bool ProcessCommand(byte[] buffer, int offset, int count) //处理分完包的数据,子类从这个方法继承
        {
            ThroughputSocketCommand command = StrToCommand(m_incomingDataParser.Command);
            m_outgoingDataAssembler.Clear();
            m_outgoingDataAssembler.AddResponse();
            m_outgoingDataAssembler.AddCommand(m_incomingDataParser.Command);
            if (command == ThroughputSocketCommand.CyclePacket)
                return DoCyclePacket(buffer, offset, count);
            else
            {
                Program.Logger.Error("Unknow command: " + m_incomingDataParser.Command);
                return false;
            }
        }

        public ThroughputSocketCommand StrToCommand(string command)
        {
            if (command.Equals(ProtocolKey.CyclePacket, StringComparison.CurrentCultureIgnoreCase))
                return ThroughputSocketCommand.CyclePacket;
            else
                return ThroughputSocketCommand.None;
        }

        public bool DoCyclePacket(byte[] buffer, int offset, int count)
        {
            int cycleCount = 0;
            if (m_incomingDataParser.GetValue(ProtocolKey.Count, ref cycleCount))
            {
                m_outgoingDataAssembler.AddSuccess();
                cycleCount = cycleCount + 1;
                m_outgoingDataAssembler.AddValue(ProtocolKey.Count, cycleCount);
            }
            else
                m_outgoingDataAssembler.AddFailure(ProtocolCode.ParameterError, "");
            return DoSendResult(buffer, offset, count);
        }
    }
}
DEMO下载地址: http://download.youkuaiyun.com/detail/sqldebug_fan/7467745
免责声明:此代码只是为了演示C#完成端口编程,仅用于学习和研究,切勿用于商业用途。水平有限,C#也属于初学,错误在所难免,欢迎指正和指导。邮箱地址:fansheng_hx@163.com。

C#高性能大容量SOCKET并发(零):代码结构说明 C#版完成端口具有以下特点: 连接在线管理(提供在线连接维护,连接会话管理,数据接收,连接断开等相关事件跟踪); 发送数据智能合并(组件会根据资源使用情况,对多个同时发送向同一连接的多个消息数据进行合并写入缓冲区; 内存池管理(提供一系列的接收和发送buffer可复用池); 数据读写封装; 通讯协议封装; 阅读详情

相关推荐

C# Socket高并发

C#高性能大容量SOCKET并发(转) C#高性能大容量SOCKET并发(零):代码结构说明 C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍 C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 C#高性能大容量SOCKET并发(三):接收、发送 C#高性能大容量SOCKET并发(四):缓存设计 C#高性能大容量SOCKET并发...

ishenjiaming的博客 2897

C# 优雅的解决TCP Socket粘包、分包问题

文章目录一、概述二、探索问题2.1 源码位置2.2 Demo位置2.3 预热2.4 Config配置详解三、解决思路四、数据处理适配器概述4.1 正常数据处理适配器(`NormalDataHandlingAdapter`)4.2 固定包头数据处理适配器(`FixedHeaderPackageAdapter`)4.3 固定长度数据处理适配器(`FixedSizePackageAdapter`)4.4 终止因子分割数据处理适配器(`TerminatorPackageAdapter`)五、数据处理适配器工作原理六

若汝棋茗 的博客 1万+

C#高性能大容量SOCKET并发(十一):编写上传客户端

C#的TcpClient封装了NET的底层Socket操作,基于TCP协议,提供了阻塞和非阻塞模式调用,具体是设置m_tcpClient.Client.Blocking = true表示使用阻塞模式,反之则使用非阻塞模式。阻塞模式表示接收完指定长度的数据才返回,非阻塞模式表示收到一点数据就返回。 如我们调用m_tcpClient.Client.Receive(m_recvBuffer.Buffer, sizeof(int), packetLength, SocketFlags.None),假设传入的长度为1

SQLDebug_Fan的专栏 1万+

基于C#的TCP协议的Socket本地数据传输实现

好吧,又是一个周末,看来这个周末又没什么事,女友又给我订了外卖,为什么她不给我做饭呢,明明会做的… 好了,闲话少说,突然想起我大学里第一次自己研究Socket的时候,那时候写了两页很短很短的代码,仅仅是实现了一句话的本地传输,当时兴奋地要命,今天就当回忆了把它写出来 基于C#的TCP协议 //服务端 using System; using System.Collections.Generic; u...

weixin_43863320的博客 729

C#高性能大容量SOCKET并发(三):接收、发送

异步数据接收有可能收到的数据不是一个完整包,或者接收到的数据超过一个包的大小,因此我们需要把接收的数据进行缓存。异步发送我们也需要把每个发送的包加入到一个队列,然后通过队列逐个发送出去,如果每个都实时发送,有可能造成上一个数据包未发送完成,这时再调用SendAsync会抛出异常,提示SocketAsyncEventArgs正在进行异步操作,因此我们需要建立接收缓存和发送缓存。

SQLDebug_Fan的专栏 3万+

c# Socket 多线程 分包 发送/接受数据

c# Socket 多线程 分包 发送/接受数据 客户端 分包发送数据,服务端接受并相应, 第一次接触Socket也不知道理解的对不对 有问题的还请见谅

C# Socket 高并发

https://www.cnblogs.com/AaronAndJoe/p/5796369.html C#高性能大容量SOCKET并发(零):代码结构说明 C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍 C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 C#高性能大容量SOCKET并发(三):接收、发送 C#高性能大容量SOCKET...

qq_18932003的博客 1079

C#解决socket粘包问题,通信提供了一个自定义通信协议类MsgProtol,该类封装了打包和解包方法,可以解决分包和粘包问题

C#解决socket通信过程中粘包分包问题,本项目是一个只有6个C#代码文件的开源小工程,用来学习基于TCP的套接字通信包,可以自定义通信协议,处理分包和粘包,内置一个服务端和客户端的套接字程序,也有测试代码和对应的博客讲解代码。在消息正文的前方加入一个固定长度的数字表示消息正文的长度,这样就可以基本解决分包和粘包问题。整个工程基本基于良好的面向对象思想,代码注释清晰简洁。

C#高性能大容量SOCKET并发(六):超时Socket断开(守护线程)和心跳包

在服务端版Socket编程需要处理长时间没有发送数据的Socket,需要在超时多长时间后断开连接,我们需要独立一个线程(DaemonThread)来轮询,在执行断开时,需要把Socket对象锁定,并调用CloseClientSocket来断开连接。有超时连接,相对应的需要设计心跳包,心跳包用来检测连接和维护连接状态,心跳包的原理是客户端发送一个包给服务器,服务器收到后发一个响应包给客户端,通过检测是否有返回来判断连接是否正常。

SQLDebug_Fan的专栏 2万+

c# tcp 基于完成端口开发 高性能 高并发 吞吐量大 包含服务端 客户端完整代码 支持最大连接数支持65535个长连接

c# tcp 基于完成端口开发 高性能 高并发 吞吐量大 包含服务端 客户端完整代码 支持最大连接数支持65535个长连接

c#完全端口高性能网络TCP服务器和客户端源码

一、源码特点 1、采用完成端口(IOCP)编写的高性能TCP网络服务器,进行收发测试代码。 2、报文收发处理速度非常快 3、pu使用率低 4、测试环境:双核2G内存 5、自带客户端和服务器通讯代码 6、服务器能连接5000以上客户端连接,上限取决于服务器性能了 二、功能介绍 1、实现网络通讯类的封装,这些类可以在项目中直接使用; 2、实现高并发情况下,数据接收和发送性能测试。 三、注意事项 1、开发环境为Visual Studio 2010,使用.net 4.0开发; 2、默认监听端口9900 3、将IP地址更换成本机的,再进行调试 4、如果提示本机积极拒绝访问,就把防火墙关了试试

探索C高性能大容量SOCKET并发通信的利器

探索C#高性能大容量SOCKET并发通信的利器 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 在当今的网络应用中,高性能和大容量的SOCKET并发处理是许多开发者面临的挑战。为了应对这一需求,我们推出了一个基于C#的示例项目,展示了如何使用完成端口(IOCP)技术来实现高性能、大容量的SOCKET并发通信。该项目不仅包含了一个高性能的服务端实现,还提供了一个C#客户端...

gitblog_09750的博客 836

YOLOv11 网络结构详解:一文看懂 Backbone、Head、C3k2、SPPF 和 Detect 检测头

在使用 YOLOv11 进行目标检测时,我们经常会接触到或这样的模型配置文件。backbone和head分别是什么意思?ConvC3k2SPPFC2PSA这些模块分别起什么作用?P3/8、P4/16、P5/32 代表什么?为什么最后 Detect 要同时接收 3 个不同尺度的特征图?YOLOv11n、YOLOv11s、YOLOv11m、YOLOv11l、YOLOv11x 的区别在哪里?

learnerYC的博客 1808
上一篇: C#高性能大容量SOCKET并发(四):缓存设计
下一篇: C#高性能大容量SOCKET并发(六):超时Socket断开(守护线程)和心跳包
SQLDebug_Fan
博客等级 码龄17年 710粉丝 26原创
评论 11
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值