C++ 网络编程学习四

大小端字节序

  • 网络字节序为大端序,计算机内部存储有可能是大端,有可能是小端。
    在这里插入图片描述
  • 网络传输时,需要将本地数据转换为大端序,实现字节序的转换。

判断是大端还是小端:

// 判断当前系统的字节序是大端序还是小端序
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从网络字节序转成本地字节序。

  • 在服务器的发送数据时会构造消息节点,构造消息节点时,将发送长度由本地字节序转化为网络字节序.

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';
    }

消息队列控制

  • 发送的数据放到消息队列中,异步发送数据时,要用队列来保证发送的时序性,有的时候发送的频率过高会导致队列增大,所以要对队列的大小做限制,当队列大于指定数量的长度时,就丢弃要发送的数据包,以保证消息的快速收发。
#define MAX_SENDQUE 1024
// 实现发送接口
void CSession::Send(char* msg, int max_length) {
	bool pending = false; // pending为true表示上一次数据没有发完。
	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

  1. protobuf是谷歌设计一种轻便高效的序列化数据结构的协议,独立于语言和平台。是一种二进制的格式,比使用 xml 、json进行数据交换快许多
  2. http信息交互中有的信息类是抽象数据构成的,而tcp是面向字节流的,需要将类结构序列化为字符串来进行传输。
  3. 下载protobuf的源码之后,用cmake构建C++代码,生成适用于各种构建系统的构建文件(如makefile,visual studio解决方案等)
  4. 使用 protobuf 能够多(数据结构支持复杂结构、语言支持程度覆盖主流编程语言)、快(编解码效率)、好(数据保存方式为二进制,保存更节省空间、读取转换时间)、省(数据保存大小)。

windows下编译protobuf

  1. 下载Cmake桌面程序,点击source code选择protobuf源码路径下的cmake目录
  2. build binaries目录选择自己创建的visualstudio目录
  3. 点击config,再点击generate就可以生成了。
  4. 就可以看到visualstudio目录内生成了.sln文件
  5. 点击.sln文件打开项目
    在这里插入图片描述
  6. 单独选择libprotobuf编译。
  7. 编译时可以选择debug和release,建议每一个都编译一遍,这样就有debug和release两个版本的库了。
  8. debug版本的库,编译之后,会在visualstudio目录下的Debug文件夹里。
  9. 生成了protobuf相关的lib库和dll库,一个是静态类型的,一个是动态类型的。
  10. 在该文件夹内创建一个bin文件夹(用来存储刚才protobuf生成的库)和include文件夹(用来存储protobuf的头文件)。有了库,需要给用户头文件,让用户去使用。
  11. libprotobufd.lib和libprotocd.lib, 以及protoc.exe拷贝到bin目录下,将protobuf文件夹下src文件夹里的google文件夹及其内容拷贝到protoc的include文件夹
  12. 添加环境变量,将protoc命令配置到环境变量,在系统环境变量里添加一个环境变量PROTOBUF_HOME,设置它的值为D:\cppsoft\protoc\bin。这个环境变量的值要根据自己创建的bin目录去设置。
  13. 可以直接使用protoc.exe了。
  14. 在visual studio中使用protobuf,需要进行一下配置,和boost的配置是一样的
  15. 配置选择Debug,平台选择X64,选择VC++目录
  16. 在包含目录中添加 D:\cppsoft\protoc\include
  17. 在库目录中添加 D:\cppsoft\protoc\bin
  18. 在链接器里的输入选项中添加protobuf用到的lib库
libprotobufd.lib
libprotocd.lib

Linux下编译protobuf

# clone code
https://github.com/protocolbuffers/protobuf/releases

# 解压缩
tar zxvf protobuf-cpp-3.21.12.tar.gz

# 进入到解压目录
cd protobuf-3.21.12/

# 构建并安装
./configure         # 检查安装环境, 生成 makefile
make                # 编译
sudo make install   # 安装

# 测试
protoc --version

# 动态库位于/usr/local/lib目录,接下来需要需要将这目录添加到/etc/ld.so.conf这个配置文件
sudo vim /etc/ld.so.conf
# 打开文件后,把目录添加到第二行
/usr/local/lib

# 重新load配置文件
sudo ldconfig

# 测试
protoc --version
libprotoc 3.21.12

使用protobuf

  1. protobuf可以序列化单一数据类型和复合数据类型。
  2. 创建一个新的文件, 文件名随意指定, 文件后缀为 .proto
  3. 根据protobuf的语法, 编辑.proto文件
  4. 使用 protoc 命令将 .proto 文件转化为相应的 C++ 文件
  5. 源文件: xxx.pb.cc –> xxx对应的名字和 .proto文件名相同。
  6. 头文件: xxx.pb.h –> xxx对应的名字和 .proto文件名相同。
  7. 需要将生成的c++文件添加到项目中, 通过文件中提供的类 API 实现数据的序列化/反序列化

生成pb文件
8. 使用protobuf的序列化功能,需要生成pb文件,pb文件包含了我们要序列化的类信息。我们先创建一个msg.proto,该文件用来定义我们要发送的类信息。

syntax = "proto3"; # 指定Protobuf的版本号
message Book
{
   string name = 1; # 数字标识符,用于标识该字段在二进制流中的位置。
   int32 pages = 2;
   float price = 3;
}

用protoc.exe 基于msg.proto生成我们要用的C++类

protoc --cpp_out=. ./msg.proto

目录生成了msg.pb.h和msg.pb.cc两个文件,这两个文件就是我们要用到的头文件和cpp文件

将这两个文件添加到项目里,然后在主函数中包含msg.pb.h,做如下测试:将book对象先序列化为字符串,再将字符串反序列化为book2对象。代码中调用的函数都是自动生成的

#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); //将book对象先序列化为字符串
    std::cout << "serialize str is " << bookstr << std::endl;
    Book book2;
    book2.ParseFromString(bookstr); // 字符串反序列化为book2对象。
    std::cout << "book2 name is " << book2.name() << " price is " 
        << book2.price() << " pages is " << book2.pages() << std::endl;
    getchar();
}

