zoukankan      html  css  js  c++  java
  • 蛙蛙推荐:蛙蛙教你解析网络包

    蛙蛙教你解析网络包

    摘要:做网络应用,封包,解包是家常便饭,但如何做到准确、稳定而且性能好,却不太容易做到,这次和大家分享一下我在解析网络包上的经验。

    思路:设计一个网络协议,一般都会分包,一个包就相当于一个逻辑上的命令。
    1、如果我们用udp协议,省事的多,一次会收到一个完整的包,但UDP不可靠,顺序也不能保证,当然像QQ对UDP封装的很好,模拟了TCP的可靠性。网上也有一些封装好的可靠的UDP组件,大家用的话可以找找。关于用什么协议好这个问题,本贴不讨论。
    2、如果我们用TCP协议不是长连接,像HTTP(不考虑KeepAlive)那样,一个连接上只发送一个包,我们也会很清晰的区分出接受到的每一个包。
    3、还有就是我们还用TCP长连接,但每次发送固定长度的包,如果要发送的数据长度不够就用\0补齐,如果大于固定长度,就分成几个发,这个也很简单实用。
    4、再有就是一个包有特定的开始和结尾,比如包头是<bof>包尾是<eof>,我们在可以从头读到尾,并把一个一个的包放入队列,由处理线程去处理。
    5、再有一种就是每个包有固定长度的header,这个header里包含一个包的长度信息,我们可以先从头里读出长度信息,然后再借着读这么长的数据,完了这就是一个包。

    关于封包的几种类型我就想到这么多,其中的利弊大家一看便知,我就不忽悠了,本文主要介绍最后一种方式,好多网络协议用的都是这种,包括CMPP协议,我们自己设计协议的时候一般不用像CMPP协议那样,因为二进制协议虽然虽然节省网络流量,但可读性不好。出问题,抓个包分析起来太麻烦。我们可以用.net自带的序列功能把要发送的类序列化成XML字符串发送出去,这多好看呀。

    由于Socket缓冲区设置及其他的原因,Socket在接受数据的时候有时候不能完整的收到一个包,就是你读出包的长度后,可能不能一次就读取这么多数据。而如果读个半截儿的包就用UTF8Encoding等来解析,会解析出乱码的,我们这里用Encoding.UTF8.GetDecoder()来对包进行成块儿的解析,它就是用来做这种事情的。

    下面就来看一下代码,代码的注释很全,演示了一个包从发到接受、解析的全过程,其中接受的过程没有一次收全所有的包,而是收了好几次,但我们最终还是成功的解析了收到的包。

     

     

    public static void UnPack()
    {
        
    //1、声明通过socket发送的字符串
        string toSendStringBySocket = "娃娃士大夫%#¥%My name is 蛙蛙王子!!";
        
    //2、转换成utf-8字节数组
        byte[] bsInput = Encoding.UTF8.GetBytes(toSendStringBySocket);

        
    //3、计算要发送的字节数组的长度,并写到第一块儿字节数组的开头
        
    //一般协议设计里都有一个长度的Header,这里就是写这个Header
        int inputBytesCount = bsInput.Length;
        
    byte[] bs1 = new byte[4 + 3]; //4是一个int的长度,3是底一块字节数组除了Header剩余的大小
        Buffer.BlockCopy(BitConverter.GetBytes(inputBytesCount), 0, bs1, 04);

        
    //4、把要发送的字节数组拆分成3块儿发出去,因为socket在接受字节数组的时候
        
    //也可能半截半截儿的接收,我们就是要模拟这种效果下的拆包,因为第一块包写了
        
    //一个4个字节的Header,而第一块字节数组长度是7,所以再写三个字节长度的数据
        int offSet = 0;
        Buffer.BlockCopy(bsInput, offSet, bs1, 
    43);
        offSet 
    += bs1.Length - 4;

        
    //5、写第二块儿数据
        byte[] bs2 = new byte[8];
        Buffer.BlockCopy(bsInput, offSet, bs2, 
    0, bs2.Length);
        offSet 
    += bs2.Length;

        
    //6、写第三块儿数据,我们这里模拟在最后一块数据的末尾加一些乱七八糟的数据
        
    //这些乱七八糟的数据有可能是下一个包的header。
        byte[] bs3 = new byte[bsInput.Length - offSet + 4];
        Buffer.BlockCopy(bsInput, offSet, bs3, 
    0, bsInput.Length - offSet);
        Buffer.BlockCopy(
    new byte[] 1234 }0, bs3, bs3.Length - 44);

        
    //7、Socket的接收方在执行BeginReceive函数,并回调函数里把收到的数据放入一个队列里
        
    //dotNet的队列内部就是一个环形数组,这里直接就当环形缓冲区来用了。
        Queue<byte[]> bufferPool = new Queue<byte[]>();
        bufferPool.Enqueue(bs1);
        bufferPool.Enqueue(bs2);
        bufferPool.Enqueue(bs3);

        
    //8、初始化一些变量准备解包
        
        
    //声明一个字符串缓冲区,大小是你的协议里规定的最大的包体长度
        char[] chars = new char[256]; 
        
    //定义一个UTF-8的Decoder,它可以成块的解包,内部自动维护解析状态
        
    //关于它的使用请参考MSDN或者《.net框架设计》
        Decoder d = Encoding.UTF8.GetDecoder();
        
    int charLen = 0//定义每次解包返回的字符长度
        int parseBytesCount = 0;  //定义已解包的字节数
        int LenghHeader = 0//定义收到包的长度
        bool needReadLengthHeader = true//是否需要读取长度的头

        
    int srcOffSet = 0//定义要解析的数据块的偏移量
        byte[] tempBuffer;
        
    //9、当环形缓冲里有数据的时候就一直解析
        while (bufferPool.Count > 0)
        
    {
            
    //10、读取数据包的长度信息,LengthHeader
            
    //因为第一块儿包包含长度信息,所以要先读出来
            
    //读了长度包后,要把数据库解析偏移量加4
            if(needReadLengthHeader)
            
    {
                LenghHeader 
    = BitConverter.ToInt32(bs1, parseBytesCount);
                needReadLengthHeader 
    = false;
                srcOffSet 
    = 4;
            }

            
            
    //11、从环形缓冲区取出一块儿数据
            tempBuffer = bufferPool.Dequeue();
            parseBytesCount 
    += tempBuffer.Length-srcOffSet; //更改已解析的字节数

            
    //12、如果已解析的字节数大于数据的长度,那么只解需要解析的字节
            if (parseBytesCount > LenghHeader)
            
    {
                parseBytesCount 
    -= tempBuffer.Length;
                d.GetChars(tempBuffer, srcOffSet, inputBytesCount 
    - parseBytesCount, chars, charLen);
                
    //这里记录下当前的临时缓冲区已解析到了什么位置,准备解析下一个包
                srcOffSet = inputBytesCount - parseBytesCount; //
                break;
            }

            
    //13、解析这半拉包
            charLen += d.GetChars(tempBuffer, srcOffSet, tempBuffer.Length-srcOffSet, chars, charLen);
            srcOffSet 
    = 0;
        }


        
    string s = new string(chars);
        
    //14、通知包处理线程来处理这个包
        Console.WriteLine(s); 
    }
  • 相关阅读:
    使用Apache Curator监控Zookeeper的Node和Path的状态
    mongo创建用户
    window下关闭nginx
    spring 下载地址
    Quartz Spring与Spring Task总结
    oracle 11g 空表也导出
    修改oracle字符集
    linux 查看最大文件
    JAVA https证书相关
    抽象类与接口
  • 原文地址:https://www.cnblogs.com/onlytiancai/p/unpack_network_package.html
Copyright © 2011-2022 走看看