当前位置:C++技术网 > 精选软件 > 云平台开发架构分析系列:24 远程控制的两个服务器间的进程间通信实现

云平台开发架构分析系列:24 远程控制的两个服务器间的进程间通信实现

更新时间:2017-07-15 01:24:36浏览次数:1+次

    在《云平台开发架构分析系列23:远程控制设备的实现基础》中,我们已经了解了整个云平台的控制流程。现在要具体讲述下最后一个关键的通信环节,即两个服务器间的进程间通信。
    为了提高效率,我们在TCP服务器程序开发时,写了一套基础类库,方便后续的编程。而在实现web服务器程序与TCP服务器程序的进程间通信,我们直接将通信的实现代码,封装在动态库中,提供给python调用即可。这样一来,我们只需要用C++代码实现进程间的通信,这样代码两部分的代码统一,以可以避免参数不匹配问题。毕竟不同的语言在处理参数的传递时,如果直接进行参数的处理,会比较麻烦。另外,使用C++来实现进程间通信,两边的代码是一致的,也不需要写两套代码,只需要微调一下就可以了。
    在Windows下用C++写python动态库的方法,见:Windows下使用C++编写Python的扩展模块详细说明
    在Linux下用C++写python动态库的方法,见:Linux下C++编写Python扩展模块和Python调用so动态库

    写好的动态库,放在python的动态库目录下即可。而在python中只需要简单导入动态库,然后直接调用函数即可。
    本文是云平台开发架构分析系列最后一篇文章,也是非常关键的一篇文章。正是由于进程间的通信,才将两条通信链路打通,从而为云平台打开了广袤的想象空间。我们这里也就是将你引导到云平台开发的开始,以后的路需要由你自己继续往下走。在这个基础上,你可以实现各种远程控制,不要说控制家里的空调电冰箱了,你要去控制导弹都可以,哈哈。导弹就是一个设备端即TCP客户端。手机一点,就可以发射导弹,这感觉是不是贼爽。想象空间是无限的,你要怎么做,就是业务处理了。只要提供不同的api即写不同的python脚本(或者你配置其他的服务器使用其他语言实现api也行),支持不同的控制功能。然后在设备端实现对应的命令的响应,再由本文即将讲的进程间通信,将命令由web服务器发到TCP服务器,就完成了整个通路了。
    我现在实现的版本是Linux版本,所以通过一番选择,选中了消息队列。而管道则比较原始,无法满足我的要求。因为我要实现两组控制命令,再加上命令回复,一共有四个命令。为了方便命令的识别,我们使用命令ID。消息队列的模型,正好给我们提供了一个消息ID,这个消息ID正好可以当做我们的命令ID,这样消息队列就可以自动实现命令ID的过滤提取,我们就不用为这个费心了。
    当然,进程间还有很多通信机制,选择合适自己的或者自己喜欢的,就好了。
    这个消息队列作为两个进程的通信介质,需要在服务器启动的时候就存在。而每次发起web端的restful api调用,是执行了一次python脚本,创建了一个临时的进程来处理。这个临时进程在处理完通信之后,就会关闭。而TCP服务器进程创建之后就要一直运行着。所以我们自然而然要在TCP服务器程序启动的时候建立这个消息队列。当TCP服务器程序启动的时候,就会建立单独的线程,而线程运行的时候也就会创建消息队列。此时必然是没有web端的api调用的,所以一般都是TCP服务器这边创建了消息队列。
    linux中创建消息队列的函数是:msgget。在第二个参数中传递IPC_CREAT,如果消息队列不存在就会创建消息队列,如果存在就直接获取。示例代码如下:
int msgid = msgget((key_t)MSG_QUEUE_ID, 0666 | IPC_CREAT);
    返回的值是消息队列的ID,在后续的消息的发送函数和接受函数都会用到。而msgget的第一个参数是作为我们消息队列的键,相当于我们的消息队列的名称。正是这个名称,对于系统中其他进程来说都是可以利用的。其他进程可以通过这个名称来获取我们这个消息队列的ID。这样就实现了消息队列的共享。如果其他进程不知道我们消息队列的名称,那就无法找到这个消息队列了。这个消息队列的键实际上是一个数值,我个人理解为名称。msgget第二个参数是标志位,可以用位或操作写入操作权限和操作方式。具体的可以查看Linux函数文档。
    为了方便解析消息中携带的数据,我们实现定义了一个结构体,然后再定义为linux规定的消息结构体。定义如下:
