消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的,只有内核重启或人工删除时,该消息队列才会被删除。
对于系统中的每个消息队列,内核维护一个定义在 sys/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 */
__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 */
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 */
};
我们可以将内核中的某个特定的消息队列画为一个消息链表,如图假设有一个具有三个消息的队列,消息长度分别为1字节,2字节和3字节,而且这些消息就是以这样的顺序写入该队列的。再假设这三个消息的类型分别100,200,300.
system V 消息队列运用的一系列函数有如下:
###msgget 函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgget(key_t key,int flag)
返回值:若成功,返回消息队列 ID;若出错,返回-1
参数说明:
- Key:是基于指定的key产生的,而key即可以是ftok的返回值,也可以是常值IPC_PRIVATE
- Flag:oflag是读写权限值的组合
###msgsnd函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgsnd(int msqid,const void *prt,size_t size,int flag)
返回值:若成功,返回0;若出错,返回-1
参数说明:
- msqid:消息队列的队列 ID
- prt:指向消息结构的指针。
- size:消息的字节数,不要以 null 结尾
- flag:
- IPC_NOWAIT: 若消息并没有立即发送而调用进程会立即返回
- 0:msgsnd 调用阻塞直到条件满足为止
ptr是一个结构指针,该结构具有如下模板,它定义在 sys/msg.h 中,如下所示:
struct msgbuf{
long mtype;//消息类型
char mtext[1];//消息正文
}
###msgrcv函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgrcv(int msgid,struct msgbuf *msgp,int size,long msgtype,int flag)
返回值:若成功,返回接收到消息的bytes;若出错,返回-1
参数说明:
-
msqid:消息队列的队列 ID
-
msgp:消息缓冲区
-
size:消息的字节数,不要以 null 结尾
-
Msgtype:
- 0:接收消息队列中第一个消息
- 大于 0:接收消息队列中第一个类型为 msgtyp 的消息
- 小于 0:接收消息队列中第一个类型值不小于 msgtyp 绝对值且类型值又最小的消息
-
flag:
- MSG_NOERROR:若返回的消息比 size 字节多,则消息就会截短到size 字节,且不通知消息发送进程
- IPC_NOWAIT: 若消息并没有立即发送而调用进程会立即返回
- 0:msgsnd 调用阻塞直到条件满足为止
###msgctl 函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf )
返回值:若成功,返回0;若出错,返回-1
参数说明:
-
msqid:消息队列的队列 ID
-
cmd:
- IPC_STAT:读取消息队列的数据结构 msqid_ds,并将其存储在buf 指定的地址中
- IPC_SET:设置消息队列的数据结构 msqid_ds 中的 ipc_perm 元素的值。这个值取自 buf 参数
- IPC_RMID:从系统内核中移走消息队列
-
Buf:消息队列缓冲区
###示例程序:
/* message_queues.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFF 1024
//msg_type说明:
struct msg_st
{
long int msg_type;
char text[BUFF];
};
//子进程发送消息,父进程接收消息
int main ()
{
pid_t pid;
struct msg_st data;
char buffer[BUFF];
key_t key;
int qid,len;
//系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
//根据不同的路径和关键表示产生标准的 key
if((key=ftok(".",1))== -1)
{
perror("ftok");
exit(-1);
}
/*创建消息队列*/
if((qid = msgget(key,IPC_CREAT|0666))== -1)
{
perror("msgget");
exit(-1);
}
printf("opened queue %d\n",qid);
if ((pid = fork()) < 0)
{
perror("fork");
exit(-1);
}
else if (pid == 0) //子进程
{
//输入数据
printf("Enter some text: ");
fgets(buffer, BUFF, stdin);
data.msg_type = 2; //注意 !发送的信息的类型为2
strcpy(data.text, buffer);
len = strlen(data.text);
//向队列发送数据
if(msgsnd(qid, (void*)&data, len, 0) == -1)
{
perror("msgsnd");
exit(-1);
}
sleep(2);
return 0;
}
else //父进程
{
//读取消息队列,没读取到数据的时候会阻塞
//第四个参数 0:接收消息队列中第一个消息;大于 0:接收消息队列中第一个类型为 msgtyp 的消息
if(msgrcv(qid,&data,BUFF,2,0)<0)
{
perror("msgrcv");
exit(-1);
}
printf("recv message from child:%s\n",(&data)->text);
printf("Waiting for the child process to exit\n");
//等待子进程退出
waitpid(pid,NULL,0);
printf("remove the Message Queues\n");
/*从系统内核中移除该消息队列。*/
if((msgctl(qid,IPC_RMID,NULL))<0)
{
perror("msgctl");
exit(1);
}
printf("The child process has exited,now exit the father process\n");
exit(0);
}
return 0;
}
实验结果:
ubuntu:~/test/process_test$ gcc message_queues.c -o message_queues
ubuntu:~/test/process_test$ ./message_queues
opened queue 163840
Enter some text: 1354
recv message from child:1354
Waiting for the child process to exit
remove the Message Queues
The child process has exited,now exit the father process
以上程序,先是调用了msgget函数创建消息队列,key是基于ftok函数产生的。然后fork出子进程,在子进程中调用msgsnd函数往消息队列中添加数据,父进程中调用msgrcv函数从消息队列中获取数据后,就调用msgctl函数,把该消息队列从系统内核中移除。这里需要注意消息类型,msgrcv函数的第四个参数,为0表示接收消息队列中第一个消息,大于0表示接收消息队列中第一个类型为 msgtyp 的消息。所以,接收消息和发送消息的类型一定要对应。
我们这里把msgsnd函数消息类型改成data.msg_type = 1,其他不变,实验一下父进程能不能接收到消息。
实验结果:
ubuntu:~/test/process_test$ gcc message_queues.c -o message_queues
ubuntu:~/test/process_test$ ./message_queues
opened queue 196608
Enter some text: 123456
实验结果是,父进程一直阻塞在msgrcv函数,父进程读不到消息类型为1的消息,因为父进程把消息类型指定为2。这个时候再用ipcs -q 查看,结果如下:
ubuntu:~/test/process_test$ ipcs -q
------ Message Queues --------
key msqid perms used-bytes messages
0x011f24c7 196608 666 7 1
因为是手动结束的进程,父进程并没有调用msgctl函数把该消息队列从系统内核中移除。所以,我们创建的消息队列并没有销毁。这也证实了我们前面所说的“消息队列是随内核持续的,只有内核重启或人工删除时,该消息队列才会被删除”。
把msgsnd函数消息类型改成data.msg_type = 2,把程序编译后执行一遍。执行ipcs命令,结果如下,说明消息队列已被移除。
ubuntu:~/test/process_test$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
接下来,我们把发送消息和接收消息各写成一个程序,然后在不同的终端试验一下。
/* msgsend.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFF 1024
struct msg_st
{
long int msg_type;
char text[BUFF];
};
int main ()
{
struct msg_st data;
char buffer[BUFF];
key_t key;
int qid,len;
//系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
//根据不同的路径和关键表示产生标准的 key
if((key=ftok(".",1))== -1)
{
perror("ftok");
exit(-1);
}
printf("key = 0x%x\n",key);
/*创建消息队列*/
if((qid = msgget(key,IPC_CREAT|0666))== -1)
{
perror("msgget");
exit(-1);
}
printf("opened queue %d\n",qid);
while(1)
{
//输入数据
printf("Enter some text: ");
fgets(buffer, BUFF, stdin);
data.msg_type = 2; //注意 !发送的信息的类型为2
strcpy(data.text, buffer);
len = strlen(data.text);
//向队列发送数据
if(msgsnd(qid, (void*)&data, len, 0) == -1)
{
perror("msgsnd");
exit(-1);
}
}
return 0;
}
/* msgrcv.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFF 1024
//msg_type说明:
struct msg_st
{
long int msg_type;
char text[BUFF];
};
int main ()
{
struct msg_st data;
char buffer[BUFF];
key_t key;
int qid,len,i;
//系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
//根据不同的路径和关键表示产生标准的 key
if((key=ftok(".",1))== -1)
{
perror("ftok");
exit(-1);
}
printf("key = 0x%x\n",key);
/*创建消息队列*/
if((qid = msgget(key,IPC_CREAT|0666))== -1)
{
perror("msgget");
exit(-1);
}
printf("opened queue %d\n",qid);
for(i = 0;i < 3;i++)
{
//读取消息队列,没读取到数据的时候会阻塞
//第四个参数 0:接收消息队列中第一个消息;大于 0:接收消息队列中第一个类型为 msgtyp 的消息
if(msgrcv(qid,&data,BUFF,2,0)<0)
{
perror("msgrcv");
exit(-1);
}
printf("recv message from child:%s\n",(&data)->text);
}
/*从系统内核中移除该消息队列。*/
if((msgctl(qid,IPC_RMID,NULL))<0)
{
perror("msgctl");
exit(1);
}
return 0;
}
A终端,执行程序,根据提示依次输入"123",“456”,“789”。
ubuntu:~/test/process_test$ ./msgsend
key = 0x11f24c7
opened queue 229376
Enter some text: 123
Enter some text: 456
Enter some text: 789
Enter some text: 9
msgsnd: Invalid argument
B终端,每当A终端输入字符串,B终端就显示相应的字符串。
ubuntu:~/test/process_test$ ./msgrcv
key = 0x11f24c7
opened queue 229376
recv message from child:123
recv message from child:456
recv message from child:789
C终端
A终端输入123后执行ipcs -q 命令
ubuntu:~$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x011f24c7 229376 chenting 666 0 0
程序结束后执行ipcs -q 命令
ubuntu:~$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
实验表明,在非亲缘关系进程中,消息队列也同样可以操作。情况和示例程序 message_queues.c 分析的差不多。