使⽤ProtocolBuffer实现⽹络协议⼆进制格式
1.综述
客户端与服务器交互时都需要双⽅协商,确定消息的⼆进制格式。客户端在向服务器发起请求时会根据协议创建⼆进制数据块,然后依托tcp, udp, http等协议将⼆进制内容传递给服务器,后者根据协议的规则按照特定次序从接收到的⼆进制内存块中读取给定字段。
这种做法存在⼀些问题。⼀是⾃定义的协议往往缺乏好的可扩展性,例如以后需要添加新字段,特别是字段要插⼊到以前字段的中间时,客户端和服务端协议解析代码得做相同修改。随着业务的发展,原先某些字段得删除时,协议解析代码⼜得修改,因此⾃定义协议解析在⾯对协议变化上因为需要重新编码因此会提升⼯作量降低效率,特别时代码的修改⾮常容易引⼊错误。
⽬前业内也有⼀些通⽤协议格式,例如jason, xml等,他们也存在⼀些问题。各种编程语⾔都有既定接⼝或模块之间解析这些格式,但是存在⼀个问题就是效率低下。当协议中的字段增多时,这些格式的解析耗时较长,我个⼈觉得这些格式存在⼀个不好使之处在于他们在发送⼆进制数据上。当协议字段对应字符串或是int这类长度较短的⼆进制数据时,他们的使⽤很⽅便,但如果使⽤他们传递图⽚内容能长度较长的⼆进制数据,那么我们需要进⾏base64编码后才⽅便将数据存储在这些格式中。
因此我们最好能到⼀种可扩展性强,也就是协议格式能灵活的应对字段的删减⽽不必引⼊过多的代码修改;同时字段的查询效率⾼,⼆进制数据发送接收也⽅便的协议格式,那么就能⼤⼤提升我们制定⽹络协议的效率。
2,接下来安装cmake程序,该程序的安装包也在给定百度盘共享⽬录下。
3,步骤1的命令会在对应⽬录下创建⼀个build⽬录并进⼊该⽬录,然后执⾏命令:
cmake -G “Visual Studio 15 2017” -DCMAKE_INSTALL_PREFIX=…/…/…/install -Dprotobuf_BUILD_TESTS=OFF …/
因为我使⽤的是2017,所以-G 后⾯对应为给定字符串,如果你使⽤的是vs2019,则对应字符串应该为”Visual Studio 16 2019”。命令执⾏后会在本地⽬录⽣成⼀系列vs⼯程⽂件,如图1所⽰:
图1  cmake命令⽣成vs⼯程⽂件
4,到protobuf.sln,使⽤vs打开,在⼯程中有⼀个名为INSTALL的项⽬,选中它,右键选择“⽣成”编译protobuf可执⾏⽂件和对应的头⽂件和lib库。
5,完成后在路径上⼏层会⽣成⼀个install⽂件夹,⾥⾯会有编译好的结果,如图2所⽰:
图2 protobuf 编译结果。
bin⽂件夹中包含,将该⽂件夹对应⽬录设置到环境变量path中,这样我们可以直接在命令⾏使⽤该程序。
3.Protocol Buffer使⽤⽅法
Protocol Buffer 是⾕歌发明的⼀种⾼效⼆进制协议制定⽅法,其使⽤基本流程如图3所⽰:
图3 protocol buffer 基本使⽤流程
从图3可以看到,⾸先我们需要使⽤protocol buffer提供的语法来定义要使⽤的通信协议格式,它的语法不是编程语⾔,只是功能有限,特别⽤于描述数据结构的脚本语⾔,我们看个来⾃⾕歌说明⽂档的具体例⼦:
syntax = ‘proto2’;
package example;
message Person {
cmake如何使用required string name = 1;
required int32 id = 2;
optional string email = 3;
optional bytes bin_data = 4;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
optional string number = 1;
optional PhoneType type= 2 [default = HOME];
}
repeated PhoneNumber phones = 5;
message AddressBook {
repeated Person people = 1;
}
}
从上⾯定义语⾔可以看出,protocol buffer⽤于定义⼆进制结构的语法跟编程语⾔⾮常像,其中message,required, optional, repeated 都属于语法的关键字,string, enum, int32都属于字段的数据类型,这⾥需要提前说明的是,类似name=1;这种写法不是赋初值,⼀定要注意这点,这种写法的作⽤后⾯再解释。
message对应C语⾔中的struct关键字,它描述⼀块⼆进制内存中的字段分布情况。protocol buffer定义数据字段时能⽀持所有编程语⾔中使⽤到的数据类型,例如int, byte, string, float,double等,这⾥需要注意的是,如果我们想在协议中发送⼆进制数据串,那么对应类型就是bytes,当使⽤protocol buffer编译器将类似如上的⼆进制协议定义⽂件编译成c++代码时,bytes对应类型为string, 在java中则对应ByteString。
这⾥需要注意两个关键字,required表⽰在设置⼆进制协议字段时,required关键字修饰的字段必须要设置,如果你不设置但在代码中⼜去读取这些字段,那么代码就会抛出异常。⽽optional修饰的字段表⽰如果你不给该字段赋值,那么protocol buffer会⾃动帮你赋初值,例如int32类型会⾃动设置为0,string类型⾃动设置为空字符串等。如果不是⾮常必要,通常情况下我们使⽤optional修饰字段就⽐较灵活。
在上⾯例⼦中字段还可以赋初值,例如optional PhoneType type= 2 [default = HOME];它表⽰PhoneNumber中有⼀个字段名为type,它初始化值为2,也就是WORK,如果没有赋初值那么他就默认为HOME。
我们还能看到message可以间套,这就类似struct内部还能定义struct⼀样。同时关键字repeated对应编程语⾔的for,被该关键字修饰的字段可以重复0或多次。
前⾯强调过string name=1;后⾯的”=1“不是赋值,它是的作⽤是标签,我们可以认为这些数值是字段的牌号,在编译成⼆进制内容后protocol buffer知道如何去读取这些字段。
将上⾯描述的数据定义内容存储成以.proto为后缀的⽂件然后就可以使⽤protocol buffer将其编译成给定编程语⾔对应的代码⽂件,如图4所⽰:
图4 编译.proto协议定义⽂件
protoc就是编译器, -I指定要编译的proto⽂件所在⽬录, --cpp_out表⽰将其编译成c++代码,我们可以将定义⽂件编译成所有当前主流编程语⾔,如果我们客户端⽤c++开发,服务器⽤java开发,那么就可以将该定义⽂件再编译出⼀份对应的java代码,于是客户端和服务器都可以⽤⽣成的代码对同⼀定义的协
议数据进⾏读写操作。
图4命令执⾏后,在本地可以看到⽣成的对应语⾔的代码⽂件,如图5所⽰:
图5  编译出的代码⽂件
其中.cc和.h⾥⾯的代码提供了接⼝让我们读写.proto⽂件定义的数据结构,如果你打开.cc和.h⽂件查看可以发现⾥⾯代码⾮常复杂,因此我们千万不要去改动⾥⾯的内容要不然会造成难以理解的错误,我们只需要引⽤代码⾥⾯给出的接⼝来读写proto⽂件定义的协议数据结构即可。
对应每个字段,在.h中都定义了set和get接⼝,例如对应name字段它⾥⾯就有set_name这种接⼝让我们设置字段内容。对于被repeated 关键字修饰的字段例如people,它还⽣成了people_size(),⽤户获取people字段中有多少个实例对象,如图6所⽰:
图6 protoc⽣成代码
接下来我们看看如何使⽤代码来读取或⽣成proto协议⽂件规定的数据结构。
4.使⽤代码读写协议字段
要使⽤代码来读写协议规定好的数据结构,我们先使⽤vs创建⼀个⼯程,然后先执⾏如下配置:
1,在⼯程⽬录右键选中属性,在c/c+±>常规中设置包含⽬录,如所⽰:
图7 设置包含⽬录
2,在链接器->常规->附加库⽬录输⼊lib对应路径,如图8所⽰:
图8  设置lib路径
3,在连接器->输⼊->附加依赖项输⼊如图9所⽰内容:
图9 附加依赖项内容
4,在c/c++ ->代码⽣成->运⾏库,设置如图10所⽰:
图9 设置运⾏时库
完成后将前⾯编译的.h和.cc⽂件拷贝到⼯程的本地⽬录并加⼊到项⽬,接下来编写如下代码:#include
#include
#include
#include
#include “address_book.pb.h”
using namespace std;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。