linux既支持POSIX标准的消息队列,也支持System V的消息队列。
消息队列是这样的一种进程通信方式,是由一条有消息连接而成的链表,是消息的链式队列,它保存在内核中,通过消息队列的引用标识符来访问。要运行通信的信息被放置在一个预定义的消息结构中,进程生成的消息指明了该消息的类型,并把它放入一个由系统负责维护的消息队列中去。而访问消息队列的进程可以根据消息的类型,有选择地从队列中遵照FIFO原则读取特定类型的消息。消息队列为用户提供了从多个生产者手中获得多元消息的一种手段。
先看几个基本的概念,这些概念在Syste V通信方式中的信号量和共享内存当中同样也是适用的。
标识符:每个System V的进程通信机制中的对象都和唯一的一个引用标识符相联系,如果进程要访问此IPC对象,则需要在系统中传递这个唯一的引用标识符。具有一定权限的任何进程都可以往给定的队列当中放置一个消息,同样,具有一定权限的进程都可以从消息队列当中读取一个消息。
关键字:是用来定位System V中IPC机制的对象的引用标识符的。关键字的类型为key_t,是系统中预先定义好的,它在头文件当中定义,是一个32位的整数。函数ftok()就是用来产生关键字的,它把一个已存在的路径名和一个整数标识符转换成一个ket_t值,称为IPC键(下面会详细介绍这个函数)。其实,这个关键字的作用就是不同进程根据它来创建IPC的标识符的。
消息队列与管道的区别:最主要的区别是管道通信是要求两个进程之间要有亲缘关系,只能承载无格式的字节流,而消息队列当中,通信的两个进程之间可以是完全无关的进程,它是有格式的。至于它与有名管道的区别,首先FIFO是要存储在磁盘上的一种通信方式,而消息队列是在内存中的,它们两个都是以先进先出的方式进程插入与删除的。
与消息队列相关的数据结构主要有两个:一个是msqid_ds消息队列数据结构,用来标识整个消息队列的情况;一个是msg消息队列数据结构,是整个消息队列的主体。
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 */
......
}
其中的msg_perm主要包括整个消息队列的权限(每次IPC操作都会对使用该操作的进程进行一次权限测试),msg_first用来指向消息队列的第一个消息,msg_last用来指向消息队列的最后一个消息。
/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type;
int m_ts; /* message text size */
struct msg_msgseg* next;
void *security;
/* the actual message follows immediately */
};
struct msg消息队列在内存中定义(这里我也不是很理解)。
消息队列模型
下面我们来探讨一下有关消息队列的操作及其实现这些操作的函数。
1.根据关键字生成标识符。
key_t ftok(const char *pathname, int proj_id);
这个函数创建key值的过程当中使用到了pathname中文件属性的st_dev和st_ino。
(31--24)bit:为proj_id的低8位;
(23--16)bit:为该文件的st_dev属性的底8位;
(15--0)bit:为该文件的st_ino属性的低16位。
2.打开或创建消息队列。
int msgget(key_t key, int msgflg);
key为ftok的返回值,msgflg用来设置消息队列的权限。
3.消息队列的属性控制。
int msgctl();
4.发送信息到消息队列。
int msgsnd();
5.从消息队列中接收信息。
int msgrcv();
对于消息队列的操作也就这么多,下面我们看几个实例。
- /*mes_que.c:创建删除消息队列*/
- #include <stdio.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include <sys/types.h>
-
#include <sys/ipc.h>
-
#include <sys/msg.h>
-
-
int main()
-
{
-
key_t key;
-
int mid;
-
-
key = ftok(".", 'a');
-
if((mid = msgget(key, IPC_CREAT|0770)) == -1) {
-
perror("message queue create error");
-
exit(1);
-
}
-
printf("%d\n", mid);
-
system("ipcs -q");
-
msgctl(mid, IPC_RMID, (struct msqid_ds *)0);
-
system("ipcs -q");
-
exit(0);
-
}
这个程序的运行结果是这样的:
- O_O[sunny@sunny-laptop ~/summer/message]168$ gcc mes_que.c
-
^_^[sunny@sunny-laptop ~/summer/message]169$ ./a.out
-
1310720
-
-
------ Message Queues --------
-
key msqid owner perms used-bytes messages
-
0x610978cb 1310720 sunny 770 0 0
-
-
-
------ Message Queues --------
-
key msqid owner perms used-bytes messages
-
-
^_^[sunny@sunny-laptop ~/summer/message]170$
程序说明:使用msgget函数创建一个消息队列,如果这个消息队列存在的话,就会返回此消息队列的标识符,最后调用msgctl函数将创建的这个消息队列删除掉。
下面我们来看一下有关客户——服务器模型的一个例子。
这个是server端的程序:
- /*server.c*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <unistd.h>
-
#include <sys/types.h>
-
#include <sys/ipc.h>
-
#include <sys/msg.h>
-
-
#define TEST 'a'
-
#define MAX_TEXT 100
- /*这个结构体是自己定义的,但是有一点通信双方都要一样*/
- struct mybuf {
-
int type;
-
char msg_text[MAX_TEXT];
-
};
-
-
int main()
-
{
-
int mid, ret;
-
key_t key;
-
char buf[MAX_TEXT];
-
struct mybuf data;
-
-
key = ftok(".", TEST);
-
if(key == -1) {
-
perror("ftok error");
-
exit(1);
-
}
-
mid = msgget(key, IPC_CREAT|0777);
-
if(mid == -1) {
-
perror("msgget error");
-
exit(2);
-
}
-
-
while(1) {
-
ret = msgrcv(mid, (void *)&data, MAX_TEXT, 0, 0);
-
if(strncmp(data.msg_text, "exit", 4) == 0) {
-
break;
-
}
-
if(ret == -1) {
-
exit(3);
-
perror("msgrcv error");
-
}
-
printf("%d %s", data.type, data.msg_text);
-
}
-
msgctl(mid, IPC_RMID, (struct msqid_ds *)0);
-
return 0;
-
}
client端的程序:
- /*client.c*/
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <unistd.h>
-
#include <sys/types.h>
-
#include <sys/ipc.h>
-
#include <sys/msg.h>
-
-
#define TEST 'a'
-
#define MAX_TEXT 100
-
-
/*这个结构体是自己定义的,但是有一点通信双方都要一样*/
-
struct mybuf {
-
int type;
-
char msg_text[MAX_TEXT];
-
};
-
-
int main()
-
{
-
int mid, ret, type;
-
key_t key;
-
char buf[MAX_TEXT];
-
struct mybuf data;
-
-
key = ftok(".", TEST);
-
if(key == -1) {
-
perror("ftok error");
-
exit(1);
-
}
-
mid = msgget(key, IPC_CREAT|0777);
-
if(mid == -1) {
-
perror("msgget error");
-
exit(2);
-
}
-
-
while(1) {
-
fputs("输入你要传送的数据类型:", stdout);
-
setbuf(stdin, NULL);
-
scanf("%d", &type);
-
data.type = type;
-
fputs("输入要传送的字符数据:", stdout);
-
//清空键盘缓冲区,在linux下getchar(),fflush(stdin),rewind(stdin)都不起作用
-
setbuf(stdin, NULL);
-
fgets(buf, MAX_TEXT, stdin);
-
memcpy(data.msg_text, buf, MAX_TEXT);
-
ret = msgsnd(mid, (void *)&data, MAX_TEXT, 0);
-
if(ret == -1) {
-
exit(3);
-
perror("msgsnd error");
-
}
-
if(strncmp(data.msg_text, "exit", 4) == 0) {
-
exit(0);
-
}
-
}
-
-
return 0;
-
}
测试结果:首先打开两个终端,在server端如下:
- ^_^[sunny@sunny-laptop ~/summer/message]110$ gcc server.c -o server
-
^_^[sunny@sunny-laptop ~/summer/message]111$ ./server
-
1 123456
-
2 8719255
-
3 helloworld
-
^_^[sunny@sunny-laptop ~/summer/message]112$
在client端如下:
- ^_^[sunny@sunny-laptop ~/summer/message]83$ gcc client.c -o client
-
^_^[sunny@sunny-laptop ~/summer/message]84$ ./client
-
输入你要传送的数据类型:1
-
输入要传送的字符数据:123456
-
输入你要传送的数据类型:2
-
输入要传送的字符数据:8719255
-
输入你要传送的数据类型:3
-
输入要传送的字符数据:helloworld
-
输入你要传送的数据类型:4
-
输入要传送的字符数据:exit
-
^_^[sunny@sunny-laptop ~/summer/message]85$
程序说明:可以用一句话概括一下这个程序,client端先创建消息队列,然后循环发送类型和数据到消息队列,然后server端循环读取消息,直到读取结束,删除消息队列。 在client端,首先创建了一个消息队列,创建的时候我们使用的参数是IPC_CREAT,这个表示的是如果此消息队列存在的话,则返回此消息队列的标识,如果不存在则创建,并返回创建好的消息队列标识。这里需要注意一下了,在执行了这个函数之后,如果创建了一个消息队列的话,那么会有一个msqid_ds数据结构的成员被初始化。当有了消息队列之后,client端的任务就是循环发送数据到mid这个消息队列当中,当遇到"exit"标识时,这端的程序就会退出。在这里我发现了一个问题,为什么当我输入的type的值是<=0的时候,也就会退出?这个要传送数据的数据结构是自己定义的,为什么会出现这个情况,等我弄明白了在补上。
在server端,首先的话,开始和client端类似,不在话下。接下来的话,就是循环从消息队列当中取得client发送过来的消息,直到接收到exit为止,自动退出。等出现“exit”时,就会删除刚才创建的消息队列。
至此,我们就利用System V的消息队列简单实现了客户-服务器模型,当然这个例子还可以扩充一下,比如client端发送命令到server端,然后server端处理完了之后将结果发送到client端并显示。或者可以改为生产者-消费者模型(这个我会在信号量那里来实现)。