消息队列

消息队列是消息的链表,存放在内核中并由消息队列标识符标识。在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。这跟管道和FIFO是相反的,对后两者来说,除非读出者已存在,否则先有写入者是没有意义的。

 

管道和FIFO都是随进程持续的,XSI IPC(消息队列、信号量、共享内存)都是随内核持续的。

当一个管道或FIFO的最后一次关闭发生时,仍在该管道或FIFO上的数据将被丢弃。消息队列,除非内核自举或显式删除,否则其一直存在。

 

对于系统中的每个消息队列,内核维护一个定义在<sys/msg.h>头文件中的信息结构。

struct msqid_ds {
    struct ipc_perm msg_perm ; 
    struct msg*    msg_first ; //指向队列中的第一个消息
    struct msg*    msg_last ; //指向队列中的最后一个消息
    ……
} ;

 

msgget函数

调用的第一个函数通常是msgget,其功能是打开一个现存队列或创建一个新队列。

#include <sys/msg.h>

int  msgget (key_t key,  int oflag) ;

返回值是一个整数标识符,其他三个msg函数就用它来指代该队列。它是基于指定的key产生的,而key既可以是ftok的返回值,也可以是常值IPC_PRIVATE

oflag是读写权限的组合(用于打开时)。它还可以是IPC_CREATE或IPC_CREATE | IPC_EXCL(用于创建时)。

 

创建消息队列(实例)

[cpp]  view plain  copy
  1. //创建一个消息队列,在创建时指定队列的最大消息数和每个消息的最大大小  
  2. //本程序使用说明:  
  3. //./create -e 路径名  
  4. #include <unistd.h>  
  5. #include <sys/types.h>  
  6. #include <sys/msg.h>  
  7. #include <sys/ipc.h> //包含ftok()  
  8. #include <fcntl.h>   //包含getopt() 及相关的外部变量\  
  9. #include <stdio.h>  
  10. #include <stdlib.h>  
  11.   
  12. int   
  13. main(int argc, char** argv)  
  14. {  
  15.     int c, flags ;  
  16.     int mqid ; //消息队列id  
  17.   
  18.     flags = IPC_CREAT ; //设置消息队列的标记  
  19.     //从命令行读取 队列的最大消息数 消息的最大大小  
  20.     while ((c = getopt(argc, argv, "e")) != -1)  
  21.     {  
  22.         switch(c)  
  23.         {  
  24.         case 'e':  
  25.             flags |= IPC_EXCL ; //排他性创建:若已存在此名的消息队列,则返回错误  
  26.             break;  
  27.         }  
  28.     }  
  29.   
  30.     //若用户没有指定选项  
  31.     if (optind != argc -1)  
  32.        puts("请按格式输入:[-e] [-m maxmsg] [-z msgsize] <name>") ;  
  33.   
  34.     //创建消息队列  
  35.     mqid = msgget(ftok(argv[optind], 0), flags) ;  
  36.   
  37.     exit(0) ;  
  38. }  

 

msgsnd函数

使用msgsnd打开一个消息队列后,我们使用msgsnd往其上放置一个消息。

#include <sys/msg.h>

int  msgsnd (int msqid,  const void *ptr,  size_t length,  int flag) ;

其中msqid是由msgget返回的标识符。ptr是一个结构指针,该结构具有如下模板(我们需要按这个模板自己定义结构体)

struct mymesg {
    long  mtype ;     //消息类型(大于0)
    char  mtext[512] ;  //消息数据
} ;

 //结构体的名字和其中变量名都由我们自己确定,我们只要按照这个模板定义即可。

消息数据mtext中,任何形式的数据都是允许的,无论是二进制数据还是文本,内核根本不解释消息数据的内容。(我们可以在消息的数据部分 再分割一部分 根据需要定义自己的通信协议)

参数length指定了待发送消息数据部分的长度。

参数flag的值可以指定为IPC_NOWAIT。这类似于文件IO的非阻塞IO标志。若消息队列已满,则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN

如果没有指定IPC_NOWAIT,则进程阻塞直到下述情况出现为止:①有空间可以容纳要发送的消息 ②从系统中删除了此队列(返回EIDRM“标识符被删除”)③捕捉到一个信号,并从信号处理程序返回(返回EINTR)

 

向消息队列发送消息(示例)

