System V IPC – 消息队列
-
消息队列的创建
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg);
返回值:成功返回消息队列的标识符,失败返回-1并置错误码
参数1:key值(通常是IPC_PRIVATE或者使用ftok生成:https://blog.csdn.net/weixin_48617416/article/details/120435007)
参数2:消息队列的标志位,可以加消息队列的权限和标记,如
- IPC_CREAT:如果没有与指定的key对应的消息队列,那么就创建一个消息队列,并返回该队列的标识符;如果找到那么就返回该对象的标识符。
- IPC_EXCL:在指定IPC_CREAT的前提下使用该标记,并且与指定的key对应的队列已经存在,那么就会调用失败,并返回EEXIST错误。
代码示例:
#include <stdio.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> int main(int argc, char **argv) { //可以使用ftok生成key值 /* key_t key = ftok("./msgque.c", 1); */ //创建消息队列,key值也可以指定某个整型值,第一次调用是创建消息队列, //当消息队列存在之后,再执行该代码,不会再创建新的消息队列 int msgid = msgget(128, IPC_CREAT|0660); printf("msgid = %d\n", msgid); return 0; } 输出结果: msgid = 131072 (ps:环境:Ubuntu18.04,经过测试第一次创建返回的id是0,删除后再重新创建就是一个非零值了)
通过ipcs命令可以查看是否创建成功
[wy@wy ~/textCode/text/ipc]$ ipcs --------- 消息队列 ----------- 键 msqid 拥有者 权限 已用字节数 消息 0x00000080 131072 wy 660 0 0
可以看到,msqid是和程序输出的结果是相同的,说明创建成功。仔细观察键值0x00000080是十六进制的值,转换成10进制后是128,正好是msgget的第一个参数!
创建的消息队列可以通过ipcrm命令删除
- ipcrm -Q + 键值
- ipcrm -q + msqid
也可以使用msgctl()函数删除
-
消息的发送
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
返回值:成功返回0,失败返回-1并置错误码
参数1:msgget返回值
参数2:指向一个结构体的指针,结构体的定义如下
struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[1]; /* message data */ };
- 第一个成员是消息类型,值必须大于0,
- 第二个成员存储发送的消息,可以看到此时数组大小为1,显然不太满足实际的需求,所以需要程序员复写该结构体,为保证程序健壮性,建议只修改结构体数组大小即可!
参数3:指定mtext字段包含的字节数
参数4:标记位,控制msgsnd的操作
代码示例:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> //复写msgbuf结构体 struct mbuf { long mtype; char mtext[64]; }; int main(int argc, char **argv) { //创建消息队列,key值也可以指定某个整型值 int msgid = msgget(128, IPC_CREAT|0660); printf("msgid = %d\n", msgid); struct mbuf msg; memset(&msg, 0, sizeof(msg)); //接收消息类型 msg.mtype = atoi(argv[1]); //接收消息,保存到数组当中 strcpy(msg.mtext, argv[2]); msgsnd(msgid, &msg, strlen(msg.mtext), 0); return 0; }
[wy@wy ~/textCode/text/ipc]$ ./msgque 1 hello [wy@wy ~/textCode/text/ipc]$ ipcs -q --------- 消息队列 ----------- 键 msqid 拥有者 权限 已用字节数 消息 0x00000080 131072 wy 660 5 1
向消息对列里发送了5个字节的数据,数据类型为1,可以看到通过ipcs命令查看到的信息有了变化!
-
消息的接收
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
返回值:成功返回接收到的字节数,失败返回-1,并置错误码
参数1、2同msgsnd
参数3:最大接收的数据量
参数4:接收的数据类型
参数5:标志位
代码示例:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> //复写msgbuf结构体 struct mbuf { long mtype; char mtext[64]; }; int main(int argc, char **argv) { int msgid = msgget(128, IPC_CREAT|0660); struct mbuf msg; memset(&msg, 0, sizeof(msg)); //接收消息类型(不同于msgsnd,msgrcv函数的mtype没有必须大于0的限制 msg.mtype = atoi(argv[1]); msgrcv(msgid, &msg, sizeof(msg.mtext), msg.mtype, 0); printf("msg = %s\n", msg.mtext); return 0; }
[wy@wy ~/textCode/text/ipc]$ ./msgrcv 1 msg = hello [wy@wy ~/textCode/text/ipc]$ ipcs -q --------- 消息队列 ----------- 键 msqid 拥有者 权限 已用字节数 消息 0x00000080 131072 wy 660 0 0
如果执行接收代码,传入的消息类型不在消息队列中,msgrcv是会阻塞的,可以将msgflg设置为IPC_NOWAIT使其变成非阻塞的!
-
消息队列和FIFO很像,但是两者差异很大,最大的莫过于,管道是基于字节流进行通信的,数据之间是没有边界的,而消息队列是有的,如下
[wy@wy ~/textCode/text/ipc]$ ./msgque 1 hello [wy@wy ~/textCode/text/ipc]$ ./msgque 1 world [wy@wy ~/textCode/text/ipc]$ ipcs -q --------- 消息队列 ----------- 键 msqid 拥有者 权限 已用字节数 消息 0x00000080 131072 wy 660 10 2
向消息队列里写入了两条数据类型为1的数据
[wy@wy ~/textCode/text/ipc]$ ./msgrcv 1 msg = hello [wy@wy ~/textCode/text/ipc]$ ./msgrcv 1 msg = world
两次接收,每次只接收一次传入的数据,而且接收顺序是按照消息入队顺序(先进先出)接收的
-
下面传入一些不同类型的数据,以及使用不同方式的接收,来看一下消息队列的特性
-
[wy@wy ~/textCode/text/ipc]$ ./msgque 1 hello [wy@wy ~/textCode/text/ipc]$ ./msgque 2 world [wy@wy ~/textCode/text/ipc]$ ./msgrcv 2 msg = world [wy@wy ~/textCode/text/ipc]$ ./msgrcv 1 msg = hello
传入了类型1、2的数据,先接收2类型的数据,并不会造成阻塞,此时队列的特点是只对相同类型的数据起作用
-
[wy@wy ~/textCode/text/ipc]$ ./msgque 1 hello [wy@wy ~/textCode/text/ipc]$ ./msgque 2 world [wy@wy ~/textCode/text/ipc]$ ./msgrcv 0 msg = hello [wy@wy ~/textCode/text/ipc]$ ./msgrcv 0 msg = world
接收时,数据类型填成了0,接收的数据是按入队顺序无差别接收的
-
./msgque 2 hello [wy@wy ~/textCode/text/ipc]$ ./msgque 3 nihao [wy@wy ~/textCode/text/ipc]$ ./msgque 1 world [wy@wy ~/textCode/text/ipc]$ ./msgrcv -2 msg = world [wy@wy ~/textCode/text/ipc]$ ./msgrcv -2 msg = hello [wy@wy ~/textCode/text/ipc]$ ./msgrcv -2 /* 阻塞 */
接收时,数据类型填成了负值,接收时是按照所有小于等于该负值的绝对值的数据类型的大小顺序接收的!所以先接收了类型为1的数据,再接收数据类型为2的数据,第三次接收时,由于对列中没有小于等于该负数绝对值的数据类型了,所以msgrcv阻塞
msgrcv接收数据总结:
mtype 接收数据行为 mtype > 0 按数据类型接收,如果存在多个相同的数据类型的数据,那么按入队先后顺序接收 mtype = 0 无差别数据类型接收,且按入队顺序接收 mtype < 0 接收所有 ≤ |mtype|的类型数据,且按照数据类型的由小到大的顺序依次接收 -
-
消息队列的控制
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
返回值:不同的cmd,成功返回值不同,但是失败都是返回-1,并置错误码
参数1:msgget返回值
参数2:cmd指定在队列上的操作
参数3:指向存储消息队列相关信息的结构体,定义如下
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 in queue (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) */ }; The ipc_perm structure is defined as follows (the uid、gid、mode fields are settable using IPC_SET): struct ipc_perm { key_t __key; /* Key supplied to msgget(2) */ uid_t uid; /* Effective UID of owner */ gid_t gid; /* Effective GID of owner */ uid_t cuid; /* Effective UID of creator */ gid_t cgid; /* Effective GID of creator */ unsigned short mode; /* Permissions */ unsigned short __seq; /* Sequence number */ };
代码示例:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> int main(int argc, char **argv) { int msgid = msgget(128, IPC_CREAT|0660); //删除消息队列,此时忽略第三个参数 /* msgctl(msgid, IPC_RMID, NULL) ; */ struct msqid_ds ds; memset(&ds, 0, sizeof(ds)); //获取消息队列的相关信息 msgctl(msgid, IPC_STAT, &ds); printf("msg_perm = %d, msg_qbytes = %ld\n", ds.msg_perm.mode, ds.msg_qbytes); return 0; } 运行结果: msg_perm = 432(权限转换成8进制正好是660), msg_qbytes = 16384(队列中允许的最大字节数)
-
system V的消息队列存在几个缺点:
- 消息队列通过标识符来使用的,所以没办法使用select、epoll等这个I/O操作!
- 通过key值来生成消息队列,增加了程序的复杂性,无论使用ftok还是IPC_PRIVATE方式都不是最完美的。ftok有生成相同key值的风险,尽管概率很小;IPC_PRIVATE方式随可以生成唯一的队列标识符,但是会对非亲缘关系的进程不可见!
- 消息队列的总数、消息大小、单个队列容量都是有限制的。可以通过
ipcs -lq
查看
-
总结:实际开发当中最好不要使用System V的消息队列!如果在多进程编程中碰到需要按照类型选择消息的工作时,应该采用其它替代方式,如POSIX的消息队列(本文章点赞超过5,将出POSIX的消息队列教程),或者使用基于文件描述符的通信方式。
能力有限,如有错误望各位大佬不吝指正,原创不易,转载请注明出处