进程间通信的方式(三):消息队列

         消息队列是在两个不相关进程间传递数据的一种简单、高效方式,她独立于发送进程、接受进程而存在。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为是一个管道,接收进程可以独立地接收含有不同管道的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。消息队列与命名管道一样,每个数据块都有一个最大长度的限制。我们可以将每个数据块当作是一种消息类型(频道),发送和接收的内容就是这个类型(频道)对应的消息(节目),每个类型(频道)相当于一个独立的管道,相互之间互不影响。

1.概述


        消息队列可以是一种消息链表。有足够的权限的线程可以往队列中放置消息,有足够读权限的线程可以从队列中取走消息。每个消息都是一个记录,它由发送者赋予一个优先级。在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。这根管道和FIFO是相反的,对于后者来说,除非读出者已经存在,否则现有 写入者是没有意义的。
        一个进程可以往某个队列中写入一些消息,然后终止,再让另外一个进程在以后某个时刻读出这些消息。我没说过消息队列具有随内核的持续性,这跟管道和FIFO不一样,当一个管道和FIFO的最后一次关闭发生时,仍然在该管或FIFO上的数据将被丢弃。

1.1 消息队列结构

       我们将内核中的某个特定的消息队列画为一个消息链表。假设有一个具有三个消息的队列,消息长度分别为1字节、2字节和三字节,而且这些消息就是以这样的顺序写入该队列的。再假设这三个消息的类型(type)分别是100、200、300。


1.2 消息队列和管道的对比

1.匿名管道是跟随进程的,消息队列是跟随内核的,也就是说进程结束之后,匿名管道就死了,但是消息队列还会存在(除非显示调用函数销毁)
2.管道是文件,存放在磁盘上,访问速度慢,消息队列是数据结构,存放在内存,访问速度快
3.管道是数据流式存取,消息队列是数据块式存取

2 代码实现消息队列的通信

        首先从宏观的角度了解一下消息队列的工作机制。因为消息队列独立于进程而存在,为了区别不同的消息队列,需要以key值标记消息队列,这样两个不相关进程可以通过事先约定的key值通过消息队列进行消息收发。例如进程A向key消息队列发送消息,进程B从Key消息队列读取消息。在这一过程中主要涉及到四个函数:

#include <sys/msg.h> # 消息队列相关函数及数据结构头文件


int msgctl(int msqid, int cmd, struct msqid_ds *buf);# 控制消息队列函数


int msgget(key_t key, int msgflg); # 创建消息队列,key值唯一标识该消息队列


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);# 发送消息
server 消息接收方
# msg1.c 接收端


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h> # 包含消息队列相关函数及数据结构的头文件
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);# 创建标识符为key = 1234 的消息队列,注意发送端与接收端该值的一致性
    if (msgid == -1) {
        fprintf(stderr, “msgget failed with error: %d\n”, errno);
        exit(EXIT_FAILURE);
    }# 错误处理:msgget调用成功返回消息队列标识符,调用失败返回-1
    
    while(running) {
        if (msgrcv(msgid, (void *)&some_data, BUFSIZ,msg_to_receive, 0) == -1) { # 从消息队列接收消息,如果接收失败执行if语句并退出
                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) { # 如果接收到文本含有“end”,将running设置为0,效果是:退出while循环
            running = 0;
        }
    }
    
    if (msgctl(msgid, IPC_RMID, 0) == -1) { # 删除消息队列,如果删除失败执行if语句并退出
        fprintf(stderr, “msgctl(IPC_RMID) failed\n”);
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}
client 客户端消息发送方
# msg2.c 发送端


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#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);# 创建消息标识符key = 1234的消息队列。如果该队列已经存在,则直接返回该队列的标识符,以便向该消息队列收发消息
    if (msgid == -1) {
        fprintf(stderr, “msgget failed with error: %d\n”, errno);
        exit(EXIT_FAILURE);
    }# 错误处理,同接收者msg1
    while(running) {
        printf(“Enter some text: “);
        fgets(buffer, BUFSIZ, stdin);# 由控制台输入文本,并将其存放在buffer之中
        some_data.my_msg_type = 1;# 类型填充,在本例中没有特别含义
        strcpy(some_data.some_text, buffer);# 将buffer数据复制到some_text之中
        if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1) { # 向消息队列发送消息,如果发送失败执行if语句并退出
            fprintf(stderr, “msgsnd failed\n”);
            exit(EXIT_FAILURE);
        }
        if (strncmp(buffer, “end”, 3) == 0) {# 如果发送的“end”,则在发送“end”之后,退出while,结束程序
            running = 0;
        }
    }
    exit(EXIT_SUCCESS);
}



猜你喜欢

转载自blog.csdn.net/qq_21125183/article/details/80634110