文档中心

TD开发平台_基础

05.进程间通信

操作系统中的每个进程都在自己的虚拟内存空间运行,不能访问其他进程的内存数据。在实际使用中,多个进程之间往往需要互相协作和交换数据。操作系统已经提供了一些用于进程间通信的方法,如管道,共享内存和 socket,但是直接利用这些方法较为不便,并且也不符合常用的模式。进程间通讯的常用模式有以下两种:
一种是远程方法调用,也叫远程函数调用,是由客户端进程对服务器进程发出一个执行某个方法的请求,客户端提供方法调用的参数,服务器执行,并将执行结果返回给客户端。远程方法常用于服务器对外提供服务,由客户端主动发起调用。
一种是订阅 /发布方式的消息通知。订阅 /发布提供了一个简单直观的方式来发送数据。它将数据的发布者与数据的接收者分离开来。这里的数据可以理解为消息(包括消息的主题和消息的内容)。数据订阅者只需要声明他们对什么主题的消息感兴趣,当发布者发布某个主题的消息时,数据会自动的传递到一个或多个订阅者。发布者不需要知道有哪些订阅者。订阅 /发布模型简单、灵活,使得分布式应用程序的设计简单和模块化。
TD开发平台提供的进程间通信机制同时实现了远程方法调用和订阅 /发布方式的消息通知,即实现了一个专门用于进程间通讯的代理对象。

1)远程方法调用
typedef Tint (*TMessageRemoteMethod_Func)(void *msg_obj, T_ID method, TTable *in_out, void *arg);
void *TMessageCreate(char *name, TMessageRemoteMethod_Func func, void *arg);
首先调用 TMessageCreate()创建一个用于进程间通讯的对象(该函数内部其实还是调用 TObjectCreate),参数 name表示这个通讯对象的名字,如果系统中已经有相同名字的通讯对象存在,那么这次创建将失败,其他通讯对象必须知道该通讯对象的名字,才能相互通讯。参数 name也可以传 NULL,表示要创建一个匿名的对象,匿名对象不能对外提供服务,因为没有固定的名字,别人不能主动通讯。匿名对象可以调用他人的远程方法。
参数 func是该匿名通讯对象提供的一个函数,当这个通讯对象收到他人请求执行该匿名通讯对象提供的远程方法时,就会自动调用这个函数(函数原型为 TMessageRemoteMethod_Func,参数 msg_obj 为通讯对象,参数 method标识一个方法名,参数 in_out封装了他人提供的调用参数,参数 arg就等于 TMessageCreate的参数 arg)。在这个函数中,该通讯对象应该根据 method来分支执行不同的方法,从 in_out中获取开发人员的输入参数,然后把输出参数也填入到 in_out这个表中。最后要注意该函数有一个整数的返回值,表示是否执行成功。
客户端通过 TMessageRemoteMethod()函数来请求执行远程方法:
typedef void (*TMessageRemoteMethodReturn_Func)(void *msg_obj,T_ID method, TTable *out, void *arg, Tint ret, char *remote);
Tint TMessageRemoteMethod(void *msg_obj, char *remote, T_ID method, TTable *in,
TMessageRemoteMethodReturn_Func func, void *arg);
typedef void (*TMessageRemoteMethodReturn_Func)(void *msg_obj,
参数 msg_obj是客户端自己的通讯对象,参数 remote是远程服务端的名字,参数 method标识一个方法,参数 in封装输入参数,参数 func是开发人员提供的结果处理函数,当服务器执行完请求的远程方法并有结果返回时,通讯对象会自动调用这个函数。函数原型为 TMessageRemoteMethodReturn_Func,其中参数 msg_obj是自己的通讯对象,method标识指定的方法,out封装了返回的结果,参数 ret就是服务器执行函数的整数返回值。参数 remote就是服务器的名字。
该函数并没有阻塞到结果返回,只是发出请求就返回。当远程服务器不存在时,该函数内部会缓存这次方法调用,会在服务器起来后再自动发送。所以当成功发出请求或者服务器不存在时,TMessageRemoteMethod都返回成功,否则返回失败原因。如果上次远程方法还没有返回,那么不允许再次调用同一个远程对象的同一个远程方法。
注意参数 func可以传 NULL,表示不需要返回的结果,那么当服务器不存在时不会缓存,会返回发送失败的原因(对方不存在)。
远程方法的返回是异步的,有时可能永远都不返回,有时开发人员想阻塞直到方法返回,有时又想立即取消没有返回的方法。这时就要用到下面的函数:
Tbool TMessageWaitRemoteReturn(void *msg_obj, char *remote, T_ID
method, TTimeout timeout, Tbool ignore);
TMessageWaitRemoteReturn()会最长阻塞指定的时间,并决定是否继续阻塞还是取消这次调用,返回在阻塞的过程中是否收到结果(没有收到结果时返回 FALSE,否则返回 TRUE)。参数 msg_obj是自己的通讯对象,参数 remote和 method分别指明远程对象和远程方法,参数 timeout表示最长阻塞的时间(以毫秒为单位),参数 ignore表示 阻塞时间到时是否取消这次调用。
当 timeout为-1时,表示永远等待;当 timeout为 0时,表示立即不等待(这种情况常用于查询是否收到结果或立即取消调用)。
一次远程调用的过程如下(点划线箭头表示可能不会发生):

1、进程 1调用 TMessageRegisterRemoteEvent订阅进程 2的消息;

2、进程 1的通讯对象通过操作系统提供的进程间通讯方法,将订阅情况发给进程 2的通讯对象;

3、如果进程 2不存在,那么在进程 2起来时,进程 1会自动再次发送刚才的订阅情况;

4、进程 2调用 TMessageTriggerRemoteEvent发布消息; 

5、消息被传递给订阅的通讯对象,可能有多个订阅者; 

6、进程 1的通讯对象回调相应的消息处理函数。

例如:有一个服务进程提供两项服务: 1、查询当前声音输出的音量; 2、设置声音输出的音量。约定服务进程通讯对象的名字叫“ volume_manager”,方法 1的名字叫“ get_volume”,方法 2的名字叫“ set_volume”,那么服务进程的代码如下:

#include <stdio.h>
#include <TCore/TCore.h>
static Tint volume;
Tint remote_method_func(void *obj, T_ID method, TTable *in_out, void*arg)
{
    if(method == T_STRING_ID(get_volume))
    {
        printf(“someone get volume %d\n”, volume);
        TTableAddInt(in_out, T_STRING_ID(volume), volume);
        return T_SUCCESS;
    }
    if(method == T_STRING_ID(set_volume))
    {
        Tint new_volume;
        new_volume = TTableGetInt(in_out, T_STRING_ID(volume));
        printf(“someone set volume %d\n”, new_volume);
        if(new_volume != 0)
        {
            /*设置新的音量*/
            volume = new_volume;
        }
        return T_SUCCESS;
    }
    return T_NO_IMPLEMENT;
}
int main(int argc, char *argv[])
{
    void *msg;
    /*初始化声音设备,并获取音量的初始值赋给volume*/
    msg = TMessageCreate(“volume_manager”, remote_method_func,NULL);
    if(msg == NULL)
    {
        printf("already have same name: volume_manager\n");
        return 1;
    }
    while(1) TTaskLoopOnce(0);
    return 0;
}

客户端获取音量的代码如下:

#include <stdio.h>
#include <TCore/TCore.h>
void get_volume_return_func(void *obj, T_ID method, TTable *out, void*arg, Tint ret, TExist *exist)
{
    Tint volume = TTableGetInt(in_out, T_STRING_ID(volume));
    printf("volume = %d\n", volume);
    exit(0);
}
int main(int argc, char *argv[])
{
    void *msg;
    msg = TMessageCreate(NULL, NULL, NULL); /*创建匿名通讯对象*/
    if(msg == NULL)
    {
        printf("TMessageCreate failed\n");
        return 1;
    }
    TMessageRemoteMethod(msg,“volume_manager”,TStringID(get_volume),NULL,
    get_volume_return_func, NULL); /*可以没有输入参数*/
    while(1) TTaskLoopOnce(0);
    return 0;
}

客户端请求设置音量的代码如下:

TTable * t = TTableCreate();
TTableAddInt(t, T_STRING_ID(volume), 100);
TMessageRemoteMethod(msg, “volume_manager”,
TStringID(set_volume), t, NULL, NULL);/*不需要返回结果*/
TTableDestroy(t);

TD图形软件系统平台提供了一个命令行程序,来方便执行一个远程对象的方法:

./sendmsg [-v] [-t timeout(s)] --remote name --method name 'id="string";id=int'

其中 -t指定一个超时时间(以秒为单位),最长等待超时时间获取执行结果,不设置时默认为 0,表示不需要执行结果,即使远程对象不存在也立即退出。

比如上面设置音量的例子就可以直接用 sendmsg来实现: 

./sendmsg --remote volume_manager --method set_volume ‘volume=100’
2)订阅/发布方式的消息通知

