进程间通信-消息队列

消息队列提供了一种在两个不相关的进程之间传递数据的简单有效方法,独立于发送和接收进程而存在。消息队列在进程间以数据块为单位传递数据,每个数据块都有一个类型标记,接受进程可以独立地接受含有不同类型值的数据块。Linux系统有两个宏定义MSGMAX与MSGMNB,它们以字节为单位分别定义了一条消息和一个队列的最大长度。

消息队列的结构

System V消息队列是在消息传输过程中的保存消息的容器,使用msqid_ds结构体记录其属性,本质上是内核中的一个消息链表,而消息是链表中的一条记录。消息队列存在于操作系统中的内核中,发送进程把新消息链接到队列尾部,接收进程按某种顺序每次从队列中摘取一条消息。

消息队列函数

1.msgget函数

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

返回值:成功时返回一个正整数,即队列标识符,失败时返回-1。

key:一个整数类型的键值,用来命名某个特定的消息队列。特殊键值IPC_PRIVATE用来创建私有队列,从理论上讲,他应该只能被当前进程访问,但是消息队列在Linux系统中事实上并非私有,因此该键值意义不大。

flag:权限标志位,要创建队列,msgflg参数应该包含标志IPC_CREAT。如果消息队列已经存在,IPC_CREAT标志将被忽略。

2.msgsnd函数

msgsnd函数用来把消息添加到消息队列中。

#include <sys/msg.h>
int msgsnd(int msqid, const void *msg_ptr, size_t mt_sz, int flag);

返回值:成功时,这个函数返回0,把消息副本链入消息队列,失败时,返回-1。

msqid:msgget函数返回的消息队列标示符。

msg_ptr:指向准备发送的消息的指针,消息的前4个字节是一个有符号整数,称为消息类型,后面是消息正文,可以自定义,模板结构体msgbuf:

/* Template for struct to be used as argument for `msgsnd' and `msgrcv'.  */
struct msgbuf
  {
    __syscall_slong_t mtype;    /* type of received/sent message */
    char mtext[1];        /* text of the message */
  };

mt_sz:mt_ptr消息正文的长度,不包括消息类型字段的长度。

flag:控制消息队满或者某些特殊情况下的处理方法。如果flag中设置了IPC_NOWAIT标志,函数将立即返回,不发送消息且返回值是-1,如果flag中无IPC_NOWAIT标志,则发送进程挂起以等待队列中腾出可用的空间。一般该参数设置为0。

3.msgrcv函数

msgrcv函数从消息队列中获取消息。

#include <sys/msg.h>
int msgrcv (int msqid, void *msgp, size_t msgsz,long int msgtyp, int msgflg);

msqid:由msgget函数返回的消息队列标示符。

msgp:指向准备接收消息的缓冲区的指针。

msgsz:消息缓冲区的大小,包括消息类型的部分。

msgtyp:长整形参数,指定接受的消息类型,实现一种简单的接收优先级。若为非0值,就获取类型与之匹配的第一条消息,若为0,则获取消息队列中的第一条消息。

flag:用于控制消息队列中无指定类型时的处理方法。若flag设置了IPC_NOWAIT标志,函数会立刻返回,返回值是-1。若IPC_NOWAIT标志未被设置,并且无可读的消息,则调用进程被阻塞,直到一条相应类型的消息到达队列。一般情况下,该参数设置为0,无消息可读时,调用线程被阻塞。

4.msgctl函数

msgctl是消息队列的控制函数。

#include <sys/msg.h>
int msgctl(int msqid, int command, struct msqid_ds *buf);

返回值:成功返回0,失败时返回-1。如果删除消息队列时,某个进程在msgsnd或者msgrcv函数中等待,这两个函数将失败。

msqid:msgget函数返回的消息队列标示符。

command:消息队列的控制操作类型,可以取3个值。

  • IPC_STAT:把消息队列相关属性读取到msqid_ds缓冲区buf中。
  • IPC_SET:如果进程拥有权限,就用msqid_ds结构体的值设置消息队列。
  • RMID:删除消息队列。

消息队列通信

msgcreate.c创建一个键值为0x12345的消息队列,将消息队列的键值作为命令行参数argv[1]提供。

#include <sys/msg.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc,char * argv[]){
    int trn;
    int msqid;
    key_t  key;
    if (argc <=1){
        fprintf(stderr,"请以./msgcreate <key>的形式运行给出的消息队列!
");
    return -1;
    }
    sscanf(argv[1],"%x",&key);
    msqid = msgget(key,IPC_CREAT|0644);
    return 0;
}

2.消息发送程序

msgsnd.c将通过命令行参数提供的消息包装成类型为1的消息,发送到键值为0x12345的消息队列,命令行参数argv[1]是消息队列的键值,argv[2]为待发送的消息。

#include <sys/msg.h>
#include <string.h>
#include <stdio.h>
char mbuf[1024];
int main(int argc,char * argv[]){
    int rtn;
    int msqid;
    key_t key;
    if (argc!=3){
        fprintf(stderr,"程序的启动方法为msgsnd <消息队列键值> <待发送消息>
");
        return -1;
    }
    sscanf(argv[1],"%x",&key);
    msqid = msgget(key,0644);
    *((long*)mbuf) = 1;
    memcpy(mbuf+4,argv[2],strlen(argv[2])+1);
    rtn = msgsnd(msqid,mbuf,strlen(mbuf+4)+1,0);
    printf("You send a message "%s" to msg %d
",argv[1],msqid);
    return 0;
}

3.消息接收程序

msgrcv.c将打开命令行参数为argv[1]为键值的消息队列,读取类型为1的消息并输出显示。

#include <stdio.h>
#include <sys/msg.h>


typedef struct MESSAGE{
    int mtype;
    char mtext[512];
} mymsg,*pmymsg;
int main(int argc,char *argv[]){

    int rtn;
    int msqid;
    key_t key;
    mymsg msginfo;
    if (argc!=2){
        fprintf(stderr,"程序的启动方法为msgrcv <消息队列键值>
");
        return -1;
    }
    sscanf(argv[1],"%x",&key);
    msqid = msgget(key,0644);
    rtn = msgrcv(msqid,(pmymsg)&msginfo,512,0,0);
    printf("%s
",msginfo.mtext);
    return 0;
}

4.删除消息队列

msgdel.c删除用命令行参数指定键值的消息队列。

#include <sys/msg.h>
#include <stdio.h>

int main(int argc,char *argv[]){
    int rtn;
    int msqid;
    key_t key;
    sscanf(argv[1],"%x",&key);
    msqid = msgget(key,0644);
    rtn = msgctl(msqid,IPC_RMID,NULL);
    return 0;
}