为什么选择 protobuf
protobuf是谷歌推出的字节序列化协议。
常见的传输数据方式
1.结构体+多态+定协议头传输数据
2.第三方字节序列库,如Qt的QDataStream,protobuf
QDataStream
- >><< ,以操作符重载的方式输入输出提高了代码的可读性;
- QByteArray,封装的字节类,用于流传输;
- 示例:
class PageHeader :public PageStream
{
public:
PageHeader(QByteArray data); //from data
PageHeader(); //default
PageHeader( QString token,
quint8 ack,
quint16 pageType=static_cast<quint16>(PageType::INVAILED),
quint16 pageLength=sizeof(PageHeader));
///[1] override PageStream
virtual QByteArray data() override;
virtual void fromData(QByteArray & data) override;
virtual void adjustSize()override;
///[1]
bool isValid();
///[2] set or get
quint16 getPageType() const;
void setPageType(const PageType &value);
quint16 getPageLength() const;
void setPageLength(quint16 value);
QString getSendTime() const;
void setSendTime(const QString &sendTime);
QString getToken() const;
void setToken(const QString &token);
quint8 getAck() const;
void setAck(const quint8 &ack);
private:
QString token_;//first from server,other from session
quint8 ack_;//序号 查重
quint16 pageType_;
QString sendTime_;
quint16 pageLength_;
};
QDataStream & operator<<(QDataStream & out,const ltalk::net::PageHeader & pageHeader);
QDataStream &operator>>(QDataStream & in,ltalk::net::PageHeader & pageHeader);
QDataStream & operator<<(QDataStream & out,const PageHeader & pageHeader)
{
out<<pageHeader.getToken()
<<pageHeader.getAck()
<<pageHeader.getPageType()
<<pageHeader.getSendTime()
<<pageHeader.getPageLength();
return out;
}
QDataStream &operator>>(QDataStream & in,PageHeader & pageHeader)
{
QString token;//first from server,other from session
quint8 ack;//序号 查重
quint16 pageType;
QString sendTime;
quint16 pageLength;
in>>token>>ack>>pageType>>sendTime>>pageLength;
pageHeader.setToken(token);
pageHeader.setAck(ack);
pageHeader.setPageType(static_cast<PageType>(pageType));
pageHeader.setSendTime(sendTime);
pageHeader.setPageLength(pageLength);
return in;
}
protobuf
- 第一次接触protobuf是逛github的时候看到grpc里面使用的这个协议
- 第二次是qt.io上的博客提到了QDataStream和其他协议库的效率相关博客
- protobuf是基于c++的一个缓存协议库,提供方便的序列化操作
protobuf 基本操作
- 了解protobuf中.proto文件
/*基本类型有 int32,uin32,int64,uint64,enum,bytes,repeated*/ message UserLoginRequest { bytes userAccount = 1;//以;结束 bytes是类型 userAccount是名 1是tag 位置 ..... } enum LoginResult { kInvild = 0;//以;结束 且第一个以0开始,为了向下兼容 kPassWordError = 1;//passwd error .... } message FriendInfoItem{ bytes friendUuid = 1; } message PullFriendsInfo{ repeated FriendInfoItem friends = 1; //表示多个item }
-了解c++的应用
- PageHeader用于读入头,和写入头
/*由于protobuf是对数字类型进行压缩的,所以protobuf序列化过后的大小对方是不知道的,所以我们需要指定大小*/
//为了节约大家翻阅时间部分代码省略
class PageHeader
{
public:
inline static PageHeader parseFromArray(const char * data){
int pos = 0;
uint8_t type;
uint32_t pageSize;
auto pData = data;
memcpy(&type,pData,sizeof (uint8_t));
pos+=sizeof(uint8_t);
memcpy(&pageSize,pData+pos,sizeof(uint32_t));
return PageHeader{
static_cast<RequestType>(type),pageSize
};
}
void WriteToArray(char * data){
uint8_t type = requestType();
int pos = 0;
auto pData = data;
memcpy(pData,&type,sizeof(uint8_t));
pos += sizeof (uint8_t);
memcpy(pData+pos,&pageSize_,sizeof(uint32_t));
}
private:
RequestType requestType_;//指定类型 用于后面的读包操作
uint32_t pageSize_;//指定大小
};
- 写入head和body
template<class T1,class T2>
inline QByteArray writeToArray(T1 t1,T2 t2){
t1.setPageSize(t2.ByteSize());
QByteArray data = QByteArray(t2.ByteSize()+PageHeader::kPageHead_Size,'\0');
t1.WriteToArray(data.data());
t2.SerializeToArray(data.data()+PageHeader::kPageHead_Size,t2.ByteSize());
return data;
};
- 写入头和身体 body
http::PageHeader pageHeader;
pageHeader.setRequestType(http::RequestType::kUserLogin);
request.set_version("0.01");
request.set_device("win");
request.set_useraccount("19982087305");
request.set_userpassword(utils::hexEncrpy("T123456.",QCryptographicHash::Md5).toStdString());
utils::checkTimeCost(false,"protobuf test");
auto page = writeToArray(pageHeader,request);
const auto &&reply = JQNet::HTTP::post("http://127.0.0.1:23412/LTalk/v1/httpapi/",page);
- 读消息
auto pData = data.data();
auto pageHeader = PageHeader::parseFromArray(pData);
qDebug()<<pageHeader.requestType()<<pageHeader.pageSize();
pData = pData +pageHeader.kPageHead_Size;
switch (pageHeader.requestType()) {
case RequestType::kUserLogin:
qDebug()<<"onLogin";
httpapi::UserLoginRequest loginRequest;
loginRequest.ParseFromArray(pData,pageHeader.pageSize());
break;
default:
break;
}
总结
从前面的内容不难发现大部分的序列操作都差不多,只是在具体的处理上有所不同,上述代码没有加错误处理,为了防止错误数据读入应该加上错误处理