zoukankan      html  css  js  c++  java
  • 编写一个基于TCP协议的包解析器

    总体思路:
    这里用select IO模型
    当接收到网络数据流的时候, 直接把数据丢到一个缓冲区中去
    这里封装了对缓冲区操作的类, 提供的接口操作包括:
    1.添加(追加)流数据到缓冲区
    2.取出缓冲区第一个合法的包(拆掉包头包尾等数据)

    就两个操作接口, 很简单的操作

    为了方便操作数据流, 可以用一些现成的容器去处理, 如果用Qt开发, 可以用QByteArray, 如果用VC开发, 可以用string

    Qt:
    .h

    class DataPack : public QObject
    {
        Q_OBJECT
    public:
        explicit DataPack(QObject *parent = 0);
        
        bool appendStream(char* data, int len);
        QByteArray Pack();
    
    private:
        QByteArray buf;
        QMutex muxBuf;
    };


    .cpp

    DataPack::DataPack(QObject *parent) :
        QObject(parent)
    {
        buf.clear();
    }
    
    bool DataPack::appendStream(char *data, int len)
    {
        QMutexLocker locker(&muxBuf);
        if(buf.size() > 64 * 1024)
        {//缓存超过64k
            return false;
        }
        buf.append(data, len);
        return true;
    }
    
    QByteArray DataPack::Pack()
    {//检测缓冲区, 取出第一个合法数据包
        QByteArray front = "KT";    //包头标志
        QByteArray tail = "END";    //包尾标志
    
        QMutexLocker locker(&muxBuf);
        do
        {
            if(-1 == buf.indexOf(front))
            {//未有KT开头的标志位, 数据流非法, 清空缓存
                buf.clear();
                return 0;
            }
            if(!buf.startsWith(front))
            {//缓存区数据不为KT标志开头, 肯能存在垃圾数据, 删除前面部分数据流到已KT开始为止
                buf.remove(0, buf.indexOf(front));
            }
            //"KT" len flag data(不定大小) crc "END"
            if(buf.length() < 17)
                return 0;
            qint32 len = *reinterpret_cast<qint32*>(buf.mid(2, 4).data());
    
            if(len <= 0 || len > 32 * 1024)
            {//包的大小有误, 非法数据 或 之前找的 KT开头标志有误, 继续找下一个包头标志
                buf.remove(0, 2); //删除KT
                continue;
            }
            if(buf.size() < len + 6)
            {//非完整包
                return 0;
            }
            quint32 flag = *reinterpret_cast<quint32*>(buf.mid(2 + 4, 4).data());
            quint32 crc = *reinterpret_cast<quint32*>(buf.mid(2 + 4 + len - 7, 4).data());
            if(tail != buf.mid(2 + 4 + len - 3, 3))
            {//包尾标志错误, 丢弃这个包缓存
                buf.remove(0, 2 + 4 + len);
                continue;
            }
            QByteArray pack = buf.mid(2 + 4 + 4, len - 4 - 3);
            buf.remove(0, 2 + 4 + len);
            return pack;
        }while(true);
    }


    这里的解析是针对一个特定的结构来处理的, 这里处理的包结构是这样的(内存分布):

    "KT"    //包头标志 --16位
    len    //后面数据长度 --32位
    flag    //包的标志(什么标志可以自己定, 看具体业务需要) --32位
    data    //包数据(可以进一步封装成一定结构) --不定长
    crc    //数据包校验值(校验用, 看具体业务需不需要用) --32位
    "END"    //包尾标志 --24位

    整合成一个结构体:

    typedef struct _Pack
    {
        char front[2];
        long len;
        long flag;
        struct Data{
            ...
        };
        long crc;
        char end[3];
        _Pack() {
            front[0] = 'K';
            front[1] = 'T';
            ...
            end[0] = 'E';
            end[1] = 'N';
            end[2] = 'D';
        }
    }Pack;


    注意要内存对齐, 要这样做:

    #pragma pack(1)
    typedef struct _Pack
    {
        char front[2];
        long len;
        long flag;
        struct Data{
            ...
        };
        long crc;
        char end[3];
        _Pack() {
            front[0] = 'K';
            front[1] = 'T';
            ...
            end[0] = 'E';
            end[1] = 'N';
            end[2] = 'D';
        }
    }Pack;
    #pragma pack()

    VC:
    .h

    class XXX
    {
    public:
        string Pack();        //返回数据包
    private:
        string recvBuf;        //接收缓存区
    };


    .cpp

    string XXX::Pack()
    {
        string pack;
        if(this->recvBuf.size() <= 0)
            return "";
        const string front = "KT";        //包开头标志
        const string end = "END";        //包结尾标志
    
        do
        {
            int pos = this->recvBuf.find_first_of(front);
            if(string::npos == pos)
            {//数据流缓存区未找到开头标志的包, 清空缓存区
                this->recvBuf = "";
                return "";
            }
            if(0 != pos)
            {//缓冲区开头数据不为"KT", 删除掉KT标志前的"垃圾"数据
                this->recvBuf.erase(0, pos);
            }
            // "KT" len flag data crc "END"
            if(this->recvBuf.size() < 17)
                return "";
            int len = *reinterpret_cast<const int*>(this->recvBuf.c_str() + 2);
            if(len <= 0 || len > 32 * 1024)
            {//包的大小有误, 非法数据 或 之前找的 KT开头标志有误, 继续找下一个包头标志
                this->recvBuf.erase(0, 2);
                continue;
            }
            if(this->recvBuf.size() < len + 6)
            {//不完整包
                return "";
            }
            int flag = *reinterpret_cast<const int*>(this->recvBuf.c_str() + 2 + 4);
            int crc = *reinterpret_cast<const int*>(this->recvBuf.c_str() + 2 + 4 + len - 3 - 4);
            if(0 != this->recvBuf.substr(2 + 4 + len - 3, 3).compare(end))
            {//不是已END结果的包, 丢弃这个包
                this->recvBuf.erase(0, 2 + 4 + len);
                continue;
            }
            
            pack = this->recvBuf.substr(2 + 4 + 4, len - 4 - 3);
            this->recvBuf.erase(0, 2 + 4 + len);
            return pack;
        }while (true);
        return pack;
    }




    添加到缓冲区就直接写this->recvBuf.append(buf, bufLen);



    ok了, 就是这样而已, 代码是随便写出来的demo, 没做严格的测试, 没经过推敲优化...只是写下一个很朴素的思路...


    !!!!!!!!!!!!!!!!!!!!!!!!!!!....................2222222222


  • 相关阅读:
    csv,exl自动提取表头两列英文字段按英文名称排序显示
    javascript:的用法
    OLAP ODS 项目总结 BI 中的关键
    一些性能查询的SQL 备忘
    ArcGIS 10 SDE for ORACLE 迁移 (3)
    如何测试一个ETL_BI 系统
    ArcGIS 10 SDE for ORACLE 迁移 (2)
    fsck.ext3: Unable to resolve 'LABEL=/design'
    ArcGIS 10 SDE for ORACLE 迁移 (4)
    BI 中关于度量的SQL计算
  • 原文地址:https://www.cnblogs.com/jianc/p/2879599.html
Copyright © 2011-2022 走看看