进程间通信机制之一:消息队列

该系列文章将介绍几种进程间通信的方法,包括信号量、共享内存、消息队列、管道、FIFO。其中,信号量用于管理对资源的访问;共享内存用于在程序之间高效地共享数据;消息队列用于在程序之间传递数据的一种简单方法。

首先来介绍消息队列(message queue)。

消息队列与命名管道有许多相似之处,但少了在打开和关闭管道方面的复杂性。但使用消息队列并未解决我们在使用命名管道时遇到的一些问题,比如管道满时的阻塞问题。

消息队列提供了一种在两个不相关的进程之间传递数据的相当简单且有效的方法。与命名管道相比,消息队列的优势在于,它独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。而且,每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型值的数据块。好消息是,我们可以通过发送消息来几乎完全避免命名管道的同步和阻塞问题。更好的是,我们可以用一些方法来提前查看紧急消息。坏消息是:与管道一样,每个数据块都有一个最大长度的限制,系统中所有队列所包含的全部数据块的总长度也有一个上限。

虽然X/Open规范说明这些限制是强制的,但它并未提供发现这些限制的方法,只是告诉我们超过这些限制是引起一些消息队列函数失败的原因之一。Linux系统有两个宏定义MSGMAXMSGMNB,它们以字节为单位分别定义了一条消息的最大长度和一个队列的最大长度。其他系统中的这些宏定义可能会不一样或甚至根本就不存在。

消息队列函数的定义如下:

#include <sys/msg.h>


int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgget(key_t key, int msgflg);
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通常被msg.h自动包含进程序。

1. msgget函数

我们用msgget函数来创建和访问一个消息队列。

int msgget(key_t key, int msgflg);

与其他IPC机制一样,程序必须提供一个键值来命名某个特定的消息队列。特殊键值IPC_PRIVATE用于创建私有队列,从理论上来说,它应该只能被当前进程访问,但同信号量和共享内存的情况一样,消息队列在某些Linux系统中事实上并非私有。由于私有队列没有什么用处,所以这并不是一个很严重的问题。与以前一样,第二个参数msgflg由9个权限标志组成。由IPC_CREAT定义的一个特殊位必须和权限标志按位或才能创建一个新的消息队列。在设置IPC_CREAT标志时,如果给出的是一个已有消息队列的键也不会产生错误。如果消息队列已有,则IPC_CREAT标志就被悄悄地忽略掉。

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


2.msgsnd函数

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

int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

消息的结构受到两方面的约束。首先,它的长度必须小于系统规定的上限;其次,它必须以一个长整型成员变量开始,接收函数将用这个成员变量来确定消息的类型。当使用消息时,最好把消息结构定义为下面这样:

struct my_message
{
    long int message_type;
    /* The data you wish to transfer*/
};

由于在消息的接收中要用到message_type,所以你不能忽略它。你必须在声明自己的数据结构时包含它,并且最好将它初始化为一个已知值。

第一个参数msqid是由msgget函数返回的消息队列标识符。

第二个参数msg_ptr是一个指向准备发送消息的指针,消息必须像刚才说的那样以一个长整型成员变量开始。

第三个参数msg_sz是msg_ptr指向的消息的长度。这个长度不能包括长整型消息类型成员变量的长度。

第四个参数msgflg控制在当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。如果msgflg中设置了IPC_NOWAIT标志,函数将立刻返回,不发送消息并且返回值为-1。如果msgflg中的IPC_NOWAIT标志被清除,则发送进程将挂起以等待队列中腾出可用空间。

成功时这个函数返回0,失败时返回-1。如果调用成功,消息数据的一份副本将被放到消息队列中。


3.msgrcv函数

msgrcv函数从一个消息队列中获取消息:

int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);

第一个参数msqid是由msgget函数返回的消息队列标识符。

第二个参数msg_ptr是一个指向准备接收消息的指针,消息必须像前面msgsnd函数中介绍的那样以一个长整型成员变量开始。

第三个参数msg_sz是msg_ptr指向的消息的长度,它不包括长整型消息类型成员变量的长度。

