C++ asio学习四
大小端字节序
网络字节序为大端序
,计算机内部存储有可能是大端,有可能是小端。
- 网络传输时,需要将本地数据转换为大端序,实现字节序的转换。
判断是大端还是小端:
1 2 3 4 5 6 7 8 9 10 11
| bool is_big_endian() { int num = 1; if (*(char*)&num == 1) { return false; } else { return true; } }
|
在 boost::asio 库中,可以使用boost::asio::detail::socket_ops::host_to_network_long()
和 boost::asio::detail::socket_ops::host_to_network_short()
函数将主机字节序转换为网络字节序。
network_to_host_short从网络字节序转成本地字节序。
在服务器的发送数据时会构造消息节点,构造消息节点时,将发送长度由本地字节序转化为网络字节序.
1 2 3 4 5 6 7 8
| MsgNode(char * msg, short max_len):_total_len(max_len + HEAD_LENGTH),_cur_len(0){ _data = new char[_total_len+1](); int max_len_host = boost::asio::detail::socket_ops::host_to_network_short(max_len); memcpy(_data, &max_len_host, HEAD_LENGTH); memcpy(_data+ HEAD_LENGTH, msg, max_len); _data[_total_len] = '\0'; }
|
消息队列控制
- 发送的数据放到消息队列中,异步发送数据时,要用队列来保证发送的时序性,有的时候发送的频率过高会导致队列增大,所以要对队列的大小做限制,当队列大于指定数量的长度时,就丢弃要发送的数据包,以保证消息的快速收发。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #define MAX_SENDQUE 1024
void CSession::Send(char* msg, int max_length) { bool pending = false; int send_que_size = _send_que.size(); if (send_que_size > MAX_SENDQUE) { cout << "session: " << _uuid << " send que fulled, size is " << MAX_SENDQUE << endl; return; } _send_que.push(make_shared<MsgNode>(msg, max_length)); if (send_que_size>0) { return; } boost::asio::async_write(_socket, boost::asio::buffer(msg, max_length), std::bind(&CSession::HandleWrite,this,std::placeholders::_1,shared_from_this())); }
|
protobuf
- protobuf是谷歌设计一种轻便高效的序列化数据结构的协议,独立于语言和平台。是一种二进制的格式,比使用 xml 、json进行数据交换快许多。
http信息交互中有的信息类是抽象数据构成的,而tcp是面向字节流的,需要将类结构序列化为字符串来进行传输。
- 下载protobuf的源码之后,用cmake构建C++代码,生成适用于各种构建系统的构建文件(如makefile,visual studio解决方案等)
- 使用 protobuf 能够多(数据结构支持复杂结构、语言支持程度覆盖主流编程语言)、快(编解码效率)、好(数据保存方式为二进制,保存更节省空间、读取转换时间)、省(数据保存大小)。
windows下编译protobuf
- 下载Cmake桌面程序,点击source code选择protobuf源码路径下的cmake目录。
- build binaries目录选择自己创建的visualstudio目录。
- 点击config,再点击generate就可以生成了。
- 就可以看到visualstudio目录内生成了.sln文件。
- 点击.sln文件打开项目
- 单独选择libprotobuf编译。
- 编译时可以选择debug和release,建议每一个都编译一遍,这样就有debug和release两个版本的库了。
- debug版本的库,编译之后,会在visualstudio目录下的Debug文件夹里。
- 生成了protobuf相关的lib库和dll库,一个是静态类型的,一个是动态类型的。
- 在该文件夹内创建一个bin文件夹(用来存储刚才protobuf生成的库)和include文件夹(用来存储protobuf的头文件)。有了库,需要给用户头文件,让用户去使用。
- 将libprotobufd.lib和libprotocd.lib, 以及protoc.exe拷贝到bin目录下,将protobuf文件夹下src文件夹里的google文件夹及其内容拷贝到protoc的include文件夹。
- 添加环境变量,将protoc命令配置到环境变量,在系统环境变量里添加一个环境变量PROTOBUF_HOME,设置它的值为D:\cppsoft\protoc\bin。这个环境变量的值要根据自己创建的bin目录去设置。
- 可以直接使用protoc.exe了。
- 在visual studio中使用protobuf,需要进行一下配置,和boost的配置是一样的
- 配置选择Debug,平台选择X64,选择VC++目录
- 在包含目录中添加 D:\cppsoft\protoc\include
- 在库目录中添加 D:\cppsoft\protoc\bin
- 在链接器里的输入选项中添加protobuf用到的lib库
1 2
| libprotobufd.lib libprotocd.lib
|
Linux下编译protobuf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| https://github.com/protocolbuffers/protobuf/releases
tar zxvf protobuf-cpp-3.21.12.tar.gz
cd protobuf-3.21.12/
./configure make sudo make install
protoc --version
sudo vim /etc/ld.so.conf
/usr/local/lib
sudo ldconfig
protoc --version libprotoc 3.21.12
|
使用protobuf
- protobuf可以序列化单一数据类型和复合数据类型。
- 创建一个新的文件, 文件名随意指定, 文件后缀为 .proto
- 根据protobuf的语法, 编辑.proto文件
- 使用 protoc 命令将 .proto 文件转化为相应的 C++ 文件
- 源文件: xxx.pb.cc –> xxx对应的名字和 .proto文件名相同。
- 头文件: xxx.pb.h –> xxx对应的名字和 .proto文件名相同。
- 需要将生成的c++文件添加到项目中, 通过文件中提供的类 API 实现数据的序列化/反序列化
生成pb文件
8. 使用protobuf的序列化功能,需要生成pb文件,pb文件包含了我们要序列化的类信息。我们先创建一个msg.proto,该文件用来定义我们要发送的类信息。
1 2 3 4 5 6 7
| syntax = "proto3"; message Book { string name = 1; int32 pages = 2; float price = 3; }
|
用protoc.exe 基于msg.proto生成我们要用的C++类
1
| protoc --cpp_out=. ./msg.proto
|
目录生成了msg.pb.h和msg.pb.cc两个文件,这两个文件就是我们要用到的头文件和cpp文件。
将这两个文件添加到项目里,然后在主函数中包含msg.pb.h,做如下测试:将book对象先序列化为字符串,再将字符串反序列化为book2对象。代码中调用的函数都是自动生成的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> #include "msg.pb.h" int main() { Book book; book.set_name("CPP programing"); book.set_pages(100); book.set_price(200); std::string bookstr; book.SerializeToString(&bookstr); std::cout << "serialize str is " << bookstr << std::endl; Book book2; book2.ParseFromString(bookstr); std::cout << "book2 name is " << book2.name() << " price is " << book2.price() << " pages is " << book2.pages() << std::endl; getchar(); }
|
网络消息服务中使用protobuf
- 先为服务器定义一个用来通信的proto
1 2 3 4 5 6
| syntax = "proto3"; message MsgData { int32 id = 1; string data = 2; }
|
- 用proto生成对应的pb.h和pb.cc文件,将proto,pb.cc,pb.h三个文件复制服务器项目里并且配置。
- 修改服务器接收数据和发送数据的逻辑。
- 当服务器收到数据后,完成切包处理后,将信息反序列化为具体要使用的结构,打印相关的信息,然后再发送给客户端。
1 2 3 4 5 6 7 8 9 10
| MsgData msgdata; std::string receive_data; msgdata.ParseFromString(std::string(_recv_msg_node->_data, _recv_msg_node->_total_len)); std::cout << "recevie msg id is " << msgdata.id() << " msg data is " << msgdata.data() << endl; std::string return_str = "server has received msg, msg data is " + msgdata.data(); MsgData msgreturn; msgreturn.set_id(msgdata.id()); msgreturn.set_data(return_str); msgreturn.SerializeToString(&return_str); Send(return_str);
|
- 客户端在发送的时候也利用protobuf进行消息的序列化,然后发给服务器。
JSONCPP
JSONCPP源码链接:https://github.com/open-source-parsers/jsoncpp
- JSOCPP源码下载以后,首先复制一份include文件夹下的json文件夹,头文件留着后续备用。
- 使用Cmake生成项目。在IDE中编译jsoncpp_lib,可以在项目的lib/Debug文件夹下找到jsoncpp.lib,在bin/Debug/文件夹下找到jsoncpp.dll。将头文件和动态链接库文件,放入项目中即可使用。
jsoncpp库中的类被定义到了一个Json命名空间中,使用时最好先声明这个命名空间。
配置Jsoncpp:
项目属性中,VC++包含目录设置为 D:\JSONCPP\include
库目录选择为 VC++库目录设置为 D:\JSONCPP\lib
使用jsoncpp库解析json格式的数据,三个类:
Value 类:将json支持的数据类型进行了包装,最终得到一个Value类型。
FastWriter类:将Value对象中的数据序列化为字符串。
Reader类:反序列化,将json字符串解析成Value类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <iostream> #include <json/json.h> #include <json/value.h> #include <json/reader.h> int main() { Json::Value root; root["id"] = 1001; root["data"] = "hello world"; std::string request = root.toStyledString(); std::cout << "request is " << request << std::endl; Json::Value root2; Json::Reader reader; reader.parse(request, root2); std::cout << "msg id is " << root2["id"] << " msg is " << root2["data"] << std::endl; }
|
在网络通信中的JSONCPP
1 2 3 4 5 6 7 8 9 10 11
| Json::Value root; root["id"] = 1001; root["data"] = "hello world"; std::string request = root.toStyledString(); size_t request_length = request.length(); char send_data[MAX_LENGTH] = { 0 }; int request_host_length = boost::asio::detail::socket_ops::host_to_network_short(request_length); memcpy(send_data, &request_host_length, 2); memcpy(send_data + 2, request.c_str(), request_length); boost::asio::write(sock, boost::asio::buffer(send_data, request_length + 2));
|
1 2 3 4 5
| Json::Reader reader; Json::Value root; reader.parse(std::string(_recv_msg_node->_data, _recv_msg_node->_total_len), root); std::cout << "recevie msg id is " << root["id"].asInt() << " msg data is " << root["data"].asString() << endl;
|
参考列表:
https://subingwen.cn/cpp/protobuf/?highlight=proto