Linux进程间通信——消息队列

来源:互联网 发布:剑网3谢云流捏脸数据 编辑:程序博客网 时间:2024/04/30 08:21

 1 消息队列基本概念

 消息队列是系统内核地址空间中的一个内部的链表。消息可以按照顺序发送到队列中,也可以以几种不同的方式从队列中读取。每一个消息队列用一个唯一的IPC标识符表示。

  了解在系统内核中的数据结构是了解IPC机制如何工作的最好的方法。

  首先我们看一下数据结构msgbuf。此数据结构可以说是消息数据的模板。虽然此数据结构需要用户自己定义,但了解系统中有这样一个数据结构是十分重要的。在<sys/msg.h>中,此数据结构是这样定义的:

struct msgbuf{     long mtype;         /*type of message, must>0*/    char mtext[1];      /*message text*/};

 在数据结构msgbuf中共有两个元素:

     mtype指消息的类型,它由一个整数来代表,并且它只能是大于0的整数。

     mtext是消息数据本身。

 mtext字段不但可以存储字符,还可以存储任何其他的数据类型。此字段可以说是完全任意的,因为程序员自己可以重新定义此数据结构。请看下面重新定义的例子:

struct my_msgbuf{    long mtype;           /*Message type*/    char request_id;      /*Request identifier*/    struct client info;   /*Client information structure*/};

 这里的消息类型字段和前面的一样,但数据结构的其余部分则由其他的两个字段所代替,而其中的一个还是另外一个结构。这就体现了消息队列的灵活之处。内核本身并不对消息结构中的数据做任何翻译。你可以在其中发送任何信息,但存在一个内部给定的消息大小的限制。在Linux系统中,消息的最大的长度是4056个字节,其中包括mtype,它占用4个字节的长度。


创建消息队列

 系统调用msgget()用于创建一个新的消息队列,或者存取一个已经存在的消息队列,其函数原型是:

#include<sys/types.h>#include<sys/ipc.h>#include<sys/msg.h>int msgget(key_t key, int msgflg);

 

 系统调用msgget()中的第一个参数是消息队列关键字值,可以由ftok()获得。第二个参数msgflg是一些标志,包括:

   IPC_CREAT:如果内核中没有此队列,则创建它。

   IPC_EXCL:当和IPC_CREAT一起使用时,如果队列已经存在,则返回错误。

 当msgget()执行成功时,返回消息队列的标识符,否则返回-1,通过errno和perror()函数查看错误信息.

下面是一个打开和创建一个消息队列的例子,函数返回消息队列的标识符

 

int open_ queue(key_ t keyval){  int qid;  if((qid = msgget(keyval,IPC_CREAT|0660)) == -1){    perror(”msgget”);    return(-1);  }  return(qid);}


发送和接收消息

当得到了消息队列标识符,就可以在队列上执行发送或者接收消息了。msgsnd()系统调用用于向队列发送一条消息,其函数原型是:   

int msgsnd(int msqid, struct msgbuf *msgp, sizet msgsz, int msgflg); 


 

第一个参数是消息队列标识符。第二个参数msgp,是指向消息缓冲区的指针。参数msgsz指定了消息的字节大小,但不包括消息类型的长度(4个字节)。参数msgflg可以设置为:

   0:此时为忽略此参数,如果消息队列已满,调用进程将会挂起,直到消息可以写入到队列中。

  IPC_NOWAIT:如果消息队列己满,那么此消息则不会写入到消息队列中,控制将返回到调用进程中。消息队列写入成功时,函数返回0,否则返回-1。

下面是一个发送消息的例子:

int send_message(int qid, struct mymsgbuf *qbuf){  int result, length;  /*The length is essentially the size of the structure minus sizeof(mtype)*/  length = sizeof(struct mymsgbuf) - sizeof(long);  if((result = msgsnd(qid, qbuf, length, O)) == -1){     perror(”msgsnd”);     return(-1);  }  return(result);}


 

msgrcv()系统调用用于从消息队列读取一条消息,其函数原型是:

ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflg);


 

第一个参数是消息队列的标识符。第二个参数代表要存储消息的缓冲区的地址。第三参数是消息缓冲区的长度,不包括mtype的长度,它可以按照如下的方法计算:

msgsz=sizeof(struct mymsgbuf)-sizeof(long);

第四个参数是要从消息队列中读取的消息的类型。

如果msgtype=0,接收消息队列的第一个消息。大于0接收队列中消息类型等于这个值的第一个消息。小于0接收消息队列中小于或者等于msgtype绝对值的所有消息中的最小一个消息。一般为0。

第五个参数msgflg取值为:

  0:从队列中取出最长时间的一条消息。

 IPC_NOWAIT:当队列没有消息时,调用会立即返回ENOMSG错误。否则,调用进程将会挂起,直到队列中的一条消息满足msgrcv()的参数要求。

   当函数成功时,返回写入缓冲区的数据大小,否则返回-1。

  下面是一个接收消息的例子:

int read_message(int qid, long type, struct mymsgbuf *qbuf){    int result, length;/* The length is essentially the size of the structure minus sizeof(mtype)*/length = sizeof(struct mymsgbuf) - sizeof(long);  if((result = msgrcv(qid, qbuf, length, type, O))==-l){  perror(¨msgrcv¨);  return(-1);}return(result);}


消息队列的控制

消息队列标识符的属性被记录在一个msgid_ds结构体:

 

