SYSV IPC-消息队列

1.消息队列简介

消息队列是消息的链接表,存放在内核中并由消息队列标识符标识.我们将称消息队列为“队列”,其标识符为“队列ID”.-APUE(14.7)
消息队列是由操作系统维护的以字节序列为基本单位的间接通信机制,每个消息(Message)是一个字节序列,相同标识的消息组成一个先进先出的消息队列.

2.消息队列的系统调用

头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数实现:
int msgget(key_t key, int msgflg);//获取消息队列标识
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//发送消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);//接收消息
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//消息队列控制

3.msgget


头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数实现:
int msgget(key_t key, int msgflg);//获取消息队列标识
(1)key 申请的消息队列的key值
(2)msgflg 消息队列的属性,与创建文件相似(This is analogous to the effect of the combination O_CREAT|O_EXCL for open).
       The value msgflg is composed of:
       IPC_CREAT:创建共消息队列,位图一定要加上权限的,创建文件并制定权限IPC_CREAT|0664
       IPC_EXCL:只能和IPC_CREAT一起使用,检测消息队列是否存在,若消息队列存在,则报错,errno=EEXIST.(其实这里和共享内存非常相似)

RETURN VALUE
       If successful, the return value will be the message queue identifier (a nonnegative integer), otherwise -1 with errno indicating the error.
       成功则返回创建的消息队列的描述符,理解为消息队列的ID,这个ID是唯一的.
       失败返回-1,失败的原因写在errno中.
ERRORS
       On failure, errno is set to one of the following values:
       EACCES A message queue exists for key, but the calling process does not have permission to access the queue, and does not have the CAP_IPC_OWNER capability.
       EEXIST A message queue exists for key and msgflg specified both IPC_CREAT and IPC_EXCL.
       ENOENT No message queue exists for key and msgflg did not specify IPC_CREAT.
       ENOMEM A message queue has to be created but the system does not have enough memory for the new data structure.
       ENOSPC A message queue has to be created but the system limit for the maximum number of message queues (MSGMNI) would be exceeded.

4. msgsnd&msgrcv

 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/msg.h>
 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:
Send messages to  a System V message queue.(给SYS V中的消息队列发送信息)
msgsnd
参数解释:
1.msqid:消息队列的ID
2.msgp:一个万能指针,实际上这个指针最好指向以下struct msgbuf这个数据类型,
The msgp argument is a pointer to caller-defined structure of the following general form:
           struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[1];    /* message data */
           };
The mtype  field  must have a strictly positive integer value. This value can be used by the receiving process for message selection. 
3.msgsz:实际发送的数据的长度;
4.msgflg:消息的特殊要求,如下,有IPC_NOWAIT,MSG_EXCEPT和MSG_NOERROR等.
          如果IPC_NOWAIT被指定,那么如果调用msgrcv失败的时候,错误码会被设置成EAGAIN,
          此时程序员可设计一下看看咋样处理这种情况.

msgrcv:
参数解释:
1.msqid:消息队列的ID
2.msgp:一个万能指针,实际上这个指针最好指向以下struct msgbuf这个数据类型,
The msgp argument is a pointer to caller-defined structure of the following general form:
           struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[1];    /* message data */
           };
The mtype  field  must have a strictly positive integer value. This value can be used by the receiving process for message selection. 
3.msgsz:实际发送的数据的长度;
4.msgtyp:可以来指定是否接受某些消息,具体可以参照下面的官方文档;
5.msgflg:消息的特殊要求,如下,有IPC_NOWAIT,MSG_EXCEPT和MSG_NOERROR等.
        
官方详细说明手册:
msgsnd()
       The msgsnd() system call appends a copy of the message pointed to by msgp to the message queue whose identifier is specified by msqid.
       If  sufficient space is available in the queue, msgsnd() succeeds immediately.  (The queue capacity is defined by the msg_qbytes field in the associated data structure for the message queue.  During
       queue creation this field is initialized to MSGMNB bytes, but this limit can be modified using msgctl(2).)  If insufficient space is available in the queue, then the default behavior of msgsnd()  is to block until space becomes available.  If IPC_NOWAIT is specified in msgflg, then the call instead fails with the error EAGAIN.

       A blocked msgsnd() call may also fail if:
       * the queue is removed, in which case the system call fails with errno set to EIDRM; or
       * a  signal  is caught, in which case the system call fails with errno set to EINTR;see signal(7).  (msgsnd() is never automatically restarted after being interrupted by a signal handler, regardless
         of the setting of the SA_RESTART flag when establishing a signal handler.)

       Upon successful completion the message queue data structure is updated as follows:
              msg_lspid is set to the process ID of the calling process.
              msg_qnum is incremented by 1.
              msg_stime is set to the current time.

