一、MessagePack简介
科普一个冷门却强大的技术:MessagePack,简称msgpack。msgpack并非软件,而是一个标准,我们可以先把它看作二进制的JSON。提到“二进制JSON”,容易让人联想到更流行的标准BSON。如果你不了解BSON,可以自行查阅相关资料。总之,msgpack和BSON属于同类型的竞争产品,但在网络传输方面,msgpack无论是速度还是体积都完胜BSON。
JSON在序列化领域堪称神一般的存在。那什么是序列化呢?其实就是“降维打击”,任何多维的数据对象都必须被降维成一维,才能进行存储和网络传输。msgpack同样是一种序列化手段,只不过它序列化的结果是二进制格式,而非JSON的文本格式。这就如同HTTP 1.1进化到HTTP 2.0,从文本格式转变为二进制格式。
以一个JSON串为例,在UTF-8编码下每个字符占一个字节,总共27字节,转换成msgpack格式后仅剩下18字节,压缩了三分之一。这是因为JSON串本身存在许多无用信息,比如每个key的双引号可以省略,简单的布尔类型用四五个字节表示(true和false)。而XML的无用信息更多,这些都是文本格式本身的弊端,二进制格式则能解决这些问题,例如数字就应该用二进制表示。
msgpack对实数类型有一个特色,即可变长的实数类型,以至于128以下的正整数都可以用1个字节表示!如果说BSON编码在某些情况下比JSON还大,那么msgpack在任何情况下都比JSON小,最坏的情况如10以内的正整数,压缩率达100%,并且即使在这种情况下,msgpack的速度依然很快。
项目地址:https://github.com/msgpack
官网地址:https://msgpack.org/
二、在C++中使用MessagePack
1. 获取代码
在C++中使用MessagePack,我们可以从仓库地址:https://github.com/msgpack/msgpack-c 获取代码,然后切换到 cpp_master 分支:
git clone https://github.com/msgpack/msgpack-c
cd msgpack-c
git checkout cpp_master
2. 编译安装
使用以下命令进行编译安装:
cmake .. -DMSGPACK_USE_BOOST=OFF -DMSGPACK_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/root/arm_install
make install
这样,MessagePack就会被安装在 /root/arm_install
路径下。当然这个CMAKE_INSTALL_PREFIX可以指定为你想要的任何目录。
注意,这里并未指定交叉编译工具链,那么在嵌入式上可以使用吗?答案是的,因为messagepack都是头文件的形式提供的。嵌入式linux项目代码的编译则需要指定工具链。真正在嵌入式linux上使用请往下看。
3. 使用示例
以下是一个简单的使用示例:
#include <msgpack.hpp>
#include <string>
#include <iostream>
#include <sstream>
int main()
{
// 创建一个包含整数、布尔值和字符串的元组
msgpack::type::tuple<int, bool, std::string> src(1, true, "example");
// 序列化对象到缓冲区
std::stringstream buffer;
msgpack::pack(buffer, src);
// 模拟发送缓冲区数据
buffer.seekg(0);
// 从缓冲区反序列化数据到msgpack::object实例
std::string str(buffer.str());
msgpack::object_handle oh = msgpack::unpack(str.data(), str.size());
// 获取反序列化后的对象
msgpack::object deserialized = oh.get();
// 输出反序列化后的对象
std::cout << deserialized << std::endl;
// 将msgpack::object实例转换回原始类型
msgpack::type::tuple<int, bool, std::string> dst;
deserialized.convert(dst);
// 或者创建一个新的实例
msgpack::type::tuple<int, bool, std::string> dst2 = deserialized.as<msgpack::type::tuple<int, bool, std::string>>();
return 0;
}
测试demo的CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.5)
project(msgpack_example)
# 设置目标架构
#set(TARGET_ARCH "arm")
# 设置CMAKE_PREFIX_PATH以帮助CMake找到msgpack的安装路径
#set(CMAKE_PREFIX_PATH "/root/arm_install")
# 获取 ZeroMQ 库
#find_package(ZeroMQ REQUIRED)
#include_directories(${ZeroMQ_INCLUDE_DIRS})
# 获取 msgpack 库
find_package(msgpack-cxx REQUIRED)
#include_directories(${msgpack_INCLUDE_DIRS})
# 添加你的可执行文件
add_executable(msgpack_example main.cpp)
# 链接 ZeroMQ 库
#target_link_libraries(msgpack_example ${ZeroMQ_LIBRARIES})
# 链接 msgpack 库 重要
target_link_libraries(msgpack_example msgpack-cxx)
target_link_libraries(msgpack_example msgpack-cxx)重要,因为The library is header-only and target_link_libraries command just adds path to msgpack-c headers to your compiler’s include path.
If you do not use cmake, you can just add path yo msgpack-c headers to your include path:
g++ -I msgpack-c/include -I path_to_your_prj your_source_file.cpp
三、交叉编译在嵌入式Linux上的使用
1. 构建命令
执行以下命令进行交叉编译构建:
首先在项目的根目录下创建个build文件夹,然后进入build目录下执行。
#1
mkdir build
#2
cd build
#3
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake -DTARGET_ARCH=arm -DCMAKE_INSTALL_PREFIX=/root/arm_install -Dmsgpack-cxx_DIR=/root/arm_install/lib/cmake/msgpack-cxx
#最后,执行make开始编译
make
注意,这里需要确保 toolchain.cmake
文件配置正确,它包含了交叉编译所需的工具链信息。同时,msgpack-cxx_DIR
指定了 msgpack-cxx
的配置文件所在目录,以便CMake能够找到它。这个msgpack-cxx_DIR
也很关键,没配置它会导致你踩坑(cmake编译错误)。
注意上述示例中的/root/arm_install根据实际的情况,看上面安装在了具体哪个路径下。
上述如何不指定CMAKE_TOOLCHAIN_FILE,则会生成在宿主机linux系统下可运行的demo.
2. 注意事项
- 工具链文件:
toolchain.cmake
文件需要根据具体的嵌入式Linux平台进行配置,包括编译器路径、目标架构等信息。 - 路径问题:确保
msgpack-cxx_DIR
指向的路径包含msgpack-cxxConfig.cmake
或msgpack-cxx-config.cmake
文件。
3.工具链toolchain.cmake文件示例
我的测试环境为: 正点原子的imx6ul开发板。
# 交叉编译工具链配置文件
# 用于嵌入式Linux系统和RISC-V MCU的交叉编译
# 设置系统名称
set(CMAKE_SYSTEM_NAME Linux)
# 设置处理器架构变量,可以通过命令行参数传入
# 例如: cmake -DTARGET_ARCH=arm ..
if(NOT DEFINED TARGET_ARCH)
set(TARGET_ARCH "arm" CACHE STRING "Target architecture (arm or riscv)")
endif()
# 设置ARM工具链路径
set(ARM_TOOLCHAIN_PATH "/opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi")
# 设置RISC-V工具链路径
set(RISCV_TOOLCHAIN_PATH "/opt/tronlong/tina5.0_v1.0/rtos/lichee/rtos/tools/riscv64-elf-x86_64-20201104")
# 根据目标架构设置主工具链
if(${TARGET_ARCH} STREQUAL "arm")
# ARM Linux工具链配置
set(CMAKE_C_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-g++)
set(CMAKE_FIND_ROOT_PATH /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi/)
set(CMAKE_SYSTEM_PROCESSOR arm)
# 设置额外的编译标志
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)
# 设置链接器
set(CMAKE_LINKER ${CMAKE_C_COMPILER})
set(CMAKE_AR ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-ar)
set(CMAKE_RANLIB ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-ranlib)
elseif(${TARGET_ARCH} STREQUAL "riscv")
# RISC-V工具链配置 (C906核心)
set(CMAKE_C_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gcc)
set(CMAKE_CXX_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-g++)
set(CMAKE_FIND_ROOT_PATH ${RISCV_TOOLCHAIN_PATH}/riscv64-unknown-elf)
set(CMAKE_SYSTEM_PROCESSOR riscv)
# 设置RISC-V特定的编译标志 (C906核心)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=rv64gcv0p7 -mabi=lp64d" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gcv0p7 -mabi=lp64d" CACHE STRING "" FORCE)
# 设置链接器
set(CMAKE_LINKER ${CMAKE_C_COMPILER})
set(CMAKE_AR ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ar)
set(CMAKE_RANLIB ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ranlib)
else()
message(FATAL_ERROR "不支持的目标架构: ${TARGET_ARCH}. 请使用 'arm' 或 'riscv'")
endif()
# 定义ARM工具链变量,供CMakeLists.txt使用
set(ARM_C_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-gcc)
set(ARM_CXX_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-g++)
set(ARM_AR ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-ar)
set(ARM_RANLIB ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-ranlib)
set(ARM_C_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")
set(ARM_CXX_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")
# 定义RISC-V工具链变量,供CMakeLists.txt使用
set(RISCV_C_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gcc)
set(RISCV_CXX_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-g++)
set(RISCV_AR ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ar)
set(RISCV_RANLIB ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ranlib)
set(RISCV_C_FLAGS "-march=rv64gcv0p7 -mabi=lp64d")
set(RISCV_CXX_FLAGS "-march=rv64gcv0p7 -mabi=lp64d")
# 设置查找规则
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# 禁用系统库路径
set(CMAKE_SKIP_RPATH TRUE)
# 设置交叉编译环境的库和头文件搜索路径
set(CMAKE_SYSROOT ${CMAKE_FIND_ROOT_PATH})
# 关键!!!,不能漏掉这个设置
set(CMAKE_FIND_ROOT_PATH /root/arm_install)
总结
通过以上步骤,我们就可以在C++项目中使用MessagePack进行数据序列化和反序列化,并且可以将其交叉编译到嵌入式Linux平台上。MessagePack的高性能和小体积特性使其在网络传输和嵌入式系统中具有很大的优势。