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
英文地址,請注明出處:http://en.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;二&…