网络消息服务中使用protobuf

  1. 先为服务器定义一个用来通信的proto
syntax = "proto3";
message MsgData
{
   int32  id = 1;
   string data = 2;
}
  1. 用proto生成对应的pb.h和pb.cc文件,将proto,pb.cc,pb.h三个文件复制服务器项目里并且配置。
  2. 修改服务器接收数据和发送数据的逻辑。
  3. 当服务器收到数据后,完成切包处理后,将信息反序列化为具体要使用的结构,打印相关的信息,然后再发送给客户端
	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);
  1. 客户端在发送的时候也利用protobuf进行消息的序列化,然后发给服务器。

JSONCPP

JSONCPP源码链接:https://github.com/open-source-parsers/jsoncpp

  1. JSOCPP源码下载以后,首先复制一份include文件夹下的json文件夹,头文件留着后续备用。
    在这里插入图片描述
  2. 使用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类型。
#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); // 反序列化为value类型。
    std::cout << "msg id is " << root2["id"] << " msg is " << root2["data"] << std::endl;
}

在网络通信中的JSONCPP

  • 客户端发送数据时,将发送的数据进行序列化
	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); //先拷贝2字节的数据长度信息
    memcpy(send_data + 2, request.c_str(), request_length); // 再拷贝数据本身
    boost::asio::write(sock, boost::asio::buffer(send_data, request_length + 2)); // 封装到buffer中进行发送
  • 服务器进行反序列化,注意要转换成本地字节序
	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

相关推荐

  1. C++ 网络编程学习

    2024-03-14 14:40:04       18 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-14 14:40:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-14 14:40:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-14 14:40:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-14 14:40:04       20 阅读

热门阅读

  1. CXL (Compute Express Link) Technology——论文阅读

    2024-03-14 14:40:04       36 阅读
  2. c++面经

    2024-03-14 14:40:04       18 阅读
  3. 人类的谋算与量子计算

    2024-03-14 14:40:04       15 阅读
  4. 用C语言链表实现图书管理

    2024-03-14 14:40:04       18 阅读
  5. 算法刷题day30:递归

    2024-03-14 14:40:04       23 阅读
  6. Dijkstra&floyed

    2024-03-14 14:40:04       20 阅读
  7. 3. Linux标准I/O库

    2024-03-14 14:40:04       18 阅读
  8. Linux 学习笔记(15)

    2024-03-14 14:40:04       20 阅读
  9. vue常用6种数据加密方式的使用

    2024-03-14 14:40:04       17 阅读
  10. Python 正则表达式

    2024-03-14 14:40:04       21 阅读
  11. C++ 智能指针

    2024-03-14 14:40:04       16 阅读