007_ipc概述及消息队列

【概述】

消息队列是一种进程间通信(IPC)机制,允许进程以消息为单位进行数据交换。消息队列提供了一种解耦的生产者-消费者模型,其中生产者进程创建消息并将其发送到队列,消费者进程从队列中接收消息并处理。

消息队列的特点

  • 异步通信:生产者和消费者不需要同时运行,消息可以在消费者准备好之前放入队列。
  • 解耦:生产者不需要知道消费者的身份,消费者也不需要知道生产者的身份。
  • 缓冲:消息队列可以存储多个消息,直到它们被处理。
  • 可靠性:大多数消息队列系统提供消息持久化,确保消息不会丢失。
  • 顺序保证:消息通常按照发送的顺序被接收。

消息队列的使用场景

  • 任务分发:将任务放入队列,由多个工作进程处理。
  • 日志收集:应用程序将日志消息发送到队列,由专门的日志处理服务处理。
  • 事件驱动架构:使用消息队列来触发不同服务的事件。
  • 数据流处理:在实时数据处理系统中,消息队列用于传输数据。

常见的消息队列系统

  • POSIX消息队列:UNIX-like系统提供的标准消息队列API。
  • System V消息队列:另一种UNIX-like系统上的消息队列机制。
  • RabbitMQ:一个流行的开源消息代理,支持多种协议。
  • Apache Kafka:一个分布式流处理平台,适用于高吞吐量的场景。
  • Amazon SQS:亚马逊提供的托管消息队列服务。
  • Redis:虽然主要是一个键值存储,但也支持发布/订阅模式和列表,可以用于简单的消息队列。

POSIX消息队列的基本操作

POSIX消息队列的相关函数包括:

  • mq_open():打开或创建一个消息队列。
  • mq_send():发送消息到队列。
  • mq_receive():从队列接收消息。
  • mq_close():关闭消息队列描述符。
  • mq_unlink():删除消息队列。

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

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

System V消息队列

【概述】

System V消息队列是一种较早的IPC实现,与POSIX消息队列相比,它允许消息队列在内核重启后仍然存在,直到被手动删除。这种机制通过在内核中维护一个消息队列来实现,队列中的每个成员都是一个数据块,包含消息类型和数据内容

【特点】

  1. 持久性:消息队列的生命周期不随进程结束而结束,而是随操作系统的运行而持续存在,需要显式删除
  2. 消息类型:每条消息都包含一个正整数的类型,接收进程可以根据类型选择性地读取特定消息
  3. 容量限制:消息队列的大小是有限制的,需要合理管理和清空,避免队列满导致阻塞
  4. 异步通信:发送方和接收方不需要同时进行,消息会存储在队列中,直到接收方读取

【使用方法】

System V消息队列的使用涉及以下几个主要步骤:

  1. 创建消息队列: 使用msgget函数创建消息队列,需要提供一个键值(key_t)和标志(msgflg)。key可以是ftok函数的返回值或IPC_PRIVATEmsgflg可以是IPC_CREAT(创建新队列)、IPC_EXCL(与IPC_CREAT一同使用,表示如果队列已存在则返回错误)或IPC_NOWAIT(如果操作无法立即完成则返回错误)

  2. 发送消息: 使用msgsnd函数发送消息到队列中。该函数需要消息队列的标识符(msqid)、消息结构体指针和消息类型

  3. 接收消息: 使用msgrcv函数从队列中接收消息。该函数需要消息队列的标识符(msqid)、消息结构体指针、消息类型和选项

  4. 控制消息队列: 使用msgctl函数对消息队列进行控制,如删除队列、调整队列属性等

【应用场景】

System V消息队列适用于需要在不同进程间进行灵活、持久性通信的场景,尤其是在需要根据消息类型进行筛选和处理的应用中

msgget

用于创建或获取System V消息队列的标识符的关键系统调用

函数原型

#include <sys/msg.h>
int msgget(key_t key, int msgflg);

参数说明

  1. key

    • 这是一个键值,用于标识消息队列。该键值由用户定义,通常通过ftok函数生成一个唯一的键值。ftok函数基于一个路径名和一个项目标识符生成一个唯一的键值。
    • 也可以使用IPC_PRIVATE作为键值,这会创建一个只能由当前进程及其子进程访问的私有消息队列。
  2. msgflg

    • 这是一个标志位,用来设定消息队列的创建权限和其他属性。它与文件的访问权限类似,可以是0666等。
    • 常用的标志位包括:
      • IPC_CREAT:如果消息队列不存在,则创建一个新的消息队列。
      • IPC_EXCL:与IPC_CREAT一起使用,如果消息队列已存在,则返回错误。
      • IPC_NOWAIT:如果请求的操作无法立即完成,则返回错误而不是阻塞。

返回值

  • 如果调用成功,msgget返回一个非负整数,即消息队列的标识符。
  • 如果调用失败,则返回-1,并设置errno以指示错误的原因。

与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.

示例代码