msgrcv()
       The msgrcv() system call removes a message from the queue specified by msqid and places it in the buffer pointed to by msgp.
       The argument msgsz specifies the maximum size in bytes for the member mtext of the structure pointed to by the msgp argument.  If the message text has length greater than msgsz,  then  the  behavior depends  on  whether MSG_NOERROR is specified in msgflg.  If MSG_NOERROR is specified, then the message text will be truncated (and the truncated part will be lost); if MSG_NOERROR is not specified,then the message isn't removed from the queue and the system call fails returning -1 with errno set to E2BIG.
       The argument msgtyp specifies the type of message requested as follows:
       * If msgtyp is 0, then the first message in the queue is read.
       * If msgtyp is greater than 0, then the first message in the queue of type msgtyp is read, unless MSG_EXCEPT was specified in msgflg, in which case the first message in the queue of type  not  equal to msgtyp will be read.
       * If msgtyp is less than 0, then the first message in the queue with the lowest type less than or equal to the absolute value of msgtyp will be read.
       The msgflg argument is a bit mask constructed by ORing together zero or more of the following flags:
       IPC_NOWAIT
              Return immediately if no message of the requested type is in the queue.  The system call fails with errno set to ENOMSG.
       MSG_EXCEPT
              Used with msgtyp greater than 0 to read the first message in the queue with message type that differs from msgtyp.
       MSG_NOERROR
              To truncate the message text if longer than msgsz bytes.
 If no message of the requested type is available and IPC_NOWAIT isn't specified in msgflg, the calling process is blocked until one of the following conditions occurs:
       * A message of the desired type is placed in the queue.
       * The message queue is removed from the system.  In this case the system call fails with errno set to EIDRM.
       * The  calling process catches a signal.  In this case the system call fails with errno set to EINTR.  (msgrcv() is never automatically restarted after being interrupted by a signal handler, regardless of the setting of the SA_RESTART flag when establishing a signal handler.)
       Upon successful completion the message queue data structure is updated as follows:
              msg_lrpid is set to the process ID of the calling process.
              msg_qnum is decremented by 1.
              msg_rtime is set to the current time.

RETURN VALUE
       On failure both functions return -1 with errno indicating the error, otherwise msgsnd() returns 0 and msgrcv() returns the number of bytes actually copied into the mtext array.

ERRORS
       When msgsnd() fails, errno will be set to one among the following values:
  EACCES The calling process does not have write permission on the message queue, and does not have the CAP_IPC_OWNER capability.
  EAGAIN The message can't be sent due to the msg_qbytes limit for the queue and IPC_NOWAIT was specified in msgflg.
  EFAULT The address pointed to by msgp isn't accessible.
  EIDRM  The message queue was removed.
  EINTR  Sleeping on a full message queue condition, the process caught a signal.
  EINVAL Invalid msqid value, or nonpositive mtype value, or invalid msgsz value (less than 0 or greater than the system value MSGMAX).
   ENOMEM The system does not have enough memory to make a copy of the message pointed to by msgp.
   When msgrcv() fails, errno will be set to one among the following values:
   E2BIG  The message text length is greater than msgsz and MSG_NOERROR isn't specified in msgflg.
   EACCES The calling process does not have read permission on the message queue, and does not have the CAP_IPC_OWNER capability.
   EAGAIN No message was available in the queue and IPC_NOWAIT was specified in msgflg.
   EFAULT The address pointed to by msgp isn't accessible.
   EIDRM  While the process was sleeping to receive a message, the message queue was removed.
   EINTR  While the process was sleeping to receive a message, the process caught a signal; see signal(7).
   EINVAL msgqid was invalid, or msgsz was less than 0.
   ENOMSG IPC_NOWAIT was specified in msgflg and no message of the requested type existed on the message queue.

CONFORMING TO
       SVr4, POSIX.1-2001.

