linux 进程间通信之System V 消息队列

  • 用来引用消息队列的句柄是一个由msgget()调用返回的标识符。这些标识符与文件描述是不同的。
  • 消息队列进行的通信是面向消息的,即读者接收到写者写入的整条消息。也就是说,不能只读取一条消息的一部分也不能一次性读取多条消息。
  • 除了包含数据之外,每条消息还有一个用整数表示的类型。

2.创建或打开一个消息队列

#include<sys/types.h>
#include<sys/msg.h>
int msgget(key_t key,int msgflg);
//return message queue identifier on success,or -1 on error
复制代码
  • key: 参数可以设置值为IPC_PRIVATE,系统会创建一个全新的IPC对象,或者使用ftok()函数生成一个(接近唯一)key,key相同的情况下,系统会返回一个已创建相同key的IPC对象。

  • msgflg:指定施加于新消息队列之上的权限和检查一个既有队列的权限的位掩码(类似与文件权限)

    • IPC_CREAT: 如果没有与指定的key对应的消息队列,那么就创建一个新队列,否则返回已创建的队列
    • IPC_EXCL:与IPC_CREAT一起使用,如果指定的key对应的队列已经存在,那么调用就会失败并返回EEXIST错误。

创建一个消息队列

int msqid=msgget(IPC_PRIVATE,IPC_CREAT|S_IRUSR|S_IWUSR);//Read and Write by owner
if(msqid==-1){
    errExit("msgget");
}
复制代码

3.交换消息

msgsnd()和msgrcv()系统调用执行消息队列上的I/O。这两个系统调用接收的第一个参数是消息队列标识符(msqid)。第二个是参数msgp是一个由程序员定义的结构的指针,该结构用于存放被发送或接受的消息,结构的常规形式如下:

struct msg{
    long type;//message type
    void* body;//message body
    ...
}
复制代码

消息的第一个部分必须指明了消息的类型,它用一个类型为long的整数来表示,而消息的剩余部分则是自定义的一个结构,其长度、内容、字段名和类型都都可以是任意的,也可以多个字段。

需特别指出:消息的大小是除了type字段外的所有字段的大小

3.1 发送消息

从消息队列中写入消息需要队列上的写权限

#include<sys/types.h>
#include<sys/msg.h>
int msgsnd(int msqid,const void *msgp,size_t msgsz, int msgflg);
//return 0 on success,or -1 on error
复制代码

使用msgsnd()发送消息必须要将消息结构中的type字段值设为一个大于0的值并将需要传递的信息复制到自定义的body字段中。

  • msqid队列的标识符

  • msgp消息的结构体指针

  • msgsz参数指定了body字段包含的字节数,即消息的大小。

  • msgflg是一组标记的位掩码,用于控制msgsnd()操作

    • IPC_NOWAIT:执行一个非阻塞的发送操作:当消息队列满时,msgsnd()会阻塞直到队列中有足够的空间来存放这条消息。如果指定了这条消息,那么msgsnd()就会立即返回EAGAIN错误

使用msgsnd()发送一条消息

struct mbuf{
    long mtype; //message type
    char mtext[1024]; //message body
}
struct mbuf msg;
int msqid,msgLen;

...

msgLen=strlen(msg.mtext);
if(msgsnd(msqid,&msg,msgLen,IPC_NOWAIT)==-1){
    errExit("msgsnd");
}
...
复制代码

3.2 接收消息

从消息队列中读取消息需要队列上的读权限