以下是一个简单的示例,展示如何使用msgget函数创建一个消息队列:

#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>

int main() {
    key_t key;
    int msgid;

    // 生成唯一键
    key = ftok("queuefile", 65);

    // 创建消息队列
    msgid = msgget(key, 0666 | IPC_CREAT);

    if (msgid == -1) {
        perror("msgget");
        exit(1);
    }

    printf("Message queue created with ID: %d\n", msgid);

    // 后续操作...

    return 0;
}

注意事项

  1. 键值唯一性:确保key是唯一的,通常使用ftok函数生成。
  2. 权限设置:正确设置msgflg中的权限参数,以确保消息队列的访问权限符合预期。
  3. 错误处理:在使用msgget时,应检查返回值并进行适当的错误处理,以确保程序的稳定性和安全性。

msgsnd

函数用于向System V消息队列发送消息

函数原型

#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数说明

  1. msqid

    消息队列的标识符,由msgget函数返回。
  2. msgp

    消息缓冲区指针,指向准备发送的消息。消息的数据结构必须以一个长整型成员变量开始,这个成员变量用于表示消息的类型。消息结构通常定义如下:
struct msgbuf {
    long mtype;
    char mtext[1];
};

可以根据需要扩展mtext数组的大小。

3.msgsz

消息数据的长度,不包括消息类型成员变量的长度。

注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。

4.msgflg

控制函数行为的标志:默认行为是阻塞,直到消息可以被发送;IPC_NOWAIT:如果消息队列已满或消息个数已满,函数不会阻塞,而是返回-1并设置errnoEAGAIN

返回值

  • 如果调用成功,返回0
  • 如果调用失败,返回-1并设置errno以指示错误的原因。

示例代码

以下是一个简单的示例,展示如何使用msgsnd函数向消息队列发送消息:

#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>

struct msgbuf {
    long mtype;
    char mtext[100];
};

