日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

自定义协议的命令解析器

發布時間:2024/3/13 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自定义协议的命令解析器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
  • Switch...case類型命令解析的弊端
  • 程序的改進目標
  • 對命令解析器的分析
  • 命令解析器的代碼示例
  • 用于代碼工程需要解決的問題
  • 可用于代碼工程的命令解析器實現
  • 一些新的認識

前言

??對于目前公司的命令/報文解析方式不爽有段時間了,因為一個函數竟然寫了百行左右,那些switch…case 的case 直接用數字表示,而且那個數字真是毫無順序可言(至少我是找不到),每次找命令都是從頭看到尾,一遍沒看到,再看一遍,還是沒有,才能確認確實沒有相應的指令。對于修改和維護來說,我一直有種自己在“添亂”的感覺。但是更不爽的是我沒能力解決這個問題,直到看到了那篇文章:【命令解析器 :C語言對象化設計實例】

??如果沒有面向對象編程經驗的話那篇文章可以先跳過,看看我的描述能不能解決實際問題,如果不能的話,起碼會產生種意識:這個問題很小,看一篇文章就能解決,但博主語言組織能力太差,害我不得不多看一篇文章。

??自定義協議的命令解析器: 是在接收到自定義協議的報文的情況下用于對命令進行解析。本質上就是發生相應的事件,執行相應功能。

Switch…case類型命令解析的弊端

??那篇文章提出了switch…case 那種方式更客觀清晰的弊端描述,像“添亂”這個詞太主觀了,大概只有我自己才知道這個具體意味著什么。

  • 大量的case分支對程序的復雜度時明顯增加的,非常不便于查找,排錯和維護。

  • 命令增加引起跨模塊修改。只是增加了GPIO相關功能,命令處理邏輯沒變(依然只是判斷字符串相等),為什么卻要改動cmd.c(命令解析源文件)的命令處理邏輯。

  • 大量的外部函數,模塊間高度耦合。在cmd.c (命令解析源文件)中直接通過函數名調用,兩個文件(命令解析文件 和 被調用函數所在文件)像纏綿的情侶般高度耦合,這種緊密的聯系破壞了C程序設計的一個基本原則—模塊的獨立性。采用 了模塊化編程,然而每個模塊卻不能獨立使用,這是會破壞模塊化的,若不加維護和避免,可能會拖垮優良的架構。

程序的改進目標

  • 命令的處理函數要去耦合,高內聚低耦合的的代碼是易于復用和維護的
  • 增加或減少命令不影響cmd.c(命令解析源文件)

對命令解析器的分析

【命令】本身可以封裝為 包含 【命令名】和【對應操作】兩個成員的結構體

  • 【命令名】:屬性,可以用字符數組存儲

  • 【對應操作】:行為/函數,由于C語言結構體不支持函數,可用函數指針存儲。

【命令解析器】的處理過程:命令注冊,查找匹配 和 執行指令

通過調用【命令解析器】主動注冊命令,而不是通過代碼寫死,從而**避免了跨模塊修改**

【命令解析器】需要從一對命令中匹配一個,因此需要一種能存儲命令集合的數據結構:線性表(列表內容;列表長度)。線性表的基本操作與命令處理過程結合(命令的注冊和匹配 其實就是 插入和查找過程)

命令解析器的代碼示例

【數據結構】:結構體數組(線性表)+ 存儲命令個數的變量

typedef void (*handler)(void); // 命令操作函數指針類型/* 命令結構體類型 */ typedef struct cmd {char cmd_name[MAX_CMD_NAME_LENGTH + 1]; // 命令名 handler cmd_operate; // 命令操作函數 } CMD;// 文件名稱:cmd.h/* 命令列表結構體類型 */ typedef struct cmds {CMD cmds[MAX_CMDS_COUNT]; // 列表內容int num; // 列表長度 } CMDS;

【注冊命令函數】:接收一個命令類型數組,插入到命令解析器的命令列表中。

// 文件名稱:cmd.cvoid register_cmds(CMD reg_cmds[], int length) {int i;if (length > MAX_CMDS_COUNT){return;}for (i = 0; i < length; i++){if (commands.num < MAX_CMDS_COUNT) // 命令列表未滿{strcpy(commands.cmds[commands.num].cmd_name, reg_cmds[i].cmd_name);commands.cmds[commands.num].cmd_operate = reg_cmds[i].cmd_operate;commands.num++;} } } // 文件名稱:led.cstatic void led_on(void) {LED1 = 0; }static void led_off(void) {LED1 = 1; }void led_init(void) {/* 填充命令結構體數組 */CMD led_cmds[] = {{"led on", led_on},{"led off", led_off}};/* 注冊命令 */register_cmds(led_cmds, ARRAY_SIZE(led_cmds)); /* 初始化硬件 */led_off(); }

【命令匹配函數】:接收一個待匹配的命令字符串作為參數,對命令列表進行遍歷比較操作。

// 文件名稱:cmd.cvoid match_cmd(char *str) {int i;if (strlen(str) > MAX_CMD_NAME_LENGTH){return;}for (i = 0; i < commands.num; i++) // 遍歷命令列表{if (strcmp(commands.cmds[i].cmd_name, str) == 0){commands.cmds[i].cmd_operate();}} }

【執行指令】:調用相應的函數指針來執行相應的操作。被調用的函數都是static 修飾的內部函數,在其他模塊中不能通過函數名直接調用,而是通過函數指針的方式傳遞,實現了模塊間解耦。

