Linux进程通信之消息队列的双向通信

来源:互联网 发布:算法工程师有哪些证书 编辑:程序博客网 时间:2024/06/05 05:02

  上一篇博客我写了进程间通信基于管道的通信,但是管道的通信无疑有很大的缺点,最显著的就是只能单向通信,例如:server向client发消息,client无法回复;第二个就是只能在有血缘关系的进程间进行通信,虽然命名管道解决了第二点,但是第一点还是一个很大的问题。开个玩笑:微信如果只能单向通信的话,你还会用吗?我估计发个消息能难受死你。
  所以,这就是接下来几种进程间通信的产生条件。消息队列、信号量、共享内存都是基于system v 标准的进程间通信,均可产生双向通信的效果,并且并不局限于进程的关系,即:两个毫无关系的进程,只要均可以访问一块内核上的公共资源,就可以进行这三种基于system v的进程间通信。它是由操作系统IPC专门设定的一个接口,是一个进程向另一个进程发送数据块的方法,看过我上一篇博客的,我应该说过:管道是基于数据流的通信,所以这也是管道和基于system v的三种通信方式之间的差别。
  消息队列也相当于一个资源,每个都有自己的编号,下面这个函数就是获得msgid的函数:
  

        #include <sys/types.h>        #include <sys/ipc.h>        #include <sys/msg.h>        int msgget(key_t key, int msgflg);

  其中msgflg表示生成消息队列的方式和权限,一般使用的话,有三个常用参数,IPC_CREAT、IPC_EXCL、umask,IPC_CREAT单独使用时代表如果没有此消息队列,那就生成一个,如果当前的消息队列存在,那就打开它;IPC_EXCL单独使用没有任何意义,至少目前没有;二者同时使用表示如果没有此消息队列,则生成一个,如果有,那就出错并返回。而umask表示创建的当前的消息队列的权限,采用8进制表示。
  解释一下,key值标识一个唯一的消息队列, 可以由系统指定产生,也可以由用户自己指定一个。在key的地方传IPCPRIVATE时表示系统指定key值。自定义生成key时,需要调用ftok函数。
  

        #include <sys/types.h>        #include <sys/ipc.h>        key_t ftok(const char *pathname, int proj_id);

  pathname表示当前文件的名称或路径,proj_id可以忽略直接设为0。
  则执行完msgget函数后,返回一个msgid。
  

  首先我们要知道一点,因为之前说过消息队列是按数据块进行通信的,所以每个发送货接收的消息数据都在一个个的块里,每个块由一个结构体表示:
  

 struct msgbuf {               long mtype;       /* message type, must be > 0 */               char mtext[1];    /* message data */           };

  发送函数:
  

       #include <sys/types.h>       #include <sys/ipc.h>       #include <sys/msg.h>       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

  msgp表示这个结构体的地址,msgsz表示块中数据字段的长度,msgflg也忽略直接设为0;应注意,因为msgsnd函数中没有传入数据的类型和具体字符串,这就要求我们在传入结构体的时候,将结构体里面的两个字段都设置好。
  接收函数:
  

       #include <sys/types.h>       #include <sys/ipc.h>       #include <sys/msg.h>      ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,                      int msgflg);

  msgp、msgsz、msgflg的表示不变,msgtyp表示需要显式的传入类型。
  
  在使用完毕消息队列后,我们要销毁创建的消息队列,防止占用系统资源。有两种方法可以做到:
  1、直接在终端键入命令
  ipcs -q:显式当前系统中的所有消息队列;
  ipcrm -q [msgpid]:跟上msgid表示删除对应msgid的消息队列。
  2、调用msgctl函数,顾名思义,控制函数,传入对应的参数就可以终止一个消息队列:
  

       #include <sys/types.h>       #include <sys/ipc.h>       #include <sys/msg.h>       int msgctl(int msqid, int cmd, struct msqid_ds *buf);

  cmd处传入IPC_RMID就表示删除对应的消息队列,后面的参数可以先不管,直接传入NULL即可。

  实现消队列的代码:
  

//comm.h#include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#include <string.h>#define TEXT_SIZE 1024#define SERVER_TYPE 1#define CLIENT_TYPE 10struct msgbuf{    long mtype; /* type of message */    char mtext[TEXT_SIZE]; /* message text */};struct msgbuf msg;int creatmsgid();int getmsgid();int msgsend(int msgid,long types,const char* buffer);int msgreceive(int msgid,long types,char* buffer);int destroymsg(int msgid);
//comm.c#include "comm.h"static key_t getkey(){    return ftok("comm.h",0);//获得一个key}int creatmsgid(){    int msgid=msgget(getkey(),0666|IPC_CREAT|IPC_EXCL);//创建消息队列    if(msgid<0)    {        perror("masgget");        return -1;    }    return msgid;}int getmsgid(){    return msgget(getkey(),0666|IPC_CREAT);//注意msgget的参数}int msgsend(int msgid,long types,const char* buffer){    strcpy(msg.mtext,buffer);//先将结构体的两个字段赋值    msg.mtype=types;    int ret=msgsnd(msgid,(void*)&msg,TEXT_SIZE,0);    if(ret<0)    {        perror("msgsnd");        return -3;    }    return 0;}int msgreceive(int msgid,long types,char* buffer){    msg.mtype=types;    ssize_t size=msgrcv(msgid,(void*)&msg,TEXT_SIZE,msg.mtype,0);    if(size=0)    {        perror("msgrcv");        return -4;    }    printf("%s\n",msg.mtext);    return size;}int destroymsg(int msgid){    int ret = msgctl(msgid,IPC_RMID,NULL);    if(ret<0)    {        perror("msgctl");        return -2;    }    printf("message destroy done...\n");    return 0;}
//server.c#include "comm.h"int main(){    int running = 1;    int msgid=creatmsgid();    char buff[1024];    int i=0;    while(running)    {         printf("server enter# ");        fflush(stdout);        ssize_t s=read(0,buff,1024);//从键盘接收数据        buff[s-1]=0;        msgsend(msgid,SERVER_TYPE,buff);        printf("client say:");        fflush(stdout);        msgreceive(msgid,CLIENT_TYPE,buff);    }    destroymsg(msgid);    return 0;}
client.c#include "comm.h"int main(){    int msgid = getmsgid();    int running = 1;    char buff[1024];    int i=0;    while(1)    {        printf("server say:");        fflush(stdout);        msgreceive(msgid,SERVER_TYPE,buff);        printf("client enter#:");        fflush(stdout);        ssize_t s=read(0,buff,1024);        buff[s-1]=0;        msgsend(msgid,CLIENT_TYPE,buff);    }    return 0;}

实现结果:
  因为是阻塞方式实现的消息队列,所以只能等待发送或接收的操作完成后,再完成另外一个。
  所以图片的最后在client say#的时候我就放弃发送了,因为server还没有发送,处在阻塞状态,不可能发送。
  较大的图片传不上来,所以这个帧数较小。

这里写图片描述
  
  

原创粉丝点击