int main() {
    key_t key;
    int msgid;
    struct msgbuf msg;

    // 生成唯一键
    key = ftok("queuefile", 65);

    // 获取消息队列标识符
    msgid = msgget(key, 0666 | IPC_CREAT);

    if (msgid == -1) {
        perror("msgget");
        exit(1);
    }

    // 准备发送的消息
    msg.mtype = 1;
    strcpy(msg.mtext, "Hello, message queue!");

    // 发送消息
    if (msgsnd(msgid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
        perror("msgsnd");
        exit(1);
    }

    printf("Message sent successfully\n");

    // 后续操作...

    return 0;
}

注意事项

  1. 消息结构:消息结构必须以一个长整型成员变量开始,用于表示消息的类型。
  2. 消息长度msgsz参数指定的是消息数据的长度,不包括消息类型成员变量的长度。
  3. 错误处理:在使用msgsnd时,应检查返回值并进行适当的错误处理,以确保程序的稳定性和安全性。

msgrcv

用于从System V消息队列中接收消息

函数原型

#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数说明

  1. msqid

    消息队列的标识符,由msgget函数返回。
  2. msgp

    消息缓冲区指针,用于接收从消息队列中读取的消息。消息缓冲区结构通常定义如下msgbuf可以根据需要扩展mtext数组的大小。
struct msgbuf {
    long mtype;
    char mtext[1];
};

3.msgsz

接收缓冲区的大小,即预期要接收的消息的最大字节数。如果实际接收到的数据超过此值,多余部分将被截断。

  1. msgtyp

    指定要接收的消息类型:
    • 0:接收消息队列中的第一条消息。

    • >0:接收消息队列中类型为msgtyp的第一条消息。 

    • <0:接收消息队列中类型值不大于msgtyp绝对值且类型值最小的第一条消息。

  2. msgflg

    • 控制函数行为的标志:
      • IPC_NOWAIT:如果消息队列中没有符合要求的消息,则立即返回-1并设置errnoENOMSG
      • 默认行为是阻塞,直到有符合要求的消息到来。

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

返回值

  • 如果调用成功,返回接收到的消息的长度(不包括消息类型成员变量的长度)。
  • 如果调用失败,返回-1并设置errno以指示错误的原因。

示例代码

以下是一个简单的示例,展示如何使用msgrcv函数从消息队列接收消息:

#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>

struct msgbuf {
    long mtype;
    char mtext[100];
};

int main() {
    key_t key;
    int msgid;
    struct msgbuf msg;

    // 生成唯一键
    key = ftok("queuefile", 65);

    // 获取消息队列标识符
    msgid = msgget(key, 0666 | IPC_CREAT);

    if (msgid == -1) {
        perror("msgget");
        exit(1);
    }

    // 接收消息
    if (msgrcv(msgid, &msg, sizeof(msg.mtext), 0, 0) == -1) {
        perror("msgrcv");
        exit(1);
    }

    printf("Received message: %s\n", msg.mtext);

    // 后续操作...

    return 0;
}

注意事项

  1. 消息结构:消息结构必须以一个长整型成员变量开始,用于表示消息的类型。
  2. 消息长度msgsz参数指定的是接收缓冲区的大小,不包括消息类型成员变量的长度。
  3. 错误处理:在使用msgrcv时,应检查返回值并进行适当的错误处理,以确保程序的稳定性和安全性。

msgctl

msgctl函数是一个用于控制消息队列的系统调用函数,在Unix/Linux系统中使用。它用于对消息队列进行操作,如创建、删除、修改和获取消息队列的状态信息等。

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

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

参数说明

  • msqid:消息队列的标识符,由msgget函数返回。
  • cmd:命令参数,用于指定msgctl函数的操作类型,可以是IPC_STATIPC_SETIPC_RMID等。
  • buf:用于传递消息队列的状态信息,是一个指向msqid_ds结构体类型的指针。

返回值

操作成功时返回0,否则返回-1。

msgctl函数支持以下几种命令参数:

  1. IPC_STAT:获取消息队列的当前状态信息,并将其存储在buf指向的msqid_ds结构体中。
  2. IPC_SET:设置消息队列的当前状态信息,设置值由buf指向的msqid_ds结构体给出。
  3. IPC_RMID:删除消息队列。当调用此命令时,消息队列将从系统内核中删除,不需要等待引用计数降为0

示例代码

以下是一个简单的示例,展示如何使用msgctl函数来获取和设置消息队列的状态信息:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int main() {
    key_t key = ftok("queuefile", 65);
    int msgid = msgget(key, 0666 | IPC_CREAT);

    struct msqid_ds info;
    msgctl(msgid, IPC_STAT, &info);
    printf("Current queue size: %ld\n", info.msg_qnum);

    info.msg_qnum = 0; // Resetting the queue size
    msgctl(msgid, IPC_SET, &info);

    msgctl(msgid, IPC_RMID, NULL); // Removing the queue
    return 0;
}

应用场景

msgctl函数主要用于以下场景:

  1. 获取消息队列的状态信息:使用IPC_STAT命令获取消息队列的当前状态信息,例如队列中的消息数量、最后发送消息的进程ID等。
  2. 设置消息队列的状态信息:使用IPC_SET命令可以修改消息队列的某些状态信息,例如队列的权限等。
  3. 删除消息队列:使用IPC_RMID命令可以删除一个消息队列,这在不再需要该队列时非常有用。

总结

msgctl函数是Linux系统中用于控制消息队列的重要系统调用函数,支持获取、设置和删除消息队列的操作。通过使用msgctl函数,可以方便地管理消息队列的状态和行为,从而实现高效、可靠的进程间通信。

testcode

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

msgrec.c实现如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <errno.h>

#define MAX_TEXT 512

typedef struct msg_st
{
	long int msg_type;	/*消息类型*/
	char text[MAX_TEXT];	/*消息存储的地方*/
}msgsst;

int main(int argc, char** argv)
{
	msgsst data;
	char buffer[MAX_TEXT];
	int msgid = -1;
	long int msgtype = 0;//注意 1

	/*1.建立消息队列*/
	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
	if (msgid == -1)
	{
		fprintf(stderr, "msgget failed error: %d\n", errno);
		exit(EXIT_FAILURE);
	}	
	/*2.从队列中获取消息, 直到遇到end消息为止*/
	while(1)
	{
		if(msgrcv(msgid, (void *)&data, MAX_TEXT, msgtype, 0) == -1)
		{
			fprintf(stderr, "msgrcv failed width erro: %d", errno);
		}
		printf("You wrote:: %s\n", data.text);
	/*3.遇到end结束*/
		if(strncmp(data.text, "end", 3) == 0)
		{
			break;
		}	
	}

	/*4.删除消息队列*/
	if(msgctl(msgid, IPC_RMID, 0) == -1)
	{
		fprintf(stderr, "msgctl(IPC_RMID) failed\n");
	}
	exit(EXIT_SUCCESS);
}

msgsend.c实现如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <errno.h>

#define MAX_TEXT 512

typedef struct msg_st
{
	long int msg_type;	/*消息类型*/
	char text[MAX_TEXT];	/*消息存储的地方*/
}msgsst;

int main(int argc, char** argv)
{
	msgsst data;
	char buffer[MAX_TEXT];
	int msgid = -1;

	/*1.建立消息队列*/
	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
	if (msgid == -1)
	{
		fprintf(stderr, "msgget failed error: %d\n", errno);
		exit(EXIT_FAILURE);
	}	
	/*2.往队列中写入消息, 直到遇到end消息为止*/
	while(1)
	{
		printf("Enter some text: \n");
		fgets(buffer, MAX_TEXT, stdin);
		data.msg_type = 1;//注意 2
		strcpy(data.text, buffer);
	/*3.往队列里发送数据*/
		if(msgsnd(msgid, (void *)&data, MAX_TEXT, 0) == -1)
		{
			fprintf(stderr, "msgsend failed!!!");
			exit(EXIT_FAILURE);
		}
	/*4.输入end结束*/
		if(strncmp(data.text, "end", 3) == 0)
		{
			break;
		}
		sleep(1);	
	}
	exit(EXIT_SUCCESS);
}

使用gcc -o msgrec.exe msgrec.c进行编译,得到msgrec.exe可执行文件,同理可以得到msgsend.exe可执行文件

运行结果如下:

这里主要说明一下消息类型是怎么一回事,注意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命令我们可以看到它还在后台运行着。

此分析摘录于Linux进程间通信(七):消息队列 msgget()、msgsend()、msgrcv()、msgctl() - 52php - 博客园

有兴趣的朋友可以查看原链接,作者讲的很不错

POSIX消息队列

POSIX消息队列是一种进程间通信机制,允许进程通过发送和接收消息来交换数据。这些消息在队列中按优先级顺序存储和传递。以下是对POSIX消息队列接口的详细介绍和使用示例。

mq_open

用于创建一个新的POSIX消息队列或打开一个已存在的消息队列。

函数原型

#include <fcntl.h>  // For O constants
#include <sys/stat.h>  // For mode constants
#include <mqueue.h>

mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);

