Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1879801
  • 博文数量: 217
  • 博客积分: 4362
  • 博客等级: 上校
  • 技术积分: 4180
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-20 09:31
文章分类

全部博文(217)

文章存档

2017年(1)

2015年(2)

2014年(2)

2013年(6)

2012年(42)

2011年(119)

2010年(28)

2009年(17)

分类: C/C++

2011-07-16 08:53:57

    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();
    对于消息队列的操作也就这么多,下面我们看几个实例。
  1. /*mes_que.c:创建删除消息队列*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/ipc.h>
  7. #include <sys/msg.h>

  8. int main()
  9. {
  10.     key_t key;
  11.     int mid;

  12.     key = ftok(".", 'a');
  13.     if((mid = msgget(key, IPC_CREAT|0770)) == -1) {
  14.         perror("message queue create error");
  15.         exit(1);
  16.     }
  17.     printf("%d\n", mid);
  18.     system("ipcs -q");
  19.     msgctl(mid, IPC_RMID, (struct msqid_ds *)0);
  20.     system("ipcs -q");
  21.     exit(0);
  22. }    
    这个程序的运行结果是这样的:
  1. O_O[sunny@sunny-laptop ~/summer/message]168$ gcc mes_que.c
  2. ^_^[sunny@sunny-laptop ~/summer/message]169$ ./a.out
  3. 1310720

  4. ------ Message Queues --------
  5. key msqid owner perms used-bytes messages
  6. 0x610978cb 1310720 sunny 770 0 0


  7. ------ Message Queues --------
  8. key msqid owner perms used-bytes messages

  9. ^_^[sunny@sunny-laptop ~/summer/message]170$
    程序说明:使用msgget函数创建一个消息队列,如果这个消息队列存在的话,就会返回此消息队列的标识符,最后调用msgctl函数将创建的这个消息队列删除掉。
    下面我们来看一下有关客户——服务器模型的一个例子。
    这个是server端的程序:
  1. /*server.c*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <sys/types.h>
  7. #include <sys/ipc.h>
  8. #include <sys/msg.h>

  9. #define TEST 'a'
  10. #define MAX_TEXT 100

  1. /*这个结构体是自己定义的,但是有一点通信双方都要一样*/
  2. struct mybuf {
  1.     int type;
  2.     char msg_text[MAX_TEXT];
  3. };

  4. int main()
  5. {
  6.     int mid, ret;
  7.     key_t key;
  8.     char buf[MAX_TEXT];
  9.     struct mybuf data;

  10.     key = ftok(".", TEST);
  11.     if(key == -1) {
  12.         perror("ftok error");
  13.         exit(1);
  14.     }
  15.     mid = msgget(key, IPC_CREAT|0777);
  16.     if(mid == -1) {
  17.         perror("msgget error");
  18.         exit(2);
  19.     }
  20.     
  21.     while(1) {
  22.         ret = msgrcv(mid, (void *)&data, MAX_TEXT, 0, 0);
  23.         if(strncmp(data.msg_text, "exit", 4) == 0) {
  24.             break;
  25.         }
  26.         if(ret == -1) {
  27.             exit(3);
  28.             perror("msgrcv error");
  29.         }
  30.         printf("%d    %s", data.type, data.msg_text);
  31.     }
  32.     msgctl(mid, IPC_RMID, (struct msqid_ds *)0);
  33.     return 0;
  34. }
    client端的程序:
  1. /*client.c*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <sys/types.h>
  7. #include <sys/ipc.h>
  8. #include <sys/msg.h>

  9. #define TEST 'a'
  10. #define MAX_TEXT 100

  11. /*这个结构体是自己定义的,但是有一点通信双方都要一样*/
  12. struct mybuf {
  13.     int type;
  14.     char msg_text[MAX_TEXT];
  15. };

  16. int main()
  17. {
  18.     int mid, ret, type;
  19.     key_t key;
  20.     char buf[MAX_TEXT];
  21.     struct mybuf data;

  22.     key = ftok(".", TEST);
  23.     if(key == -1) {
  24.         perror("ftok error");
  25.         exit(1);
  26.     }
  27.     mid = msgget(key, IPC_CREAT|0777);
  28.     if(mid == -1) {
  29.         perror("msgget error");
  30.         exit(2);
  31.     }

  32.     while(1) {
  33.         fputs("输入你要传送的数据类型:", stdout);
  34.         setbuf(stdin, NULL);
  35.         scanf("%d", &type);
  36.         data.type = type;
  37.         fputs("输入要传送的字符数据:", stdout);
  38.         //清空键盘缓冲区,在linux下getchar(),fflush(stdin),rewind(stdin)都不起作用
  39.         setbuf(stdin, NULL);
  40.         fgets(buf, MAX_TEXT, stdin);
  41.         memcpy(data.msg_text, buf, MAX_TEXT);
  42.         ret = msgsnd(mid, (void *)&data, MAX_TEXT, 0);
  43.         if(ret == -1) {
  44.             exit(3);
  45.             perror("msgsnd error");
  46.         }
  47.         if(strncmp(data.msg_text, "exit", 4) == 0) {
  48.             exit(0);
  49.         }
  50.     }

  51.     return 0;
  52. }
    测试结果:首先打开两个终端,在server端如下:
  1. ^_^[sunny@sunny-laptop ~/summer/message]110$ gcc server.c -o server
  2. ^_^[sunny@sunny-laptop ~/summer/message]111$ ./server
  3. 1    123456
  4. 2    8719255
  5. 3    helloworld
  6. ^_^[sunny@sunny-laptop ~/summer/message]112$
    在client端如下:
  1. ^_^[sunny@sunny-laptop ~/summer/message]83$ gcc client.c -o client
  2. ^_^[sunny@sunny-laptop ~/summer/message]84$ ./client
  3. 输入你要传送的数据类型:1
  4. 输入要传送的字符数据:123456
  5. 输入你要传送的数据类型:2
  6. 输入要传送的字符数据:8719255
  7. 输入你要传送的数据类型:3
  8. 输入要传送的字符数据:helloworld
  9. 输入你要传送的数据类型:4
  10. 输入要传送的字符数据:exit
  11. ^_^[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端并显示。或者可以改为生产者-消费者模型(这个我会在信号量那里来实现)。




阅读(6748) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~