首先是订阅消息:

typedef void (*TObjectEvent_Func)(void *msg_obj, T_ID event, TTable*in, void *arg, TExist *exist);
Tint TMessageRegisterRemoteEvent(void *msg_obj, char *remote, T_ID event,
TObjectEvent_Func func, void *arg);

参数 msg_obj是你的通讯对象,remote是你订阅谁的消息,event表示订阅的消息主题,func是你提供的消息处理函数,当 remote发布了你订阅的消息之后,你的通讯对象就会自动调用该函数(函数原型是 TObjectEvent_Func,其中参数 msg_obj就是你的通讯对象,event标识消息主题,in是该消息的内容,arg就等于 TMessageRegisterRemoteEvent的参数 arg)。

注意:如果 remote这时不存在,那么这次订阅的动作会被缓冲,在 remote起来时自动会再次订阅。

然后是发布消息: 

void TMessageTriggerRemoteEvent(void *msg_obj, T_ID event, TTable*in);

msg_obj 是发布消息的通讯对象,event 是消息的主题,in 是封装消息的内容。

注意:发布消息不会被缓冲。

订阅/发布方式的消息通知过程如下(点划线箭头表示可能不会发生):

1、进程 1调用 TMessageRegisterRemoteEvent订阅进程 2的消息; 

2、进程 1的通讯对象通过操作系统提供的进程间通讯方法,将订阅情况发给进程 2的通讯对象;

3、如果进程 2不存在,那么在进程 2起来时,进程 1会自动再次发送刚才的订阅情况;

4、进程 2调用 TMessageTriggerRemoteEvent发布消息;

5、消息被传递给订阅的通讯对象,可能有多个订阅者;

6、进程 1的通讯对象回调相应的消息处理函数。

消息通讯可以和远程方法组合一起用,如之前例子中的服务器可以在音量发生改变时发布一个音量改变的消息,这样所有的客户端就可以及时更新音量值。