参数说明

  1. name:消息队列的名称。这个名字在文件系统中有一个对应的路径,但并不直接在文件系统中创建一个普通文件。消息队列的名称必须以一个/开头,并且不能包含其他的/
  2. oflag:打开消息队列的标志,可以是以下几种:
    • O_RDONLY:以只读方式打开消息队列。
    • O_WRONLY:以只写方式打开消息队列。
    • O_RDWR:以读写方式打开消息队列。
    • O_CREAT:如果消息队列不存在,则创建它。此时需要提供modeattr参数。
    • O_EXCL:与O_CREAT一起使用,如果消息队列已存在,则mq_open调用失败并返回EEXIST错误。
    • O_NONBLOCK:以非阻塞模式打开消息队列,mq_sendmq_receive在无消息可发送或接收时不会阻塞。
  3. mode:如果oflag中指定了O_CREAT,则需要提供mode参数,用于设置消息队列的权限。这个参数与open函数的mode参数类似。
  4. attr:如果oflag中指定了O_CREAT,则需要提供attr参数,用于设置消息队列的属性。attr是一个指向mq_attr结构的指针,该结构包含了消息队列的各种属性,如最大消息数和消息大小等。

返回值

  • 成功时返回消息队列描述符(mqd_t类型)。
  • 失败时返回-1,并设置errno以指示错误21。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

int main() {
    mqd_t mqdes;
    struct mq_attr attr;
    const char *mq_name = "/my_message_queue";

    // 设置消息队列属性
    attr.mq_maxmsg = 10;  // 最大消息数
    attr.mq_msgsize = 1024;  // 消息最大大小

    // 创建消息队列
    mqdes = mq_open(mq_name, O_CREAT | O_RDWR, 0666, &attr);
    if (mqdes == -1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    printf("Message queue created successfully\n");

    // 关闭消息队列
    if (mq_close(mqdes) == -1) {
        perror("mq_close");
        exit(EXIT_FAILURE);
    }

    return 0;
}

关键点

  • mq_open函数不仅可以创建新的消息队列,还可以打开已存在的消息队列。
  • 使用O_CREAT标志时,需要提供modeattr参数来设置消息队列的权限和属性。
  • O_EXCL标志用于防止创建已存在的消息队列。
  • O_NONBLOCK标志使mq_sendmq_receive操作以非阻塞模式执行。

mq_send

用于将消息发送到POSIX消息队列中

函数原型

#include <mqueue.h>

int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);

参数说明

  1. mqdes:消息队列描述符,由mq_open函数返回。
  2. msg_ptr:指向要发送的消息的指针。
  3. msg_len:消息的长度(以字节为单位),必须小于或等于消息队列的mq_msgsize属性,否则函数执行失败。
  4. msg_prio:消息的优先级,一个无符号整数,值越大优先级越高。优先级高的消息会被插入到优先级低的消息前面;如果优先级相同,则新的消息会被插入到旧消息的后面。优先级的值必须小于MQ_PRIO_MAX

返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误。

错误码

  • EAGAIN:消息队列已满,且O_NONBLOCK标志被设置。
  • EBADFmqdes不是一个有效的消息队列描述符。
  • EFAULTmsg_ptr指向的地址无效。
  • EINTR:函数执行被信号中断。
  • EINVALmsg_len大于消息队列的mq_msgsize属性。
  • EIO:消息队列发生I/O错误

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>

