C语言使用Protobuf进行网络通信

笔者前面博文Go语言网络游戏服务器模块化编程介绍了Go语言在开发网络游戏时如何进行模块化编程,在其中使用了Protobuf进行网络通信。在Protobuf官方实现中并没有生成C语言的实现,不过有一个开源的protobuf-c可以使用。

先来看看protobuf-c生成的代码,假如如下PB:

syntax = "proto3";
package netmsg;enum NetCmdID
{CmdNone 	= 0;Ping 		= 1;Pong 		= 2;
}message PingPong
{uint32 time = 1;
}

生成的C代码类似这样:

typedef struct Netmsg__PingPong Netmsg__PingPong;struct  Netmsg__PingPong
{ProtobufCMessage base;uint32_t time;
};
#define NETMSG__PING_PONG__INIT \{ PROTOBUF_C_MESSAGE_INIT (&netmsg__ping_pong__descriptor) \
, 0 }/* Netmsg__PingPong methods */
void   netmsg__ping_pong__init(Netmsg__PingPong         *message);
size_t netmsg__ping_pong__get_packed_size(const Netmsg__PingPong   *message);
size_t netmsg__ping_pong__pack(const Netmsg__PingPong   *message,uint8_t             *out);
size_t netmsg__ping_pong__pack_to_buffer(const Netmsg__PingPong   *message,ProtobufCBuffer     *buffer);
Netmsg__PingPong *netmsg__ping_pong__unpack(ProtobufCAllocator  *allocator,size_t               len,const uint8_t       *data);
void   netmsg__ping_pong__free_unpacked(Netmsg__PingPong *message,ProtobufCAllocator *allocator);extern const ProtobufCMessageDescriptor netmsg__ping_pong__descriptor;

可以看到,PingPong在生成的C代码中结构是原样输出的,而函数中又是以C风格ping_pong输出的;同时包名netmsg在结构中首字母是大写,而在函数中又是全部小写

如果要解析PB,它提供了一个API:

PROTOBUF_C__API
ProtobufCMessage *
protobuf_c_message_unpack(const ProtobufCMessageDescriptor *descriptor,ProtobufCAllocator *allocator,size_t len,const uint8_t *data);

这里需要传入PB的描述符结构ProtobufCMessageDescriptor *,针对PingPong消息即netmsg__ping_pong__descriptor的地址。调用后会返回一个ProtobufCMessage *,只需要强转成Netmsg__PingPong*即可。这个返回值在使用完后需要调用函数protobuf_c_message_free_unpacked释放内存:

PROTOBUF_C__API
void
protobuf_c_message_free_unpacked(ProtobufCMessage *message,ProtobufCAllocator *allocator);

笔者想要的理想情况是与前面博文Go语言网络游戏服务器模块化编程中的一样,可以在网络消息处理函数中直接写需要处理的具体的PB消息。

在处理它时,直接在参数中写生成的C结构指针:

void onPing(user *u, Netmsg__PingPong *pb) {
}

直接使用一个宏DEFINE_HANDLER来映射网络消息ID与处理函数之间的关系,比如这样写:

DEFINE_HANDLER(Ping, PingPong, ping_pong);

宏的第一个参数是消息ID,第二个参数是C结构的写法,第三个参数是函数以及描述符的写法。

为了方便我们只写一次映射关系,DEFINE_HANDLER宏有两个实现,一个是作处理函数的注册用,另一个是实现网络消息的解析并调用自定义的处理函数。

  1. DEFINE_HANDLER宏作为处理函数注册用

DEFINE_HANDLER宏调用自定义函数regNetMsg来注册消息处理函数:

typedef void (*fnCB)(user *u, char *buf, uint32_t len);
static void regNetMsg(Netmsg__NetCmdID cmd, fnCB cb);
#define DEFINE_HANDLER(NetCmdID, PbStructType, PbDescType)                     \extern void on_##PbStructType(user *u, char *buf, uint32_t len);             \regNetMsg(NETMSG__NET_CMD_ID__##NetCmdID, on_##PbStructType);
  1. DEFINE_HANDLER宏实现网络消息的解析并调用自定义的处理函数

DEFINE_HANDLER宏内依次调用protobuf_c_message_unpack,自定义的消息处理函数,protobuf_c_message_free_unpacked即可:

#define DEFINE_HANDLER(NetCmdID, pbStructType, pbDescType) \
void on_##pbStructType(user *u, char *buf, uint32_t len) {\
Netmsg__##pbStructType* pb = \
protobuf_c_message_unpack(&netmsg__##pbDescType##__descriptor,\
len - sizeof(uint16_t), (const uint8_t *)&buf[sizeof(uint16_t)]);\
extern void on##NetCmdID(user *u, Netmsg__##pbStructType *pb);\
on##NetCmdID(u, pb); \
protobuf_c_message_free_unpacked((ProtobufCMessage *)pb, nullptr);