NOTES
       The inclusion of <sys/types.h> and <sys/ipc.h> isn't required on Linux or by any version of POSIX.  However, some old implementations required the inclusion of these header files, and the SVID  also documented their inclusion.  Applications intended to be portable to such old systems may need to include these header files.

       The msgp argument is declared as struct msgbuf * with libc4, libc5, glibc 2.0, glibc 2.1.  It is declared as void * with glibc 2.2 and later, as required by SUSv2 and SUSv3.

       The following limits on message queue resources affect the msgsnd() call:
       MSGMAX Maximum size for a message text: 8192 bytes (on Linux, this limit can be read and modified via /proc/sys/kernel/msgmax).
       MSGMNB Default  maximum  size  in bytes of a message queue: 16384 bytes (on Linux, this limit can be read and modified via /proc/sys/kernel/msgmnb).  The superuser can increase the size of a message queue beyond MSGMNB by a msgctl(2) system call.

       The implementation has no intrinsic limits for the system wide maximum number of message headers (MSGTQL) and for the system wide maximum size in bytes of the message pool (MSGPOOL).
EE ALSO
       msgctl(2), msgget(2), capabilities(7), mq_overview(7), svipc(7)

COLOPHON
       This page is part of release 3.53 of the Linux man-pages project.  A description of the project, and information about reporting bugs, can be found at http://www.kernel.org/doc/man-pages/.

5. msgctl

头文件:
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
函数原型:  
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//官方参数名给的优点问题啊,应该是msgid.
删除消息队列用的函数是msgctl,这个函数功能很多.

首先对这个函数做一些说明:
函数描述:对消息队列进行一些操作.

函数参数:
       msqid:一般msgget函数返回的共享内存的ID值,或者通过命令icpmk创建的那个id;
       cmd:
          IPC_STAT:获得贡献内存的状态信息(此时第三个参数buf的值不能为空)
          IPC_SET:设置共享内存信息.
          IPC_RMID:删除共享内存(此时第三个参数buf的值可以为空)
          ...
返回值:成功返回0,失败返回-1,失败信息被写入errno中,errno的值可以查手册,此处不赘述.
       
再对这个函数用作删除消息队列的功能做出说明:
       当cmd参数给出的是IPC_RMID的时候,函数实现的功能就是删除这个消息队列并通知各个在等待的各个线程.调用这个函数的进程不管以何种形式必须有合适的权限去对这个消息队列进行操作.对于返回值来说,当cmd是IPC_RMID的时候,只有当权限不足的时候才可能报错,返回-1并将errno设置成EPERM,删除不掉这个队列.

给出官方手册中的说明,
mannul中在使用IPC_RMID的时候指出:
IPC_RMID
        Immediately remove the message queue, awakening all waiting reader and writer processes (with an error return and errno set to EIDRM).  The calling process must have appropriate privileges or its effective user ID must be either that of the creator or owner of the message queue.
当cmd是IPC_RMID时候的返回值:
          EPERM  The  argument cmd has the value IPC_SET or IPC_RMID, but the effective user ID of the calling process is not the creator (as found in msg_perm.cuid) or the owner (as found in msg_perm.uid) of the message queue, and the process is not privileged (Linux: it does not have the CAP_SYS_ADMIN capability).
其实这里就指出,当cmd是IPC_RMID的时候,只有当权限不足的时候才可能报错,删除不掉这个队列.


6.消息队列代码实例

6.1 proto.h

#ifndef PROTO_H__
#define PROTO_H
#define KEYPATH  "/etc/services"
#define KEYPROJ  'g'
#define NAMESIZE  32
struct msg_st
{
    long mtype;
    char name[NAMESIZE];
    int math;
    int chinese;
};
#endif 

6.2 rcver.c

