erpc(EmbeddedRPC)入門筆記

RPC

最近在忙一個IOT設備的項目,想設計一個通信系統通過串口控制設備(freertos)的運行。按照傳統的設計思路,先要定義一套串口通信協議,在這套協議中傳輸層協議、應用層協議一個都不能少。每一層協議都要自己實現。數據編碼/解碼,數據校驗,容錯,這些非常基礎的東西都要自己實現。
等這些協議都實現了,纔是能開始設計真正的業務邏輯。
和同事商議後,一致認爲要是照這麼幹,黃花菜都涼了。我們的生命不能浪費在這些無意義的勞動上!
我想到了RPC概念是適用於我們的應用場景的。實際我們就是在串口上實現一個客戶端請求->服務端響應的模型。除了傳輸層是串行通信,這與我們一般在tcp/ip網絡上常見的client/server模型沒啥區別,就是1對1簡化版的client/server模型。比如也許google的基於protocol bufffers的grpc就能滿足要求。如果能利用現成的開發框架,可以大大減化開發流程,減少開發時間。

定義下了RPC這個開發方向後我和同事分頭去網上找相關資料,進行開發框架的選型調研。最終同事找到了恩智浦(NXP)的開源項目erpc(EmbeddedRPC)。看到這個項目說明,我感覺它就是爲我們量身定做的。

https://github.com/EmbeddedRPC/erpc

eRPC (Embedded RPC) is an open source Remote Procedure Call (RPC) system for multichip embedded systems and heterogeneous multicore SoCs.
eRPC (embeddedreprocedure-Call)是一個面向多芯片嵌入式系統和異構多核soc的開源RPC框架。

哇哦,與grpc面向通用tcp網絡不同,erpc就是面向嵌入式系統設計的。體積還很小(根據官方介紹可以小到5KB)。完美,下面的事情就是入門學習了。

erpc目前支持的傳輸層如下,除了我不認識的協議外,主要就是串口通信和TCP/IP了,TCP/IP主要用於測試:

Supported transports:
CMSIS UART
NXP Kinetis SPI and DSPI
POSIX and Windows serial port
TCP/IP (mostly for testing)
NXP RPMsg-Lite

也就是說我們可以在不依賴具體設備的情況通過使用TCP/IP傳輸層模擬串口,就可以x86平臺的電腦上實現RPC調用的兩端(client/server)的模擬通信了。採用這方式無疑可以大大提高開發效率–不需要所有的測試都在具備硬件設備上運行,PC模擬可以快捷方便的完成很多事件。

於是我們重新開始在ubuntu 16.04下開始了erpc搭建通信框架的過程

編譯準備

安裝依賴庫

	# install flex & bison
	sudo apt-get install flex bison
	# install boost
	sudo apt-get install libboost-dev libboost-system-dev libboost-filesystem-dev

下載erpc

克隆erpc 項目到本地(erpc文件夾)

git clone https://github.com/EmbeddedRPC/erpc.git

erpcgen : IDL編譯器

erpcgen是IDL編譯器,用於將.erpc後綴的接口定義(IDL)文件生成對應的client/server代碼。
進入erpc/erpcgen子項目編譯erpcgen並安裝到/usr/local/bin下:

cd erpc/erpcgen
make -j8
sudo make install 

NOTE: -j8 爲並行編譯選項,指定使用8個線程同時編譯,以加快編譯速度

eprc : erpc核心庫

erpc/erpc_c下是erpc的核心庫,需要編譯供後續項目使用

cd erpc/erpcgen
make -j8
# 默認安裝到 /usr/local/include/erpc /usr/local/lib
sudo make install 

定義IDL

上面的一切都準備就緒了,接口定義就是我們最關注的業務邏輯了,如下定義一個簡單的IDL,這個接口中只有一個函數:
erpcdemo.erpc

/*! 定義項目名稱,也是所有生成的源碼文件名前綴 */
program erpcdemo

/*! 定義返回狀態枚舉類型 */
enum lockErrors_t
{
  lErrorOk_c = 0,
  lErrorOutofMemory_c,
  // ......定義狀態碼
  // 最大枚舉類型值
  lErrorMaxError_c
}

/*! 定義服務接口函數 */
interface DEMO {
    RD_demoHello(binary txInput) -> binary
}

IDL語法參見erpc官方文檔:

