进程间通信方式(二)----------------消息队列

XSI IPC 

有三种IPC我们称作XSI IPC,即消息队列、信号量以及共享内存,它们之间有很多相似之处,

具体可以查看XSI IPC相同特征

这篇博客主要讲述它们的不同之处:

消息队列

 消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。如图:

进程间通信方式(二)----------------消息队列_第1张图片

消息队列与命名管道有许多相似之处,但少了在打开和关闭管道方面的复杂性。但使用消息队列并未解决我们在使用命名管道时遇到的问题,比如管道满时的阻塞问题。

消息队列提供了一种在两个不相关的进程之间传递数据的相当简单且有效的方法。

与命名管道相比,消息队列它独立于发送和接收进程而存在,这消除了在同步命名管道的打开与关闭时可能产生的一些困难。

优点:

          我们可以通过发送的消息来几乎完全避免命名管道的同步与阻塞问题。

          我们可以用一些方法来提前查看紧急消息。

缺点:

          与管道一样,每个数据块都有一个最大长度的限制,系统中所有队列所包含的全部数据块的总长度也有一个上限。

消息队列创建:

#include 
#include 
#include 

int msgget(key_t key,int flag);  

/*
key:和消息队列关联的key值;可以用宏定义IPC_PRIVATE,也可以用ftok()函数
flag:消息队列的访问权限
返回值:若成功则返回消息队列ID,若出错则返回-1
*/

msgget用于创建一个一个新队列或者打开一个现存的队列。

每个消息队列都有一个msqid_ds结构与其相关联:


struct msqid_ds {
    struct ipc_perm msg_perm;     /* Ownership and permissions */
    time_t          msg_stime;    /* Time of last msgsnd(2) */
    time_t          msg_rtime;    /* Time of last msgrcv(2) */
    time_t          msg_ctime;    /* Time of last change */
    unsigned long   __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */
    msgqnum_t       msg_qnum;     /* Current number of messages in queue */
    msglen_t        msg_qbytes;   /* Maximum number of bytes allowed in queue */
    pid_t           msg_lspid;    /* PID of last msgsnd(2) */
    pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};

此结构规定了队列的当前状态。结构中所显示的各成员是由Single UNIX Specification定义的。

当创建一个新队列时,初始化msqid_ds结构中的下列成员:

XSI IPC为每一个IPC结构设置了一个ipc_perm 结构。该结构规定了权限和所有者。它至少包含以下成员:

 struct ipc_perm{     
              uid_t             uid;               /*共享内存所有者的有效用户ID */
              gid_t             gid;              /* 共享内存所有者所属组的有效组ID*/
              uid_t             cuid;            /* 共享内存创建者的有效用户ID*/
              gid_t             cgid;           /* 共享内存创建者所属组的有效组ID*/
              unsigned short    mode;          /* Permissions + SHM_DEST和SHM_LOCKED标志*/
              unsignedshort     seq;          /* 序列号*/
.
.
.
};

每种实现在其ipc_perm结构中会包含另外一些成员。在创建IPC结构时,对所有字段都赋初值。此后,可以调用msgctl、semctl或shmctl修改uid、gid和mode字段。为了改变这些值,调用进程必须是IPC结构的创建者或超级用户。

该结构中的mode成员按flag中的相应权限位设置,但是对于任何IPC结构都不存在执行权限。flag字段的值用下表指定:

进程间通信方式(二)----------------消息队列_第2张图片

msg_qnum、msg_lspid、msg_lrpid、msg_stime和msg_rtime都设置为0。

msg_ctime设置为当前时间。

msg_qbytes设置为系统限制值。

若执行成功,msgget返回非负队列ID。此后,该值就可以被用于其他三个消息队列函数。

操作消息队列:

#include 
#include 
#include 

int msgctl (int msqid, int cmd, struct msqid_ds *buf );

/*
msqid:消息队列的队列ID
cmd:  说明对由msqid指定的队列所要执行的命令:
            
          IPC_STAT     取此队列的msqid_ds结构,并将它存放在buf所指向的结构中。

          IPC_SET       按由buf指向结构中的值,设置与此队列相关结构中的下列四个字段:

                     msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_qbytes。
                     此命令只能由下列两种进程执行:
                     一种是其有效用户ID等于msg_perm.cuid或者msg_perm.uid;
                     另一种是具有超级用户权限的进程。只有超级用户才能增加msg_qbyz的值。

         IPC_RMID    从系统中删除该消息队列以及仍在该队列中的所有数据。这种删除立即生效。
                     仍在使用这一消息队列的其他进程在它们下一次试图对队列进行操作时,
                      将出错返回EIDRM。此命令只能由下列两种进程执行:
                     一种是其有效用户ID等于msg_perm.cuid或者msg_perm.uid;
                     另一种是具有超级用户特权的进程。

这三条命令(IPC_STAT、IPC_SET和IPC_RMID)也可用于信号量和共享内存。

buf:消息队列缓冲区
返回值:若成功则返回0,若出错则返回-1
*/

             

