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还是有不少优势的,比较推荐。

相关推荐
秦jh_10 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
ChoSeitaku27 分钟前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
DdddJMs__13532 分钟前
C语言 | Leetcode C语言题解之第557题反转字符串中的单词III
c语言·leetcode·题解
娃娃丢没有坏心思1 小时前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
keep__go2 小时前
Linux 批量配置互信
linux·运维·服务器·数据库·shell
矛取矛求2 小时前
Linux中给普通账户一次性提权
linux·运维·服务器
Fanstay9852 小时前
在Linux中使用Nginx和Docker进行项目部署
linux·nginx·docker
问道飞鱼2 小时前
【微服务知识】开源RPC框架Dubbo入门介绍
微服务·rpc·开源·dubbo
大熊程序猿2 小时前
ubuntu 安装kafka-eagle
linux·ubuntu·kafka
极地星光2 小时前
JSON-RPC-CXX深度解析:C++中的远程调用利器
c++·rpc·json