消息队列
概念
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
本质
是存储在内核中的一个消息的队列(链表)
特点
- 和管道一样,每个消息的最大长度是有上限的(MSGMAX)(16),每个消息队列的总字节数也是有上限的(MSGMNB)(8192),系统上的消息队列总数也是有上限的(MSGMNI)(16384)
- 是一个全双工通信,可读可写,双向通讯。
- 生命内核随周期,关机即结束消息队列。
查看消息队列
命令行输入 ipcs -q
运行结果示例如下
IPC对象数据结构
IPC对象数据结构:/usr/include/linux/ipc.h
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
消息队列数据结构
消息队列结构:/usr/include/linux/msg.h
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
};
消息结构
struct msgbuf {
long mtype; /* type of message */
char mtext[1]; /* message text */
};
函数
msgget(创建消息队列)
int msgget(key_t key, int msgflg); //成功时返回消息队列ID,出错返回-1
key:内核中消息队列的标识
msgflg:
IPC_CREAT 不存在则创建,存在则打开
IPC_EXCL 与IPC_CREAT同时使用,存在则报错
msgrcv(从消息队列接收消息)
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
//调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。
- msgid:msgget函数返回的消息队列标识符。
- msg_ptr:指向准备接收消息的结构体指针
- msg_st:msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。
- msgtype:msgtype可以实现一种简单的接收优先级,消息是按照接收顺序存于消息队列中,每条消息对应一个消息类型号(频道号)。如果msgtype为0,就获取队列中的第一条消息。如果它的值大于零,将获取具有相同类型号(频道号)的第一条消息。如果它小于零,就获取频道号等于或小于msgtype的绝对值的第一个消息。
- msgflg:用于控制当队列中没有相应类型的消息可以接收时将发生的事情。msgflg=0,阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待;msgflg=IPC_NOWAIT,如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG;msgflg=IPC_EXCEPT,与msgtype配合使用返回队列中第一个类型不为msgtype的消息;msgflg=IPC_NOERROR,如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃。
msgsnd (给消息队列发送消息)
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
//如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1。msgflg=0,当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列;msgflg=IPC_NOWAIT,当消息队列已满的时候,msgsnd函数不等待立即返回;
- msgid:msgget函数返回的消息队列标识符。
- msg_ptr:指向准备发送消息的结构体指针。
- msg_sz:msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。
- msgflg:用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。
msgctl(释放消息队列)
int msgctl(int msgid, int command, struct msgid_ds *buf);
//返回值:成功时返回0,失败时返回-1。
-
msgid:msgget函数返回的消息队列标识符。
-
command:是将要采取的动作,它可以取3个值:
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
-
buf:指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。
ftok(通过文件的inode节点号和一个proj_id计算出一个key值)
key_t ftok(const char *path, int id);
缺点:如果文件被删除或替换生,通过key值打开的不是同一个msgqueue
示例代码
server1.c
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>
#define TYPE_SER 1
#define TYPE_CLI 2
struct msgbuf {
long mtype;
char mtext[1024];
};
int main()
{
int msgid = -1;
int key = ftok("./fork.c", 'a'); //fork.c是我文件夹中任一文件 记得替换成自己的文件
if(key < 0){
perror("ftok error");
return -1;
}
msgid = msgget(key, IPC_CREAT | 0664);
if (msgid < 0) {
perror("msgget error");
return -1;
}
while(1){
struct msgbuf buf;
memset(&buf, 0x00, sizeof(struct msgbuf));
buf.mtype = TYPE_CLI;
scanf("%s", buf.mtext);
msgsnd(msgid, &buf, 1024, 0);
msgrcv(msgid, &buf, 1024, TYPE_SER, 0);
printf("lzr:[%s]\n", buf.mtext);
}
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
client1.c
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>
#define TYPE_SER 1
#define TYPE_CLI 2
struct msgbuf {
long mtype;
char mtext[1024];
};
int main()
{
int msgid = -1;
int key = ftok("./fork.c", 'a'); //fork.c是我文件夹中任一文件 记得替换成自己的文件 保证客户端和服务器的文件名相同
if(key < 0){
perror("ftok error");
return -1;
}
msgid = msgget(key, IPC_CREAT | 0664);
if(msgid < 0){
perror("msgget error");
return -1;
}
while(1){
struct msgbuf buf;
msgrcv(msgid, &buf, 1024, TYPE_CLI, 0);
printf("sx:[%s]\n", buf.mtext);
memset(&buf, 0x00, sizeof(struct msgbuf));
buf.mtype = TYPE_SER;
scanf("%s", buf.mtext);
msgsnd(msgid, &buf, 1024, 0);
}
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
运行结果如下图:
查看消息队列如下图所示:
注:此代码我更换过key,所以会出现两个key
不过此代码也有不足的地方,因为最佳体现方式是服务器和客户轮流发送消息,否则通讯效果不佳,后续我会尝试用消息队列和父子进程的方式尝试是否能解决此类问题。
如有疑问或者质疑的地方欢迎评论区留言哦!