第四个参数msgtype是一个长整数,它可以实现一种简单形式的接收优先级。如果msgtype的值为0,就获取队列中的第一个可用消息。如果它的值大于零,将获取具有相同消息类型的第一个消息。如果它的值小于零,将获取消息类型等于或小于msgtype的绝对值的第一个消息。

这个函数看起来好像很复杂,但实际应用时很简单。如果只想按照消息发送的顺序来接收它们,就把msgtype设置为0。如果只想获取某一特定类型的消息,就把msgtype设置为相应的类型值。如果想接收类型等于或小于n的消息,就把msgtype设置为-n。

第五个参数msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情。如果msgflg中的IPC_NOWAIT标志被设置,函数将会立刻返回,返回值是-1.如果msgflg中的IPC_NOWAIT标志被清除,进程将会挂起以等待一条相应类型的消息到达。

成功时msgrcv函数返回放到接收缓冲区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓冲区中,然后删除消息队列中的对应消息。失败时返回-1。


4.msgctl函数

最后一个消息队列函数是msgctl,它的作用与共享内存的控制函数非常相似:

int msgctl(int msqid, int command, struct msqid_ds *buf);

msqid_ds结构至少包含以下成员:

struct msqid_ds
{
    uid_t msg_perm.uid;
    uid_t msg_perm.gid;
    mode_t msg_perm.mode;
};

第一个参数msqid是由msgget返回的消息队列标识符。

第二个参数command是将要采取的动作。它可以取3个值,如下:

Command                                                             Description
IPC_STAT                         把msqid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET                            如果进程有足够的权限,就把消息队列的当前关联值设置为msqid_ds结构中给出的值
IPC_RMID                          删除消息队列

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

例子:

msg_receive.c用于接收消息;msg_send.c用于发送消息。

msg_receive.c代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h> /*for message queue functions*/


struct 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;

    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);/*create message queue*/
    if (msgid == -1)
    {
        fprintf(stderr, “msgget failed with error: %d\n”, errno);
        exit(EXIT_FAILURE);
    }

    while(running)
    {
        
        /*receive the data from message queue*/
if (msgrcv(msgid, (void *)&some_data, BUFSIZ,
msg_to_receive, 0) == -1)
{
fprintf(stderr, “msgrcv failed with error: %d\n”, errno);
exit(EXIT_FAILURE);
}

printf(“You wrote: %s”, some_data.some_text);


if (strncmp(some_data.some_text, “end”, 3) == 0)
{
running = 0;
}
}


if (msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, “msgctl(IPC_RMID) failed\n”);
exit(EXIT_FAILURE);
}

exit(EXIT_SUCCESS);
}

msg_send.c代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/*for message queue functions*/
#include <sys/msg.h> 

#define MAX_TEXT 512

struct my_msg_st
{
    long int my_msg_type;
    char some_text[MAX_TEXT];
};

int main()
{
    int running = 1;
    struct my_msg_st some_data;
    int msgid;
    char buffer[BUFSIZ];
    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);/*create message queue*/
    if (msgid == -1)
    {
        fprintf(stderr, “msgget failed with error: %d\n”, errno);
        exit(EXIT_FAILURE);
     }

    while(running)
    {
        printf(“Enter some text: “);
        fgets(buffer, BUFSIZ, stdin);
        some_data.my_msg_type = 1;
        strcpy(some_data.some_text, buffer);

        if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1)/*send data to message queue*/
        {
            fprintf(stderr, “msgsnd failed\n”);
            exit(EXIT_FAILURE);
        }

        if (strncmp(buffer, “end”, 3) == 0)
        {
            running = 0;
        }
    }
    exit(EXIT_SUCCESS);
}

注:影响消息队列的系统限制

Description

Typical values

FreeBSD 5.2.1

Linux 2.4.22

Mac OS X 10.3

Solaris 9

可发送最长消息的字节数

16,384

8,192

notsup

2,048

一个特定队列的最大字节数(亦即队列中所有消息之和)

2,048

16,384

notsup

4,096

系统中最大消息队列数

40

16

notsup

50

系统中最大消息数

40

            derived

notsup

40




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值