int main() {
    mqd_t mqdes;
    struct mq_attr attr;
    const char *mq_name = "/my_message_queue";
    char msg[] = "Hello, POSIX message queue!";
    unsigned int msg_prio = 1;

    // 设置消息队列属性
    attr.mq_maxmsg = 10;  // 最大消息数
    attr.mq_msgsize = 1024;  // 消息最大大小

    // 创建消息队列
    mqdes = mq_open(mq_name, O_CREAT | O_RDWR, 0666, &attr);
    if (mqdes == -1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    // 发送消息
    if (mq_send(mqdes, msg, strlen(msg) + 1, msg_prio) == -1) {
        perror("mq_send");
        mq_close(mqdes);
        exit(EXIT_FAILURE);
    }

    printf("Message sent successfully\n");

    // 关闭消息队列
    if (mq_close(mqdes) == -1) {
        perror("mq_close");
        exit(EXIT_FAILURE);
    }

    return 0;
}

关键点

  • mq_send函数将消息插入到消息队列中,消息按照优先级从高到低排列。
  • 如果消息队列已满,且未设置O_NONBLOCK标志,mq_send会阻塞直到队列中有可用空间。
  • 如果设置了O_NONBLOCK标志,消息队列已满时,mq_send会立即返回EAGAIN错误。
  • 消息的优先级通过msg_prio参数设置,优先级越高,消息越早被处理。

mq_receive

从POSIX消息队列中接收消息

函数原型

#include <mqueue.h>

ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);

参数说明

  1. mqdes:消息队列描述符,由mq_open函数返回。
  2. msg_ptr:指向用于存储接收到的消息的缓冲区的指针。
  3. msg_len:缓冲区的大小(以字节为单位),必须大于或等于消息队列的mq_msgsize属性,否则函数执行失败。
  4. msg_prio:指向用于存储接收到的消息优先级的变量的指针。如果不需要获取优先级,可以设置为NULL

返回值

  • 成功时返回接收到的消息的字节数。
  • 失败时返回-1,并设置errno以指示错误

错误码

  • EAGAIN:消息队列是空的,并且O_NONBLOCK标志被设置。
  • EBADFmqdes不是一个有效的消息队列描述符。
  • EFAULTmsg_ptr指向的地址无效。
  • EINTR:函数执行被信号中断。
  • EINVALmsg_len小于消息队列的mq_msgsize属性。
  • EIO:消息队列发生I/O错误

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

