使用消息队列来进行进程间通信,消息队列与命名管道有很多相似之处。
一、什么是消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。
二、在linux中使用消息队列
linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间通信。它的用法与其他两个System V IPC机制,即信号量和共享内存相似。
1、msgget函数
该函数用来创建和访问一个消息队列。它的原型为:
int msgget(key_t key,int msgflg);
与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
它返回一个key命名的消息队列的标识符(非零整数),失败时返回-1。
2、msgsnd函数
该函数用来把消息添加到消息队列中。它的原型为:
int msgsend(int msgid,const void* msg_ptr,size_t msg_sz,int msgflg);
msgid是由msgget函数返回的消息队列标识符。msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:
struct my_message
{
long int message_type;
//the data you wish to transfer
};
msg_sz是msg_ptr指向的消息长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。
msgflg用于控制当前消息队列满或消息队列到达系统范围的限制时将要发生的事情。
若调用成功,消息数据的一份副本将被放到消息队列中,并返回0,失败返回-1.
3、msgrcv函数
该函数用来从一个消息队列获取消息,它的原型为
int msgrcv(int msgid,void* msg_ptr,size_t msg_st,long int msgtype,int msgflg);
msgid,msg_ptr,msg_st的作用也与函数msgsnd函数的一样。
msgtype可以实现一种简单的接收优先级。若msgtype为0,就获取队列中的第一个消息。若它的值大于0,将获取具有相同消息类型的第一个消息。若它小于0,就获取类型等于或小于msgtype的绝对值的第一个消息。
msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情。
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.
4、msgctl函数
该函数用来控制消息队列,它与共享内存的shmctl函数相似,它的原型为:
int msgctl(int msgid,int command,struct msgid_ds* buf);
command是将要采取的动作,它可以取3个值,IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值;IPC_SET:若进程有足够的权限,就把消息队列的当前关联值设置为msgid_ds结构给出的值;IPC_RMID:删除消息队列。
buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
成功时返回0,失败返回-1.
三、使用消息队列进行进程间通信
消息队列进行进程间通信可以让不相关的进程进行通信,所以我们在这写两个程序:msgreceive和msgsned来表示接收和发送消息。我们允许两个程序都可以创建消息,但只有接收者在接收完最后一个消息之后,它才把它删除。
msgrcv.c
#include
#include
#include
#include
#include
#include
#define MAX_DATA_LEN 30
typedef struct RecvData_T
{
long int datatype;
char data[MAX_DATA_LEN];
}RecvData;
int main(int argc, char* argv[])
{
int msgid;
int running = 1;
RecvData recvdata;
msgid = msgget((key_t)1234,0666 | IPC_CREAT);
if(msgid == -1)
{
perror("msgrecv msgget\n");
exit(EXIT_FAILURE);
}
while(running)
{
if(msgrcv(msgid,&recvdata,MAX_DATA_LEN,0,0) == -1)
{
perror("msgrcv\n");
exit(EXIT_FAILURE);
}
printf("msgrcv data:%s\n",recvdata.data);
if(strncmp(recvdata.data,"end",3) == 0)
{
running = 0;
}
}
if(msgctl(msgid,IPC_RMID,0) == -1)
{
perror("msgctl\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
msgsnd.c
#include
#include
#include
#include
#include
#include
#define MAX_LENGTH 30
typedef struct Trans_data_T
{
long int msg_type;
char data[MAX_LENGTH];
}Trans_data;
int main(int argc,char* argv[])
{
int msgid;
int running = 1;
msgid = msgget((key_t)1234,0666 | IPC_CREAT);
if(msgid == -1)
{
perror("msgsnd msgget\n");
exit(EXIT_FAILURE);
}
Trans_data data_s;
while(running)
{
data_s.msg_type = 1;
printf("pls input transfer data:\n");
fgets(data_s.data,MAX_LENGTH,stdin);
printf("msgsnd data:%s\n",data_s.data);
if(msgsnd(msgid,&data_s,MAX_LENGTH,0) == -1)
{
perror("msgsnd\n");
exit(EXIT_FAILURE);
}
if(strncmp(data_s.data,"end",3) == 0)
{
running = 0;
}
}
exit(EXIT_SUCCESS);
}
四、demo分析——消息类型
msgtyp参数作为msgrcv函数接收消息类型的值,0表示获取队列第一个可用的信息。msgsnd中msg_type设置发送消息的类型。
五、消息队列与命名管道的比较
消息队列通信的进程可以是不相关的进程,皆是通过发送和接收的方式来传递数据的。
与命名管道相比,消息队列的优势在于,1、消息队列可独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己提供同步方法。3、接收程序可通过消息类型有选择的接收数据,而不是像命名管道那样,只能默认地接收。