[cpp]  view plain  copy
  1. //向消息队列发送消息(把一个指定了长度和类型的消息放置到某个队列中)  
  2. //我们必须在不同进程中约定好 标识消息队列的 路径名和工程ID,  
  3. //可把此两者放置到一个头文件中,让用消息队列通信的进程代码都包含此头文件。  
  4. //  
  5. #include <stdio.h>  
  6. #include <stdlib.h>  
  7. #include <string.h>  
  8. #include <sys/ipc.h> //包含ftok  
  9. #include <sys/msg.h>  
  10. #include <sys/types.h>  
  11. #include <sys/fcntl.h>  
  12.   
  13. #define MSG_W 0200  
  14. #define BUF_SIZE 512  
  15.   
  16. typedef struct msgbuf  
  17. {  
  18.     long mtype ;  
  19.     char mdata[BUF_SIZE] ;  
  20. } mymsg_t ;  
  21.   
  22. int   
  23. main(int argc, char** argv)  
  24. {  
  25.     int            mqid ;    //消息队列的描述符  
  26.     size_t         msglen ;  //消息的长度  
  27.     long           msgtype ; //消息的类型  
  28.     mymsg_t*  ptr ;     //消息结构的指针  
  29.   
  30.     //用户未按格式输入  
  31.     if (argc != 3)  
  32.         puts("usage: send <pathname> <type>") ;  
  33.   
  34.     msgtype = atoi(argv[2]) ;  
  35.   
  36.     //获取已存在消息队列的描述符  
  37.     mqid = msgget(ftok(argv[1], 0), MSG_W) ;  
  38.   
  39.     //构造一条消息  
  40.     ptr = calloc(sizeof(long) + msglen, sizeof(char)) ;  
  41.     ptr->mtype = msgtype ;  
  42.     snprintf(ptr->mdata, BUF_SIZE, "Hi,Boy~") ;  
  43.   
  44.     //发送消息  
  45.     msglen = strlen(ptr->mdata) ;  
  46.     msgsnd(mqid, ptr, msglen, 0) ;  
  47.   
  48.    exit(0) ;  
  49. }  

 

msgrcv函数

使用msgrcv函数从某个消息队列中读出一个消息。

#include <sys/msg.h>

ssize_t  msgrcv (int msqid,  void* ptr,  size_t length,  long type,  int flag) ;

参数ptr指定所接收消息的存放位置。参数length指定了数据部分大小(只想要多长的数据)

参数type指定希望从队列中读出什么样的消息。

type == 0 返回队列中的第一个消息

type > 0  返回队列中消息类型为type的第一个消息

type < 0  返回队列中消息类型值小于或等于type绝对值的消息,如果这种消息有若干个。则取类型值最小的消息。