int main() {
    mqd_t mqdes;
    struct mq_attr attr;
    const char *mq_name = "/my_message_queue";
    char msg[1024];
    unsigned int msg_prio;

    // 设置消息队列属性
    attr.mq_maxmsg = 10;  // 最大消息数
    attr.mq_msgsize = 1024;  // 消息最大大小

    // 创建消息队列
    mqdes = mq_open(mq_name, O_CREAT | O_RDWR, 0666, &attr);
    if (mqdes == -1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    // 接收消息
    ssize_t received_bytes = mq_receive(mqdes, msg, sizeof(msg), &msg_prio);
    if (received_bytes == -1) {
        perror("mq_receive");
        mq_close(mqdes);
        exit(EXIT_FAILURE);
    }

    printf("Received message: %s\n", msg);
    printf("Message priority: %u\n", msg_prio);

    // 关闭消息队列
    if (mq_close(mqdes) == -1) {
        perror("mq_close");
        exit(EXIT_FAILURE);
    }

    return 0;
}

关键点

  • mq_receive函数从消息队列中接收最高优先级的最早消息。
  • 如果消息队列是空的,并且未设置O_NONBLOCK标志,mq_receive会阻塞直到队列中有消息到来。
  • 如果设置了O_NONBLOCK标志,消息队列是空时,mq_receive会立即返回EAGAIN错误。
  • 消息的优先级通过msg_prio参数返回,优先级越高,数值越大。

mq_close

关闭POSIX消息队列的描述符

函数原型

#include <mqueue.h>

int mq_close(mqd_t mqdes);

参数说明

mqdes:消息队列描述符,由mq_open函数返回

返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误

错误码

  • EBADF:参数mqdes不是一个有效的消息队列描述符。

功能描述

  • mq_close函数断开消息队列描述符与对应消息队列之间的连接。关闭后,调用进程不能再使用该描述符。
  • 如果进程在消息队列上附加了通知请求,该请求会被移除,其他线程可以在此消息队列上附加通知。
  • 关闭消息队列并不会删除消息队列本身,要删除消息队列,必须调用mq_unlink函数

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

int main() {
    mqd_t mqdes;
    struct mq_attr attr;
    const char *mq_name = "/my_message_queue";

    // 设置消息队列属性
    attr.mq_maxmsg = 10;  // 最大消息数
    attr.mq_msgsize = 1024;  // 消息最大大小

    // 创建消息队列
    mqdes = mq_open(mq_name, O_CREAT | O_RDWR, 0666, &attr);
    if (mqdes == -1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    // 使用消息队列...

    // 关闭消息队列
    if (mq_close(mqdes) == -1) {
        perror("mq_close");
        exit(EXIT_FAILURE);
    }

    return 0;
}

关键点

  • mq_close函数用于关闭消息队列描述符,确保进程不再使用该描述符。
  • 关闭描述符不会删除消息队列,删除队列需要使用mq_unlink函数。
  • 如果在消息队列上附加了通知请求,关闭时会移除这些请求

mq_unlink

删除POSIX消息队列

函数原型

#include <mqueue.h>

int mq_unlink(const char *name);

参数说明

name:消息队列的名称。名称必须以/开头,且名称中不能包含其他的/字符

返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误

错误码

  • EACCES:没有权限删除指定的消息队列。
  • ENOENT:指定的消息队列不存在。
  • ENAMETOOLONG:参数name的长度超过系统定义的最大长度1。

功能描述

  • mq_unlink函数会从内核中删除名为name的消息队列。
  • 如果该函数被调用,但是仍然有进程已经打开了这个消息队列,那么这个消息队列的销毁会被推迟到所有的引用都被关闭时执行。函数mq_unlink不需要阻塞到所有的引用都被关闭为止,它会立即返回12。
  • 一旦打开队列的其他任何进程关闭其引用该队列的描述符,队列本身就会被销毁

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

int main() {
    const char *mq_name = "/my_message_queue";

    // 删除消息队列
    if (mq_unlink(mq_name) == -1) {
        perror("mq_unlink");
        exit(EXIT_FAILURE);
    }

    return 0;
}

关键点

  • mq_unlink函数用于删除消息队列的名称,并标记队列以便在所有进程关闭该队列后销毁。
  • 删除操作是异步的,即函数会立即返回,而队列的销毁会在所有引用关闭后进行。
  • 删除消息队列并不会影响已经打开的描述符,但这些描述符在关闭后不再有效

mq_getattr

获取POSIX消息队列的属性

函数原型

#include <mqueue.h>

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);

参数说明

  • mqdes:消息队列描述符,由mq_open函数返回。
  • attr:指向mq_attr结构体的指针,用于存放获取的消息队列属性。
  • mq_attr结构体

    mq_attr结构体定义如下:

struct mq_attr {
    long mq_flags;    /* Flags: 0 or O_NONBLOCK */
    long mq_maxmsg;   /* Max. # of messages on queue */
    long mq_msgsize;  /* Max. message size (bytes) */
    long mq_curmsgs;  /* # of messages currently in queue */
};
  • mq_flags:与打开的消息队列描述符相关的标志,可以是0(阻塞)或O_NONBLOCK(非阻塞)。
  • mq_maxmsg:消息队列中最多能容纳的消息个数。
  • mq_msgsize:每个消息的最大字节数。
  • mq_curmsgs:当前消息队列中正在排队的消息个数

错误码

  • EBADF:参数mqdes不是有效的消息队列描述符。

返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误。

示例代码

#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main() {
    mqd_t mqdes;
    struct mq_attr attr;

    // 假设mqdes是已经打开的消息队列描述符
    if (mq_getattr(mqdes, &attr) < 0) {
        perror("mq_getattr");
        exit(EXIT_FAILURE);
    } else {
        printf("消息队列属性:\n");
        printf("mq_flags: %ld\n", attr.mq_flags);
        printf("mq_maxmsg: %ld\n", attr.mq_maxmsg);
        printf("mq_msgsize: %ld\n", attr.mq_msgsize);
        printf("mq_curmsgs: %ld\n", attr.mq_curmsgs);
    }

    return 0;
}

通过这个示例,可以获取并打印出消息队列的各种属性

mq_setattr

设置POSIX消息队列的属性

函数原型

#include <mqueue.h>

int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);

参数说明

  • mqdes:消息队列描述符,由mq_open函数返回。
  • newattr:指向新的消息队列属性结构体的指针,用于设置新的属性。
  • oldattr:指向旧的消息队列属性结构体的指针,如果非NULL,则用于存放修改前的属性。

mq_attr结构体

mq_attr结构体定义如下:

struct mq_attr {
    long mq_flags;    /* Flags: 0 or O_NONBLOCK */
    long mq_maxmsg;   /* Max. # of messages on queue */
    long mq_msgsize;  /* Max. message size (bytes) */
    long mq_curmsgs;  /* # of messages currently in queue */
};
  • mq_flags:与打开的消息队列描述符相关的标志,可以是0(阻塞)或O_NONBLOCK(非阻塞)。
  • mq_maxmsg:消息队列中最多能容纳的消息个数。
  • mq_msgsize:每个消息的最大字节数。
  • mq_curmsgs:当前消息队列中正在排队的消息个数12。

属性修改限制

  • mq_flags:可以通过mq_setattr函数设置或清除消息队列的非阻塞标志(O_NONBLOCK)。
  • mq_maxmsgmq_msgsizemq_curmsgs:这些属性在消息队列创建时被初始化,之后无法通过mq_setattr修改

返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误。

错误码

  • EBADF:参数mqdes不是有效的消息队列描述符。
  • EFAULT:newattr或oldattr指向的内存地址无效。
  • EINVAL:newattr中的属性值无效

示例代码

