protobuf、protobuf-c、protobuf-c-rpc在Linux(Ubuntu18.04)编译安装及交叉编译arm\aarch64版本
文章目录
- protobuf、protobuf-c、protobuf-c-rpc在Linux(Ubuntu18.04)编译安装及交叉编译arm\aarch64版本
一、前言
如下介绍c语言的protobuf+rpc的开源库protobuf-c和protobuf-c-rpc,其适合于嵌入式分布式场景,利用protobuf协议的可扩展性比较方便进行协议兼容升级,利用rpc接口的网络易用性,不需要再从头到尾实现一遍socket通信、通信接口设计,只需要实现C函数接口设计和开发以及利用proto设计好交互协议即可,并且具备一定的跨编程语言交互特性,每个具备联网能力的嵌入式设备都可作为rpc客户端和服务端,可以快速进行嵌入式设备业务组网开发(此外,还有一些适合于stm32的protobuf库,这里的库个人感觉更适合Linux和嵌入式Linux)。
二、protobuf、rpc、protobuf-c、protobuf-c-rpc介绍
1、protobuf
Protocol Buffers(简称:ProtoBuf)是一种开源跨平台的序列化数据结构的协议。其对于存储资料或在网络上进行通信的程序是很有用的。这个方法包含一个接口描述语言,描述一些数据结构,并提供程序工具根据这些描述产生代码,这些代码将用来生成或解析代表这些数据结构的字节流。
起初接触protobuf是在go语言上,当时go的grpc框架对我影响很大,分布式场景的快速组网开发使得设备之间的交互扩展性极强,增加新功能只需要简单修改protobuf交互协议文档,新的服务就可以快速生成并开发,高低版本的交互协议也很容易兼容,其相较于xml和json使用简单,更接近于代码层面的数据类型以及高效的性能和兼容性,在分布式场景下应用越来越广。
2、protobuf-c
这是Google Protocol Buffers数据序列化格式的 C 实现。它包括一个实现 protobuf 编码和解码的纯 C 库,以及一个基于原始 .protobuf 文件将 Protocol Buffer 文件转换为 C 描述符代码的代码生成器。之前是包含rpc实现的,后面单独拆分出来了,更将强调了 protobuf和rpc的单独性(虽然protobuf和rpc以及grpc一起使用,但protobuf可以像json、xml等序列化协议一样可以单独去使用)。
3、protobuf-c-rpc
用于将protobuf和rpc结合使用的C语言实现库,以此类推,也有将json和rpc结合使用的库等等。
三、Ubuntu18.04下编译安装及交叉编译
1、前置准备-升级cmake
shell
#卸载老版本
sudo apt-get autoremove cmake
#直接下载对应运行包:https://cmake.org/files/
wget https://cmake.org/files/v3.20/cmake-3.20.0-linux-x86_64.tar.gz
#解压
tar zxvf cmake-3.20.0-linux-x86_64.tar.gz
#移动到常用安装目录
mv cmake-3.20.0-linux-x86_64 /opt/cmake-3.20.0
#创建软链接
ln -sf /opt/cmake-3.20.0/bin/cmake /usr/bin/cmake
#查看版本
cmake --version
2、protobuf编译安装及交叉编译
GitHub仓库及下载地址:
https://github.com/google/protobuf/releases
https://github.com/protocolbuffers/protobuf/archive/refs/tags/v3.6.0.tar.gz
2.1、正常编译、安装及错误解决方法
该过程可以写入简单shell脚本一键执行:
shell
#下载源码
wget https://github.com/protocolbuffers/protobuf/archive/refs/tags/v3.6.0.tar.gz
#解压
tar zxvf v3.6.0.tar.gz
cd protobuf-3.6.0/
#安装autogen、autoconf、libtool、m4
apt-get install autogen
apt-get install autoconf
apt-get install libtool
apt-get install m4
#configure编译并设置安装路径
./autogen.sh
apt-get install g++
./configure --prefix=/usr/local/protobuf
#make编译时间稍微长一些
make
make install
#bin目录可以加入/etc/profile中,之后就可以执行protoc命令将.proto文件生成代码了
export PATH=/usr/local/protobuf/bin/:$PATH
source /etc/profile
#查看版本,安装成功的话可以看到类似:libprotoc 3.6.0
protoc --version
#PKG_CONFIG_PATH最好不要添加,交叉编译时路径需要修改,所以每次编译前执行一下该命令即可,如果不需要交叉编译则可以添加,避免找不到库
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
- 错误1:
shell
# ./autogen.sh
+ autoreconf -f -i -Wall,no-obsolete
./autogen.sh: 32: ./autogen.sh: autoreconf: not found
解决:安装autogen、autoconf
- 错误2:
shell
# ./autogen.sh
+ autoreconf -f -i -Wall,no-obsolete
configure.ac:104: error: possibly undefined macro: AC_PROG_LIBTOOL
If this token and others are legitimate, please use m4_pattern_allow.
See the Autoconf documentation.
autoreconf: /usr/bin/autoconf failed with exit status: 1
解决:安装libtool、m4
- 错误3:
shell
configure: error: in `/home/plc/protobuf-3.6.0':
configure: error: C++ preprocessor "/lib/cpp" fails sanity check
See `config.log' for more details
解决:安装g++
2.2、交叉编译arm、aarch64版本及错误解决方法
首先需要确认你自己的交叉编译工具链,这主要取决于你的开发板,这里以linaro的gnu工具链为例。
下载地址:https://www.linaro.org/downloads/#gnu_and_llvm
https://snapshots.linaro.org/gnu-toolchain/
这里以目前最新的的14版本为例下载x86_64的arm和aarch的arm交叉编译工具链(这取决于你的PC机的芯片)。
这里注意不要下载版本太高的交叉编译工具链,否则其依赖的libc版本太高的话会导致无法使用。
shell
#wget直接下载,会比较慢,建议使用其它方法下载好传到虚拟机上
wget https://snapshots.linaro.org/gnu-toolchain/11.3-2022.06-1/aarch64-linux-gnu/gcc-linaro-11.3.1-2022.06-x86_64_aarch64-linux-gnu.tar.xz
wget https://snapshots.linaro.org/gnu-toolchain/11.3-2022.06-1/arm-linux-gnueabihf/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf.tar.xz
#解压
tar -xvf gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf.tar.xz
tar -xvf gcc-linaro-11.3.1-2022.06-x86_64_aarch64-linux-gnu.tar.xz
#移动到常用位置
mv gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/ /usr/local/
mv gcc-linaro-11.3.1-2022.06-x86_64_aarch64-linux-gnu/ /usr/local/
#进行配置、编译、安装
CC=/usr/local/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc CXX=/usr/local/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ ./configure --prefix=/usr/local/protobuf/arm --host=arm-linux
make clean && make && make install
#aarch版本
CC=/usr/local/gcc-linaro-11.3.1-2022.06-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc CXX=/usr/local/gcc-linaro-11.3.1-2022.06-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-g++ ./configure --prefix=/usr/local/protobuf/aarch64 --host=aarch64-linux
make clean && make && make install
- 错误1:
shell
#configure报错
checking for arm-linux-gcc... /usr/local/gcc-linaro-14.0.0-2023.06-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
checking whether the C compiler works... no
#然后使用/usr/local/gcc-linaro-14.0.0-2023.06-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc发现libc版本不够
/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by /usr/local/gcc-linaro-14.0.0-2023.06-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
strings /lib/x86_64-linux-gnu/libc.so.6 |grep GLIBC_
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_2.13
GLIBC_2.14
GLIBC_2.15
GLIBC_2.16
GLIBC_2.17
GLIBC_2.18
GLIBC_2.22
GLIBC_2.23
GLIBC_2.24
GLIBC_2.25
GLIBC_2.26
GLIBC_2.27
GLIBC_PRIVATE
解决方法:下载低版本的交叉编译工具链或者升级glibc,升级glibc会导致一系列系统问题,不太建议。
3、protobuf-c编译安装及交叉编译
https://github.com/protobuf-c/protobuf-c
https://github.com/protobuf-c/protobuf-c/releases/download/v1.4.1/protobuf-c-1.4.1.tar.gz
目前安装的1.4.1,protobuf-c和protobuf-c-rpc已经分离成两个项目了。
这里注意正常编译时不需要配置disable-protoc,用于生成bin、lib等内容,而交叉编译时则可以不需要
3.1、正常编译安装
shell
wget https://github.com/protobuf-c/protobuf-c/releases/download/v1.4.1/protobuf-c-1.4.1.tar.gz
tar zxvf protobuf-c-1.4.1.tar.gz
cd protobuf-c-1.4.1/
apt-get install pkg-config
#注意设置PKG_CONFIG_PATH,否则会找不到protobuf的头文件等
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
./configure --prefix=/usr/local/protobuf-c CFLAGS="-fPIC"
make clean && make && make install
export PATH=/usr/local/protobuf-c/bin/:$PATH
- 错误1:
shell
checking pkg-config is at least version 0.9.0... ./configure: line 12928: /usr/local/protobuf/lib/pkgconfig/: Is a directory
no
configure: error: pkg-config is required!
解决方法:安装pkg-config
如果确认你安装了pkg-config但还是一直报类似错误:
shell
configure: error: in `/home/plc/protobuf-c-1.4.1':
configure: error: The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
Alternatively, you may set the environment variables protobuf_CFLAGS
and protobuf_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see <http://pkg-config.freedesktop.org/>.
See `config.log' for more details
那么可能是环境变量污染导致的,export看一下PKG_CONFIG是不是被设置了,如果是应该就是这个问题,reboot重启一下设备就好了。
3.2、交叉编译arm、aarch64版本
shell
#交叉编译时使用的bin下的程序还是Linux正常编译安装的,只改变PKG_CONFIG_PATH链接对应的库和头文件
export PKG_CONFIG_PATH=/usr/local/protobuf/arm/lib/pkgconfig/
#增加--disable-protoc,只生成库和头文件
CC=/usr/local/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc ./configure --prefix=/usr/local/protobuf-c/arm --host=arm-linux --disable-protoc CFLAGS="-fPIC"
make clean && make && make install
#aarch64版本
CC=/usr/local/gcc-linaro-11.3.1-2022.06-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc ./configure --prefix=/usr/local/protobuf-c/aarch64 --host=aarch64-linux --disable-protoc CFLAGS="-fPIC"
make clean && make && make install
- 错误1:
shell
/bin/bash: /usr/local/protobuf/arm/bin/protoc: cannot execute binary file: Exec format error
Makefile:2707: recipe for target 'protobuf-c/protobuf-c.pb.cc' failed
make: *** [protobuf-c/protobuf-c.pb.cc] Error 126
protoc该程序只是用于编译proto文件,可以不需要交叉编译的protoc程序,当然如果交叉编译环境要使用该程序,则修改环境变量让其使用上面我们交叉编译protobuf时生成的protoc即可,我这里直接disable方便一些。
4、protobuf-c-rpc编译安装
https://github.com/protobuf-c/protobuf-c-rpc
4.1、正常编译安装
shell
git clone https://github.com/protobuf-c/protobuf-c-rpc.git
cd protobuf-c-rpc/
export PKG_CONFIG_PATH=/usr/local/protobuf-c/lib/pkgconfig/
./autogen.sh
./configure --prefix=/usr/local/protobuf-c-rpc CFLAGS="-fPIC"
export C_INCLUDE_PATH=/usr/local/protobuf-c/include/
make clean && make && make install
- 错误1:
shell
In file included from protobuf-c-rpc/protobuf-c-rpc-data-buffer.c:37:0:
protobuf-c-rpc/protobuf-c-rpc.h:32:10: fatal error: protobuf-c/protobuf-c.h: No such file or directory
#include <protobuf-c/protobuf-c.h>
^~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
Makefile:853: recipe for target 'protobuf-c-rpc/protobuf-c-rpc-data-buffer.lo' failed
解决方法:设置环境变量C_INCLUDE_PATH指定头文件位置即可(export C_INCLUDE_PATH=/usr/local/protobuf-c/include/),交叉编译时也是如此。
4.2、交叉编译arm、aarch64版本
shell
#交叉编译时使用的bin下的程序还是Linux正常编译安装的,只改变PKG_CONFIG_PATH链接对应的库和头文件
export PKG_CONFIG_PATH=/usr/local/protobuf-c/arm/lib/pkgconfig/
CC=/usr/local/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc ./configure --prefix=/usr/local/protobuf-c-rpc/arm --host=arm-linux CFLAGS="-fPIC"
export C_INCLUDE_PATH=/usr/local/protobuf-c/arm/include
make clean && make && make install
#aarch64版本
export PKG_CONFIG_PATH=/usr/local/protobuf-c/arm/lib/pkgconfig/
CC=/usr/local/gcc-linaro-11.3.1-2022.06-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc ./configure --prefix=/usr/local/protobuf-c-rpc/aarch64 --host=aarch64-linux CFLAGS="-fPIC"
export C_INCLUDE_PATH=/usr/local/protobuf-c/aarch64/include/
make clean && make && make install
四、接口调用示例及演示
protobuf-c-rpc示例主要参考这里:https://github.com/protobuf-c/protobuf-c-rpc/blob/master/t/test-rpc.c
- 域socket方式指定socket文件的路径即可,比如"./socket";
- tcp方式服务端指定端口即可,传递的name是字符串类型的端口如"8888";而客户端需要指定ip和端口如"127.0.0.1:8888";
这里以获取一个版本号为例写一个简单的rpc客户端服务端示例,客户端去调用rpc接口获取版本信息:
1、创建proto文件test.proto
protobuf
syntax = "proto3";
service Test {
rpc GetVersion(VersionReq) returns (VersionRes) {}
}
message VersionReq {
string dev = 1; //请求版本的设备标识
}
message VersionRes {
int32 err_code = 1; //错误码,为0是正常,非0错误
string message = 2; //回复错误信息
string version = 3; //版本信息
}
2、使用protoc-c编译proto文件生成服务和客户端代码文件
shell
/usr/local/protobuf-c/bin/protoc-c --c_out=. test.proto
之后会生成test.pb-c.c和test.pb-c.h,每次更新.proto文件重新生成一下即可。
3、完成rpc客户端和服务端程序
c
//server.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include "protobuf-c/protobuf-c.h"
#include "protobuf-c/protobuf-c-rpc.h"
#include "test.pb-c.h"
static int g_run_flag = 1;
void signal_handler(int signum) {
printf("Interrupt signal:%d received.\n", signum);
g_run_flag = 0;
exit(signum);
}
void this__get_version(Test_Service *service,
const VersionReq *input,
VersionRes_Closure closure,
void *closure_data) {
VersionRes result = VERSION_RES__INIT;
printf("ser get version\n");
(void) service;
if (input->dev == NULL || !strlen(input->dev))
{
result.err_code = -1;
result.message = "dev info is null";
closure(&result, closure_data);
return;
}
printf("req dev info:%s\n", input->dev);
result.err_code = 0;
result.version = "1";
result.message = "success";
closure (&result, closure_data);
}
static Test_Service test_service = TEST__INIT(this__);
void *pthread_rpc_server(void *arg) {
ProtobufC_RPC_Server *rpc_server;
ProtobufCService *local_service = (ProtobufCService *)&test_service;
//域socket方式
// protobuf_c_rpc_server_new(PROTOBUF_C_RPC_ADDRESS_LOCAL, "test.socket",
// local_service, NULL);
//tcp方式
protobuf_c_rpc_server_new(PROTOBUF_C_RPC_ADDRESS_TCP, "12345",
local_service, NULL);
while (g_run_flag) {
protobuf_c_rpc_dispatch_run(protobuf_c_rpc_dispatch_default());
sleep(1);
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t pid_rpc_server;
signal(SIGINT, signal_handler);
printf("fun rpc run.\n");
pthread_create(&pid_rpc_server, NULL, (void *(*)(void *)) &pthread_rpc_server, NULL);
while (g_run_flag) {
}
}
c
//client.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include "protobuf-c/protobuf-c-rpc.h"
#include "test.pb-c.h"
static int g_run_flag = 1;
void signal_handler(int signum) {
printf("Interrupt signal:%d received.\n", signum);
g_run_flag = 0;
exit(signum);
}
static void
handle_query_response(const VersionRes *result,
void *closure_data) {
if (result == NULL)
printf("Error processing request.\n");
else if (result->message == NULL)
printf("Not found.\n");
else {
printf("resp message:%s\n", result->message);
printf("resp ver:%s\n", result->version);
}
*(protobuf_c_boolean *) closure_data = 1;
}
static void
do_nothing (ProtobufCRPCDispatch *dispatch, void *unused)
{
}
static void
run_main_loop_without_blocking (ProtobufCRPCDispatch *dispatch)
{
protobuf_c_rpc_dispatch_add_idle (dispatch, do_nothing, NULL);
protobuf_c_rpc_dispatch_run (dispatch);
}
int main(int argc, char *argv[]) {
ProtobufCService *service;
ProtobufC_RPC_Client *client;
VersionRes resp = VERSION_RES__INIT;
signal(SIGINT, signal_handler);
//域socket方式
// service = protobuf_c_rpc_client_new(PROTOBUF_C_RPC_ADDRESS_LOCAL, "test.socket", &test__descriptor, NULL);
//tcp方式
service = protobuf_c_rpc_client_new(PROTOBUF_C_RPC_ADDRESS_TCP, "127.0.0.1:12345", &test__descriptor, NULL);
if (service == NULL) {
printf("error creating client\n");
exit(-1);
}
client = (ProtobufC_RPC_Client *) service;
//设置自动重连时间
protobuf_c_rpc_client_set_autoreconnect_period(client, 3000);
while (!protobuf_c_rpc_client_is_connected(client))
protobuf_c_rpc_dispatch_run(protobuf_c_rpc_dispatch_default());
fprintf(stderr, "done.\n");
while (g_run_flag) {
protobuf_c_boolean is_done = 0;
VersionReq req = VERSION_REQ__INIT;
req.dev = "12345678";
run_main_loop_without_blocking (protobuf_c_rpc_dispatch_default ());
test__get_version(service, &req, handle_query_response, &is_done);
while (!is_done)
protobuf_c_rpc_dispatch_run (protobuf_c_rpc_dispatch_default ());
printf("req get version:\n");
sleep(1);
}
}
4、编译并测试
shell
gcc server.c test.pb-c.c -o server -L /usr/local/protobuf-c/lib -L /usr/local/prtobuf-c-rpc/lib -lprotobuf-c-rpc -lprotobuf-c -lpthread
gcc client.c test.pb-c.c -o client -L /usr/local/protobuf-c/lib -L /usr/local/protobuf-c-rpc/lib -lprotobuf-c-rpc -lprotobuf-c -lpthread
# ./server
fun rpc run.
ser get version
req dev info:12345678
ser get version
req dev info:12345678
ser get version
req dev info:12345678
ser get version
req dev info:12345678
ser get version
req dev info:12345678
ser get version
req dev info:12345678
...
# ./client
done.
resp message:success
resp ver:1
req get version:
resp message:success
resp ver:1
req get version:
resp message:success
resp ver:1
req get version:
resp message:success
resp ver:1
req get version:
resp message:success
resp ver:1
req get version:
^CInterrupt signal:2 received.
5、关键点(重要)
- TCP方式时服务端protobuf_c_rpc_server_new的name只需要设置端口,不需要绑定ip,但是客户端这里的name为ip:port形式;
- 域socket方式时需要创建为name的文件用来做域socket通信;
- 客户端要调用的接口方法是固定的,但是服务端需要重写函数指针对应的函数并且函数名是初始化service时传入的前缀名称加上方法名称:
c
//比如这里初始化时设置前缀为this__,那么这里重写的回调函数名称就是:this_get_version,这个函数指针被注册到服务之中了,等到客户端请求时就会调用该注册的函数
static Test_Service test_service = TEST__INIT(this__);
void this__get_version(Test_Service *service,
const VersionReq *input,
VersionRes_Closure closure,
void *closure_data) {
...
}
五、最后
protobuf这种序列化方式的数据交互方式虽然相较json等上手门槛稍微高一些,但是相较于json、xml还是有不少优势的,比较推荐。