用于代碼工程需要解決的問題

??對于有狀態機思想做支撐的我來說,什么結構體數組,函數指針,表驅動,跳轉表,“注冊”的概念,都不是問題,帶我走出迷茫不知所措后剩余的問題就好解決了。

  • 我需要的是數字來表示命令,而不是字符串。
  • 【無法看到所有注冊的命令】:使用枚舉類型來列舉出所有需要的方法,也就解決了上面那問題。
  • 【如何傳遞參數】:那篇文章是沒有提到傳參的,不同函數的參數個數,參數類型是不一致的,必須統一起來,這個問題極其重要。可以通過一個指向緩沖區的指針,最終考慮為空指針,一是考慮到數據隱藏,二是考慮到有些被調用的函數不需要傳入參數,如果指定類型容易引起誤解:必須傳入參數。
  • 【對目前架構的影響】:分層架構是只允許上層調用底層,底層是不能調用上層的,命令解析器作為一個模塊,處于功能模塊層,但注冊和調用都該是應用邏輯層。根據對命令解析器的代碼了解,實際的注冊和調用都是關于功能模塊層的那個命令的結構體數組。

可用于代碼工程的命令解析器實現

??在清楚了上面幾個概念和 1.0版本的代碼理解無問題后,就能看懂下面的代碼了。可以注意一下三個被調用函數對于參數的“態度”:不使用,用于判斷,用于函數輸入

??我已經根據下面這個為模板,稍加修改,用在了代碼工程中,運行良好。關于下面代碼的測試從個人的角度來說已經沒必要再花費一兩個小時編寫測試代碼,組織多個源文件編譯的代碼,然后查看結果了,因為結果已經知道了。

//common.h #ifndef __COMMON_H__ #define __COMMON_H__#include <stdint.h>typedef enum key_val {BLUETOOTH_CONNECT_STATE = 0,LED_CONTROL,MOTOR_SPEED_SET, }KEY;typedef void (*handler)(void *); typedef struct cmd {KEY cmd_key;handler cmd_operate; }CMD;#define MAX_CMDS_COUNT 30typedef struct cmds {CMD cmds[MAX_CMDS_COUNT]; uint8_t num; } CMDS;void match_cmd(KEY value, uint8_t* payload); void register_cmds(CMD reg_cmds[], int length);void get_bluetooth_state(void *user_data); void bluetooth_init(void);void led_control(void *user_data); void led_init(void);void set_motor_speed(void *user_data); void motor_init(void);#endif //cmd.c #include "common.h"static CMDS commands = {{0}, 0}; /*** match_cmd* @brief The command matches and then executes* @param value Explain : Command Key* @param payload Explain : First element address of buffer*/ void match_cmd(KEY value, uint8_t* payload) {for (uint8_t i = 0; i < commands.num; i++) {if (commands.cmds[i].cmd_key == value){commands.cmds[i].cmd_operate((void*)payload);}} }/*** register_cmds* @brief Register command Key and function* @param reg_cmds Explain : array of CMD* @param length Explain : numbers of module functions */ void register_cmds(CMD reg_cmds[], int length) {for (uint8_t i = 0; i < length; i++){if (commands.num < MAX_CMDS_COUNT) {commands.cmds[commands.num].cmd_key = reg_cmds[i].cmd_key;commands.cmds[commands.num].cmd_operate = reg_cmds[i].cmd_operate;commands.num++;} } } //bluetooth.c #include "common.h"void get_bluetooth_state(void *user_data) {uint8_t buffer[3] = {0};buffer[0] = 0x01; //number of parametersbuffer[1] = 0x01; //methodbuffer[2] = 0x01; //bluetooth stateble_send(buffer); }void bluetooth_init(void) {CMD bluetooth_cmds[] = {{BLUETOOTH_CONNECT_STATE, get_bluetooth_state},};/* ... ... bluetooth 硬件初始化 *//* 注冊命令 */register_cmds(bluetooth_cmds, ARRAY_SIZE(bluetooth_cmds)); } //led.c #include "common.h"static void led_on(void) {LED1 = 0; }static void led_off(void) {LED1 = 1; }static void led_toggle(void) // 增加 LED 翻轉函數 {LED1 = ~LED1; }void led_control(void *user_data) {uint8_t* data = (uint8_t*)(user_data);if(data == NULL){return;}if(data[0] == 0){led_on();}else{led_toggle();} }void led_init(void) {/* 填充命令結構體數組 */CMD led_cmds[] = {{LED_CONTROL, led_control},};/* ... ... led 硬件初始化 *//* 注冊命令 */register_cmds(led_cmds, ARRAY_SIZE(led_cmds)); } //motor.c #include "common.h"void set_motor_speed(void *user_data) {uint8_t *data = (uint8_t*)(user_data);if(data == NULL){return;}motor_speed_set(data[0]); }void motor_init(void) {CMD motor_cmds[] = {{MOTOR_SPEED_SET, set_motor_speed},};/* ... ... motor 硬件初始化 *//* 注冊命令 */register_cmds(motor_cmds, ARRAY_SIZE(motor_cmds)); }

一些新的認識

??我也從這個命令解析器中品到了一絲零散代碼組織后執行的方式,再添加上零散代碼功能的說明(菜單),以及多個源文件編譯的方式,可能再添加上單元測試框架,對于我之后的學習有可能成為一個助力,不過現在認識還很模糊,連開始準備也顯得那么困難。

總結

以上是生活随笔為你收集整理的自定义协议的命令解析器的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。