消息队列与管道区别

消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人

员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。这两点是管道以及有名管道所不

能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管

道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。

注意消息队列加粗位置

一、什么是消息队列

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。  每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。

Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。

二、在Linux中使用消息队列

Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V PIC机制,即信号量和共享内存相似。

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所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样: 

1

2

3

4

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,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于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结构至少包括以下成员: 

1

2

3

4

5

6

struct msgid_ds

{

    uid_t shm_perm.uid;

    uid_t shm_perm.gid;

    mode_t shm_perm.mode;

};

成功时返回0,失败时返回-1.

三、使用消息队列进行进程间通信

马不停蹄,介绍完消息队列的定义和可使用的接口之后,我们来看看它是怎么让进程进行通信的。由于可以让不相关的进程进行行通信,所以我们在这里将会编写两个程序,msgreceive()和msgsned()来表示接收和发送信息。根据正常的情况,我们允许两个程序都可以创建消息,但只有接收者在接收完最后一个消息之后,它才把它删除。

接收信息的程序源文件为msgreceive.c的源代码为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/msg.h>

#include <errno.h>

 

struct msg_st

{

    long int msg_type;

    char text[BUFSIZ];

};

 

int main(int argc, char **argv)

{

    int msgid = -1;

    struct msg_st data;

    long int msgtype = 0;   // 注意1

 

    // 建立消息队列

    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);

    if (msgid == -1)

    {

        fprintf(stderr, "msgget failed width error: %d\n"errno);

        exit(EXIT_FAILURE);

    }

 

    // 从队列中获取消息,直到遇到end消息为止

    while (1)

    {

        if (msgrcv(msgid, (void *)&data, BUFSIZ, msgtype, 0) == -1)

        {

            fprintf(stderr, "msgrcv failed width erro: %d"errno);

        }

 

        printf("You wrote: %s\n", data.text);

 

        // 遇到end结束

        if (strncmp(data.text, "end", 3) == 0)

        {

            break;

        }

    }

 

    // 删除消息队列

    if (msgctl(msgid, IPC_RMID, 0) == -1)

    {

        fprintf(stderr, "msgctl(IPC_RMID) failed\n");

    }

 

    exit(EXIT_SUCCESS);

}

发送信息的程序的源文件msgsend.c的源代码为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <sys/msg.h>

#include <errno.h>

 

#define MAX_TEXT 512

 

struct msg_st

{

    long int msg_type;

    char text[MAX_TEXT];

};

 

int main(int argc, char **argv)

{

    struct msg_st data;

    char buffer[BUFSIZ];

    int msgid = -1;

 

    // 建立消息队列

    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);

    if (msgid == -1)

    {

        fprintf(stderr, "msgget failed error: %d\n"errno);

        exit(EXIT_FAILURE);

    }

 

    // 向消息队里中写消息,直到写入end

    while (1)

    {

        printf("Enter some text: \n");

        fgets(buffer, BUFSIZ, stdin);

        data.msg_type = 1; // 注意2

        strcpy(data.text, buffer);

 

        // 向队列里发送数据

        if (msgsnd(msgid, (void *)&data, MAX_TEXT, 0) == -1)

        {

            fprintf(stderr, "msgsnd failed\n");

            exit(EXIT_FAILURE);

        }

 

        // 输入end结束输入

        if (strncmp(buffer, "end", 3) == 0)

        {

            break;

        }

 

        sleep(1);

    }

 

    exit(EXIT_SUCCESS);

}

运行结果如下:

四、例子分析——消息类型

这里主要说明一下消息类型是怎么一回事,注意msgreceive.c文件main()函数中定义的变量msgtype(注释为注意1),它作为msgrcv()函数的接收信息类型参数的值,其值为0,表示获取队列中第一个可用的消息。再来看看msgsend.c文件中while循环中的语句data.msg_type = 1(注释为注意2),它用来设置发送的信息的信息类型,即其发送的信息的类型为1。所以程序msgreceive()能够接收到程序msgsend()发送的信息。

如果把注意1,即msgreceive.c文件main()函数中的语句由long int msgtype = 0;改变为long int msgtype = 2;会发生什么情况,msgreceive()将不能接收到程序msgsend()发送的信息。因为在调用msgrcv()函数时,如果msgtype(第四个参数)大于零,则将只获取具有相同消息类型的第一个消息,修改后获取的消息类型为2,而msgsend()发送的消息类型为1,所以不能被msgreceive()程序接收。重新编译msgreceive.c文件并再次执行,其结果如下:

我们可以看到,msgreceive并没有接收到信息和输出,而且当msgsend输入end结束后,msgreceive也没有结束,通过jobs命令我们可以看到它还在后台运行着。

五、消息队列与命名管道的比较

消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write(),接收数据用read(),则在消息队列中,发送数据用msgsnd(),接收数据用msgrcv()。而且它们对每个数据都有一个最大长度的限制。

与命名管道相比,消息队列的优势在于:

1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。

2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。

3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

 

