System V IPC指南 第一部分
http://dash1982.iteye.com/blog/296583
1. 消息队列指南
注: 原文见: http://www.ecst.csuchico.edu/~beej/guide/ipc/mq.html, 觉得他写得很好,顺手做的翻译,加了点标题和注释.
当年那些发明System V的家伙们恐怕没想到有一天这些IPC机制会在如此广泛的系统范畴内被实现(当然linux也被包括在内)。这份指南描述了相当"常规"的System V消息队列的使用方法和主要功能。
和平时一样,在深入到本质前,给你们来点概览作为开胃小菜。消息队列的工作方式有点像FIFO(先入先出),但支持了更多附加功能。一般情况下,消息从队列中取出的顺序和它们被放入的顺序是一样的。而在特定情况下,可以从队列里取出某条特定的未处于队列前端的消息,就像是插队一般(特别说明,别去位于硅谷的美�%�UB88�坚娱乐公园插队,否则你可能被逮起来.他们那里对插队控制很严格).
一个进程可以创建一个新的消息队列,或者连接到已有的某个消息队列。如此一来,两个进程就可以使用同一个消息队列交换消息了。
有关System V IPC的一个注意事项: 如果你创建了一个消息队列,除非你手动销毁它,否则它将一直存在。所有使用过它的进程都可以退出,但是队列将一直存在。一个好习惯是使用ipcs命令来查看系统里存在的消息队列,用ipcrm来销毁这些队列。
我的队列在哪里?
多说无益,让我们找点东西来练练手吧!首先你需要连接到一个队列上,倘若此队列不存在,则需要创建它。可以使用系统调用msgget()来完成此操作。
- int msgget(key_t key, int msgflg);
如果调用成功,msgget()会返回一个消息队列的ID,失败的话会返回-1(当然,它会设置相应的errno)
这些参数可能看起来有点神秘,但动动脑子就能理解了.首先,key是系统范围内唯一的标识符,用于描述想连接到的队列。其他任何想连接到此队列的进程都必须使用同一个key值。
另一个参数,msgflg告诉msgget()其行为方式。如果需要创建队列的话,这个值需要被设置为IPC_CREATE。如果需要设置权限,user-id和group-id都和普通文件的权限是一样的。
我会在接下来的章节中给出一个简单的例子.
"你是key的主人吗?"
这个key是什么乱七八糟的东西?我们怎样创建这玩意?好吧,告诉你key_t的类型其实就是long,所以你可以使用任何你想使用的数字。
如果你想使用一个魔数(magic number, hard code),或者使用同一个魔数的其他程序想使用另一个队列呢?简单指定就行不通了,好在我们可以使用ftok()函数,给ftok()两个参数,它会给你生成一个key值。
- key_t ftok(const char *path, int id);
恩,这看起来又有点玄乎了.简单点说吧,path只是进程所能读取到的某个文件。另一个参数,id,通常设置为某个任意的字符就可以了,例如'A'.ftok()函数使用命名文件(有点像inode的数字)和id来为msgget()产生一个唯一的键值,想使用同一个队列的程序,必须产生同一个key,所以必须保证这些程序也传递给ftok()相同的参数.
最后,是时候来创建这个队列了:
- #include <sys/msg.h>
- key = ftok("/home/beej/somefile", 'b');
- msqid = msgget(key, 0666 | IPC_CREAT);
上面的例子里,权限被设置为0666(rw-rw-rw-,如果你习惯这种方式的话)。现在我们拥有了可以用来从队列中发送和接收消息的标识符msqid。
每条消息由两部分组成,定义在位于sys/msg.h里的结构体msgbuf如下:
- struct msgbuf {
- long mtype;
- char mtext[1];
- };
mtype接下来会在从队列里取得消息的过程里被使用,可以被设置为任意的正数。mtext是能被添加到队列里的数据。
“什么?在队列中一条消息里只能放仅仅一个数组?这太无能了吧?!!”嘿嘿,不是这样的。在队列里你可以放入任意你想要加的结构体,只要第一个元素(mtype)是long类型就可以。例如,我们可以使用以下结构来存储一个海盗的信息:
- struct pirate_msgbuf {
- long mtype; /* 必须是正数 */
- char name[30];
- char ship_type;
- int notoriety;
- int cruelty;
- int booty_value;
- };
好,接下来我们怎么把这条信息传递到消息队列中呢?答案很简单,使用msgsnd()就可以了
- int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid是由msgget()返回的消息队列的标识符。指针msgp指向你想要加入队列的数据。msgsz是想要加入队列的数据的大小(单位:字节byte)。最后,msgflg允许你设置一些自定义的标志参数,当前我们可以忽略它,设置为0即可。
下面的代码片断展示了如何在消息队列中加入我们的用于描述“海盗”的结构体
- #include
- key_t key;
- int msqid;
- struct pirate_msgbuf pmb = {2, "L'Olonais", 'S', 80, 10, 12035};
- key = ftok("/home/beej/somefile", 'b');
- msqid = msgget(key, 0666 | IPC_CREAT);
- msgsnd(msqid, &pmb, sizeof(pmb), 0); /* 把这家伙关到队列中去 */
另外的一个注意点是,对所有这些函数的返回值,都要做错误检查。还要注意在这里我很随意地把mtype设置为了2,在下一部分里这个值将变得很重要。
从队列中取得值
既然我们已经把恐怖的海岛头子Francis L'Olonais关在了消息队列里,我们怎么把他拯救出来呢?正如你所想象的一样,针对于msgsnd,我们有msgrcv()。够奇妙吧?
msgrcv()的调用看起来是这样的:
- #include
- key_t key;
- int msqid;
- struct pirate_msgbuf pmb; /* L'Olonais将被关押的位置 */
- key = ftok("/home/beej/somefile", 'b');
- msqid = msgget(key, 0666 | IPC_CREAT);
- msgrcv(msqid, &pmb, sizeof(pmb), 2, 0); /* 把他从队列中救出来! */
在msgrcv()调用中有些新的注意事项: "2"代表什么呢?来看看msgrcv()函数调用的语法:
- int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
函数调用中指定的"2"是msgtyp。这里我们要联想到在调用msgsnd()时我们随意设置的mtype部分了,这里设置的"2"正是用来从队列里取得那条消息的。
事实上,msgrcv()的作用效果可以通过选择msgtyp来改变,可选择的值包括正数、负数和零:
msgtyp | msgrcv()调用效果 |
0 | 从队列中取得下一条消息,不考虑其mtype |
正数 | 取得下一条和指定的msgtyp相等的mtype的消息 |
负数 | 取得队列里的第一条消息,这条消息的mtype的值应该小于等于msgtyp参数的绝对值 |
表1. msgtyp参数不同设置对msgrcv()的效果
摧毁消息队列
有时你需要摧毁一个消息队列。如我之前所提到,除非你显式地删除它们,它们会一直存在;你需要显式删除它们,以节省系统开销.或者,你使用这个消息队列好多天,它已经过时了。你需要删除它,有如下两种方法:
1.使用Unix命令ipcs来列举出所有定义的消息队列,然后使用ipcrm命令来删除队列
2.写段程序来帮你这么干。
通常情况下,后者比较适当,因为你可能需要你的程序在某个特定时候执行清理工作。我们需要用到msgctl()来完成这个功能。
msgctl()函数调用的语法如下:
- int msgctl(int msqid, int cmd, struct msqid_ds *buf);
显然, msgid是我们从msgget()中得到的队列表识符。cmd告诉msgctl()如何行动,它可以是很多种事件的集合,但这里我们只打算讲讲IPC_RMID,它被用来删除消息队列。为了使用IPC_RMID,buf参数被设置为NULL。
刚才我们创建了一个用于关押海盗的队列。现在我们可以用下面的函数调用来销毁这个队列了。
- #include
- .
- .
- msgctl(msqid, IPC_RMID, NULL);
调用完毕,消息队列将不复存在。
来个示例?
为了讲义的完备性,我将在后面附带上一个使用消息队列通信的示例。kirk.c用于把消息加入到消息队列中,spock.c则从消息队列中取得它们。
如下是kirk.c的源文件。
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- struct my_msgbuf {
- long mtype;
- char mtext[200];
- };
- int main(void)
- {
- struct my_msgbuf buf;
- int msqid;
- key_t key;
- if ((key = ftok("kirk.c", 'B')) == -1) {
- perror("ftok");
- exit(1);
- }
- if ((msqid = msgget(key, 0644 | IPC_CREAT)) == -1) {
- perror("msgget");
- exit(1);
- }
- printf("Enter lines of text, ^D to quit:\n");
- buf.mtype = 1; /* 在这个例子中我们并不真正需要关注它 */
- while(gets(buf.mtext), !feof(stdin)) {
- if (msgsnd(msqid, (struct msgbuf *)&buf, sizeof(buf), 0) == -1)
- perror("msgsnd");
- }
- if (msgctl(msqid, IPC_RMID, NULL) == -1) {
- perror("msgctl");
- exit(1);
- }
- return 0;
- }
kirk工作的方式是允许你逐行输入文本,每一行都会被绑定到消息里,并被添加到消息队列中去。spock则会从消息队列读取出每条消息。
下面是spock.c的源代码:
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- struct my_msgbuf {
- long mtype;
- char mtext[200];
- };
- int main(void)
- {
- struct my_msgbuf buf;
- int msqid;
- key_t key;
- if ((key = ftok("kirk.c", 'B')) == -1) { /*和kirk.c 中一样的值*/
- perror("ftok");
- exit(1);
- }
- if ((msqid = msgget(key, 0644)) == -1) { /* 连接到队列*/
- perror("msgget");
- exit(1);
- }
- printf("spock: ready to receive messages, captain.\n");
- for(;;) { /* 死循环,永远监听! */
- if (msgrcv(msqid, (struct msgbuf *)&buf, sizeof(buf), 0, 0) == -1) {
- perror("msgrcv");
- exit(1);
- }
- printf("spock: \"%s\"\n", buf.mtext);
- }
- return 0;
- }
注意spock在调用msgget()的时候没有包含IPC_CREATE选项。我们将它留给kirk来创建消息队列,如果kirk还没有创建完毕消息队列,spock将返回一个错误。
注意当你在不同的窗口中运行两个程序时,你kill掉一个或另一个程序时,会发生什么。你也可以试试有两个kirk或两个spock工作的情况下会发生什么----这种情况下,有两个读取者或两个写入者。还有个有趣的演示是首先运行kirk,输入一大堆消息,然后运行spock来看看它是如何“突然”获得一大堆消息的。在这些“玩具”程序上费点时间,你就能充分理解到实际运行时发生了什么。
结论
这个简短的指南显然不能覆盖消息队列的全部。记得查查你系统里的man手册页看看还有什么你能做的,尤其是msgctl()的参数。同样,还有些选项可以作为参数传递,以控制当队列满/空时msgsnd()/msgrcv()的行为方式。
HPUX man 手册
如果你不使用HPUX, 去查你本地的man页就可以!
* ftok()
* ipcs
* ipcrm
* msgctl()
* msgget()
* msgsnd()
注释
Jean-David Nau (c. 1635 - c. 1668, 巴拿马), 大名François l'Olonnais, 是1660年代闻名于加勒比海地区的一名法国海盗.