#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main() {
    mqd_t mqdes;
    struct mq_attr new_attr, old_attr;

    // 假设mqdes是已经打开的消息队列描述符
    new_attr.mq_flags = O_NONBLOCK; // 设置为非阻塞模式

    if (mq_setattr(mqdes, &new_attr, &old_attr) < 0) {
        perror("mq_setattr");
        exit(EXIT_FAILURE);
    } else {
        printf("消息队列属性修改成功。\n");
        printf("旧属性:\n");
        printf("mq_flags: %ld\n", old_attr.mq_flags);
        printf("mq_maxmsg: %ld\n", old_attr.mq_maxmsg);
        printf("mq_msgsize: %ld\n", old_attr.mq_msgsize);
        printf("mq_curmsgs: %ld\n", old_attr.mq_curmsgs);
    }

    return 0;
}

通过这个示例,可以设置消息队列的非阻塞标志,并获取修改前的属性

mq_notify

为POSIX消息队列注册或注销异步通知

函数原型

#include <mqueue.h>

int mq_notify(mqd_t mqdes, const struct sigevent *notification);

参数说明

  • mqdes:消息队列描述符,由mq_open函数返回。
  • notification:指向sigevent结构的指针,描述了当消息到达时如何通知进程。如果notification为NULL,则注销当前进程接收通知的注册。

sigevent结构

sigevent结构定义如下:

union sigval {
    int sival_int;    /* Integer value */
    void *sival_ptr;  /* Pointer value */
};

struct sigevent {
    int sigev_notify;              /* Notification method */
    int sigev_signo;               /* Notification signal */
    union sigval sigev_value;      /* Data passed with notification */
    void (*sigev_notify_function)(union sigval); /* Function used for thread notification (SIGEV_THREAD) */
    pthread_attr_t sigev_notify_attributes; /* Attributes for notification thread (SIGEV_THREAD) */
    pid_t sigev_notify_thread_id; /* ID of thread to signal (SIGEV_THREAD_ID); Linux-specific */
};
  • sigev_notify:指定通知方法,可以是SIGEV_NONE、SIGEV_SIGNAL或SIGEV_THREAD。
  • sigev_signo:指定通知信号,当sigev_notify为SIGEV_SIGNAL时使用。
  • sigev_value:与通知一起传递的数据。
  • sigev_notify_function:当sigev_notify为SIGEV_THREAD时,指定用于线程通知的函数。
  • sigev_notify_attributes:当sigev_notify为SIGEV_THREAD时,指定通知线程的属性。
  • sigev_notify_thread_id:当sigev_notify为SIGEV_THREAD_ID时,指定要通知的线程ID(Linux特定)。

通知方式

  • SIGEV_NONE:不发送通知。
  • SIGEV_SIGNAL:通过发送信号来通知进程。信号的信息可以通过siginfo_t结构获取。
  • SIGEV_THREAD:创建一个线程来执行sigev_notify_function指定的函数

返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误。

示例代码

#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void notify_function(union sigval val) {
    printf("Received notification: %d\n", (int)val.sival_int);
}