<think>好的,用户现在问的是消息队列信号管道的联系对比。我需要先回顾一下这两个概念的基本知识,然后分析它们的异同点。 首先,消息队列信号管道都是进程间通信(IPC)的机制。消息队列属于System V IPC的一种,而管道则分为匿名管道命名管道(FIFO)。用户之前的问题涉及了FIFO的创建,所以可能对管道有一定了解,现在想进一步比较消息队列信号管道消息队列允许进程以消息的形式发送接收数据,每个消息有类型,接收方可以根据类型选择接收特定的消息。它独立于进程存在,生命周期长,适合需要复杂通信的场景。而信号管道可能是指管道(pipe)或FIFO,它们是字节流的方式,先进先出,没有消息类型的概念,通常用于父子进程或有亲缘关系的进程间通信。 接下来需要比较它们的联系区别。联系方面,两者都是进程间通信的方式,都基于内核提供的机制,都支持异步操作。区别的话,可以从数据格式、通信方式、同步机制、生命周期、使用复杂度等方面展开。 比如,消息队列的数据是结构化的,有类型,而管道是字节流;消息队列可以实现双向通信,而管道通常是单向的;消息队列需要调用msgget、msgsnd、msgrcv等函数,而管道用pipe或mkfifo,以及read/write;消息队列的生命周期独立于进程,需要显式删除,而匿名管道随进程终止而消失,FIFO需要显式删除。 另外,同步机制方面,消息队列自带同步,发送接收可以阻塞等待,而管道需要用户自己处理同步,比如用信号量或条件变量。可靠性方面,消息队列可能更可靠,因为有内核维护,而管道可能容易断裂,比如读端关闭时写入会触发SIGPIPE。 用户可能是在开发多进程应用时,需要选择合适的IPC机制,所以需要了解两者的优缺点适用场景。比如,需要复杂的消息路由持久化使用消息队列,而简单的父子进程通信用管道更轻量。 还要考虑用户可能存在的深层需求,比如性能、易用性、可移植性等。比如,消息队列可能需要更多的系统资源,而管道更轻量但功能有限。POSIX消息队列System V的消息队列是否有区别?用户可能对不同系统下的实现有疑问,但问题中没有提到,可能暂时不需要深入。 需要确保回答结构清晰,对比明确,帮助用户做出选择。最后按照要求生成相关问题,比如两者的同步机制、生命周期管理、数据格式差异等。</think>### 消息队列信号管道的联系对比 #### **1. 核心定义** | **概念** | **定义** | **关键特性** | |------------------|--------------------------------------------------------------------------|-----------------------------------------------------------------------------| | **消息队列** | System V IPC 机制,允许进程以**结构化消息**形式发送/接收数据(支持消息类型、优先级)。 | - 内核维护的链表<br>- 支持多进程并发访问<br>- 消息生命周期独立于进程 | | **信号管道** | 通常指**匿名管道**或**命名管道(FIFO)**,以**字节流**形式传输数据。 | - 半双工通信(单向)<br>- 匿名管道依赖父子进程关系<br>- FIFO 支持无亲缘进程通信 | --- #### **2. 联系** - **进程间通信(IPC)**:两者均用于进程间数据交换。 - **内核支持**:依赖内核提供的缓冲区管理(消息队列由内核维护,管道由文件系统或内核实现)。 - **异步操作**:均支持阻塞/非阻塞模式(如 `msgrcv` 阻塞等待消息,`read` 阻塞等待数据)。 - **同步需求**:需配合信号量或互斥锁实现复杂同步(消息队列内置部分同步机制,管道需手动管理)。 --- #### **3. 关键区别** | **对比维度** | **消息队列** | **信号管道(FIFO)** | |------------------|---------------------------------------------|------------------------------------------| | **数据格式** | 结构化消息(含类型、长度、正文) | 字节流(无结构,需用户定义协议) | | **通信方向** | 双向(通过消息类型选择性接收) | 单向(需两个管道实现双向) | | **同步机制** | 内置同步(如消息不存在时 `msgrcv` 阻塞) | 需手动同步(如 `read` 阻塞或轮询) | | **生命周期** | 独立存在(需显式删除 `msgctl(..., IPC_RMID)`)| 匿名管道随进程终止销毁;FIFO 需显式删除 | | **使用复杂度** | 复杂(需管理消息类型、队列标识符) | 简单(类似文件读写操作) | | **性能开销** | 较高(消息拷贝、内核链表管理) | 较低(基于内核缓冲区的流式传输) | | **可靠性** | 高(内核保障消息完整性) | 低(管道断裂可能导致 `SIGPIPE` 信号) | --- #### **4. 典型应用场景** | **场景** | **推荐机制** | **原因** | |---------------------------|------------------|--------------------------------------------------------------------------| | 多进程任务调度 | 消息队列 | 支持消息类型过滤(如优先级调度) | | 日志收集系统 | 消息队列 | 持久化消息存储,避免数据丢失 | | 简单父子进程通信 | 匿名管道 | 轻量级实现,无需额外权限管理 | | 无亲缘关系进程通信 | FIFO | 通过路径名访问,无需共享内存或消息队列 | | 实时数据传输(如音频流) | FIFO | 字节流模式适合连续数据传输 | --- #### **5. 代码示例对比** - **消息队列发送消息**: ```c #include <sys/msg.h> struct msgbuf { long mtype; // 消息类型 char mtext[1024]; // 消息正文 }; int msqid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT); struct msgbuf msg = {1, "Hello, Queue"}; msgsnd(msqid, &msg, sizeof(msg.mtext), 0); // 发送消息 ``` - **FIFO 读取数据**: ```c #include <fcntl.h> int fd = open("myfifo", O_RDONLY); // 打开 FIFO char buffer[1024]; read(fd, buffer, sizeof(buffer)); // 阻塞等待数据 ``` --- #### **6. 注意事项** - **消息队列**: - 需限制队列长度(避免内存耗尽)。 - 消息类型需预先约定(如 1 表示请求,2 表示响应)。 - **信号管道**: - 匿名管道仅限父子进程使用。 - FIFO 需处理 `O_WRONLY` 写端打开时的阻塞问题(可使用 `O_NONBLOCK`)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值