#include<sys/types.h>
#include<sys/msg.h>
ssize_t msgrcv(int msqid,void *msgp,size_t maxmsgsz,long msgtyp,int msgflg);
//return number of bytes copied into body field, or -1 on error
复制代码
  • msqid 队列的标识符

  • maxmsgsz 参数值要大于或等于需读取的消息的大小

  • msgp 缓冲区中消息的最大可用空间是通过maxmsgz参数来指定的。如果队列中待删除的消息体的大小超过了maxmsgsz字节,那么就不会从队列中删除消息,并且会返回错误E2BIG。

  • mtype 读取消息的顺序可以根据mtype字段来选择

    • mshtyp==0 将会删除队列中的第一条消息并将其返回给调用进程。

    • msgtyoe>0 将队列中第一条消息里的type字段值等于msgtype的消息删除并返回给进程。可以用于让各个进程选取与自己的进程ID匹配的消息,这样就会竞争读取同一条消息。

    • msgtype<0 将队列变成优先队列即最小堆形式。队列中消息type最小并且其值小于或等于msgtype的绝对值的第一条消息删除并返回给调用进程,如果没有,则堵塞直到出现匹配的消息为止。

  • msgflg 控制msgrcv()操作

    • IPC_NOWAIT 执行一个非阻塞接收。如果队列中没有匹配的消息将会堵塞。设置该标记则会立即返回ENOMSG错误。
    • MSG_EXCEPT 需msgtype>0 才起作用,使得队列返回第一条type不等于msgtype的消息删除并返回给调用进程(Linux特有)。
    • MSG_NOERROR 如果需读取的消息的大小超过了可用空间将会返回E2BIG错误。设置该标记将会把消息的大小截短为maxmsgsz字节,然会返回给调用者,其余的消息将丢弃。

使用msgrcv()读取一条消息

struct mbuf{
    long mtype;
    char mtext[1024];
}
int msqid,msgLen;
struct mbuf msg;
....

msgLen=msgrcv(msqid,&msg,1024,0,IPC_NOWAIT);
if(msgLen==-1){
    errExit("msgrcv");
}
复制代码

4.消息队列控制操作

#include<sys/types.h>
#include<sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
// return  0 on success,or -1 on error
复制代码
  • cmd 参数制定了在队列上执行的操作。
    • IPC_RMID 立即删除消息队列对象及其关联的msqid_ds数据结构,并且队列中的消息都会丢失,被阻塞的读者和写者进程会立即醒来,msgsnd()和msgrcv会失败并返回错误EIDRM。这个操作会忽略传递进来的buf参数。
    • IPC_STAT 将消息队列关联的msqid_ds 数据结构的副本放到buf中。
    • IPC_SET 使用buf提供的值更新队列关联的msqid_ds数据结构的字段。

还有一些其他的cmd参数,感兴趣可以网上查看下,这里只列出常见参数。

使用msgctl删除System V 消息队列

...
if(msgctl(msqid,IPC_RMID,NULL)==-1){
    errExit("msgctl");
}
复制代码

4.1 消息队列关联的数据结构

struct msqid_ds
{
  struct ipc_perm msg_perm;	/* structure describing operation permission */
  __time_t msg_stime;		/* time of last msgsnd command */
  __time_t msg_rtime;		/* time of last msgrcv command */
  __time_t msg_ctime;		/* time of last change */
  __syscall_ulong_t __msg_cbytes; /* current number of bytes on queue */
  msgqnum_t msg_qnum;		/* number of messages currently on queue */
  msglen_t msg_qbytes;		/* max number of bytes allowed on queue */
  __pid_t msg_lspid;		/* pid of last msgsnd() */
  __pid_t msg_lrpid;		/* pid of last msgrcv() */
  __syscall_ulong_t __glibc_reserved4;
  __syscall_ulong_t __glibc_reserved5;
};

struct ipc_perm
  {
    __key_t __key;			/* Key.  */
    __uid_t uid;			/* Owner's user ID.  */
    __gid_t gid;			/* Owner's group ID.  */
    __uid_t cuid;			/* Creator's user ID.  */
    __gid_t cgid;			/* Creator's group ID.  */
    unsigned short int mode;		/* Read/write permission.  */
    unsigned short int __pad1;
    unsigned short int __seq;		/* Sequence number.  */
    unsigned short int __pad2;
    __syscall_ulong_t __glibc_reserved1;
    __syscall_ulong_t __glibc_reserved2;
  };