struct msqid_ds{  struct ipc_perm msg_perm;      /*所有者和权限*/  time_t          msg_stime;     /*最后一次向队列发送消息的时间*/  time_t          msg_rtime;     /*最后一次从队列接收消息的时间*/  time_t          msg_ctime;     /*队列最后一次改动的时问*/  unsigned long   __msg_cbytes;  /*当前队列所有消息的总长度*/  msgqnum_t       msg_qnum;      /*当前队列中的消息数量*/  msglen_t        msg_qbytes;    /*消息队列的最大消息总长度*/  pid_t           msg_lspid;     /*最一次给队列发送消息的进程PID*/  pid_t           msg_lrpid;     /*最后一次从队列接收消息的进程PID*/}


通过msgctl()可以对消息队列进行控制或者一些属性的修改,其函数原型为:

int msgctl(int msqid, int cmd, struct msqid ds *buf;  

第一个参数是消息队列的标识符,第二个参数cmd指定了操作,下面是几个常用的操作:

  IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中。

  IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm、msg_qbytes、msg_ctime元素的值。这个值取自buf参数。  

  IPC_RMID:从系统内核中移走消息队列。

比如下面是一个删除消息队列的例子。

int remove_queue(int qid){  if(msgctl(qid, IPC_RMID, 0)==-1){    perror(”msgctl”);    return(-1);}  return(O);}


综合示例msgtool

我们来实现一个简单的消息队列工具,用于创建消息队列、发送、读取消息、改变权限以及删除消息队列。

它的用法如下:

(1)  发送消息

msgtool s (type)”text”

(2)  读取消息

msgtool r (type)

(3)  改变权限

msgtool l m (mode)

(4)  删除队列

msgtool d

源代码msgtoo1.c

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#define MAX_SEND_SIZE 80struct mymsgbuf {long mtype;char mtext[MAX_SEND_SIZE];};void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text);void read_message(int qid, struct mymsgbuf *qbuf, long type);void remove_queue(int qid);void change_queue_mode(int qid, char *mode);void usage(void);int main(int argc, char *argv[]){key_t key;int msgqueue_id;struct mymsgbuf qbuf;if(argc == 1)usage();/* Create unique key via call to ftok() */key = ftok(".", 'm');/* Open the queue - create if necessary */if((msgqueue_id = msgget(key, IPC_CREAT|0660)) == -1) {perror("msgget");exit(1);}printf("message queue id = [%d]\n", msgqueue_id );switch(tolower(argv[1][0])){case 's': if( argc < 4 ){usage();break;}send_message(msgqueue_id, (struct mymsgbuf *)&qbuf, atol(argv[2]), argv[3]);break;case 'r': if( argc < 3 ){usage();break;}read_message(msgqueue_id, &qbuf, atol(argv[2]));break;case 'd': remove_queue(msgqueue_id);break;case 'm': if( argc < 3 ){usage();break;}change_queue_mode(msgqueue_id, argv[2]);break;default: usage();}return(0);}void send_message(int qid, struct mymsgbuf *qbuf, long type, char *text){/* Send a message to the queue */printf("Sending a message ...\n");qbuf->mtype = type;strcpy(qbuf->mtext, text);if((msgsnd(qid, (struct msgbuf *)qbuf, strlen(qbuf->mtext)+1, 0)) ==-1){perror("msgsnd");exit(1);}}void read_message(int qid, struct mymsgbuf *qbuf, long type){/* Read a message from the queue */printf("Reading a message ...\n");qbuf->mtype = type;msgrcv(qid, (struct msgbuf *)qbuf, MAX_SEND_SIZE, type, 0);printf("Type: %ld Text: %s\n", qbuf->mtype, qbuf->mtext);}void remove_queue(int qid){/* Remove the queue */msgctl(qid, IPC_RMID, 0);}void change_queue_mode(int qid, char *mode){struct msqid_ds myqueue_ds;/* Get current info */msgctl(qid, IPC_STAT, &myqueue_ds);/* Convert and load the mode */sscanf(mode, "%ho", &myqueue_ds.msg_perm.mode);/* Update the mode */msgctl(qid, IPC_SET, &myqueue_ds);}void usage(void){fprintf(stderr, "msgtool - A utility for tinkering with msg queues\n");fprintf(stderr, "USAGE: msgtool (s)end <type> <messagetext>\n");fprintf(stderr, " (r)ecv <type>\n");fprintf(stderr, " (d)elete\n");fprintf(stderr, " (m)ode <octal mode>\n");exit(1);} 


 

我们先来发送一条消息

./msgtool s 1 ”Hello”message queue id = [32768]Sending a message...


 

用ipcs命令可以看到创建的消息队列:

ipcs -q------ Message Queues --------key        msqid      owner      perms      used-bytes   messages    0x6d01c86f 32768      user       660        6            1     


 

可以看到现在队列里有1条消息。

再用msgtool读出先读出一条消息,然后再次读取消息:

./msgtool r 0message queue id = [32768]Reading a message ...Type: 1 Text: Hello[alex@alex-/work/tutorai l]$./msgtool r 0message queue id 2[32768]Reading a message...


 

此时消息队列已经为空,进程被阻塞等待消息。我们到另外一个终端发送一条消息:

./msgtool s 2 “Another message”message queue id=[32768]Sending a message…


这是第一个终端的msgtool收到消息:

Type:2 Text:Another message

下面我们修改消息队列的访问权限
 
./msgtool m 600message queue id=[32768][alex@alex一/work/tutorail]$ ipcs –q------ Message Queues --------key        msqid      owner      perms      used-bytes   messages    0x6d01c86f 32768      user       660        6            1   


从ipcs命令看到的消息队列中,perms列已经被改为600。最后删除消息队列

/msgtool dmessage queue id=[32768][alex@alex一/work/tutorail]$ ipcs –q------ Message Queues --------key        msqid      owner      perms      used-bytes   messages 


 

 

0 0
原创粉丝点击