Linux进程间通信之消息队列

来源:互联网 发布:日本动漫哪个好看 知乎 编辑:程序博客网 时间:2024/06/02 06:29

消息队列


我们可以把消息队列理解为共享的消息链表,它存储在内核中,由消息队列标识符标识。消息队列传输数据的单位是有类型的数据块,这点与管道不同,管道的信息传输是基于字节流。
不同进程发送的数据块可被认为都有自己的类型,而接受端进程接受的数据块可以有不同的类型。
消息队列与管道相同的地方在于他们都是有限的。及既然他们都是一块共享的内存,那么他们必定有大小。

  • msgmax,每个消息的最大长度是有上限的。
    这里写图片描述
  • msgmnb,每个消息队列的最大字节数也是有上限的。
    这里写图片描述
  • msgmni,每个系统上可以存在多少个消息队列也有自己的限制。
    这里写图片描述

XSI IPC结构与消息队列结构


IPC是UNIX系统中进程间通信的统称,有三种称作XSI IPC:消息队列,信号量,共享内存。每创建这三种之间的任何一种,都相当于建立了一个XSI IPC对象。系统为每个IPC对象都维护了一种结构ipc_perm。

struct ipc_perm {key_t __key; /* Key supplied to xxxget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */};

其它的先不用管,我们先看它的第一个成员__key,它的类型为key_t。这是系统对IPC资源的唯一标识符。在申请IPC资源前,先要获取此标识符。我们可以通过ftok函数来获得它。

#include<sys/ipc.h>key_t ftok(const char* path,int id);

第一个参数为给定的路径名,一般我们设置为当前路径“.”。第二个参数id为项目id,我们可以指定一个0~255之前的数来最为项目id。ftok函数会将路径名与项目id相结合,通过特定的方法得出标识符key。

除此之外,每个消息队列都有自己的结构:

这里写图片描述
(信号量,共享内存也有类似的结构)

操作函数


获取消息队列


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

返回值:成功返回一个消息队列的msgid,作为进程对它的唯一标识;失败返回-1。

参数key便不再多说,就上我们上面提到的key值。flag为创建的方式(IPC_CREAT和IPC_EXCL),一般有以下两种选项:

  • IPC_CREAT单独使用,表示申请创建一个IPC资源,若申请的资源已经存在,则直接获取;若不存在,则创建新的IPC资源。
  • IPC_CREAT与IPC_EXCL一起使用,表示申请创建一个IPC资源,若申请的资源不存在,则创建新的IPC资源;若已经存在,则返回-1。

IPC_EXCL单独使用没有任何意义,他的存在就是与IPC_CREAT一起使用,从而保证得到的是一个新创建的资源。

摧毁消息队列


#include<sys/msg.h>int msgctl(int msgid,int cmd,struct msqid_ds *buf)

第一个参数msgid就是msgget的返回值。第二个参数cmd指定了对该消息队列指定执行的命令。msgctl不仅仅有删除的功能。
cmd:

  • IPC_STAT,获取此消息队列的msqid_ds结构,并将其存放在buf指向的结构中。
  • IPC_SET,将参数buf中的某些字段复制到消息队列msqid_ds 结构中对应的字段。此命令只有以下两种用户可以执行,一种是其有效用户ID等于msg_perm.cuid ,msg_perm.uid。另一种是超级用户root。
  • IPC_RMID,从系统中删除该消息队列以及该消息队列的所有数据。删除立即生效。

所以我们执行删除动作的话,将cmd设置成IPC_RMID即可。

发送消息


#include<sys/msg.h>int msgsnd(int msqid,const void* ptr,size_t nbytes,int flag)

返回值:成功返回0,失败返回-1。
我们一般让ptr指针指向一个结构体,结构如下:

struct msgbuf{    long mtype;    char mtext[MYSIZE];};

将mtype设置为要发送数据的数据类型,mtext存放发送的数据。这也是为什么我们在上面说,消息队列传输的是有类型的数据。

nbytes为传输数据的大小。
flag可设置称IPC_NOWAIT,也可以不设置(设为0)。IPC_NOWAIT类似于文件I/O的非阻塞I/O标志。当消息队列已满,指定IPC_NOWAIT后,msgsnd立即出错返回EAGAIN,如果没有指定,该进程将会一直阻塞。知道直到消息队列有空间容纳发送的消息或者系统删除了此消息队列或者收到哦了一个信号,从信号处理程序中返回。