发送消息:

#include 
#include 
#include 

int msgsnd(int msqid,const void* ptr,size_t nbytes,int flag); 

/*
msqid:消息队列的ID
ptr:指向消息的指针。常用消息结构msgbuf如下:
struct msgbuf

{

    long mtype;          //消息类型

    char mtext[N]       //消息正文

}; 

nbytes:发送的消息正文的字节数

flag:

           IPC_NOWAIT  消息没有发送完成函数也会立即返回。

            0:直到发送完成函数才返回

返回值:若成功则返回0,若出错则返回-1
*/

msgsnd将新消息添加到消息队列尾端。每个消息由三部分组成,他们是一个正长整型类型字段,一个非负长度以及实际数据字节(对应于长度),所有这些都将消息添加到消息队列时,传送为msgsnd。

ptr参数指向一个长整型数,它包含了正的整形消息类型,在其后紧跟着消息数据。(若nbytes是0,则无消息数据)

若发送的最长消息是512字节,则可定义下列结构:

struct mymesg{

  long mtype;
  char mtext[512];
};

于是,ptr就是一个指向mymesg结构的指针。接收者可以使用消息类型一非先进先出的次序取消息。

参数nbytes指mymseg.mtext的数据长度。

参数flag的值可以指定为IPC_NOWAIT。这类似于文件I/O的非阻塞I/O标志。若消息队列已满(或者消息队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。如果没有指定IPC_NOWAIT,则进程阻塞直到下述情况出现为止:

(1)有空间可以容纳要发送的消息

(2)从系统中删除了此队列,返回EIDRM("标识符被删除")

(3)捕捉到一个信号,并从信号处理程序返回,返回EINTR。

注意,对删除消息队列的处理不是很完善。因为对每个消息队列并没有设置一个引用计数器(对打开文件则有这种引用计数),所以删除一个队列会造成仍在使用这一队列的进程在下次对队列进行操作时出错返回。信号量机制也会以同样方式处理将其删除。相反,删除一个文件时,要等到使用该文件的最后一个进程关闭它的文件描述符,才能删除文件的内容。

当msgsnd成功返回,与消息队列相关的msqid_ds结构得到更新,以标识发出该调用的进程ID(msg_lspid)、进行该调用的时间(msg_stime),并指示队列中增加了一条消息(msg_qnum)。

接收消息

#include 
#include 
#include 


int msgrcv(int msqid,  void* ptr,  size_t  nbytes,  long type,  int  flag);


/*
msqid:消息队列的ID
ptr:接收消息的缓冲区
nbytes:要接收的消息的字节数  若返回的消息大于nbytes,而且在flag中设置了MSG_NOERROR,则该消息被截短(这种情况下,不通知我们消息被截短了,消息的截去部分被丢弃)。如果没有设置这一标志,而消息又太长,则出错返回E2BIG(消息仍在队列中)

type:指定想要哪一种消息   type值非0用于以非先进先出次序读消息
             0:返回消息队列中第一个消息。
         大于0:返回消息队列中第一个类型为type的消息.
         小于0:返回消息队列中类型值不大于type的绝对值的消息,
               如果这种消息有若干个,则取类型值最小的消息。


flag: 
          IPC_NOWAIT:若没有指定类型的消息,msgrv会立即返回-1,errrno设置为ENOMSG。
                    0:若无指定类型的消息函数会一直阻塞直至出现以下情况才终止:
                       有了指定类型的消息
                       从系统中删除了此队列(出错则返回-1且errno置为EIDRM)
                       捕捉到一个信号并从信号处理程序返回(msgrcv返回-1,errno设置为 
                        EINTR)
                 
返回值:若成功则返回消息的数据部分长度,若出错则返回-1
*/

msgrcv成功执行时,内核更新与该消息队列相关的msqid_ds结构,以指示调用者的进程ID(msg_lrpid)和调用时间(msg_rtime),并将队列中的消息数(msg_qnum)减1。
 

 

你可能感兴趣的:(Linux,进程间通信,消息队列)