进程间通信之消息队列实现原理,进程间通信之消息队列缺点优点
现在让我们讨论第三个也是最后一个System V IPV工具:消息队列。在许多方面,消息队列类似于众所周知的管道,但它与打开和关闭管道没有复杂的关系。然而,使用消息队列并没有解决我们在使用著名管道时遇到的问题,例如管道上的拥塞。
消息队列为在两个不相关的进程之间传输数据提供了一种简单而有效的方法。与著名管道相比,消息队列具有独立于发送和接收进程的优点,降低了同步打开和关闭著名管道的难度。
消息队列为一个进程向另一个进程发送块数据提供了一种方式。另外,每个数据块被视为一种类型,接收过程可以独立接收不同类型的数据块。消息队列的好处是我们几乎可以完全避免同步问题,通过发送消息可以屏蔽命名管道的问题。更好的是,我们可以使用一些紧急方法来发送消息。缺点是,与管道类似,每个数据块都有一个最大大小限制,并且系统中所有消息队列的块大小也有一个最大大小限制。
尽管有这些限制,X/Open规范并没有定义这些限制的具体值,除了超出这些大小是某些消息队列功能失败的原因。Linux系统有两个定义,MSGMAX和MSGMNB,分别定义了单个消息和一个队列的最大大小。这些宏定义在其他系统上可能不一样,甚至可能不存在。
消息队列函数定义如下:
#包含sys/msg.h
int msgctl(int msqid,int cmd,struct msqid _ ds * buf);
int msgget(key_t key,int msg flg);
int msgrcv(int msqid,void *msg_ptr,size_t msg_sz,long int msgtype,int msgflg);
int msgsnd(int msqid,const void *msg_ptr,size_t msg_sz,int msgflg);
与信息号和共享内存一样,通常需要头文件sys/types.h和sys/ipc.h。
标识符
我们可以使用msgget函数来创建和访问消息队列:
int msgget(key_t key,int msg flg);
与其他IPC工具类似,程序必须提供一个指定特定消息队列的键值。特殊值IPC_PRIVATE创建一个私有队列,理论上只能由当前进程访问。像信息量和共享内存一样,在一些Linux系统上,消息队列不是私有的。因为私有队列不太有用,所以这不是一个严重的问题。和以前一样,第二个参数msgflg由9个权限标志组成。要创建新的消息队列,IPC_CREAT特殊位必须与其他权限位或。设置IPC_CREAT标志和指定现有消息队列不是错误。如果消息队列已经存在,IPC_CREAT标志将被忽略。
如果成功,msgget函数将返回一个正数作为队列标识符,如果失败,返回-1。
msgsnd
msgsnd函数允许我们将消息添加到消息队列中:
int msgsnd(int msqid,const void *msg_ptr,size_t msg_sz,int msgflg);
消息结构有两种定义方式。首先,它必须小于系统限制,其次,它必须以long int开头,这将在接收函数中用作消息类型。当我们使用消息时,最好以下列形式定义我们的消息结构:
构造我的消息{
长整型message _ type
/*您希望传输的数据*/
}
因为message_type是用于消息接收的,所以不能简单的忽略它。我们必须定义自己的数据结构来包含和初始化它,以便它可以包含一个已知的值。
第一个参数msgid是msgget函数返回的消息队列标识符。
第二个参数msg_ptr是指向要发送的消息的指针。如前所述,该消息必须以long int类型开头。
第三个参数msg_sz是msg_ptr指向的消息的大小。该大小不得包含长整型消息类型。
第四个参数msgflg控制在当前消息队列已满或达到排队消息的系统限制时应该做什么。如果msgflg标志设置了IPC_NOWAIT,函数会立即返回,不发送消息,返回值为-1。如果msgflg标志清除了IPC_NOWAIT标志,发送进程将被挂起,等待队列中的可用空间。
如果成功,该函数将返回0,如果失败,将返回-1。如果调用成功,系统会将一个复杂的消息数据放入消息队列中。
取消息
msgrcv函数从消息队列中接收消息:
int msgrcv(int msqid,void *msg_ptr,size_t msg_sz,long int msgtype,int msgflg);
第一个参数msqid是msgget函数返回的消息队列标记。
第二个参数msg_ptr是指向要接收的消息的指针。如msgsnd函数所述,该消息必须以long int类型开头。
第三个参数msg_sz是msg_ptr指向的消息大小,不包含long int消息类型。
第四个参数msgtype是一个long int类型,它允许实现接收优先级形式。如果msgtype的值为0,将接收队列中的第一条可用消息。如果它的值大于0,将接收第一个具有相同消息类型的消息。如果其值小于0,将接收第一个相同类型或小于msgtype绝对值的消息。
这听起来比实际操作复杂多了。如果我们只想按照消息发送的顺序接收消息,我们可以将msgtype设置为0。如果我们想要接收特殊消息类型的消息,我们可以将msgtype设置为等于这个值。如果我们希望接收的消息类型为n或小于n,我们可以将msgtype设置为-n。
第五个参数msgflg控制当没有合适类型的消息等待接收时如何处理。如果msgflg中的IPC_NOWAIT位置位,调用将立即返回,返回值为-1。如果msgflg标志中的IPC_NOWAIT位被清除,进程将被挂起,并等待适当类型的消息到达。
如果成功,msgrcv将返回放入接收缓冲区的字节数,消息将被复制到msg_ptr指向的用户分配缓冲区,数据将从消息队列中删除。如果失败,它将返回-1。
消息控制操作
最后一个消息队列函数是msgctl,它非常类似于共享内存中的控制函数。
int msgctl(int msqid,int command,struct msqid _ ds * buf);
qid _ ds结构至少包含以下成员:
结构msqid_ds {
uid _ t msg _ perm.uid
uid_t msg_perm.gid
mode _ t msg _ perm.mode
}
第一个参数msqid是msgget函数返回的标记。
第二个参数command是要执行的操作。他可以取以下三个值:
命令描述
IPC_STAT设置msqid_ds结构中的数据,以反映与消息队列关联的值。
IPC_SET如果进程有权限这样做,此命令将设置与msqid_ds数据结构中提供的消息队列相关联的值。
IPC_RMID删除消息队列。
如果成功将返回0,如果失败将返回-1。如果进程在msgsnd或msgrcv函数中等待时删除了消息队列,发送或接收函数将失败。
实验-消息队列
现在我们知道了消息队列的定义,我们可以看看它们实际上是如何工作的。和以前一样,我们将编写两个程序:msg1.c接收和msg2.c发送。我们将允许任何程序创建一个消息队列,但是在接收到最后一个消息后使用接收器删除消息队列。
1下面是接收程序:
#包含stdio.h
#包含stdlib.h
#包含字符串. h
#包含错误号h
#包括unistd.h
#包含sys/types.h
#包含sys/ipc.h
#包含sys/msg.h
结构my_msg_st
{
long int my _ msg _ type
char some _ text[BUFSIZ];
};
int main()
{
int running=1;
int msgid
struct my _ msg _ st some _ data
long int msg _ to _ receive=0;
2.首先,我们设置消息队列:
msgid=msg get((key _ t)12340666 IPC _ CREAT);
if(msgid==-1)
{
fprintf(stderr, msgget失败,错误为:%d/n ,errno);
退出(EXIT _ FAILURE);
}
3然后,接收消息队列中的消息直到遇到一个目标消息。最后,消息队列被删除:
(跑步时)
{
if(msgrcv(msgid,(void *) some_data,BUFSIZ,msg_to_receive,0)==-1)
{
fprintf(stderr, msgrcv失败,错误号:%d/n ,错误号);
退出(退出_失败);
}
printf(你写了:%s ,一些数据。一些_正文);
if(strncmp(some_data.some_text, end ,3)==0)
{
跑步=0;
}
}
if(msgctl(msgid,IPC_RMID,0)==-1)
{
fprintf(stderr, msgctl(IPC_RMID)失败/n’);
退出(退出_失败);
}
退出(退出_成功);
}
四发送程序与msg1.c类似。在主要的函数中,删除消息_到_接收声明,代之以缓冲区。移除消息队列删除代码,并且在运转循环中做出如下更改。现在我们调用msgsnd来将输入的文本发送到队列中。
#包含标准视频
#包含标准库
#包括unistd.h
#包含字符串。h
#包含错误号h
#包含sys/types.h
#包含sys/ipc.h
#包含系统/消息. h
#定义MAX_TEXT 512
结构我的消息
{
long int my _ msg _ type
char some _ TEXT[MAX _ TEXT];
};
int main()
{
int running=1;
结构化我的消息某些数据
int msgid
char buffer[BUFSIZ];
msgid=msg get((key _ t)12340666 IPC _ CREAT);
if(msgid==-1)
{
fprintf(stderr, msgget失败,错误号:%d/n ,错误号);
退出(退出_失败);
}
(跑步时)
{
printf(输入一些文本:);
fgets(buffer,BUFSIZ,stdin);
一些_数据。my _ msg _ type=1;
strcpy(some_data.some_text,buffer);
if(msgsnd(msgid,(void *) some_data,MAX_TEXT,0)==-1)
{
fprintf(stderr, msgsnd failed/n );
退出(退出_失败);
}
if(strncmp(buffer, end ,3)==0)
{
跑步=0;
}
}
退出(退出_成功);
}
与管道中的例子不同,进程并没有必要提供自己的同步机制。这是消息队列比起管道的一个巨大优点。
假设消息队列有空间,发送者可以创建队列,在队列中放入一些数据,并且甚至可以在接收者启动之前退出。我们会首先运行发送者。如下面的例子输出:
$ ./msg2
输入一些文本:你好
输入一些文本:你今天好吗?
输入一些文本:结束
$ ./msg1
你写道:你好
你写道:你今天好吗?
你写道:结束
$
发送者程序使用标识符创建一个消息队列;然后使用msgsnd函数向队列中添加消息。接收者使用标识符来获得消息队列标识符,并且接收消息,直到接收到特殊消息结束。然后他会使用消息控制操作删除消息队列进行一些清理工作。