19 Star 92 Fork 45

极简美 / ERPC-doc

Create your Gitee Account
Explore and code with more than 12 million developers,Free private repositories !:)
Sign up
Clone or Download
2.QuickStart.md 18.34 KB
Copy Edit Raw Blame History
极简美 authored 2019-08-06 15:03 . add security example

快速入门

作为程序员,要掌握一个新的工具或者框架,最直接最快捷的方式就是实践。下面列举了几个非常简单的示例带你快速入门ERPC。

注意:在编译运行这里面所有例程之前,请先运行下面命令初始化ERPC环境变量ERPC_PROFILE_PATH:

$ cd examples
$ source environment.sh

目录

一、hello world

作为程序员,学习的第一个程序都是从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目录。

2.1 创建服务程序

首先创建一个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);
}

然后就可以等待其他进程的服务请求调用了。

2.2 创建业务程序

接下来创建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);
}

2.3 编译运行

在编译例程之前,先配置环境变量和库依赖路径。在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,可看到如下结果:

image

三、远程服务调用

通过前面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);
        }
    }
}

如上修改量不大,可以对比前一个例程更加容易理解。

  • 在服务调用侧,只需要创建参数对象(cJSON_CreateObject()),然后传入远程调用接口,对应在服务实现函数中释放该参数(cJSON_Delete());当然调用失败时,也需要使用者自行释放
  • 在服务实现侧,只需要创建返回对象(cJSON_CreateObject()),直接通过return 返回即可,对应服务调用侧返回后进行释放(cJSON_Delete())

这样使得不同模块、不同线程、不同进程的编码实现如同本地的使用过程一样,非常的简单;且内存的分配与释放也是编写者成对使用,非常容易掌控。

编译运行,效果如下:

image

四、服务接口封装

通过前面三个示例的讲解,相信你已经能熟练掌握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.");
    }
}

最后编译运行,输出的效果与前面是一样的:

image

相信,聪明的你已经发现:通过这种封装的方法,在服务端只有一个实体的实现,接口也可以封装成好几个(参数不同、功能不同);也可以使用一个接口实现多个服务实体的连续调用,相当于在接口实现了部分操作流程的业务。

这一点非常的重要:它可以让开发人员更好的区分功能与业务,且具有很好的扩展性;开发人员将核心的功能实现全部放在实体实现侧,而将与业务相关的部分放在接口处实现,这样更具有通用性和可扩展性,并且也更加容易修改和维护

在实际项目应用过程中,我们通常将实体实现成后台服务进程,以二进制可执行程序的形式存在,直接提供使用;同时提供一个*.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);
}

重新编译运行,可以看到如下效果:

  • app每隔3S远程调用一次hello_world_service()服务,服务端收到"hello, I'm helloworld app.",应用端收到"hello, I'm helloworld service.";
  • 服务端在hello_world_service()内通知状态status的变更,参数为"status: hello";
  • 服务端的状态回调被调用,打印:"status params: status, hello";
  • 应用端的状态回调被调用,打印:"status params: status, hello";

如此反复,每隔3秒循环一次:

image

六、使用周期任务

接下来,我们使用框架内部自带的周期任务来触发状态变更。本节对应源代码在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中的获取请求者的方法,还可以定制与某个服务通信数据加密,其他通信不加密;或者与某个服务通信特制加密算法,而其他通信采用通用加密算法等方法。

C
1
https://gitee.com/simpost/ERPC-doc.git
git@gitee.com:simpost/ERPC-doc.git
simpost
ERPC-doc
ERPC-doc
master

Search