我們已經可以基于vsomeip實現SOME/IP應用,并且服務端和客戶端之間進行消息的通信,消息的內容稱為Payload。
但是設想一下,如果當我們需要傳遞的消息內容是一個比較復雜的數據結構,比如一個結構體,一兩個倒也沒事,多了以后,Payload的打包、解析和聯調都會是件麻煩的事。
這時,我們會想到序列化,比如用Google Protocol Buffer之類的,是不是可以解決問題呢?
對于非AUTOSAR設備之間的通信,是可以解決的,但對于與AUTOSAR設備之間的通信,恐怕就行不通了,因為Payload是需要遵循AUTOSAR規范的,如圖:
于是,我們又會想到,如果有人能把序列化這一步也幫我們做好那就更好了,這就是RPC(Remote Procedure Call,遠程過程調用)可以做到的事了。
GENIVI的CommonAPI C++是基于vsomeip實現的RPC框架,今天就讓我們一起看一下它是怎么用的吧~
搭建CommonAPI的開發環境,有點費勁的,除了依賴于boost和vsomeip,還有CommonAPI和CommonAPI-SomeIP,以及C++代碼生成工具,這里就不一一說明了,我已經整理好,放在Github上,關注公眾號,回復“演示代碼”,可以獲得項目鏈接。
環境OK了以后,我們可以就創建第一個HelloWorld工程了,按照如圖所示CommonAPI的工作流程:
其中,FrancaIDL是一種接口描述語言,和編程語言無關。fidl文件是用IDL寫的,它描述了服務提供的接口信息,包括類型(比如method、broadcast、attribute等)、參數、返回值。
創建HelloWorld.fidl文件:
package commonapi
interface HelloWorld {
version {major 1 minor 0}
method sayHello {
in {
String name
}
out {
String message
}
}
}
fdepl文件描述了服務的部署信息,包括Service ID、Instance ID、Method ID、Event ID等。
創建HelloWorld.fdepl文件:
import "platform:/plugin/org.genivi.commonapi.someip/deployment/CommonAPI-SOMEIP_deployment_spec.fdepl"
import "HelloWorld.fidl"
define org.genivi.commonapi.someip.deployment for interface commonapi.HelloWorld {
SomeIpServiceID = 4660
method sayHello {
SomeIpMethodID = 123
}
}
define org.genivi.commonapi.someip.deployment for provider as MyService {
instance commonapi.HelloWorld {
InstanceId = "test"
SomeIpInstanceID = 22136
}
}
CommonAPI代碼生成工具幾乎支持Franca的全部功能。
用準備好的工具生成代碼:
commonapi-core-generator-linux-x86_64 -sk ./fidl/HelloWorld.fidl
commonapi-someip-generator-linux-x86_64 ./fidl/HelloWorld.fdepl
在src-gen/v1/commonapi目錄里,可以看到如下這些生成的代碼文件:
萬事俱備,可以開發應用程序咯~
對于服務端,主程序代碼如下:
std::shared_ptr
其中,HelloWorldStubImpl是繼承于工具生成的HelloWorldStubDefault:
class HelloWorldStubImpl: public v1_0::commonapi::HelloWorldStubDefault {
public:
HelloWorldStubImpl();
virtual ~HelloWorldStubImpl();
virtual void sayHello(const std::shared_ptr;
};
HelloWorldStubImpl實現了sayHello接口,正如fidl定義的,當客戶端發送name,回復“Hello name !”:
void HelloWorldStubImpl::sayHello(const std::shared_ptr
{
std::stringstream messageStream;
messageStream << "Hello " << _name << "!";
std::cout << "sayHello('" << _name << "'): '" << messageStream.str() << "'\\n";
_reply(messageStream.str());
};
對于客戶端,主程序如下:
std::shared_ptr < CommonAPI::Runtime > runtime = CommonAPI::Runtime::get();
std::shared_ptr
客戶端不需要實現接口,直接使用工具生成的HelloWorldProxy就可以了。
編譯運行的結果如下:
現在再看一下應該選擇CommonAPI還是vsomeip呢?用vsomeip的話,依賴的東西少,Payload的打包和解析要自己寫,工作量大,自由發揮的空間也大,用CommonAPI的話,依賴的東西多,環境搭建相對復雜,接口可以用IDL描述,這在SOA中非常有用,很多代碼由工具生成,基本通信幾乎不需要聯調,主要的開發工作是實現服務的接口,相當于填充業務邏輯,工作量少,同時可以發揮的空間也小。很多事都是這樣吧,獲得便利的同時也會損失一些自由,如何選擇還是要具體分析。
通過這個示例,我們看到使用RPC通信和上一篇中基于消息的通信是截然不同的編程體驗,RPC讓客戶端可以像調用本地函數一樣調用服務端的函數,很顯然它們并不在同一個進程中,這是如何做到的呢?
下面,我們結合一張經典的RPC原理框圖來看一下客戶端的sayHello到底是怎么調到服務端的sayHello的:
1.client調用本地接口sayHello,HelloWorldProxy就是client的stub(樁),負責將sayHello的參數進行打包,組裝成一個或者多個網絡請求(這些取決于通信協議和序列化方式);
2.client的stub通過socket向server的stub,也叫skeleton(骨架),發送請求;
3.skeleton通過socket接收到請求;
4.請求消息被發送到skeleton,在這里就是HelloWorldStubDefault,負責將收到的請求拆包,取得client發送的參數;
5.HelloWorldStubDefault把參數發給了HelloWorldStubImpl的sayHello。
6.server在HelloWorldStubImpl的sayHello里處理了請求,通過sayHelloReply_t將返回值發給了HelloWorldStubDefault,它負責把返回值進行打包,組裝成一個或者多個網絡響應;
7.server的skeleton通過socket向client的stub發出響應;
8.stub通過socket接收到響應消息;
9.響應消息被發送到client的stub,也就是HelloWorldProxy,它負責將響應消息進行解析,取得server發送的參數;
10.client通過HelloWorldProxy的sayHello,得到了returnMessage。
至此,client完成了一次RPC調用~
可以看出,在RPC框架中,樁的實現原理是非常關鍵的,它屏蔽了網絡通信的實現,讓客戶端可以像調用本地接口一樣調用服務端提供的接口,而不用關心用的什么通信協議、序列化方式,以及所有的通信細節。
CommonAPI的樁是由代碼生成器根據IDL生成的,而在有的RPC框架里,還可以用動態代理的方式得到。
審核編輯:劉清
-
AUTOSAR
+關注
關注
10文章
362瀏覽量
21583 -
RPC
+關注
關注
0文章
111瀏覽量
11536 -
SOA
+關注
關注
1文章
288瀏覽量
27475
發布評論請先 登錄
相關推薦
評論