typedef struct
{
    char dev_id[20];
    int state;//状态
}MsgData;
typedef struct
{

    long mtype;
//命令ID
    MsgData data;//数据
}Msg;
    最终发送到消息队列的消息,就是Msg结构体变量,从消息队列收取到的消息,也会用Msg结构体变量接收。这样一来,我们就非常的方便解析消息数据了。
    从Msg结构体定义我们可以看出,结构体中第一个成员为命令ID,消息队列会自动根据ID过滤消息。第二个就是我们传递的数据。
    我们将结构体变量初始化好之后,也就携带好我们要发送的命令和命令对应的参数。然后就可以发送给消息队列了。调用的函数为:msgsnd。此函数第一个参数就是msgget返回的消息队列ID,第二个是数据的地址,第三个是数据的大小,最后一个是标志,默认为0即可。代码示例如下:
Msg msg_send;
memset(&msg_send, 0, sizeof(Msg));
msg_send.mtype = 5;//发送的控制启动的命令
memcpy(msg_send.data.dev_id,"123456",strlen("123456"));
msg_send.data.state=0;
int ret = msgsnd(msgid, &msg_send, sizeof(Msg), 0);//发送信号
if (ret!=0)return false;//发送消息失败
    这样消息就发送完了。消息发送过去之后,TCP服务器那边会有一个线程一直在等待接收消息,所以消息一发过去之后,就会被取走处理。处理完之后就会发回处理结果消息。我们前面假设的是启动命令,ID为5。我们约定好返回的结果命令ID为6。所以我们就马上去等待接收命令ID为6的消息。接收消息的函数为:msgrcv。我们先创建一个接受数据的结构体变量。示例代码如下:
Msg msg_recv;
int ret = msgrcv(msgid, &msg_recv, sizeof(Msg), 6, 0);//接受启动应答消息
if(ret==-1)return false;//获取消息失败
    然后得到了返回的数据,该怎么解析就怎么解析。这样就完成了命令的发送和结果的获取,也就完成了一次控制。在TCP服务器那一端,只不过是先获取,再发送而已。至于接受到的数据如何解析,以及之后怎么做,就是业务上的功能实现了。
    不过这里还有一个问题,即多个控制端发起了启动,这样消息队列也就有很多命令ID为5的消息。TCP服务器端自然是统一获取命令ID为5的消息即可。然后处理完之后,就将命令ID为6的消息放到消息队列。而在web服务器这一段,程序自动根据ID过滤取出的。而返回的消息ID都是6,这样一来多个控制端发出的控制返回的消息ID都是一样的,如何区分呢?这样就出现了混乱。这样会导致有时候能够控制正常返回,有的时候就不能正常返回。
    为什么发送启动命令时不会出现这个问题呢?因为TCP服务器那边只有一个线程在处理消息,始终都只有一个线程在处理,无需区分。然而web这边的是多个api调用创建的多个进程,所以需要准确的对应返回的消息,如果取错了消息,就出错了。
    为了解决这个问题,我在发送消息的时候,创建一个唯一的值,类似于GUID这样的值,只不过是用时间戳加其他信息形成的整数值。每一个web进程都会产生不同的值。然后在发送消息给TCP服务器的时候,也将这个值一起过去。TCP服务器在回复消息时,就将回复的消息ID设置为这个值。这样web端就用这个值来过滤消息,也就得到了自己的消息。这样就不会得到错误的消息。所以上面代码中的ID6也就是每次生成唯一值了。这个值每次都会变化,但是每一次都是唯一的,是动态的。
    那么web服务器和TCP服务器的进程间通信也就如上述所说。当然,这只是Linux系统下的实现。Windows上的实现,请你自己去实现吧,思路是一样的。