接受消息


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

返回值:成功返回接受到的消息的bytes,失败返回-1。
第二个参数msgp为存放接受到消息的缓冲区,msgsz为指定缓冲区的大小。type为我们接受到数据的类型。
如果接受到的消息大于缓冲区的大小,并且msgflg设置了MSG_NOERROR的话,消息就会发生截断并丢弃被截去的部分。也可以将msgflag设置成IPC_NOWAIT,使操作不阻塞。

调试注意


当你在调试你的代码的时候可能会出现以下错误:

这里写图片描述

msgget发现资源文件已经存在,这说明你msgget申请IPC资源的方式是IPC_CREAT和IPC_EXCL一起使用的。
执行下面命令,查看当前的消息队列资源。

ipcs -q

这里写图片描述
你会看的那个阻止你继续运行的消息队列资源的各个属性,根据msgid,我们可以将这个IPC资源杀死。

ipcrm -q [msgid]

这里写图片描述

杀死之后,你再运行就不会有任何问题了。

代码实例

代码部分较多,我将这些接口又封装了一层,请选择性观看。

comm.h

#ifndef _COMM_#define _COMM_#include<stdio.h>#include<sys/types.h>#include<sys/ipc.h>#include<sys/msg.h>#include<string.h>#include<unistd.h>#define MYSIZE 128#define PATHNAME "."#define PROJID 0x1234#define CLIENT_TYPE 1#define SERVER_TYPE 2struct msgbuf{    long mtype;    char mtext[MYSIZE];};int commMsgQueue(int flag);int creatMsgQueue();int getMsgQueue();int sendMessage(int msgid, long type, const char *buf);int recvMessage(int msgid,long type, char *buf);int destroyMsgQueue(int msgid);#endif //_COMM_

comm.c

#include"comm.h"int commMsgQueue(int flag){    key_t _k = ftok(PATHNAME,PROJID);    if(_k < 0)    {        perror("ftok");        return -1;    }    int msg_id = msgget(_k,flag);    if(msg_id <0)    {        perror("msgget");        return -2;    }    return msg_id;}int creatMsgQueue(){    return commMsgQueue(IPC_CREAT | IPC_EXCL | 0666);}int getMsgQueue(){    return commMsgQueue(IPC_CREAT);}int sendMessage(int msg_id,long type, const char* msg){    struct msgbuf buf;    buf.mtype = type;    strcpy(buf.mtext,msg);    int ret = msgsnd(msg_id,&buf,sizeof(buf.mtext),0);    if(ret < 0)    {        perror("msgsnd");        return -1;    }    return 0;}int recvMessage(int msg_id,long type,char out[]){    struct msgbuf buf;    int size = msgrcv(msg_id,&buf,sizeof(buf.mtext),type,0);    if(size > 0)    {        printf("%d",size);        buf.mtext[size-1] = '\0';        printf("Debug 0 : %s\n",buf.mtext);        strncpy(out,buf.mtext,sizeof(buf.mtext));        return size;    }    perror("msgrcv");    return -1;}int destroyMsgQueue(int msg_id){    if(msgctl(msg_id,IPC_RMID,NULL) < 0)    {        perror("msgctl");        return -1;    }    return 0;}

接受端client.c

#include"comm.h"int main(){    int msgid = getMsgQueue();    char buf[MYSIZE];    char out[MYSIZE*2];    while(1)    {        printf("Please Enter #");        fflush(stdout);        int ret = read(0,buf,MYSIZE-1);        if(ret > 0)        {            buf[ret] = '\0';            printf("Debug client : %s\n",buf);            sendMessage(msgid,CLIENT_TYPE,buf);        }        if(recvMessage(msgid,SERVER_TYPE,out)<0)        {            break;        }        printf("server echo # %s\n",out);    }    return 0;}

发送端代码server.c

#include"comm.h"int main(){    int msgid = creatMsgQueue();        char buf[MYSIZE*2];    while(1)    {        int size = recvMessage(msgid,CLIENT_TYPE,buf);        printf("DEBUG: %d\n",size);        if(size > 0)        {            buf[size] = '\0';            printf("client say: %s\n",buf);            printf("Debug : %s\n",buf);        }        else        {            break;        }        int ret = sendMessage(msgid,SERVER_TYPE,buf);        if(ret < 0)        {            break;        }    }    destroyMsgQueue(msgid);    return 0;}
2 4