/*
作者:Muten
编码时间:20201019
编码目的:演示对SYSV 接受消息队列中的数据
代码功能:实现对消息队列的发送请求
测试环境:Linux localhost.localdomain 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
版本信息:VER1.0
注释:接受端,先启动创建出消息队列
*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include "proto.h" 
int main()
{

   key_t key;   
   int msgid;
   struct msg_st rbuf;
   key = ftok(KEYPATH,KEYPROJ);
   if(key < 0)
   {
     perror("ftok");
     exit(1);
   }

   msgid = msgget(key,IPC_CREAT|0600);
   if(msgid < 0)
   {
      perror("msgget");
      exit(1);
   }
    while(1)
   {
     if(msgrcv(msgid,&rbuf,sizeof(rbuf)-sizeof(long),0,0)<0)//msgrcv一旦成功返回(消息被读到)这条消息就会被清除
      {
       perror("msgrcv");
       exit(1);
      }
      printf("NAME = %s .\n",rbuf.name);
      printf("MATH = %d .\n",rbuf.math);
      printf("CHINESE = %d .\n",rbuf.chinese);
   }
   msgctl(msgid,IPC_RMID,NULL);
   exit(0);
 
}

6.3 snder.c

/*
作者:Muten
编码时间:20201019
编码目的:演示对SYSV 消息队列的发送请求
代码功能:实现对消息队列的发送请求
测试环境:Linux localhost.localdomain 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
版本信息:VER1.0
注释:请求端,需要等队列创建好了再启动.
*/

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "proto.h"
int main()
{
    key_t key;  
    int msgid;
    struct msg_st sbuf;
   key =  ftok(KEYPATH,KEYPROJ);
  if(key <0)
   {
      perror("fork");
        exit(1);
   }
   msgid = msgget(key,0);
   if(msgid < 0)
   {
         perror("msgget");
           exit(1);
   }
   sbuf.mtype = 1;
   strcpy(sbuf.name,"Alan");
   sbuf.math = rand()%100;
    sbuf.chinese = rand()%100;
   if(msgsnd(msgid,&sbuf,sizeof(sbuf)-sizeof(long),0)<0)//msgsnd一旦成功返回(消息成功发出),消息队列中会添加一条消息
   {
       perror("msgsnd");
       exit(1);
   }
  puts("ok!");
  exit(0);
}

7.消息队列的查看

因为消息队列是是存在于内核中的,所以如果程序没有调用msgctl进行删除或者没有调用到那块代码,那么它就会一直存在于内核中,此时我们就可以用icps相关命令去查看它的相关情况.
ipcs -l -q    --查看消息队列的资源限制
ipcs -q -i  0 --查看消息号队列号为0的消息队列
ipcmk -Q      --用命令行创建新的消息队列

消息队列的资源限制在/proc/sys/kernel/msgmax中可以看到,我的64位机器上是8192字节,
用ulimit -a 看POSIX message queues是819200字节.

8.SYSV 消息队列有什么问题?

APUE 14.7节中指出: 
对消息队列删除的处理不是很完善。因为对每个消息队列并没有设置一个引用计数
器(对打开文件则有这种计数器),所以删除一个队列使得仍在使用这一队列的进程在下次对
队列进行操作时出错返回。msgrcv从队列中取用消息,如同msgsnd中一样,ptr参数指向一个长整型数(返回的消息类型存放在其中),跟随其后的是存放实际消息数据的缓存. nbytes说明数据缓存的长度.若返回的消息大于 nbytes,而且在flags中设置了MSG_NOERROR,则该消息被截短(在这种情况下,不通知我们消息截短了).
如果没有设置这一标志,而消息又太长,则出错返回E2BIG(消息仍留在队列中).
消息队列原来的实施目的是提供比一般IPC更高速度的进程通信方法,但现在与其他形式的IPC相比,在速度方面已经没有什么差别了。
(在原来实施消息队列时,唯一的其他形式的IPC是半双工管道.)考虑到使用消息队列具有的问题(见14.6.4节),我们得出的结论是,
在新的应用程序中不应当再使用它们(无情,不该这么说或者这么翻译,应该说使用的时候要做点其他考虑才对).
APUE 14.6.4中指出:
系统V IPC的主要问题是;IPC结构是在系统范围内起作用的,没有访问计数。例如,如果
创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容并不
被删除。它们余留在系统中直至:由某个进程调用msgrcv或msgctl读消息或删除消息队列,或
某个进程执行ipcrm命令删除消息队列;或由正在再起动的系统删除消息队列.
系统V IPC的另一个问题是;这些IPC结构并不按名字为文件系统所知。我们不能用第3、4
章中所述的函数来存取它们或修改它们的特性。为了支持它们不得不增加了十多个全新的系统
调用(msgget,semop,shmat等)。我们不能用ls命令见到它们,不能用rm命令删除它们,不
能用chmod命令更改它们的存取权。于是,也不得不增加了全新的命令ipcs和ipcrm.(这不是已经加了吗?)

9.我的思考

是否因为linux系统提供的message queue有如8中APUE所说的那些问题,所以市场上出现那么
多类型的消息队列,市场上五花八门的消息队列存在的意义是什么?

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值