XSI IPC通信之消息队列
程序员文章站
2022-03-07 19:14:37
...
消息队列是消息的链接表,存储在内核中,由消息队列标识符(或称为队列ID)标识。
下表列出了影响消息队列的系统限制。其中“导出的”表示这种限制来源于其他限制。比如 Linux 系统中的最大消息数是根据最大队列数和队列中所允许的最大数据量来决定的,最大队列数又受 RAM 数量多少的影响,队列的最大字节数限制进一步限制了队列中将要存储的消息的最大长度。
每个队列都关联有一个 msqid_ds 结构,它定义了队列的当前状态,至少支持下面这些字段,其中的 ipc_perm 结构见XSI IPC 相似特征介绍一节:
msgget 函数用于创建一个新队列或打开一个现有队列。msgsnd 将新消息添加到队列尾端。msgrcv 用于从队列中取消息,但不一定是以先进先出次序取,也可以按消息的类型字段来取。msgctl 可对队列执行多种操作,它和后面章节中要介绍的另外两个与信号量及共享存储有关的函数 semctl 和 shmctl 都是 XSI IPC 的类似于 ioctl 的函数(亦即垃圾桶函数)。
在XSI IPC 相似特征介绍一节中说明了将 key 变换成一个标识符的规则。
使用 msgget 函数在创建新队列时,会初始化 msqid_ds 结构的下列成员。
* ipc_perm 结构按XSI IPC 相似特征介绍一节中的所述进行初始化,其中的 mode 成员按 flag 中的相应权限位设置。
* msg_qnum、msg_lspid、msg_lrpid、msg_stime 和 msg_rtime 都设置为 0。
* msg_ctime 设置为当前时间。
* msg_qbytes 设置为系统限制值。
msgget 执行成功后返回的非负队列 ID 就可用于其他 3 个消息队列函数。
msqsnd 函数将消息添加到队列尾端。每个消息包含一个正的长整型的字段、一个非负的长度 nbytes 以及对应于长度的实际数据字节数。msgsnd 中的 ptr 参数指向一个长整型数,它包含了正的整型消息类型,其后紧接着的是消息数据(若 nbytes 是 0 则无数据)。比如发送的最长消息是 512 字节,则可定义类似下列结构:
struct mymesg{
long mtype; // positive message type
char mtext[512]; // message data, of length nbytes
};
此时参数 ptr 就是一个指向 mymesg 结构的指针。接收者可以使用消息类型以非先进先出的次序取消息。要注意的是,有些平台同时支持 32 位和 64 位环境,这会影响到长整型和指针的大小。如果一个 32 位应用程序要经由管道或套接字与一个 64 位应用程序交换此结构,就会出问题。因为长整型的大小在 32 位环境中是 4 字节,而 64 位环境中则是 8 字节。在这种情况下,64 位应用程序的 mtype 字段的一部分会被 32 位应用程序视为 mtext 字段的组成部分,而 32 位应用程序的 mtext 字段的前 4 个字节会被 64 位应用程序解释为 mtype 字段的组成部分。即使对 IPC 系统调用的 32 位和 64 位版本具有不同的入口点,也存在潜在问题:当 64 位应用程序向 32 位应用程序发送消息时,如果它在 8 字节类型字段中设置的值大于 32 位应用程序中 4 字节类型字段可表示的值,那么 32 位应用程序在其 mtype 字段中得到的将是一个截短了的类型值。
参数 flag 的值可以指定为 IPC_NOWAIT,这类似于文件 I/O 的非阻塞标志。若消息队列已满(或是队列中的消息总数或者队列中的字节总数等于了系统限制值),则指定 IPC_NOWAIT 可使 msgsnd 立即出错返回 EAGAIN,否则进程会一直阻塞到:有空间可以容纳要发送的消息;或者从系统中删除了此队列(此时会返回 EIDRM 错误);或者捕捉到一个信号,并从信号处理程序返回(此时会返回 EINTR 错误)。
msgsnd 成功返回时,消息队列相关的 msqid_ds 结构也会随之更新。
和 msgsnd 一样,msgrcv 中的 ptr 参数指向一个长整型数(其中存储的是返回的消息类型),其后跟随的是存储实际消息数据的缓冲区。nbytes 参数指定数据缓冲区的长度。若返回的消息长度大于 nbytes,而且在 flag 中设置了 MSG_NOERROR 位,则该消息会被自动截断,并丢弃多余的部分。如果消息太长时没设置此标志,则出错返回 E2BIG,但消息仍留在队列中。
参数 type 可通过设置为下列值来指定想要哪一种消息。
* 等于 0:返回队列中的第一个消息。
* 大于 0:返回队列中消息类型为 type 的第一个消息。
* 小于 0:返回队列中消息类型值不大于 type 绝对值的消息。如果这种消息有若干个,则取类型值最小的消息。
type 值非 0 可用于以非先进先出次序读消息,例如可以用来代表优先级权值,或是表示客户进程的进程 ID 等。
flag 参数也可指定为 IPC_NOWAIT 标志,以使操作不阻塞。这样,如果没有所指定类型的消息可用,则立即返回 -1,并将 errno 设置为 ENOMSG。没指定此标志时的阻塞情况同 msgsnd。
msgrcv 执行成功时,msqid_ds 结构同样也会更新。
msgctl 函数中的 cmd 参数指定了对 msqid 参数表示的队列要执行的下列命令之一。
* IPC_STAT:取此队列的 msqid_ds 结构,并将其存放在 buf 指向的结构中。
* IPC_SET:将字段 msg_perm.uid、msg_perm.gid、msg_perm.mode 和 msg_qbytes 从 buf 指向的结构复制到此队列关联的 msqid_ds 结构中。此命令只能由下列两种进程执行:一种是其有效用户 ID 等于 msg_perm.cuid 或 msg_perm.uid,另一种是具有超级用户特权的进程。只有超级用户才能增加 msg_qbytes 的值。
* IPC_RMID:从系统中删除该消息队列以及仍在其中的所有数据。这种删除立即生效,仍在使用这个队列的其他进程在下一次试图操作此队列时将得到 EIDRM 错误。该命令也只能由上面提及的两种进程执行。注意,由于消息队列没有像文件一样维护引用计数器,所以继续操作被删除的队列的进程会出错返回,而不是像文件操作中需要等最后一个进程关闭了文件描述符后才能删除文件中的内容。后面要介绍的信号量机制也以这种方式处理其删除。
这 3 条命令也可用于后文中要介绍的信号量和共享存储。
最后要提醒的一点是,由于这种消息队列现在在速度方面已经和其他的 IPC 方式没有什么差别了,考虑到使用消息队列时遇到的问题,在新的应用程序中已经不再推荐使用了。
下表列出了影响消息队列的系统限制。其中“导出的”表示这种限制来源于其他限制。比如 Linux 系统中的最大消息数是根据最大队列数和队列中所允许的最大数据量来决定的,最大队列数又受 RAM 数量多少的影响,队列的最大字节数限制进一步限制了队列中将要存储的消息的最大长度。
每个队列都关联有一个 msqid_ds 结构,它定义了队列的当前状态,至少支持下面这些字段,其中的 ipc_perm 结构见XSI IPC 相似特征介绍一节:
struct msqid_ds{ struct ipc_perm msg_perm; msgqnum_t msg_qnum; // # of messages on queue msglen_t msg_qbytes; // max # of bytes on queue pid_t msg_lspid; // pid of last msgsnd() pid_t msg_lrpid; // pid of last msgrcv() time_t msg_stime; // last-msgsnd() time time_t msg_rtime; // last-msgrcv() time time_t msg_ctime; // last-change time /* ... */ };
msgget 函数用于创建一个新队列或打开一个现有队列。msgsnd 将新消息添加到队列尾端。msgrcv 用于从队列中取消息,但不一定是以先进先出次序取,也可以按消息的类型字段来取。msgctl 可对队列执行多种操作,它和后面章节中要介绍的另外两个与信号量及共享存储有关的函数 semctl 和 shmctl 都是 XSI IPC 的类似于 ioctl 的函数(亦即垃圾桶函数)。
#include <sys/msg.h> int msgget(key_t key, int flag); /* 返回值:若成功,返回消息队列 ID;否则,返回 -1 */ int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag); /* 返回值:若成功,返回 0;否则,返回 -1 */ ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag); /* 返回值:若成功,返回消息数据部分的长度;否则,返回 -1 */ int msgctl(int msqid, int cmd, struct msqid_ds *buf); /* 返回值:若成功,返回 0;否则,返回 -1 */
在XSI IPC 相似特征介绍一节中说明了将 key 变换成一个标识符的规则。
使用 msgget 函数在创建新队列时,会初始化 msqid_ds 结构的下列成员。
* ipc_perm 结构按XSI IPC 相似特征介绍一节中的所述进行初始化,其中的 mode 成员按 flag 中的相应权限位设置。
* msg_qnum、msg_lspid、msg_lrpid、msg_stime 和 msg_rtime 都设置为 0。
* msg_ctime 设置为当前时间。
* msg_qbytes 设置为系统限制值。
msgget 执行成功后返回的非负队列 ID 就可用于其他 3 个消息队列函数。
msqsnd 函数将消息添加到队列尾端。每个消息包含一个正的长整型的字段、一个非负的长度 nbytes 以及对应于长度的实际数据字节数。msgsnd 中的 ptr 参数指向一个长整型数,它包含了正的整型消息类型,其后紧接着的是消息数据(若 nbytes 是 0 则无数据)。比如发送的最长消息是 512 字节,则可定义类似下列结构:
struct mymesg{
long mtype; // positive message type
char mtext[512]; // message data, of length nbytes
};
此时参数 ptr 就是一个指向 mymesg 结构的指针。接收者可以使用消息类型以非先进先出的次序取消息。要注意的是,有些平台同时支持 32 位和 64 位环境,这会影响到长整型和指针的大小。如果一个 32 位应用程序要经由管道或套接字与一个 64 位应用程序交换此结构,就会出问题。因为长整型的大小在 32 位环境中是 4 字节,而 64 位环境中则是 8 字节。在这种情况下,64 位应用程序的 mtype 字段的一部分会被 32 位应用程序视为 mtext 字段的组成部分,而 32 位应用程序的 mtext 字段的前 4 个字节会被 64 位应用程序解释为 mtype 字段的组成部分。即使对 IPC 系统调用的 32 位和 64 位版本具有不同的入口点,也存在潜在问题:当 64 位应用程序向 32 位应用程序发送消息时,如果它在 8 字节类型字段中设置的值大于 32 位应用程序中 4 字节类型字段可表示的值,那么 32 位应用程序在其 mtype 字段中得到的将是一个截短了的类型值。
参数 flag 的值可以指定为 IPC_NOWAIT,这类似于文件 I/O 的非阻塞标志。若消息队列已满(或是队列中的消息总数或者队列中的字节总数等于了系统限制值),则指定 IPC_NOWAIT 可使 msgsnd 立即出错返回 EAGAIN,否则进程会一直阻塞到:有空间可以容纳要发送的消息;或者从系统中删除了此队列(此时会返回 EIDRM 错误);或者捕捉到一个信号,并从信号处理程序返回(此时会返回 EINTR 错误)。
msgsnd 成功返回时,消息队列相关的 msqid_ds 结构也会随之更新。
和 msgsnd 一样,msgrcv 中的 ptr 参数指向一个长整型数(其中存储的是返回的消息类型),其后跟随的是存储实际消息数据的缓冲区。nbytes 参数指定数据缓冲区的长度。若返回的消息长度大于 nbytes,而且在 flag 中设置了 MSG_NOERROR 位,则该消息会被自动截断,并丢弃多余的部分。如果消息太长时没设置此标志,则出错返回 E2BIG,但消息仍留在队列中。
参数 type 可通过设置为下列值来指定想要哪一种消息。
* 等于 0:返回队列中的第一个消息。
* 大于 0:返回队列中消息类型为 type 的第一个消息。
* 小于 0:返回队列中消息类型值不大于 type 绝对值的消息。如果这种消息有若干个,则取类型值最小的消息。
type 值非 0 可用于以非先进先出次序读消息,例如可以用来代表优先级权值,或是表示客户进程的进程 ID 等。
flag 参数也可指定为 IPC_NOWAIT 标志,以使操作不阻塞。这样,如果没有所指定类型的消息可用,则立即返回 -1,并将 errno 设置为 ENOMSG。没指定此标志时的阻塞情况同 msgsnd。
msgrcv 执行成功时,msqid_ds 结构同样也会更新。
msgctl 函数中的 cmd 参数指定了对 msqid 参数表示的队列要执行的下列命令之一。
* IPC_STAT:取此队列的 msqid_ds 结构,并将其存放在 buf 指向的结构中。
* IPC_SET:将字段 msg_perm.uid、msg_perm.gid、msg_perm.mode 和 msg_qbytes 从 buf 指向的结构复制到此队列关联的 msqid_ds 结构中。此命令只能由下列两种进程执行:一种是其有效用户 ID 等于 msg_perm.cuid 或 msg_perm.uid,另一种是具有超级用户特权的进程。只有超级用户才能增加 msg_qbytes 的值。
* IPC_RMID:从系统中删除该消息队列以及仍在其中的所有数据。这种删除立即生效,仍在使用这个队列的其他进程在下一次试图操作此队列时将得到 EIDRM 错误。该命令也只能由上面提及的两种进程执行。注意,由于消息队列没有像文件一样维护引用计数器,所以继续操作被删除的队列的进程会出错返回,而不是像文件操作中需要等最后一个进程关闭了文件描述符后才能删除文件中的内容。后面要介绍的信号量机制也以这种方式处理其删除。
这 3 条命令也可用于后文中要介绍的信号量和共享存储。
最后要提醒的一点是,由于这种消息队列现在在速度方面已经和其他的 IPC 方式没有什么差别了,考虑到使用消息队列时遇到的问题,在新的应用程序中已经不再推荐使用了。
下一篇: 用C语言实现圣诞树(简易版+进阶版)