编写一个基于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


原文地址:https://www.cnblogs.com/jianc/p/2879599.html