System V IPC指南 第一部分

来源:互联网 发布:sk2淘宝旗舰店是真是假 编辑:程序博客网 时间:2024/05/16 12:59

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()来完成此操作。

C代码  收藏代码
  1. 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值。

C代码  收藏代码
  1. key_t ftok(const char *path, int id);  

     恩,这看起来又有点玄乎了.简单点说吧,path只是进程所能读取到的某个文件。另一个参数,id,通常设置为某个任意的字符就可以了,例如'A'.ftok()函数使用命名文件(有点像inode的数字)和id来为msgget()产生一个唯一的键值,想使用同一个队列的程序,必须产生同一个key,所以必须保证这些程序也传递给ftok()相同的参数.
    最后,是时候来创建这个队列了:

C代码  收藏代码
  1. #include <sys/msg.h>  
  2.   
  3. key = ftok("/home/beej/somefile"'b');  
  4. msqid = msgget(key, 0666 | IPC_CREAT);  

     上面的例子里,权限被设置为0666(rw-rw-rw-,如果你习惯这种方式的话)。现在我们拥有了可以用来从队列中发送和接收消息的标识符msqid。
    每条消息由两部分组成,定义在位于sys/msg.h里的结构体msgbuf如下:

C代码  收藏代码
  1. struct msgbuf {  
  2.     long mtype;  
  3.     char mtext[1];  
  4. };  

     mtype接下来会在从队列里取得消息的过程里被使用,可以被设置为任意的正数。mtext是能被添加到队列里的数据。
    “什么?在队列中一条消息里只能放仅仅一个数组?这太无能了吧?!!”嘿嘿,不是这样的。在队列里你可以放入任意你想要加的结构体,只要第一个元素(mtype)是long类型就可以。例如,我们可以使用以下结构来存储一个海盗的信息:

C代码  收藏代码
  1. struct pirate_msgbuf {  
  2.     long mtype;  /* 必须是正数 */  
  3.     char name[30];  
  4.     char ship_type;  
  5.     int notoriety;  
  6.     int cruelty;  
  7.     int booty_value;  
  8. };  

     好,接下来我们怎么把这条信息传递到消息队列中呢?答案很简单,使用msgsnd()就可以了

C代码  收藏代码
  1. int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);  

     msqid是由msgget()返回的消息队列的标识符。指针msgp指向你想要加入队列的数据。msgsz是想要加入队列的数据的大小(单位:字节byte)。最后,msgflg允许你设置一些自定义的标志参数,当前我们可以忽略它,设置为0即可。
    下面的代码片断展示了如何在消息队列中加入我们的用于描述“海盗”的结构体

C代码  收藏代码
  1. #include  
  2.   
  3.  key_t key;  
  4.  int msqid;  
  5.  struct pirate_msgbuf pmb = {2, "L'Olonais", 'S', 80, 10, 12035};  
  6.   
  7.  key = ftok("/home/beej/somefile"'b');  
  8.  msqid = msgget(key, 0666 | IPC_CREAT);  
  9.   
  10.  msgsnd(msqid, &pmb, sizeof(pmb), 0);  /* 把这家伙关到队列中去 */  

     另外的一个注意点是,对所有这些函数的返回值,都要做错误检查。还要注意在这里我很随意地把mtype设置为了2,在下一部分里这个值将变得很重要。

从队列中取得值

    既然我们已经把恐怖的海岛头子Francis L'Olonais关在了消息队列里,我们怎么把他拯救出来呢?正如你所想象的一样,针对于msgsnd,我们有msgrcv()。够奇妙吧?
    msgrcv()的调用看起来是这样的:

C代码  收藏代码
  1. #include  
  2.   
  3. key_t key;  
  4. int msqid;  
  5. struct pirate_msgbuf pmb; /* L'Olonais将被关押的位置 */  
  6.   
  7. key = ftok("/home/beej/somefile"'b');  
  8. msqid = msgget(key, 0666 | IPC_CREAT);  
  9.   
  10. msgrcv(msqid, &pmb, sizeof(pmb), 2, 0);  /* 把他从队列中救出来! */  

     在msgrcv()调用中有些新的注意事项: "2"代表什么呢?来看看msgrcv()函数调用的语法:

C代码  收藏代码
  1. 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()函数调用的语法如下:

C代码  收藏代码
  1. int msgctl(int msqid, int cmd, struct msqid_ds *buf);  

     显然, msgid是我们从msgget()中得到的队列表识符。cmd告诉msgctl()如何行动,它可以是很多种事件的集合,但这里我们只打算讲讲IPC_RMID,它被用来删除消息队列。为了使用IPC_RMID,buf参数被设置为NULL。
    刚才我们创建了一个用于关押海盗的队列。现在我们可以用下面的函数调用来销毁这个队列了。

C代码  收藏代码
  1. #include  
  2.  .  
  3.  .  
  4.  msgctl(msqid, IPC_RMID, NULL);  

     调用完毕,消息队列将不复存在。

来个示例?
    为了讲义的完备性,我将在后面附带上一个使用消息队列通信的示例。kirk.c用于把消息加入到消息队列中,spock.c则从消息队列中取得它们。
    如下是kirk.c的源文件。

C代码  收藏代码
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <errno.h>  
  4. #include <sys/types.h>  
  5. #include <sys/ipc.h>  
  6. #include <sys/msg.h>  
  7.   
  8. struct my_msgbuf {  
  9.     long mtype;  
  10.     char mtext[200];  
  11. };  
  12.   
  13. int main(void)  
  14. {  
  15.     struct my_msgbuf buf;  
  16.     int msqid;  
  17.     key_t key;  
  18.   
  19.     if ((key = ftok("kirk.c"'B')) == -1) {  
  20.         perror("ftok");  
  21.         exit(1);  
  22.     }  
  23.   
  24.     if ((msqid = msgget(key, 0644 | IPC_CREAT)) == -1) {  
  25.         perror("msgget");  
  26.         exit(1);  
  27.     }  
  28.       
  29.     printf("Enter lines of text, ^D to quit:\n");  
  30.   
  31.     buf.mtype = 1; /* 在这个例子中我们并不真正需要关注它 */  
  32.     while(gets(buf.mtext), !feof(stdin)) {  
  33.         if (msgsnd(msqid, (struct msgbuf *)&buf, sizeof(buf), 0) == -1)  
  34.             perror("msgsnd");  
  35.     }  
  36.   
  37.     if (msgctl(msqid, IPC_RMID, NULL) == -1) {  
  38.         perror("msgctl");  
  39.         exit(1);  
  40.     }  
  41.   
  42.     return 0;  
  43. }  

     kirk工作的方式是允许你逐行输入文本,每一行都会被绑定到消息里,并被添加到消息队列中去。spock则会从消息队列读取出每条消息。
下面是spock.c的源代码:

C代码  收藏代码
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <errno.h>  
  4. #include <sys/types.h>  
  5. #include <sys/ipc.h>  
  6. #include <sys/msg.h>  
  7.   
  8. struct my_msgbuf {  
  9.     long mtype;  
  10.     char mtext[200];  
  11. };  
  12.   
  13. int main(void)  
  14. {  
  15.     struct my_msgbuf buf;  
  16.     int msqid;  
  17.     key_t key;  
  18.   
  19.     if ((key = ftok("kirk.c"'B')) == -1) {  /*和kirk.c 中一样的值*/  
  20.         perror("ftok");  
  21.         exit(1);  
  22.     }  
  23.   
  24.     if ((msqid = msgget(key, 0644)) == -1) { /* 连接到队列*/  
  25.         perror("msgget");  
  26.         exit(1);  
  27.     }  
  28.       
  29.     printf("spock: ready to receive messages, captain.\n");  
  30.   
  31.     for(;;) { /* 死循环,永远监听! */  
  32.         if (msgrcv(msqid, (struct msgbuf *)&buf, sizeof(buf), 0, 0) == -1) {  
  33.             perror("msgrcv");  
  34.             exit(1);  
  35.         }  
  36.         printf("spock: \"%s\"\n", buf.mtext);  
  37.     }  
  38.   
  39.     return 0;  
  40. }  

     注意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年代闻名于加勒比海地区的一名法国海盗.

原创粉丝点击