System V 消息队列
- 消息队列是半双工的通信方式
1.1 创建一个消息队列
-
消息队列的特点:消息只能一条的读取,不能多读取,也不能少读取,每条消息有一个类型,可以按照消息的类型读取
-
创建或者打开一条消息:
/************************* 函数功能:创建或者打开一条消息队列 返回值: If successful, the return value will be the message queue identifier (a nonnegative integer), otherwise -1 with errno indicating the error. *************************/ #include <sys/types.h> #include <sys/ipc.h> include <sys/msg.h> int msgget(key_t key, int msgflg); /*参数 key:键值,用来生成标识符 msgflag:可以指定IPC_CREATE, IPC_EXCL等标记用 | 符号连接起来 */
- 通过key值与指定的键值对应的队列匹配,如果匹配成功,返回既有队列的标识符,如果指定了IPC_CREATE, IPC_EXCL等标记,返回相关错误,如果没有匹配成功,但是指定了IPC_CREATE标记,创建一个新的消息队列对象
1.2 交换消息
-
消息的常规形式
struct mymsg { long mtype; /*Message type*/ char mtext; /*Message body*/ } /*这个结构可以由程序员自己决定,只是一般情况下的消息结构体是这样*/
-
发送消息
/***************************** 函数功能:发送一条消息 返回值: 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. *****************************/ #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); /*参数: msqid:消息队列的标识符 msgp:指向一个消息结构 msgsz:消息结构中消息本体的大小 msgflg:位掩码的组合 IPC_NOWAIT:执行一个非阻塞的发送操作,通常消息队列满的时候,msgsnd调用会阻塞,如果设置了这个标记,不会阻塞,会直接返回一个EINTR错误,并且该系统调用被打断之后,不会自动重启,就算设置了SA_RESETART标记 */
-
接受消息
/***************************** 函数功能:接受一条消息 返回值: 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. *****************************/ #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); /*参数: msqid:消息队列的标识符 msgp:指向一个存放消息结构的缓冲区 msgsz:指定了msgp指向的mtext字段(消息本体)最大能够容纳的大小,如果队列中消息大小超过这个字段,就不会从队列中读取消息,而是返回E2BIG错误 msgtyp:消息的读取并非是先进先出,而是按照msgtype的类型进行选择性的读取 1:msgtyp==0:删除队列中第一条消息,并且返回给调用进程 2:msgtyp>0:将队列中第一个mtype等于msgtyp的消息删除并将其返回给调用进程,其它不相等不管,让它还是存在于队列中 3:msgtyp<0:将等待队列中mtype值最小并且其值小于或者等于msgtyp的绝对值的第一条消息会被删除返回给调用进程 msgflg: IPC_NOWAIT:执行一个非阻塞操作,没有匹配的消息类型,返回ENOMSG错误 MSG_EXCEPT:当msgtyp>0时候,将队列中mtype不等于msgtyp的消息强制删除并且返回给调用者,这个标记linux特有的,需要定义_GNU_SOURCE之后才可以,等于msgtyp的消息忽略,让其存在于队列中 MSG_NOERROR:如果消息中mtext字段大于msgsz,会截断mtext字段的数据大小为msgsz返回给调用者,截断剩余的数据会丢失,没有这个标志的话是不会发生截断的,而是直接报错 */
1.3 消息队列控制操作
-
函数功能:在标识符msqid的消息队列上执行控制操作
-
返回值:Return 0 On success,or -1 on error
-
函数原型:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf);
-
参数:
- msqid:消息队列的标识符
- cmd:在消息队列上操作的相关命令
- IPC_RMID:立即删除消息队列对象及其关联的msqid_ds数据结构,队列中所有消息会丢失,所有被阻塞的读者和写者进程立即醒来,msgsnd和msgrcv会失败返回EIDRM,这个操作会忽略第三个参数
- IPC_STAT:将这个消息队列关联的msgid_ds数据结构的副本保存在buf指向的缓冲区
- IPC_SET:将buf指向的缓冲区提供的值更新这个消息队列关联的msqid_ds数据结构中的被选中的字段
1.4 消息队列关联数据结构
-
每个icp对象的关联数据结构中共有的一部分:
- 该部分的讲解在第45章中已经完成,这里主要对消息队列关联数据结构独有的字段进行分析
-
消息队列的关联数据结构
- msgqnum、msglen_t属于无符号整型
- msg_stime:队列创建之后,初始化为0,每次msgsnd调用会将这个字段设置为当前时间,新纪元到现在的秒数
- msg_stime:队列创建之后,初始化为0,每次msgsrcv调用会将这个字段设置为当前时间,新纪元到现在的秒数
- msg_ctime:队列创建之后,初始化为当前时间,每次IPC_SET操作会将这个字段设置为当前时间,新纪元到现在的秒数
- __msg_cbytes:队列创建之后,初始化为0,后续每个msgsnd或者msgsrcv调用都会把字段更新为队列中所有mtext字段包含的字节的总和
- msg_qnum:队列创建之后,初始化为0,后续每个msgsnd调用递增这个字段,后续每个msgrcv调用递减这个字段,以反映出队列中消息的总数
- msg_qbytes:这个字段为队列中所有消息的mtext字段包含的字节的总和设置了一个上限,队列创建之后,初始化为MSGMNB,特权进程可以使用IPC_SET操作更改该字段的值在0到INT_MAX之间,也可以在/proc/sys/kernel/msgmnb文件中做修改,以保证后续队列创建初始值以及非特权进程所能进行的修改
- msg_lspid:队列创建之后,初始化为0,后续每个msgsnd调用,会把该字段的值设置为调用进程的pid
- msg_lrpid:队列创建之后,初始化为0,后续每个msgrcv调用,会把该字段的值设置为调用进程的pid
1.5 消息队列的限制
- 大多数UNIX实现会对System V消息队列操作施加各种各样的限制,比如系统级创建消息队列的数量、单条消息可以写入的字节数、一个消息队列一次最多可以保存的字节数、系统中消息队列能存放的消息总数,linux下可以在/proc/sys/kernel里面查看并且修改这些限制 ,具体可以查阅书籍本章节
1.6 使用消息队列实现客户端-服务器应用程序
- 服务器和客户端之间可以采用一个消息队列,但是这里会存在一些问题:
- 注意区分消息类型,这里一般用进程ID作为消息类型
- 多个并行的写入可能会造成死锁等问题
- 所以我们这里最好采用多个消息队列,每一个客户端一个,每一个服务器一个,但死锁的问题还是需要自己解决
1.7 使用System V 消息队列的缺点:
- 优点:可以根据消息类型选择性的读取队列中的消息
- 缺点:
- 消息队列的引用采用标识符,而不是文件描述符
- 使用键而不是文件名来标识消息队列会增加额外的程序设计复杂性
- 内核不会对使用消息队列的进程引用计数
- 消息队列有很多种限制
- 总体上来讲最好避免使用System V 消息队列,应该尽量考虑其它方案,比如POSIX 消息队列,更加有难度的是基于多文件描述符的通信信道