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