同一个宏如何实现两个功能?

假定我们的映射代码为net_msg.h

DEFINE_HANDLER(Ping, PingPong, ping_pong);

DEFINE_HANDLER的实现为reg_msg.h,在其中使用一个宏来判断一下是第一种功能还是第二种功能,在调用前定义功能宏:

#define DEF_FUNC
#include "reg_msg.h"

这样就可以实现只需要写一份映射代码,然后写处理函数的简便操作。但是这里有一点缺憾就是DEFINE_HANDLER宏因为protobuf-c生成的代码风格的缘故需要写两个,一个是结构的写法PingPong, 一个是函数以及描述符的写法ping_pong,而且包名的大小写也不一致。其实要解决这个问题非常简单,只需要在生成结构时代码时,添加一个typedef即可,比如PingPong,添加一个typedef struct Netmsg__PingPong netmsg__ping_pong;即可统一写成DEFINE_HANDLER(Ping, ping_pong);

typedef struct Netmsg__PingPong Netmsg__PingPong;
typedef struct Netmsg__PingPong netmsg__ping_pong;

我曾经给官方提过一个issue,希望能添加,但官方未响应。可以自行修改代码:

void MessageGenerator::
GenerateStructTypedef(google::protobuf::io::Printer* printer) {printer->Print("typedef struct $classname$ $classname$;\n","classname", FullNameToC(descriptor_->full_name(), descriptor_->file()));for (int i = 0; i < descriptor_->nested_type_count(); i++) {nested_generators_[i]->GenerateStructTypedef(printer);}
}

为:

void MessageGenerator::
GenerateStructTypedef(google::protobuf::io::Printer* printer) {printer->Print("typedef struct $classname$ $classname$;\n","classname", FullNameToC(descriptor_->full_name(), descriptor_->file()));printer->Print("typedef struct $classname$ ","classname", FullNameToC(descriptor_->full_name(), descriptor_->file()));printer->Print("$lcclassname$;\n","lcclassname", FullNameToLower(descriptor_->full_name(), descriptor_->file()));for (int i = 0; i < descriptor_->nested_type_count(); i++) {nested_generators_[i]->GenerateStructTypedef(printer);}
}

然后使用修改过的protoc-gen-c来编译PB。

下面给出完成的reg_msg.h代码,支持GCC和Clang编译器:

#ifdef USE_REG
#undef USE_REG#define HANDLER_BODY(NetCmdID, PbStructType)                                   \netmsg__##PbStructType *pb =                                                 \(netmsg__##PbStructType *)protobuf_c_message_unpack(                     \&netmsg__##PbStructType##__descriptor, nullptr,                      \len - sizeof(uint16_t), (const uint8_t *)&buf[sizeof(uint16_t)]);    \extern void on##NetCmdID(user *u, netmsg__##PbStructType *pb);               \on##NetCmdID(u, pb);                                                         \protobuf_c_message_free_unpacked((ProtobufCMessage *)pb, nullptr);#ifdef __clang__
#define DEFINE_HANDLER(NetCmdID, PbStructType)                                 \static void (^on_##PbStructType)(user * u, char *buf, uint32_t len) =        \^void(user * u, char *buf, uint32_t len) {                               \HANDLER_BODY(NetCmdID, PbStructType)                                   \};                                                                       \regNetMsg(NETMSG__NET_CMD_ID__##NetCmdID, on_##PbStructType);
#else
#ifdef DEF_FUNC
#define DEFINE_HANDLER(NetCmdID, PbStructType)                                 \void on_##PbStructType(user *u, char *buf, uint32_t len) {                   \HANDLER_BODY(NetCmdID, PbStructType)                                       \}
#elif defined(REG_MSG)
#undef DEFINE_HANDLER
#define DEFINE_HANDLER(NetCmdID, PbStructType)                                 \extern void on_##PbStructType(user *u, char *buf, uint32_t len);             \regNetMsg(NETMSG__NET_CMD_ID__##NetCmdID, on_##PbStructType);
#endif
#endif#include "net_msg.h"#endif

调用:

