作为程序员,要掌握一个新的工具或者框架,最直接最快捷的方式就是实践。下面列举了几个非常简单的示例带你快速入门ERPC。
注意:在编译运行这里面所有例程之前,请先运行下面命令初始化ERPC环境变量ERPC_PROFILE_PATH:
$ cd examples
$ source environment.sh
作为程序员,学习的第一个程序都是从hello world开始的。源码在examples/helloworld目录。
创建helloworld.c源代码,包含erpc.h头文件,然后创建一个打印hello world的服务函数:
#include "erpc.h"
cJSON *hello_world_service(cJSON *params)
{
printf("Hello world!\n");
return NULL;
}
接着创建一个业务线程,每隔3S调用发起一次远程调用打印hello world服务:
void *business(void *arg)
{
sleep(1);
while(1)
{
erpc_service_proxy_call("hello", "helloworld", NULL, NULL, NULL);
sleep(3);
}
}
然后在主程序(main)中,注册服务,创建业务线程,初始化并运行ERPC框架:
pthread_t business_id;
int main(void)
{
erpc_framework_init("service");
erpc_service_register("hello", "helloworld", hello_world_service);
if(0 != pthread_create(&business_id, NULL, business, NULL))
return -1;
return erpc_framework_loop(ERPC_LOOP_DEFAULT);
}
下面贴出完整代码,非常的简单:
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include "erpc.h"
cJSON *hello_world_service(cJSON *params)
{
printf("Hello world!\n");
return NULL;
}
void *business(void *arg)
{
sleep(1);
while(1)
{
erpc_service_proxy_call("hello", "helloworld", NULL, NULL, NULL);
sleep(3);
}
}
pthread_t business_id;
int main(void)
{
erpc_framework_init("service");
erpc_service_register("hello", "helloworld", hello_world_service);
if(0 != pthread_create(&business_id, NULL, business, NULL))
return -1;
return erpc_framework_loop(ERPC_LOOP_DEFAULT);
}
在examples目录下执行如下命令即可:
mkdir build
cd build
cmake ..
make
然后运行helloworld服务,输出如下,每个3S打印一次hello world!:
$ ./helloworld/helloworld
2019-08-06 09:10:11.102ms INFO service[11844](rpc_socket.c:rpc_socket_create():420) - RPCSocket : Use epoll method for event-loop.
2019-08-06 09:10:11.102ms INFO service[11844](rpc_socket.c:rpc_socket_communicate_eventloop():354) - RPCSocket : communicate event-loop thread start, id = 140015852267264
2019-08-06 09:10:11.102ms INFO service[11844](rpc_socket.c:rpc_socket_accept_eventloop():345) - RPCSocket : accept event-loop thread start, id = 140015860659968
2019-08-06 09:10:11.102ms INFO service[11844](rpc_communicate.c:socket_sender_thread():71) - RPC : send thread running, id:140015843874560
2019-08-06 09:10:12.102ms INFO service[11844](rpc_socket.c:rpc_socket_connect():492) - RPCSocket : Start connect to /tmp/s_media_service ...
2019-08-06 09:10:12.102ms INFO service[11844](rpc_socket.c:rpc_socket_connect():525) - RPCSocket : Connect to /tmp/s_media_service OK.
2019-08-06 09:10:12.102ms INFO service[11844](rpc_socket.c:rpc_socket_listener_handler():273) - RPCSocket : New connecting from 87.127.0.0:244 on 21
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
在前面的示例中,服务提供者和服务调用者都在同一个进程中,但我们现实的实际情况往往并非如此。
有时我们的服务部署在其它进程中,而服务调用者则在另外的进程中,甚至我们都不知道他们分别部署的进程叫啥。下面就基于helloworld示例,将服务提供者和服务调用者拆分成两个进程,看看跨进程的服务如何实现,源码在examples/service目录。
首先创建一个service.c源文件,包含erpc.h头文件,然后创建一个打印hello world服务:
#include "erpc.h"
cJSON *hello_world_service(cJSON *params)
{
printf("Hello world!\n");
return NULL;
}
然后编写主程序,初始化ERPC框架后,向系统注册该服务,最后运行主框架(主循环):
int main(void)
{
erpc_framework_init("service");
erpc_service_register("hello", "helloworld", hello_world_service);
return erpc_framework_loop(ERPC_LOOP_DEFAULT);
}
然后就可以等待其他进程的服务请求调用了。
接下来创建app.c源文件,包含erpc.h头文件,创建一个业务线程,每个3S调用一次hello_world_service()服务:
#include "erpc.h"
void *business(void *arg)
{
sleep(1);
while(1)
{
erpc_service_proxy_call("hello", "helloworld", NULL, NULL, NULL);
sleep(3);
}
}
在主程序中,调用ERPC的初始化,调用pthread_create()创建上面的业务程序后,调用ERPC的主循环:
int main(void)
{
erpc_framework_init("app");
if(0 != pthread_create(&business_id, NULL, business, NULL))
return -1;
return erpc_framework_loop(ERPC_LOOP_DEFAULT);
}
在编译例程之前,先配置环境变量和库依赖路径。在examples目录下执行如下命令即可:
$ source environment.sh
然后进入examples目录,创建build目录,进入build目录后,执行cmake和make即可。
$ mkdir build
$ cd build/
$ cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
m-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
a-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
k-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /work/study/Module/RPC/examples/build
$ make
Scanning dependencies of target service
[ 25%] Building C object helloworld/CMakeFiles/service.dir/service.c.o
[ 50%] Linking C executable service
[ 50%] Built target service
Scanning dependencies of target app
[ 75%] Building C object helloworld/CMakeFiles/app.dir/app.c.o
[100%] Linking C executable app
[100%] Built target app
如上分别生成了两个可执行程序,分别打开两个终端:一个运行service,另外一个运行app,可看到如下结果:
通过前面helloworld例程,相信你对ERPC的使用有了一个整体的了解,是不是非常的简单?
下面我们基于helloworld例程进行扩展:看看参数如何传递,以及返回值如何返回的。本节对应源代码在examples/service_params目录下。
这里需要用到cJSON的相关用法,不了解的可异步到该cJSON文档查看。
首先我们修改service.c中的hello_world_service()服务,打印params参数,并返回一个cJSON对象数据:
cJSON *hello_world_service(cJSON *params)
{
char *message = NULL;
cJSON *result = NULL;
/* deal with params : print params */
message = cJSON_Print(params);
printf("params: %s\n", message);
free(message);
cJSON_Delete(params);
/* response remote */
result = cJSON_CreateObject();
cJSON_AddStringToObject(result, "hello", "I'm helloworld service.");
return result;
}
接着,我们修改app.c的业务线程,增加远程调用的参数传递和参数返回:
void *business(void *arg)
{
char *message = NULL;
cJSON *request = NULL;
cJSON *response = NULL;
while(1)
{
sleep(3);
request = cJSON_CreateObject();
if(NULL == request)
continue;
cJSON_AddStringToObject(request, "hello", "I'm helloworld app.");
if(0 != erpc_service_proxy_call("hello", "helloworld", request, &response, NULL))
{
cJSON_Delete(request);
request = NULL;
continue;
}
if(response)
{
message = cJSON_Print(response);
printf("result: %s\n", message);
free(message);
cJSON_Delete(response);
}
}
}
如上修改量不大,可以对比前一个例程更加容易理解。
这样使得不同模块、不同线程、不同进程的编码实现如同本地的使用过程一样,非常的简单;且内存的分配与释放也是编写者成对使用,非常容易掌控。
编译运行,效果如下:
通过前面三个示例的讲解,相信你已经能熟练掌握ERPC框架和远程调用接口的使用了,再深入学习cJSON的使用方法后,便可将ERPC应用在现有的各种电子产品研发了。
不过先不要急,本节的内容页非常的重要:核心讲解如何对服务的接口进行封装。其实也很简单,就是一种面向对象的设计方法:整个开发过程分为实体实现和接口实现。
那么,这里所指的封装,主要是指接口的封装,最终达到服务使用者所见即所得的效果。本节对应源代码在examples/service_api目录下。
实体实现比较好理解,平常我们使用C语言编写模块、线程等都是在进行服务实体的编码实现。对应上面的示例,实体实现的代码如下(没啥变化):
#include "erpc.h"
cJSON *hello_world_service(cJSON *params)
{
char *message = NULL;
cJSON *result = NULL;
/* deal with params : print params */
message = cJSON_Print(params);
printf("params: %s\n", message);
free(message);
cJSON_Delete(params);
/* response remote */
result = cJSON_CreateObject();
cJSON_AddStringToObject(result, "hello", "I'm helloworld service.");
return result;
}
而对于接口实现,我们需要根据使用者的角度出发,思考使用方法。比如:假如服务使用者仅仅只是传一个参数,对于返回数据不关心,那么我们可以将远程调用过程做如下封装:
void hello_helloworld(char *params)
{
char *message = NULL;
cJSON *response = NULL;
cJSON *request = cJSON_CreateObject();
if(NULL == request)
return ;
cJSON_AddStringToObject(request, "hello", params);
if(0 != erpc_service_proxy_call("hello", "helloworld", request, &response, NULL))
{
cJSON_Delete(request);
request = NULL;
return ;
}
if(response)
{
message = cJSON_Print(response);
printf("result: %s\n", message);
free(message);
cJSON_Delete(response);
}
}
那么,对业务线程business()来说,业务也就简化成了一条语句:
void *business(void *arg)
{
while(1)
{
sleep(3);
hello_helloworld("I'm helloworld app.");
}
}
最后编译运行,输出的效果与前面是一样的:
相信,聪明的你已经发现:通过这种封装的方法,在服务端只有一个实体的实现,接口也可以封装成好几个(参数不同、功能不同);也可以使用一个接口实现多个服务实体的连续调用,相当于在接口实现了部分操作流程的业务。
这一点非常的重要:它可以让开发人员更好的区分功能与业务,且具有很好的扩展性;开发人员将核心的功能实现全部放在实体实现侧,而将与业务相关的部分放在接口处实现,这样更具有通用性和可扩展性,并且也更加容易修改和维护。
在实际项目应用过程中,我们通常将实体实现成后台服务进程,以二进制可执行程序的形式存在,直接提供使用;同时提供一个*.so的接口库给应用开发者。这种开发方法,降低了应用开发者和功能开发者之间的沟通成本,而将双方聚焦到接口的通用性、易用性和可扩展性上面,这样就更加容易满足产品业务逻辑的要求,做出产品经理想要的产品。
基于前面的示例,我们继续前行。接下来,我们为hello模块添加一个观察者对象status。本节对应源代码在examples/observer目录下。
首先修改service.c主程序,创建一个status的观察者对象(erpc_observed_create()语句):
int main(void)
{
erpc_framework_init("service");
erpc_observed_create("hello", "status");
erpc_service_register("hello", "helloworld", hello_world_service);
return erpc_framework_loop(ERPC_LOOP_DEFAULT);
}
接着,在我们的hello_world_service()中添加状态通知接口实现(erpc_observer_invoke()语句):
cJSON *hello_world_service(cJSON *params)
{
char *message = NULL;
cJSON *result = NULL;
cJSON *status = NULL;
/* deal with params : print params */
message = cJSON_Print(params);
printf("params: %s\n", message);
free(message);
cJSON_Delete(params);
/* invoke status */
status = cJSON_CreateObject();
cJSON_AddStringToObject(status, "status", "hello");
erpc_observer_invoke("hello", "status", status);
/* response remote */
result = cJSON_CreateObject();
cJSON_AddStringToObject(result, "hello", "I'm helloworld service.");
return result;
}
接下来,我们在服务端添加观察者:
void hello_status_handler(cJSON *params)
{
char *message = NULL;
message = cJSON_Print(params);
printf("status params: %s\n", message);
free(message);
cJSON_Delete(params);
}
int main(void)
{
......
erpc_observer_register("hello", "status", hello_status_handler, NULL);
......
}
最后,我们在app.c应用端也添加观察者:
void hello_status_handler(cJSON *params)
{
char *message = NULL;
message = cJSON_Print(params);
printf("status params: %s\n", message);
free(message);
cJSON_Delete(params);
}
pthread_t business_id;
int main(void)
{
erpc_framework_init("app");
erpc_observer_register("hello", "status", hello_status_handler, NULL);
if(0 != pthread_create(&business_id, NULL, business, NULL))
return -1;
return erpc_framework_loop(ERPC_LOOP_DEFAULT);
}
重新编译运行,可以看到如下效果:
如此反复,每隔3秒循环一次:
接下来,我们使用框架内部自带的周期任务来触发状态变更。本节对应源代码在examples/period目录下。
首先将service的消息通知部分代码挪出来放在一个定时任务中实现:
void hello_period_task(void)
{
cJSON *status = NULL;
/* invoke status */
status = cJSON_CreateObject();
cJSON_AddStringToObject(status, "status", "hello");
erpc_observer_invoke("hello", "status", status);
}
然后在主程序中,设置周期时间和周期任务:
struct timeval period = {5, 0};
erpc_timer_period_set(period);
erpc_timer_handler_set(hello_period_task);
这样,每隔5S状态通知就会自动执行一次,service和app端都会接收到该通知。
最后,我们在实际的应用中,为了数据安全性考虑,需要对数据进行加密和解密。本节对应源代码在examples/security目录下。
下面的示例中,我们的加密方法是给每一个数据加上一个数字,解密的方法则刚好相反。算法代码如下:
int hello_encrypt_t(char *data, size_t len, int sockfd, rpc_message_handler_t handler)
{
size_t i = 0;
for(; i < len; i++)
data[i] += 3;
handler(sockfd, data, len);
return 0;
}
int hello_decrypt_t(char *data, size_t len, int sockfd, rpc_message_handler_t handler)
{
size_t i = 0;
for(; i < len; i++)
data[i] -= 3;
handler(sockfd, data, len);
return 0;
}
接着分别在service.c和app.c的主程序中,添加设置加密算法的代码:
erpc_information_security(hello_encrypt_t, hello_decrypt_t);
对于应用者而言,过程与前面是一样的,但是进程间通信的数据则蒙上了一层“蒙砂”。
结合rpc_util.h中的获取请求者的方法,还可以定制与某个服务通信数据加密,其他通信不加密;或者与某个服务通信特制加密算法,而其他通信采用通用加密算法等方法。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。