《IDL Reference(https://github.com/EmbeddedRPC/erpc/wiki/IDL-Reference)》

生成代碼

根據接口定義文件erpcdemo.erpc生成對應的client/server代碼

erpcgen erpcdemo.erpc

NOTE: 事前必須先執行erpcgen編譯安裝。

生成的文件列表:

erpcdemo.h
erpcdemo_client.cpp
erpcdemo_server.cpp
erpcdemo_server.h

Client測試程序

基於上述生成的代碼可以很簡單的寫出client端測試程序

/*
 * test_erpcdemo_client.c
 *
 *  Created on: Apr 14, 2020
 *      Author: guyadong
 */


#include <string.h>
#include <iostream>
#include <erpc_client_setup.h>
#include <erpc_port.h>
#include "erpcdemo.h"
#include "erpc_setup_tcp.h"
using namespace std;
// 釋放binary佔用的空間
static void free_binary_t_struct(binary_t * data)
{
    if (data->data)
    {
        erpc_free(data->data);
    }
}
int main(int argc, char *argv[])
{
	/* 創建client端傳輸層對象(TCP),127.0.0.1:5407 */
	auto transport = erpc_transport_tcp_init("127.0.0.1",5407, false);
	auto message_buffer_factory = erpc_mbf_dynamic_init();
	/* 初始化客戶端 */
	erpc_client_init(transport,message_buffer_factory);
	auto msg = "hello!!!!!";
	binary_t b = {(uint8_t*)msg,(uint32_t)strlen(msg)};
	{
		/* RPC 調用 */
		auto resp = RD_demoHello(&b);
		/* 輸出返回值 */
		cout << "RD_demoHello response:" << resp->data << endl;
		/* 對於返回指針類型的數據,用完後需要釋放RD_demoHello中分配的內存 */
		free_binary_t_struct(resp);
	}
	/* 關閉socket */
	erpc_transport_tcp_deinit();
}

Server端測試程序

基於上述生成的代碼可以很簡單的寫出server端測試程序,與client測試程序不同的是,server端要提供接口函數的具體實現

/*
 * test_erpcdemo_server.cpp
 *
 *  Created on: Apr 15, 2020
 *      Author: guyadong
 */

#include <iostream>
#include <time.h>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <string.h>
#include <erpc_server_setup.h>
#include "erpcdemo_server.h"
#include "erpc_setup_tcp.h"
using namespace std;
/** 返回當前時間字符串 */
static std::string now_str() {
	time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
	std::stringstream ss;
	ss << std::put_time(std::localtime(&t), "%F %T");
	return ss.str();
}
/** servicer端實現接口方法 */
binary_t * RD_demoHello(const binary_t * txInput){
	cout << "RD_demoHello called" << endl;
	string o ((char*)txInput->data);
	o.append("@").append(now_str());
	auto ol = strlen(o.c_str());
	char* buf = (char*)malloc(ol + 1);
	strncpy(buf,o.c_str(),ol);
	return new binary_t{(uint8_t*)buf,(uint32_t)ol};
}
int main(int argc, char *argv[]){
	// 創建client端傳輸層對象(TCP),127.0.0.1:5407
	auto transport = erpc_transport_tcp_init("127.0.0.1",5407, true);
	/* MessageBufferFactory initialization */
	erpc_mbf_t message_buffer_factory = erpc_mbf_dynamic_init();
	/* eRPC 服務端初始化 */
	erpc_server_init(transport, message_buffer_factory);
	/** 將生成的接口服務DEMO添加到server, 參見生成的源文件 erpcdemo_server.h */
	erpc_add_service_to_server(create_DEMO_service());
	cout << "start erpcdemo server" << endl;
	/* 啓動服務器 */
	erpc_server_run(); /* or erpc_server_poll(); */
	/* 關閉socket */
	erpc_transport_tcp_deinit();
	return 0;
}

分別編譯 test_erpcdemo_server.cpp,test_erpcdemo_client.cpp,我們就有了在linux下運行的一個最簡單的基於erpc的RPC演示系統。
在這裏插入圖片描述
NOTE:
如果你會用cygwin,就不必要在ubuntu下執行,可以在windows平臺 cygwin terminal下執行上述所有過程。上面的截圖就是windows下的cygwin 終端執行的效果

總結

在上面的過程中,涉及數據傳輸,序列化,反序列,校驗等等底層的細節都由erpc完成了。我們只是關注於定義業務接口本身,確實方便了好多啊。

完整代碼

本文所涉及的所有源碼的完整代碼及詳細說明參見碼雲倉庫:

https://gitee.com/l0km/erpcdemo.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章