复制代码

修改一个System V 消息队列的msg_qbytes 设置

...
struct msqid_ds ds;
int msqid;
...
if(msgctl(msqid,IPC_STAT,&ds)==-1){
    errExit("msgctl");
}
ds.msg_qbytes=1048576 //1MB
if(msgctl(msqid,IPC_SET,&ds)==-1){
    errExit("msgctl");
}
复制代码

5.消息队列的限制

  • MSGMNI 规定了系统中所能创建消息队列的数量。
  • MSGMAX 规定了单条消息中最多可写入的字节数(写入消息超过该值,返回EINVAL错误)。
  • MSGMNB 规定了消息队列中最多能保存的字节数,并用来初始化msqid_ds数据结构的msg_qbytes字段。如果达到了一个队列的msg_qbytes限制,那么msgsnd()会阻塞或在设置IPC_NOWAIT时返回EAGAIN错误。

还有一些其他的限制,感兴趣可以网上查看下,这里只列出常见限制。

Linux 特有的msgctl() IPC_INFO 操作能够获取一个类型为msginfo的结构,其中包含了各种消息队列限制的值

struct msginfo buf;
msgctl(0, IPC_INFO,(struct msqid_ds *)&buf);

/* buffer for msgctl calls IPC_INFO, MSG_INFO */
struct msginfo
  {
    int msgpool;
    int msgmap;
    int msgmax;
    int msgmnb;
    int msgmni;
    int msgssz;
    int msgtql;
    unsigned short int msgseg;
  };

复制代码

6. 使用消息队列实现文件服务器应用程序

一个客户端使用一个消息队列的客户端/服务器IPC

服务器端核心代码

...
for(;;){
    msgLen=msgcrv(serverId,&req,REQ_MSG_SIZE,0,0);
    if(msgLen==-1){
        if(errno==EINTR)//Interrupted by SIGCHLD handler?
        continue;
        errMsg("msgrcv");
        break;
    }
    pid=fork();
    if(pid==-1){
        errMsg("fork");
        break;
    }
    if(pid==0){
        serveRequest(&req);
        _exit(EXIT_SUCCESS);
    }
}
...
复制代码

客户端核心代码

...
clientId=msgget(IPC_PRIVATE,S_IRUSR|S_IWUSR|S_IWGRP);//确保服务端能够有写权限
...
msgLen=msgrcv(clientId,&req,RESP_MSG_SIZE,0,0);
if(msgLen==-1){
    errExit("msgrcv");
}
...
for(;;){
    msgLen=msgrcv(clientId,&resp,RESP_MSG_SIZE,0,0);
    if(msgLen==-1){
        errExit("msgrcv");
    }
    ...
}
...
复制代码

7.System V 消息队列的缺点

  • 消息队列是通过标识符引用的,而不是像大多数其他UNIX I/O机制那样使用文件描述符。这意味这在各种基于文件描述符的I/O技术(如select()、poll()以及epoll)将无法应用于消息队列上。此外,在程序中编写同时处理消息队列的输入和基于文件描述符的I/O机制的代码要比编写只处理文件描述符的代码要更加复杂。

  • 使用键而不是文件名来标识消息队列会增加额外的程序设计复杂性。ftok()函数通常能产生一个唯一的键,但却无法保证,使用IPC_PRIVATE键能确保产生唯一的队列标识符,但需要使这个标识符对需要用到它的其他进程可见。

  • 消息队列是无连接的,内核不会对待管道、FIFO以及socket那样维护引用队列的进程数,会带来几个问题:

    • 一个应用程序何时能够安全地删除一个消息队列?
    • 应用程序如何确保不再使用的队列会被删除呢?
  • 消息队列的总数、消息的大小以及单个队列的容量都是有限制的。这些限制都是可配置的,但如果一个应用程序超出了这些默认限制的范围,那么安装应用程序的时候就需要完成一些额外的工作了。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章