int main() {
    mqd_t mqdes;
    struct sigevent notify;
    struct mq_attr attr;
    mqdes = mq_open("/my_queue", O_CREAT | O_RDWR, 0644, &attr);
    if (mqdes == (mqd_t)-1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    notify.sigev_notify = SIGEV_THREAD;
    notify.sigev_value.sival_int = 123;
    notify.sigev_notify_function = notify_function;
    notify.sigev_notify_attributes = NULL;

    if (mq_notify(mqdes, &notify) < 0) {
        perror("mq_notify");
        exit(EXIT_FAILURE);
    }

    // 模拟发送消息
    mq_send(mqdes, "Hello", 5, 0);

    sleep(1); // 等待通知处理

    if (mq_notify(mqdes, NULL) < 0) { // 注销通知
        perror("mq_notify");
        exit(EXIT_FAILURE);
    }

    mq_close(mqdes);
    mq_unlink("/my_queue");

    return 0;
}

关键点

  • 任意时刻只有一个进程可以被注册为接收某个给定队列的通知。
  • 当消息队列中有新消息到达并且队列先前为空时,才会触发通知。
  • 如果在调用mq_notify时,notification参数为NULL,则注销当前进程接收通知的注册。
  • 当通知被发送给注册进程时,其注册自动撤销,进程需要重新调用mq_notify以重新注册

testcode

posix_mq_send.c实现

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>



int main(int argc, char *argv)
{
	mqd_t mqdes;/*队列标识符id*/
	const char *mq_name = "/my_message_queue";
	struct mq_attr attr;
	char msgbuf[512];
	unsigned int msg_prio = 3;
	int ret;
	
/*1.设置消息队列属性*/
	attr.mq_maxmsg = 5;    /*最大消息数*/
	attr.mq_msgsize = 1024;/*消息最大大小*/
/*2.创建消息队列*/
	mqdes = mq_open(mq_name, O_CREAT | O_RDWR, 0666, &attr);
	if (mqdes == -1)
	{
		perror("mq_open is Failed!!!");
		exit(EXIT_FAILURE);	
	}
	while(1)
	{
/*3.发送消息*/
		fgets(msgbuf, 512-1, stdin);/*获取要发送的数据*/
		ret = mq_send(mqdes, msgbuf, sizeof(msgbuf), msg_prio);/*发送数据*/
		if (ret == -1)
		{
			perror("mq_send is failed!!!");
			exit(EXIT_FAILURE);
		}
		if (strncmp(msgbuf, "end", 3) == 0)/*输入end结束输入*/
		{
			break;
		}
	}
/*4.关闭消息队列*/
	if (mq_close(mqdes) == -1)
	{
		perror("mq_close is failed!!!");
		exit(EXIT_FAILURE);
	}
	return 0;
}

编译时候遇到的问题,如下:

如下,编译OK:

运行后的问题:

发现提示消息太长,超出了最大消息1024长度,那么,BUFSIZ到底是多大?理论说在C环境下是512大小,但是在linux下呢?搜索如下:

所以应该是我们这个系统和编译器中的BUFSIZ绝对比512大的多,都超过1024了都,我们将BUFSIZ改为512后,成功运行。

posix_mq_rec.c实现:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>



int main(int argc, char *argv)
{
	mqd_t mqdes;/*队列标识符id*/
	const char *mq_name = "/my_message_queue";
	struct mq_attr attr;
	char msgbuf[BUFSIZ];
	unsigned int msg_prio;
	ssize_t rec_bytes;
	
/*1.设置消息队列属性*/
	attr.mq_maxmsg = 5;    /*最大消息数*/
	attr.mq_msgsize = 1024;/*消息最大大小*/
/*2.创建消息队列*/
	mqdes = mq_open(mq_name, O_CREAT | O_RDWR, 0666, &attr);
	if (mqdes == -1)
	{
		perror("mq_open is Failed!!!");
		exit(EXIT_FAILURE);	
	}
	while(1)
	{
/*3.接受消息*/
		rec_bytes = mq_receive(mqdes, msgbuf, sizeof(msgbuf), &msg_prio);
		if (rec_bytes == -1)
		{
			perror("mq_receive is failed!!!");
			exit(EXIT_FAILURE);
		}
		printf("Received message: %s\n", msgbuf);
		printf("Received message priority: %u\n", msg_prio);
		/*遇到end退出接收*/
		if (strncmp(msgbuf, "end", 3) == 0)
		{
			break;
		}
	}
/*4.关闭消息队列*/
	if (mq_close(mqdes) == -1)
	{
		perror("mq_close is failed!!!");
		exit(EXIT_FAILURE);
	}
	return 0;
}

验证结果如下:

【总结】

POSIX消息队列和System V消息队列都是UNIX系统中用于进程间通信(IPC)的消息队列机制,但它们在设计和使用上有一些显著的区别:

1.接口和命名

  • POSIX消息队列:使用POSIX标准定义的接口,队列通过文件系统中的路径名来标识。

  • System V消息队列:使用System V IPC接口,队列通过一个整数标识符(消息队列ID)来标识。

2. 持久性

  • POSIX消息队列:通常是持久的,即使没有进程打开队列,队列及其内容也会保留。

  • System V消息队列:也是持久的,但需要显式地删除队列以释放资源。

3. 权限管理

  • POSIX消息队列:使用文件系统权限模型,通过文件模式位来控制访问权限。

  • System V消息队列:使用独立的权限控制机制,通过消息队列的访问结构来设置权限。

4. 消息优先级

  • POSIX消息队列:支持消息优先级,可以发送带有优先级的消息,队列按优先级顺序传递消息。

  • System V消息队列:不支持消息优先级,消息按照发送的顺序传递。

5. 异步通知

  • POSIX消息队列:支持异步通知,允许进程在消息到达时接收通知。

  • System V消息队列:不支持异步通知,通常需要使用轮询或阻塞调用。

6. API复杂性

  • POSIX消息队列:API相对简单,更符合现代编程习惯。

  • System V消息队列:API较为复杂,使用起来不那么直观。

7. 跨平台兼容性

  • POSIX消息队列:符合POSIX标准,具有良好的跨平台兼容性。

  • System V消息队列:主要是UNIX系统的特性,跨平台兼容性较差。

8. 性能

  • POSIX消息队列:通常提供更好的性能,特别是在高负载情况下。

  • System V消息队列:性能可能不如POSIX消息队列,尤其是在大量消息交换时。

9. 资源限制

  • POSIX消息队列:资源限制(如最大消息数和消息大小)可以通过文件系统参数配置。

  • System V消息队列:资源限制由系统全局参数控制,可能需要系统管理员调整。

10. 使用场景

  • POSIX消息队列:适用于需要高效、可靠且易于使用的IPC机制的场合。

  • System V消息队列:在一些遗留系统中仍然使用,但对于新开发的应用,通常推荐使用POSIX消息队列。

总的来说,POSIX消息队列在功能、性能和易用性方面相对优于System V消息队列,因此在现代UNIX系统中更受欢迎。然而,System V消息队列在一些特定场景和遗留系统中仍然有其应用价值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值