1.如何将接收缓冲区中的数据装配成协议对象
- 上一节中提到的unserialize函数用于将一个
符合协议规则的字符串
装配成为一个协议对象。可是在实际工程开发过程中可能会是一个问题:接收缓冲区中的数据一定是满足协议的定义规则吗?
答案是:不一定
2.如果接收缓冲区中的数据不满足协议规则能否将其装配成一个协议对象
- 接收缓冲区中的数据足够
- 如果数据量足够是否能够装配成不只是一个对象?
- 剩下的数据如何处理(属于下一个协议对象)?
- 数据量不足
- 是否达到协议最小长度(8字节)?
- 如何处理数据量超过最小长度但不足以产生一个新对象
3.解决方案
- 定义一个类用于接收字节流数据,并装配协议对象
- 类中提供容器(队列)用于暂存字节流数据
- 当容器中至少存在8个字节时开始装配
- 首先装配协议中的类型(type)和数据区长度(length)
- 根据数据区长度从容器中取数据装配协议数据(data)
- 当协议数据装配完成后,创建协议对象并返回,否则,返回NULL
4.代码
目的:实现将字符串装配成一个完整的协议对象。
main函数测试目标 将完整的协议字符串分两次写到内存中,通过
TxtMsgAssembler类完成对协议的装配
代码:
txtmsgassembler.h
1 #ifndef TXTMSGASSEMBLER_H 2 #define TXTMSGASSEMBLER_H 3 4 #include <QObject> 5 #include <QQueue> 6 #include <QSharedPointer> 7 #include "TextMessage.h" 8 9 class TxtMsgAssembler : public QObject 10 { 11 Q_OBJECT 12 QQueue<char> m_queue;//存储字节流 13 QString m_type; 14 int m_length; 15 QString m_data; 16 17 18 public: 19 explicit TxtMsgAssembler(QObject *parent = nullptr); 20 21 QSharedPointer<TextMessage> assemble();//从内部容器中获取字节数据,尝试装配字节对象 22 QSharedPointer<TextMessage> assemble(const char* data,int length); 23 void prepare(const char* data, int len);//将数据转存到内部容器,用于后续协议对象装配 24 void reset(); 25 void clear(); 26 bool makeTypeAndLength(); 27 28 TextMessage* makeMessage(); 29 QString fetch(int n); 30 signals: 31 32 public slots: 33 }; 34 35 #endif // TXTMSGASSEMBLER_H
txtmsgassembler.cpp
1 #include "txtmsgassembler.h" 2 3 TxtMsgAssembler::TxtMsgAssembler(QObject *parent) : QObject(parent) 4 { 5 6 } 7 8 QSharedPointer<TextMessage> TxtMsgAssembler::assemble(const char *data, int length) 9 { 10 prepare(data,length); 11 return assemble(); 12 } 13 14 QSharedPointer<TextMessage> TxtMsgAssembler::assemble() 15 { 16 TextMessage* ret=NULL; 17 18 bool tryMakeMsg=false; 19 if(m_type=="") 20 { 21 tryMakeMsg=makeTypeAndLength(); 22 } 23 else 24 { 25 tryMakeMsg=true; 26 } 27 28 if(tryMakeMsg) 29 { 30 ret = makeMessage(); 31 } 32 33 if(ret!=NULL) 34 { 35 clear(); 36 } 37 38 return QSharedPointer<TextMessage>(ret); 39 } 40 41 void TxtMsgAssembler::prepare(const char* data, int len) 42 { 43 if(data!=NULL) 44 { 45 for(int i=0;i<len;i++) 46 { 47 m_queue.enqueue(data[i]); 48 } 49 } 50 } 51 52 void TxtMsgAssembler::reset() 53 { 54 clear(); 55 m_queue.clear(); 56 } 57 58 void TxtMsgAssembler::clear() 59 { 60 m_type=""; 61 m_length=0; 62 m_data=""; 63 } 64 65 bool TxtMsgAssembler::makeTypeAndLength() 66 { 67 bool ret=(m_queue.length()>=8); 68 69 if(ret) 70 { 71 m_type=fetch(4); 72 QString len =fetch(4).trimmed(); 73 m_length=len.toInt(&ret,16); 74 75 if(!ret) 76 { 77 clear(); 78 } 79 } 80 return ret; 81 } 82 83 84 85 86 TextMessage* TxtMsgAssembler::makeMessage() 87 { 88 TextMessage* ret=NULL; 89 90 if(m_type!="") 91 { 92 int needlen=m_length-m_data.length(); 93 94 int n=(needlen<=m_queue.length())?needlen:m_queue.length(); 95 m_data+=fetch(n); 96 97 if(m_length==m_data.length()) 98 { 99 ret=new TextMessage(m_type,m_data); 100 } 101 102 } 103 return ret; 104 } 105 106 QString TxtMsgAssembler::fetch(int n) 107 { 108 QString ret; 109 110 for(int i=0;i<n;++i) 111 { 112 ret+=m_queue.dequeue(); 113 } 114 115 return ret; 116 }
main.cpp
1 #include <QCoreApplication> 2 #include <QDebug> 3 #include "textmessage.h" 4 #include "txtmsgassembler.h" 5 6 int main(int argc, char *argv[]) 7 { 8 QCoreApplication a(argc, argv); 9 10 TextMessage tm("AB","1234567890"); 11 QString message=tm.serialize(); 12 qDebug()<<message; 13 14 QString s1=message.mid(0,5); 15 QString s2=message.mid(5); 16 17 QSharedPointer<TextMessage> tmt; 18 TxtMsgAssembler as; 19 20 tmt=as.assemble(s1.toStdString().c_str(),s1.length()); 21 if(tmt!=NULL) 22 { 23 qDebug()<<"assembel sucessed."; 24 qDebug()<<tmt->type(); 25 qDebug()<<tmt->length(); 26 qDebug()<<tmt->data(); 27 } 28 29 tmt=as.assemble(s2.toStdString().c_str(),s2.length()); 30 if(tmt!=NULL) 31 { 32 qDebug()<<"assembel sucessed."; 33 qDebug()<<tmt->type(); 34 qDebug()<<tmt->length(); 35 qDebug()<<tmt->data(); 36 } 37 return a.exec(); 38 }
说明:textmessage.h参考上一节同名文件
效果:
5.小结
- 从连续字节流装配协议对象是应用自定义协议的基础
- 装配类TxtMsgAssembler用于解析自定义协议
- 装配类的实现的关键是如何处理字节数据不够的情况
- 自定义协议类和装配类能够有效的解决数据粘粘的问题