(如果一个消息队列由多个客户进程和一个服务器进程使用,那么type字段可以用来包含客户进程的进程ID

 

参数flag可以指定为IPC_NOWAIT,使操作不阻塞。

[cpp]  view plain  copy
  1. //从一个消息队列中读出一个消息, -n命令行选项指定非阻塞 -t命令行选项指定接收的消息类型  
  2. //  
  3. #include <unistd.h>  
  4. #include <stdio.h>  
  5. #include <stdlib.h>  
  6. #include <sys/ipc.h> //包含ftok  
  7. #include <sys/msg.h>  
  8. #include <sys/types.h>  
  9. #include <sys/fcntl.h>  
  10. #include <sys/stat.h>  
  11.   
  12. #define MSG_R 0400  
  13. #define MSG_BUF_SIZE (512 + sizeof(long))  
  14.   
  15. typedef struct msgbuf  
  16. {  
  17.     long mtype ;  
  18.     char mdata[MSG_BUF_SIZE-sizeof(long)] ;  
  19. } mymsg_t ;  
  20.   
  21. int   
  22. main(int argc, char** argv)  
  23. {  
  24.     int            c = 0 ;  
  25.     int            recvCntlFlag = 0 ;  
  26.     int            mqid = 0 ;  
  27.     long           msgtype = 0 ;  
  28.     ssize_t        recvBytes = 0 ; //已接收的字节数  
  29.     struct msgbuf* buf = NULL ;    //存储接收消息的缓冲区的指针     
  30.   
  31.     //读取用户输入的命令行  
  32.     while ((c = getopt(argc, argv, "nt:")) != -1)  
  33.     {  
  34.         switch(c)  
  35.         {  
  36.         case 'n' :  
  37.             recvCntlFlag |= IPC_NOWAIT ;  
  38.             break ;  
  39.         case 't' :  
  40.             msgtype = atol(optarg) ;  
  41.             break ;  
  42.         }  
  43.     }  
  44.   
  45.     if (optind != argc-1)  
  46.         puts("usage:msgrcv [-n] [-t typeno] <pathname>") ;  
  47.   
  48.     //获取要接收队列的id  
  49.     mqid = msgget(ftok(argv[optind], 0), MSG_R) ;  
  50.   
  51.     //开辟缓冲区 接收消息  
  52.     buf = malloc(MSG_BUF_SIZE) ;  
  53.     recvBytes = msgrcv(mqid, buf, MSG_BUF_SIZE, msgtype, recvCntlFlag ) ;  
  54.   
  55.     //输出消息内容  
  56.     printf("recv:%d bytes type=%ld %s",recvBytes, buf->mtype, buf->mdata) ;  
  57.     exit(0) ;  
  58. }  

 

msgctl函数

msgctl函数提供在一个消息队列上的各种控制操作。

#include <sys/msg.h>

int  msgctl (int msqid,  in cmd,  struct msqid_ds * buff) ;

参数cmd说明对由msqid指定的队列要执行的命令:

IPC_STAT :取此队列的msqid_ds结构,并将它存放在buf指向的结构中。

IPC_SET  :按由buf指向结构中的值,设置与此队列相关结构中的字段。

IPC_RMID:从系统中删除该消息队列以及仍在该队列中的所有数据。

(这三条命令也可用于信号量和共享存储)

[cpp]  view plain  copy
  1. //删除一个队列,我们以IPC_RMID命令调用msgctl  
  2. //  
  3. #include <stdio.h>  
  4. #incldue <stdlib.h>  
  5. #include <sys/ipc.h>   
  6. #include <sys/msg.h>  
  7.   
  8. int   
  9. main(int argc, char** argv)  
  10. {  
  11.     int mqid ;  
  12.   
  13.     if (argc != 2)  
  14.         puts("usage:remove <pathname>") ;  
  15.   
  16.     mqid = msgget(ftok(argv[1], 0), 0) ;  
  17.     msgctl(mqid, IPC_RMID, NULL) ;  
  18.     exit(0) ;  
  19. }  
  20. ./create 任意一路径名  
  21. ./send 路径名 消息类型(正整数)  
  22. ./recv 路径名 ------或:./recv -t 消息类型 路径名  
  23. ./remove 路径名  


客户-服务器模型


 

[cpp]  view plain  copy
  1. //-------------------头文件msgqueue.h ------------------  
  2. #ifndef _MAGQUEUE_H_  
  3. #define _MAGQUEUE_H_  
  4. #include <sys/ipc.h> //包含ftok  
  5. #include <sys/msg.h>  
  6. #include <sys/types.h>  
  7.   
  8. //消息队列的读 写模式掩码  
  9. #define MSG_W 0200  
  10. #define MSG_R 0400  
  11.   
  12. //定义众所周知的消息队列键  
  13. #define MQ_KEY1 128L  
  14.   
  15. #define DATA_SIZE 512  
  16.   
  17. typedef struct msgbuf  
  18. {  
  19.     long mtype ;  
  20.     char mdata[DATA_SIZE] ;  
  21. } mymsg_t ;  
  22.   
  23. #endif  
  24.   
  25. //-----------------客户端进程-----------------  
  26.   
  27. #include "msgqueue.h"  
  28. #include <unistd.h>  
  29. #include <stdio.h>  
  30. #include <stdlib.h>  
  31. #include <string.h>  
  32.   
  33. void client(intint) ;  
  34.   
  35. int   
  36. main(int argc, char** argv)  
  37. {  
  38.     int  msgqid ;  
  39.   
  40.     //打开消息队列  
  41.     msgqid = msgget(MQ_KEY1, 0) ;  
  42.     if (msgqid < 0)  
  43.     {  
  44.         puts("Open msg queue error!\n") ;  
  45.         exit(0) ;  
  46.     }  
  47.   
  48.     client(msgqid, msgqid) ;  
  49.     exit(0) ;  
  50. }  
  51.   
  52. void  
  53. client(int readfd, int writefd)  
  54. {  
  55.     mymsg_t msgToServer ;  
  56.     mymsg_t msgFromServer ;  
  57.     char*   writePtr ;  
  58.     ssize_t pidLen ;  
  59.     ssize_t dataLen ;  
  60.     ssize_t recvBytes ;  
  61.     int     pid ;  
  62.   
  63.     //-------构造一条消息-----  
  64.     //消息类型为1  
  65.     msgToServer.mtype = 1 ;  
  66.   
  67.     //在消息头部放本进程ID和空格  
  68.     pid = getpid() ;  
  69.     snprintf(msgToServer.mdata, DATA_SIZE, "%ld ", pid) ;  
  70.     pidLen = strlen(msgToServer.mdata) ;  
  71.     writePtr = msgToServer.mdata + pidLen ;  
  72.   
  73.     //从标准输入读入文件路径  
  74.     fgets(writePtr, DATA_SIZE - pidLen, stdin) ;  
  75.     dataLen = strlen(msgToServer.mdata) ;    
  76.     if (msgToServer.mdata[dataLen-1] == '\n'//删除换行符  
  77.     {  
  78.         msgToServer.mdata[dataLen-1] = '\0' ;  
  79.     }  
  80.   
  81.     //发送消息  
  82.     if (msgsnd(writefd, &msgToServer, strlen(msgToServer.mdata), 0) == -1)  
  83.     {  
  84.         puts("Send Error!");  
  85.         exit(0) ;  
  86.     }  
  87.   
  88.     //-----接收来自服务器的消息  
  89.     while ((recvBytes = msgrcv(readfd, &msgFromServer, DATA_SIZE, pid, 0)) > 0)  
  90.     {  
  91.         write(STDOUT_FILENO, msgFromServer.mdata, recvBytes) ;  
  92.     }  
  93.   
  94. }  
  95.   
  96. //---------------服务器端进程---------------  
  97. //消息队列是双向通信的,故用单个队列就够用。  
  98. //我们用每个消息的类型来标识该消息是从客户到服务器,还是从服务器到客户。  
  99. //客户向队列发类型为1、PID和路径名。  
  100. //服务器向队列发类型为客户进程ID的文件内容。  
  101. //  
  102. //小心死锁隐患:  
  103. //客户们可以填满消息队列,妨碍服务器发送应答,于是客户被阻塞在发送中,服务器也被阻塞。  
  104. //避免的方法是:约定服务器对消息队列总是使用非阻塞写。  
  105.   
  106. #include "msgqueue.h"  
  107. #include <stdio.h>  
  108. #include <stdlib.h>  
  109. #include <string.h>  
  110.   
  111. void server(intint) ;  
  112.   
  113. int   
  114. main(int argc, char** argv)  
  115. {  
  116.     int  msgqid;  
  117.   
  118.     //创建消息队列  
  119.     msgqid = msgget(MQ_KEY1, IPC_CREAT) ;  
  120.     if (msgqid < 0)  
  121.     {  
  122.         puts("Create msg queue error!\n") ;  
  123.         exit(0) ;  
  124.     }  
  125.   
  126.     server(msgqid, msgqid) ;  
  127.     exit(0) ;  
  128. }  
  129.   
  130. void  
  131. server(int readfd, int writefd)  
  132. {  
  133.     FILE*    fp ;  
  134.     pid_t    clientPid ;  
  135.     mymsg_t* msgPtr ;  
  136.     ssize_t  recvBytes ;  
  137.     char*    pathStr ;      
  138.   
  139.     for ( ; ; )  
  140.     {  
  141.         //从消息队列中读取来自客户的请求文件路径  
  142.         msgPtr = malloc(DATA_SIZE + sizeof(long)) ;  
  143.         recvBytes = msgrcv(readfd, msgPtr, DATA_SIZE, 1, 0) ; //阻塞读  
  144.         if (recvBytes <= 0)  
  145.         {  
  146.             puts("pathname missing") ;  
  147.             continue ;  
  148.         }  
  149.         msgPtr->mdata[recvBytes] = '\0' ;  
  150.   
  151.         //分析消息,提取客户PID,文件路径  
  152.         if ((pathStr = strchr(msgPtr->mdata, ' ')) == NULL)  
  153.         {  
  154.             puts("bogus request!") ;  
  155.             continue ;  
  156.         }  
  157.         *pathStr++ = 0 ;  
  158.         clientPid = atol(msgPtr->mdata) ;  
  159.   
  160.         //读取文件内容 返回给客户  
  161.         msgPtr->mtype = clientPid ; //msgPtr既作为接收消息 又用作发送消息  
  162.         if ((fp = fopen(pathStr, "r")) == NULL)  
  163.         {  
  164.             //读取文件失败,返回给客户失败信息(在原消息内容后 添加错误信息)  
  165.             snprintf(msgPtr->mdata + recvBytes, sizeof(msgPtr->mdata) -recvBytes,   
  166.                     ": can't open!") ;  
  167.   
  168.             if (msgsnd(writefd, msgPtr, strlen(msgPtr->mdata), IPC_NOWAIT) == -1)  
  169.             {  
  170.                 puts("Send Error!");  
  171.                 exit(0);  
  172.             }  
  173.         }  
  174.         else  
  175.         {   //copy文件内容 发给客户  
  176.             while (fgets(msgPtr->mdata, DATA_SIZE, fp) != NULL)  
  177.             {  
  178.                 msgsnd(writefd, msgPtr, strlen(msgPtr->mdata), IPC_NOWAIT) ; //非阻塞写  
  179.             }  
  180.         }  
  181.     }//for()  
  182. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值