static swiss_map_t *mapNetMsg = nullptr;#ifdef __clang__
#ifdef _WIN32
void *const __imp__NSConcreteGlobalBlock[32] = {nullptr};
void *const __imp__NSConcreteStackBlock[32]	 = {nullptr};
#else
void *const _NSConcreteGlobalBlock[32] = {nullptr};
void *const _NSConcreteStackBlock[32]	 = {nullptr};
#endif
// 这里必须使用Clang的块语法
typedef void (^fnCB)(user *u, char *buf, uint32_t len);
#else
typedef void (*fnCB)(user *u, char *buf, uint32_t len);
#endifstatic void regNetMsg(Netmsg__NetCmdID cmd, fnCB cb) {swiss_map_insert(mapNetMsg, &cmd, (void *)&cb);
}// 由于GCC不支持静态嵌套函数,所以需要将解析消息的函数定义与消息的注册分开
// 而clang支持block语法,可以使用它写一个静态函数,可以直接在一起写
#if !defined(__clang__) && defined(__GNUC__)
#define USE_REG
#define DEF_FUNC
#include "reg_msg.h"
#endifvoid RegNetMsg() {mapNetMsg =new_swiss_map(sizeof(uint16_t), hashInt16, sizeof(fnCB), equal_int16);#if !defined(__clang__) && defined(__GNUC__)#undef DEF_FUNC#define REG_MSG
#endif
#define USE_REG
#include "reg_msg.h"
}

这样就可以非常方便地在netmsg.h中映射网络消息处理函数了:

DEFINE_HANDLER(ReqLogin, req_login);
DEFINE_HANDLER(Ping, ping_pong);

如果对你有帮助,欢迎点赞收藏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/88000.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88000.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3 随手笔记12--组件通信方式9/5--useAttrs

一 什么是useAttrsuseAttrs 是 Vue 3 Composition API 中提供的一个函数&#xff0c;它属于 Vue 的组合式 API 工具集的一部分。通过 useAttrs&#xff0c;你可以访问传递给组件但未被声明为 props 的所有属性。这对于处理非 prop 特性&#xff08;attributes&#xff09;特别有…

HumanRisk-自动化安全意识与合规教育平台方案

权威数据显示&#xff0c;74%以上的数据泄露与网络安全事件归根结底与人为因素有关&#xff0c;60%以上的网络安全事件是由内部人员失误造成的。这一现状揭示了一个核心命题&#xff1a;网络安全威胁正从技术漏洞转向“人为因素风险”。Gartner的调查发现&#xff0c;即便接受了…

2025年食品科学与健康大数据国际会议(SHBD 2025)

2025年食品科学与健康大数据国际会议 2025 International Conference on Food Science and Health Big Data&#xff08;一&#xff09;大会信息 会议简称&#xff1a;ICFSHBD 2025 大会地点&#xff1a;中国上…

CompareFace人脸识别算法环境部署

一、docker 安装 步骤1&#xff1a;启用系统功能 右键开始菜单 → 应用和功能 → 点击 程序和功能 → 勾选 Hyper-V 和 Windows子系统Linux 步骤2&#xff1a;获取安装包 访问Docker官网安装包下载页 &#xff0c;下载「Docker Desktop Installer.rar」压缩包 步骤3&#…

STM32固件升级设计——内部FLASH模拟U盘升级固件

目录 一、功能描述 1、BootLoader部分&#xff1a; 2、APP部分&#xff1a; 二、BootLoader程序制作 1、分区定义 2、 主函数 3、配置USB 4、配置fatfs文件系统 5、程序跳转 三、APP程序制作 四、工程配置&#xff08;默认KEIL5&#xff09; 五、运行测试 结束语…

操作系统引导过程

操作系统引导是指计算机利用 CPU 运行特定程序&#xff0c;通过程序识别硬盘&#xff0c;识别硬盘分区&#xff0c;识别硬盘分区上的操作系统&#xff0c;最后通过程序启动操作系统。 引导流程&#xff08;8步核心环节&#xff09; 1. 激活CPU 加电后CPU自动读取 ROM中的Boot…

Safetensors与大模型文件格式全面解析

Safetensors是一种专为存储大型张量数据设计的文件格式&#xff0c;由Hugging Face团队开发&#xff0c;旨在提供安全高效的模型参数存储解决方案。下面将详细介绍Safetensors格式及其特点&#xff0c;并全面梳理当前主流的大模型文件格式。 一、Safetensors格式详解 1. 基本概…

分布式理论:CAP、Base理论

目录 1、CAP理论 1.1、介绍 1.2、CAP的三种选择 1.3、CAP的注意事项 2、BASE理论 2.1、定义介绍 2.2、最终一致性的介绍 2.3、BASE的实现方式 2.4、与ACID的对比 3、CAP与BASE的联系 4、如何选择CAP 前言 在分布式系统中&#xff0c;CAP理论和BASE理论是指导系统设计…

【最新】飞算 JavaAl安装、注册,使用全流程,让ai自己给你写代码,解放双手

目录 飞算 JavaAl 产品介绍 安装飞算 JavaAl 第一步&#xff1a;点击 File->Setting 第二步&#xff1a;点击 Plugins 第三步&#xff1a;搜索 CalEx-JavaAI 第四步&#xff1a;点击 Install 进行安装 第五步&#xff1a;点击 Install &#xff0c;查看安装好的飞算…

无人设备遥控器之姿态控制算法篇

无人设备遥控器的姿态控制算法通过传感器数据融合、控制算法优化和执行机构调节实现动态平衡&#xff0c;核心算法包括PID控制、自适应控制、模型预测控制&#xff08;MPC&#xff09;&#xff0c;以及数据融合中的互补滤波和卡尔曼滤波&#xff0c;同时涉及四元数算法和深度强…

【加解密与C】Base系列(三)Base85

Base85 编码简介 Base85&#xff08;也称为 Ascii85&#xff09;是一种二进制到文本的编码方案&#xff0c;用于将二进制数据转换为可打印的ASCII字符。它的效率高于Base64&#xff0c;但生成的字符串可能包含特殊字符&#xff08;如引号或反斜杠&#xff09;&#xff0c;需在…

Docker企业级应用:从入门到生产环境最佳实践

一、Docker核心概念与架构 1.1 Docker技术栈 #mermaid-svg-CUEiyGo05ZYG524v {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-CUEiyGo05ZYG524v .error-icon{fill:#552222;}#mermaid-svg-CUEiyGo05ZYG524v .error-te…

8、保存应用数据

目录用户首选项的使用用户首选项主要API用户首选项开发流程用户首选项开发实践关系型数据库的使用关系型数据库工作流程关系型数据库开发实践用户首选项的使用 用户首选项主要API 用户首选项开发流程 成功的获取了一个名为myStore的Preferences实例 保存了一个键值对&#x…

(C++)list列表相关基础用法(C++教程)(STL库基础教程)

源代码&#xff1a;#include <iostream> #include <list>using namespace std;int main(){list<int> numbers{10,20,30};numbers.push_front(5);numbers.push_back(40);auto it numbers.begin();advance(it,2);numbers.insert(it,15);cout<<"该列…

Spring CGLIB私有方法访问成员变量为null问题

场景 代码 RestController public class TestJob {Autowiredprivate XxService xxService;XxlJob("testCGLIB")private void doTest(){System.out.println("方法调用");System.out.println("成员变量注入:"(xxService!null));this.doInnerTest()…

Paimon本地表查询引擎LocalTableQuery详解

LocalTableQueryLocalTableQuery 是 Paimon 中实现本地化、带缓存的表查询的核心引擎。它的主要应用场景是 Flink 中的 Lookup Join。当 Flink 作业需要根据一个流中的 Key 去关联一个 Paimon 维表时&#xff0c;LocalTableQuery 可以在 Flink 的 TaskManager 节点上&#xff0…

使用协程简化异步资源获取操作

异步编程的两种场景 在异步编程中&#xff0c;回调函数通常服务于两种不同场景&#xff1a; 一次性资源获取&#xff1a;等待异步操作完成并返回结果。持续事件通知。监听并响应多个状态变更。 Kotlin为这两种场景提供了解决方案&#xff1a;使用挂起函数简化一次性资源获取…

ABP VNext + Cosmos DB Change Feed:搭建实时数据变更流服务

ABP VNext Cosmos DB Change Feed&#xff1a;搭建实时数据变更流服务 &#x1f680; &#x1f4da; 目录ABP VNext Cosmos DB Change Feed&#xff1a;搭建实时数据变更流服务 &#x1f680;TL;DR ✨&#x1f680;1. 环境与依赖 &#x1f3d7;️2. 服务注册与依赖注入 &…

STM32-定时器

定时器&#xff1a;有4个独立通道&#xff1a;输入捕获&#xff1b;输出比较PWM生成&#xff1b;单脉冲模式输出&#xff1b;可通外部信号控制定时器&#xff08;TIMx-ETR&#xff09;&#xff1b;支持针对定时的增量&#xff08;正交&#xff09;编码器、霍尔传感器电路通用定…

Windows Server 2019--职业技能大赛B模块Windows服务器配置样题

一、赛题说明 &#xff08;一&#xff09;竞赛介绍 请详细阅读网络拓扑图&#xff0c;为所有计算机修改默认防火墙以便允许ICMP和相应的流量&#xff0c;不允许直接关闭主机的防火墙。除了CD-ROM/HDD驱动器&#xff0c;请不要修改虚拟机本身的硬件设置。 &#xff08;二&…