🔥个人主页:guoguoqiang. 🔥专栏:Linux的学习
文章目录
- 消息队列的引入以及基本概念
- **消息队列的基本概念**
- 消息队列与命名管道和共享内存的不同
- 消息队列的原理
- 消息队列工作流程
- System V 消息队列的主要函数
- msgget
- msgrcv
- msgsnd
- msgctl
- **获取消息队列的状态**
- 设置消息队列状态
消息队列的引入以及基本概念
- 引入背景
- 进程间通信的需求多样性:在多进程的系统环境中,进程间需要进行通信来协调工作、共享数据等。除了简单的管道通信和共享内存通信外,还存在一种情况,即进程间需要以一种异步的、有消息边界的方式进行通信。例如,在一个服务器 - 客户端模型中,客户端进程可能会不定期地向服务器进程发送请求消息,服务器进程需要按照接收消息的顺序来处理这些请求,并且这些消息可能具有不同的类型和优先级。
- 对数据传输顺序和完整性的要求:有时候,进程间传递的数据需要保证顺序性和完整性。比如,在一个分布式系统的日志收集系统中,各个节点进程产生的日志消息需要按照产生的时间顺序发送到日志服务器进程进行存储和分析,并且要保证每条消息的完整性,不能出现消息丢失或者部分消息内容丢失的情况。消息队列这种通信方式可以很好地满足这些需求。
消息队列的引入主要是为了实现解耦、异步处理、削峰填谷、容错性以及系统的高可用性。 在现如今的分布式系统中,服务之间的直接依赖可能会导致系统的复杂度增加,尤其是在面对高并发、大数据量处理时。消息队列能够作为中间层,通过在生产者和消费者之间插入一个队列,让系统各个模块之间相互独立,松耦合,从而提高系统的稳定性和性能。
- 基本概念
- 消息队列的定义:消息队列是一种进程间通信(IPC)机制,它可以看作是一个在系统内核中维护的消息链表。消息队列提供了一种异步的、有消息边界的通信方式,允许进程将消息发送到队列中,也允许其他进程从队列中接收消息。
- 消息的结构:
- 消息类型:消息通常有一个类型字段,用于区分不同类型的消息。例如,在一个网络服务器应用中,可能有连接请求消息、数据传输消息、断开连接消息等不同类型,通过消息类型字段可以对这些消息进行分类处理。消息类型可以是整数类型或者其他自定义的类型标识方式。
- 消息内容:除了消息类型,消息还包含实际要传递的数据内容。消息内容可以是各种数据结构,如字符串、结构体等。例如,在一个文件传输系统中,消息内容可能是文件的一部分数据,或者是文件的元数据(如文件名、文件大小等)。
- 消息队列的操作:
- 发送消息(msgsnd):进程通过
msgsnd
系统调用将消息发送到消息队列中。在发送消息时,需要指定消息队列的标识符(通过msgget
获取)、消息的指针、消息的大小以及一些控制标志(如是否阻塞等待消息队列有足够的空间来接收消息等)。例如,在C语言中,msgsnd
函数的原型如下:
- 发送消息(msgsnd):进程通过
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
其中`msqid`是消息队列标识符,`msgp`是指向消息结构的指针,`msgsz`是消息大小,`msgflg`是控制标志。- **接收消息(msgrcv)**:进程使用`msgrcv`系统调用从消息队列中接收消息。接收消息时,也需要指定消息队列标识符、用于存储接收消息的缓冲区指针、缓冲区大小、消息类型以及一些控制标志(如是否阻塞等待消息队列中有指定类型的消息等)。例如,`msgrcv`函数原型如下:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
其中`msgtyp`用于指定要接收的消息类型。- **创建和获取消息队列(msgget)**:通过`msgget`系统调用可以创建一个新的消息队列或者获取一个已经存在的消息队列的标识符。在创建消息队列时,需要指定一个键值(可以通过`ftok`函数生成)和一些权限标志等。例如,`msgget`函数原型如下:
int msgget(key_t key, int msgflg);
其中`key`是键值,`msgflg`包含创建标志(如`IPC_CREAT`)和权限标志等。- **控制消息队列(msgctl)**:使用`msgctl`系统调用可以对消息队列进行一些控制操作,如获取消息队列的状态信息、设置消息队列的属性、删除消息队列等。例如,`msgctl`函数原型如下:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
其中`cmd`指定了要进行的控制操作(如`IPC_RMID`用于删除消息队列),`buf`是一个用于存储消息队列状态信息的结构体指针。
- 消息队列的特性:
- 异步通信:发送消息的进程和接收消息的进程不需要同时运行。发送进程可以在任何时候将消息发送到消息队列中,接收进程可以在之后的某个时间从消息队列中获取消息进行处理。这种异步性使得进程可以独立地进行工作,提高了系统的灵活性和并发处理能力。
- 消息边界明确:每条消息在消息队列中是独立的个体,接收进程在接收消息时,是按照消息为单位进行接收的,不会出现像管道通信中可能出现的将多条消息混在一起的情况。这保证了消息的完整性和独立性,便于对消息进行处理和管理。
- 支持多种消息类型:消息队列可以通过消息类型来区分不同用途的消息,使得接收进程可以根据消息类型进行有选择地接收和处理消息,提高了消息处理的效率和针对性。
- 解耦
含义
- 在消息队列的应用场景中,解耦是指将消息的发送者和接收者在逻辑上进行分离,使得它们之间不需要直接相互依赖。每个进程只需要关注消息队列这个中间媒介,发送者将消息发送到消息队列,接收者从消息队列获取消息进行处理。
应用示例
- 以电商系统为例,订单处理系统和库存管理系统需要进行通信。如果没有消息队列,订单处理系统在处理订单时可能需要直接调用库存管理系统的接口来更新库存。这样,两个系统之间的耦合度很高,一旦库存管理系统的接口发生变化或者出现故障,就会直接影响订单处理系统的正常工作。
- 使用消息队列后,订单处理系统在生成订单后,只需将包含订单信息(如商品编号、购买数量等)的消息发送到消息队列。库存管理系统从消息队列中获取消息后进行库存更新。这样,两个系统之间通过消息队列进行解耦,它们可以独立地进行开发、部署和维护。即使库存管理系统暂时不可用(例如进行系统升级),订单处理系统仍然可以将订单消息发送到消息队列,待库存管理系统恢复后再处理这些消息。
- 异步处理
含义
- 消息队列支持异步通信模式,即发送消息的进程和接收消息的进程不需要同步执行。发送者可以在任何时间将消息发送到消息队列,而接收者可以根据自己的节奏从消息队列中获取消息并处理,不受发送者发送消息时间的限制。
应用示例
- 在一个文件上传系统中,用户通过客户端上传文件。客户端将文件数据切割成多个消息(每个消息包含一部分文件数据)发送到消息队列。服务器端的文件接收程序从消息队列中异步地获取这些消息并将文件数据重新组合保存到服务器的存储设备中。
- 在这个过程中,用户不需要等待文件完全上传并处理完成后才能进行其他操作。客户端发送消息到消息队列后就可以继续响应用户的其他操作,如显示上传进度提示等。而服务器端则可以根据自己的处理能力和资源状况,从消息队列中获取消息进行处理,提高了系统的整体响应速度和用户体验。
- 削峰填谷
含义
- 在高并发的系统场景中,消息队列可以起到缓冲作用。当系统瞬间收到大量请求(峰值)时,这些请求可以先被放入消息队列,而不是直接冲击后端的处理系统,从而避免后端系统因为无法承受瞬间的高负载而崩溃。然后,后端处理系统可以按照自己的处理能力从消息队列中逐步获取消息进行处理,在请求低谷期也能利用消息队列中的消息保持系统的持续运行,达到削平峰值、填充低谷的效果。
应用示例
- 以一个热门电商网站的促销活动为例,在促销活动开始的瞬间,大量用户同时提交订单。如果没有消息队列,这些订单请求直接发送到订单处理系统,可能会导致订单处理系统的服务器因为负载过高而出现响应缓慢甚至宕机的情况。
- 通过引入消息队列,大量的订单请求先被放入消息队列。订单处理系统可以根据自己的处理能力,如每秒处理一定数量的订单,从消息队列中获取订单消息进行处理。这样,在高峰时期,消息队列缓冲了大量的订单请求,起到了削峰的作用;在低谷时期,订单处理系统可以继续处理消息队列中剩余的订单消息,起到了填谷的作用,使得系统能够更加稳定地运行。
- 容错性
含义
- 消息队列本身可以增加系统的容错能力。当消息发送者和接收者之间的通信出现短暂中断或者接收者出现故障时,消息可以暂时保存在消息队列中,等待通信恢复或者接收者恢复正常后再进行处理。
应用示例
- 考虑一个分布式数据采集系统,各个数据采集节点(发送者)将采集到的数据发送到消息队列,数据处理中心(接收者)从消息队列中获取数据进行处理。如果数据处理中心因为网络故障或者软件故障等原因暂时无法接收消息,数据采集节点仍然可以将数据发送到消息队列。
- 当数据处理中心恢复正常后,它可以继续从消息队列中获取之前未处理的数据进行处理,保证了数据的完整性和系统的容错性。同时,一些消息队列系统还提供了消息持久化的功能,即使消息队列所在的服务器重启,消息也不会丢失,进一步增强了系统的容错能力。
消息队列的基本概念
- 消息(Message)
定义
- 消息是消息队列中传递的基本单元,它包含了进程间需要通信的信息。消息就像是一个包裹,里面装着发送者想要传递给接收者的数据和相关说明。
组成部分
- 消息头(Message Header):消息头包含了用于消息管理和路由的元数据。常见的信息有消息类型(Message Type),它用于区分不同种类的消息。例如,在一个电商系统的消息队列中,消息类型可能包括订单创建消息、库存更新消息、用户注册消息等。不同类型的消息可以让消费者(接收者)根据类型进行针对性的处理。消息头还可能包含消息的优先级(Priority),用于在消息队列中确定消息被处理的先后顺序。例如,高优先级的消息(如系统报警消息)可以优先于低优先级的消息(如常规的日志消息)被消费者获取和处理。
- 消息体(Message Body):消息体是消息的核心部分,它包含了实际要传递的内容。消息体的内容可以是各种各样的数据结构,如简单的整数、字符串、字节数组,也可以是复杂的结构体。例如,在一个物流系统中,发货通知消息的消息体可能包含发货单号、发货时间、发货地址、收货地址等信息。
作用
- 消息是生产者和消费者之间传递信息的载体,它使得进程间可以在不直接相互调用的情况下进行通信。通过消息队列传递消息,可以确保信息的有序性和完整性,并且支持异步通信模式。
- 生产者(Producer)
定义
- 生产者是消息的发送者,它是一个或多个进程,负责创建消息并将其发送到消息队列中。生产者的主要任务是生成有价值的信息,并将这些信息包装成消息格式,以便通过消息队列传递给其他进程。
行为特点
- 主动发送:生产者主动地将消息发送到消息队列,其发送时机通常由业务逻辑决定。例如,在一个传感器数据采集系统中,传感器进程(生产者)会在采集到新的数据后,将数据包装成消息并发送到消息队列。发送频率可能因业务场景而异,有些生产者可能会持续不断地发送消息,而有些可能会根据特定的事件触发才发送消息。
- 无需关注消费者状态:生产者在发送消息时,不需要知道消费者是否已经准备好接收消息。只要消息队列有足够的空间接收消息,生产者就可以成功发送。这体现了消息队列通信方式的异步性和解耦性。例如,在一个新闻发布系统中,新闻编辑进程(生产者)可以将新发布的新闻消息发送到消息队列,而不需要关心订阅用户进程(消费者)是否正在运行或者是否能够立即接收消息。
- 消费者(Consumer)
定义
- 消费者是消息的接收者,它也是一个或多个进程,负责从消息队列中获取消息并进行处理。消费者的主要任务是根据自己的业务需求,对收到的消息进行解析、操作等处理。
行为特点
- 被动接收:消费者通常是被动地从消息队列中获取消息。在阻塞模式下,如果消息队列中没有消息,消费者可能会被阻塞,等待消息的到来。在非阻塞模式下,消费者在没有消息时会直接返回,并可以根据返回的状态码判断是否需要再次尝试获取消息。例如,在一个邮件客户端系统中,邮件接收进程(消费者)在阻塞模式下会等待新邮件消息到达消息队列,一旦有新邮件消息,就会获取并进行显示等处理。
- 按规则处理消息:消费者会根据消息的类型、优先级等规则来处理消息。例如,在一个任务调度系统中,消费者进程可能会先处理高优先级的任务消息,再处理低优先级的任务消息。消费者对消息的处理方式也因业务场景而异,可能是简单的存储消息内容、更新数据库,也可能是触发其他复杂的业务流程。
- 队列(Queue)
定义
- 消息队列中的队列是一个存储消息的容器,它按照一定的顺序(通常是先进先出,即FIFO原则)来存储和管理消息。从逻辑结构上看,它类似于一个线性的数据结构,消息在队列的一端(队尾)被插入,在另一端(队头)被取出。
作用
- 消息存储与排序:队列用于存储生产者发送的消息,并且保证消息的顺序。在先进先出的模式下,先进入队列的消息会先被消费者获取。这种顺序性对于很多业务场景非常重要,例如在任务调度系统中,按照任务提交的顺序来执行任务可以确保公平性和可预测性。同时,队列也可以支持其他排序规则,如根据消息的优先级进行排序,高优先级的消息可以被放置在队列的头部,优先被消费者获取。
- 缓冲消息流量:队列起到了缓冲的作用,在生产者发送消息的速度和消费者处理消息的速度不一致时,队列可以暂时存储消息。例如,在高峰期,生产者可能会快速地发送大量消息,而消费者的处理能力有限,此时队列可以存储这些多余的消息,避免消息丢失,并使得消费者可以在后续的时间里按照自己的节奏处理这些消息。
特性
- 容量限制:队列通常有一定的容量限制,这个限制可以是由系统配置或者资源限制决定的。当队列已满时,根据消息发送操作的设置(阻塞或非阻塞),生产者可能会被阻塞,等待队列中有空间来存储新消息,或者发送操作会直接返回一个错误。例如,在一个内存有限的系统中,消息队列的容量可能会受到内存大小的限制,当队列中的消息占用的内存空间达到上限时,就无法再接收新消息。
- 消息持久化(可选):有些消息队列系统支持消息持久化功能。这意味着消息可以被存储在磁盘等非易失性存储介质上,而不仅仅是内存中。这样,即使在系统崩溃或者重启后,消息也不会丢失。例如,在一个金融交易系统中,交易消息可能需要持久化存储,以确保交易数据的完整性和可靠性。
- Broker(消息代理)
定义
- 消息代理(Broker)是消息队列系统的核心组件,它负责管理消息队列、接收生产者发送的消息、存储消息、将消息路由到合适的消费者等一系列操作。可以把Broker看作是一个消息的“中转站”和“管理者”。
功能
- 消息接收与存储:Broker接收生产者发送的消息,并将这些消息存储到相应的队列中。它需要处理消息的存储管理,包括如何分配存储资源、如何确保消息的完整性和顺序性等。例如,在一个基于分布式架构的消息队列系统中,Broker可能会将消息存储在分布式的存储系统中,通过复制和备份机制来保证消息的安全性。
- 消息路由(Routing):Broker需要根据消息的类型、消费者的订阅信息等因素来确定消息应该被发送到哪个消费者。这涉及到消息路由的功能,它可以通过消息的元数据(如消息类型)和消费者的订阅规则(如消费者只接收特定类型的消息)来进行智能的路由。例如,在一个物联网系统中,不同类型的设备(生产者)发送的消息可能需要被路由到不同的数据分析系统(消费者),Broker会根据消息的来源和内容进行正确的路由。
- 队列管理与维护:Broker负责管理消息队列的创建、删除、属性设置等操作。它可以监控队列的状态,如消息数量、队列的使用率等,并且根据这些状态进行动态的调整。例如,当一个队列的消息数量过多,接近容量上限时,Broker可以采取一些措施,如提醒管理员、扩展队列容量或者调整消费者的处理速度等。
- 保证消息的传递可靠性:Broker在消息传递过程中起到了关键的保障作用。它需要确保消息能够准确地从生产者传递到消费者,在出现网络故障、消费者故障等情况时,能够采取相应的措施,如重试机制、消息备份等,来保证消息的传递不会中断或者丢失。例如,在一个分布式消息队列系统中,Broker可以通过消息确认机制来确保消费者成功接收并处理了消息,如果没有收到确认信息,就会重新发送消息。
消息队列与命名管道和共享内存的不同
比较项目 | 消息队列 | 命名管道 | 共享内存 |
---|---|---|---|
通信方式 | 基于消息的异步通信,消息有明确边界,生产者和消费者通过消息队列间接通信,不需要同时运行 | 基于字节流的半双工同步通信,读端和写端需要协调工作,一般用于有亲缘关系或知道对方存在的进程 | 多个进程直接共享物理内存区域,可实现高效的数据共享,进程像访问本地内存一样访问共享内存 |
数据结构 | 消息由消息头(包含消息类型、长度等)和消息体组成,消息在队列中排队存储 | 字节流形式,没有消息格式的概念,数据按照写入顺序在管道中流动 | 共享内存是一块连续的物理内存区域,数据结构由使用进程定义,可以是数组、结构体等各种形式 |
数据完整性 | 每条消息独立,接收进程按消息为单位接收,能很好地保证消息完整性和独立性,不会出现消息混淆 | 数据是字节流,如果没有合适的协议或同步机制,读进程和写进程操作不当可能导致数据混乱 | 需要使用同步机制(如信号量、互斥锁)来确保数据一致性,否则多个进程同时访问共享内存易出现数据不一致 |
同步方式 | 支持异步通信,发送者和接收者独立工作,发送消息不需要等待接收者准备好,接收者也可以按自己节奏处理消息 | 同步通信,读进程和写进程在操作时需要相互协调,例如写进程写入数据后,读进程才能读取,否则可能阻塞 | 本身没有内置的同步通信机制,需要借助外部同步工具来协调多个进程对共享内存的访问 |
适用场景 | 适用于需要解耦、异步处理、削峰填谷的场景,如分布式系统中的服务间通信、事件驱动系统等 | 常用于有亲缘关系(如父子进程)的进程间通信,或者简单的单向数据传输场景,对通信效率要求不是特别高且数据传输量不大的情况 | 适合在进程间频繁交换大量数据的场景,对通信效率要求较高,并且进程间有一定的信任关系(因为共享内存操作不当易出错) |
创建和使用 | 通过msgget 创建消息队列,使用msgsnd 发送消息、msgrcv 接收消息、msgctl 控制消息队列 | 通过mkfifo 创建命名管道,使用open 获取管道文件描述符,read 和write 进行读写操作 | 通过shmget 分配共享内存段,shmat 映射到进程虚拟地址空间,通过内存读写指令访问,最后通过shmdt 撤销映射、shmctl 释放共享内存段 |
生命周期 | 消息队列的生命周期相对独立,创建后可以持续存在,直到被显式删除,消息可以在队列中等待处理 | 命名管道的生命周期与使用它的进程相关,当所有使用管道的进程关闭管道后,管道就不存在了 | 共享内存的生命周期由进程控制,创建后可以在多个进程通信期间一直存在,直到被释放 |
缓冲机制 | 本身就是一种消息缓冲机制,消息队列可以存储消息,在生产者和消费者速度不一致时起到缓冲作用,支持削峰填谷 | 管道有一定的缓冲区,但主要用于临时存储字节流数据,缓冲功能相对较弱,容易出现管道满(写阻塞)或管道空(读阻塞)的情况 | 没有专门的缓冲机制,共享内存是直接的数据共享区域,不过可以在共享内存中自己实现缓冲逻辑 |
消息队列的原理
消息队列的核心思想是将消息存储在一个有序队列中,发送进程将消息放入队列,接收进程从队列中读取消息。消息队列由操作系统内核维护,消息按照顺序或者优先级进行存放。消息队列的设计使得不同进程不需要同时存在就可以实现消息传递,这是异步通信的基础。
系统中可能存在大量的消息队列,内核需要使用如下数据结构对消息队列进行管理
消息队列工作流程
•生产者进程:通过msgsnd()将消息放入队列,并指定消息类型。如果消息队列已满,生产者进程可以选择等待队列有空位或直接返回错误。
•消费者进程:通过msgrcv()从队列中读取消息,并指定需要的消息类型。如果没有符合条件的消息,消费者可以选择阻塞等待消息的到来或直接返回错误。
•消息传递机制:消息队列在内核中排队,确保消息按照指定的顺序或优先级传递给接收者。
System V 消息队列的主要函数
msgget
msgget 是一个在 POSIX 系统中用于消息队列操作的函数。该函数用于获取一个消息队列的标识符,或者创建一个新的消息队列。
-
函数原型及功能
- 在Unix/Linux系统中,
msgget
函数用于创建一个新的消息队列或者获取一个已经存在的消息队列的标识符。其函数原型如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>int msgget(key_t key, int msgflg);
- 其中,
key
参数是一个键值,用于唯一标识一个消息队列。这个键值通常是通过ftok
函数生成的。msgflg
参数是一个标志位,用于控制消息队列的创建方式和权限。
- 在Unix/Linux系统中,
-
参数详细解释
key
参数:- 这个键值是一个整数类型(
key_t
),它的作用是在系统范围内唯一地标识一个消息队列。通过ftok
函数生成键值是一种常见的做法。ftok
函数会根据一个文件路径和一个项目标识符(proj_id
)来生成一个相对唯一的键值。例如:
key_t key = ftok(".", 'a');
- 这里以当前目录(用
"."
表示)和字符'a'
作为参数生成键值。如果ftok
函数返回-1
,则表示生成键值失败,可能是因为指定的文件不存在或者没有权限访问该文件等原因。
- 这个键值是一个整数类型(
msgflg
参数:msgflg
参数可以同时使用多个标志位,用于控制消息队列的创建和访问权限。主要的标志位有以下几种:IPC_CREAT
:如果消息队列不存在,则创建一个新的消息队列;如果消息队列已经存在,则返回已经存在的消息队列的标识符。例如:
int msgid = msgget(key, IPC_CREAT | 0666);
- 这里使用
IPC_CREAT
标志和权限0666
(表示所有者、所属组和其他用户都有读写权限)来创建或获取消息队列。 IPC_EXCL
:这个标志通常和IPC_CREAT
一起使用。当同时使用IPC_CREAT
和IPC_EXCL
时,如果消息队列已经存在,则msgget
函数返回-1
,用于确保创建的是一个全新的消息队列。例如:
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
- 这种方式可以用于在程序中保证只有一个进程能够创建特定的消息队列,避免多个进程意外地创建多个相同的消息队列。
- 权限位(如
0666
):除了IPC_CREAT
和IPC_EXCL
标志外,msgflg
还可以包含权限位,其格式和文件权限的格式相同。用于设置消息队列的所有者、所属组和其他用户的读写权限。例如,0666
表示所有者、所属组和其他用户都有读写权限,0444
表示只有读权限等。
-
返回值
- 如果
msgget
函数调用成功,它会返回一个非负整数,这个整数就是消息队列的标识符(msgid
)。之后的msgsnd
(发送消息)、msgrcv
(接收消息)和msgctl
(控制消息队列)等操作都需要使用这个标识符来指定消息队列。 - 如果
msgget
函数调用失败,它会返回-1
,并且会设置全局变量errno
来指示具体的错误原因。常见的错误原因包括:EACCESS
:权限不足,可能是因为msgflg
中指定的权限不符合要求,或者当前进程没有足够的权限来创建或访问消息队列。EEXIST
:当使用IPC_CREAT | IPC_EXCL
标志时,如果消息队列已经存在,就会返回这个错误,表示资源已经存在。ENOENT
:如果key
参数对应的消息队列不存在,并且没有使用IPC_CREAT
标志,就会返回这个错误,表示没有找到对应的资源。ENOMEM
:系统没有足够的内存来创建新的消息队列。ENOSPC
:系统已经达到了消息队列数量的上限,无法再创建新的消息队列。
- 如果
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cerrno>
#include <stdio.h>
int main(){key_t key=ftok("/home/gwq/code/lesson13",66);int msgid=msgget(key,IPC_CREAT|0666);if(msgid==-1){perror("msgget");exit(1);}return 0;
}
可以借助ipcs -q查看系统中存在的消息队列
msgrcv
-
函数原型及功能
- 在Unix/Linux系统的进程间通信中,
msgrcv
函数用于从消息队列中接收消息。其函数原型如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- 这个函数的主要功能是从标识符为
msqid
的消息队列中接收一条消息,将消息存储到msgp
指向的缓冲区中,并根据msgtyp
和msgflg
的设置来确定接收消息的类型和方式。
- 在Unix/Linux系统的进程间通信中,
-
参数详细解释
msqid
参数:- 这是消息队列的标识符,通过
msgget
函数创建或获取消息队列时返回。它用于唯一标识一个消息队列,使得msgrcv
函数能够确定从哪个消息队列中接收消息。例如,如果之前通过msgget
函数获取到一个消息队列的标识符msgid
,那么在msgrcv
函数中就可以将msqid
设置为这个msgid
来接收该消息队列中的消息。
- 这是消息队列的标识符,通过
msgp
参数:- 这是一个指向接收消息缓冲区的指针。接收消息的缓冲区应该是一个自定义的结构体,其格式通常与发送消息时使用的结构体格式相同。这个结构体一般包含消息头和消息体两部分。消息头用于存储消息的类型、长度等元数据,消息体用于存储实际的消息内容。例如,一个简单的消息结构体可能如下定义:
struct msgbuf {long mtype;char mtext[100]; };
- 在这里,
mtype
是消息类型,mtext
是消息内容的存储区域。当使用msgrcv
函数接收消息时,msgp
就可以指向一个这样的msgbuf
类型的结构体变量。
msgsz
参数:- 这个参数指定了
msgp
指向的缓冲区中消息体部分的大小。注意,这个大小不包括消息头的大小。它用于告诉msgrcv
函数最多可以接收多少字节的消息内容到缓冲区中。例如,对于上面定义的msgbuf
结构体,msgsz
可以设置为sizeof(struct msgbuf.mtext)
,即100
,表示最多接收100
字节的消息内容。
- 这个参数指定了
msgtyp
参数:- 这是一个用于指定要接收的消息类型的参数。它有以下几种取值情况:
msgtyp > 0
:接收消息队列中消息类型等于msgtyp
的第一条消息。这使得接收进程可以根据消息类型有选择地接收消息。例如,如果一个消息队列中有多种类型的消息(如消息类型为1的命令消息和消息类型为2的状态消息),接收进程可以通过设置msgtyp
为1来只接收命令消息。msgtyp = 0
:接收消息队列中的第一条消息,而不管消息的类型是什么。这在接收进程不关心消息类型,只想获取最早到达的消息时很有用。msgtyp < 0
:接收消息队列中消息类型小于等于-msgtyp
绝对值的消息中类型值最小的消息。这种方式比较特殊,一般用于实现一些特殊的消息接收策略,比如接收优先级最高的消息(假设消息类型与优先级有关,且绝对值越大优先级越高)。
- 这是一个用于指定要接收的消息类型的参数。它有以下几种取值情况:
msgflg
参数:- 这个参数用于控制接收消息的方式,它可以同时使用多个标志位,主要的标志位有以下几种:
IPC_NOWAIT
:如果设置了这个标志位,当消息队列中没有符合条件的消息时,msgrcv
函数不会阻塞等待,而是立即返回,并将返回值设置为-1
,同时设置errno
为ENOMSG
,表示没有消息。如果没有设置这个标志位,在没有符合条件的消息时,msgrcv
函数会阻塞等待,直到有符合条件的消息到达消息队列。MSG_NOERROR
:如果设置了这个标志位,当接收到的消息内容长度大于msgsz
指定的大小,会截断消息内容,只接收msgsz
大小的内容,并且不会返回错误。如果没有设置这个标志位,当接收到的消息内容长度大于msgsz
时,msgrcv
函数会返回-1
,并设置errno
为E2BIG
,表示消息太长。
- 这个参数用于控制接收消息的方式,它可以同时使用多个标志位,主要的标志位有以下几种:
-
返回值
- 如果
msgrcv
函数调用成功,它会返回实际接收到的消息内容的字节数。这个字节数不包括消息头的大小。 - 如果调用失败,会返回
-1
,并且会设置全局变量errno
来指示具体的错误原因。常见的错误原因包括:E2BIG
:当接收到的消息内容长度大于msgsz
且没有设置MSG_NOERROR
标志位时,会返回这个错误。EIDRM
:如果消息队列的标识符msqid
所对应的消息队列已经被删除,会返回这个错误。EINVAL
:参数msqid
无效,或者msgsz
的值小于0,或者msgtyp
的值无效等情况会返回这个错误。ENOMSG
:当设置了IPC_NOWAIT
标志位,且消息队列中没有符合条件的消息时,会返回这个错误。
- 如果
msgsnd
msgsnd 函数用于将一条消息发送到指定的消息队列。
-
函数原型及功能
- 在Unix/Linux系统中,
msgsnd
函数用于将消息发送到消息队列。其函数原型如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- 这个函数的主要功能是将
msgp
指向的消息发送到标识符为msqid
的消息队列中,消息体的大小由msgsz
指定,发送方式由msgflg
控制。
- 在Unix/Linux系统中,
-
参数详细解释
msqid
参数:- 这是消息队列的标识符,是通过
msgget
函数创建或获取消息队列时返回的。它用于唯一地标识要发送消息的目标消息队列。例如,在一个多进程通信的场景中,不同进程通过同一个消息队列进行通信,这个msqid
就是它们共同指向的消息队列的标识。
- 这是消息队列的标识符,是通过
msgp
参数:- 这是一个指向要发送消息的结构体的指针。这个结构体通常包含消息头和消息体两部分。消息头用于存储消息的类型、长度等元数据,消息体用于存储实际的消息内容。例如,一个简单的用于发送消息的结构体可以这样定义:
struct msgbuf {long mtype;char mtext[100]; };
- 在这里,
mtype
是消息类型,mtext
是消息内容的存储区域。发送消息时,msgp
就指向这样一个结构体变量,并且需要在发送前填充好消息类型和消息内容。
msgsz
参数:- 这个参数指定了要发送的消息体的大小,不包括消息头的大小。它用于告诉消息队列系统发送消息内容的字节数。对于上述例子中的
msgbuf
结构体,msgsz
可以设置为sizeof(struct msgbuf.mtext)
,即发送消息内容部分的大小。
- 这个参数指定了要发送的消息体的大小,不包括消息头的大小。它用于告诉消息队列系统发送消息内容的字节数。对于上述例子中的
msgflg
参数:- 这个参数用于控制发送消息的方式,它可以包含以下几种标志位:
IPC_NOWAIT
:如果设置了这个标志位,当消息队列已满时,msgsnd
函数不会阻塞等待,而是立即返回,并将返回值设置为-1
,同时设置errno
为EAGAIN
,表示资源暂时不可用。如果没有设置这个标志位,在消息队列已满的情况下,msgsnd
函数会阻塞等待,直到消息队列中有足够的空间来存储消息。0
(默认情况):按照默认的阻塞方式发送消息,即如果消息队列已满就阻塞等待。
- 这个参数用于控制发送消息的方式,它可以包含以下几种标志位:
-
返回值
- 如果
msgsnd
函数调用成功,它会返回0
,表示消息成功发送到消息队列。 - 如果调用失败,它会返回
-1
,并且会设置全局变量errno
来指示具体的错误原因。常见的错误原因包括:EAGAIN
:当设置了IPC_NOWAIT
标志位,且消息队列已满时,会返回这个错误。EACCES
:当前进程没有足够的权限向消息队列发送消息。这可能是因为在创建消息队列时设置的权限不允许该进程进行发送操作。EFAULT
:msgp
指向的地址无效,可能是因为指针未正确初始化或者指向了非法的内存区域。EIDRM
:消息队列的标识符msqid
所对应的消息队列已经被删除。EINVAL
:参数msqid
无效,或者msgsz
的值大于系统限制,或者msgp
指向的消息结构体不符合要求(如消息类型字段设置不合理)等情况会返回这个错误。ENOMEM
:系统没有足够的内存来完成消息发送操作,这种情况比较少见,但在系统资源紧张时可能会出现。
- 如果
msgctl
msgctl是POSIX系统调用之一,用于控制消息队列的行为。它允许你获取消息队列的当前状态、设置消息队列的属性,或者删除消息队列
-
函数原型及功能
- 在Unix/Linux系统中,
msgctl
函数用于对消息队列进行控制操作。其函数原型如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 这个函数可以获取消息队列的状态信息、设置消息队列的属性或者删除消息队列等操作,具体功能取决于
cmd
参数的设置。
- 在Unix/Linux系统中,
-
参数详细解释
msqid
参数:- 这是消息队列的标识符,是通过
msgget
函数创建或获取消息队列时返回的。它用于唯一地标识要进行控制操作的消息队列。例如,如果之前通过msgget
获取到一个消息队列的标识符为msgid
,那么在msgctl
函数中就将msqid
设置为这个msgid
来对相应的消息队列进行操作。
- 这是消息队列的标识符,是通过
cmd
参数:- 这个参数指定了要对消息队列执行的操作命令,它有以下几种常见的取值:
IPC_RMID
:用于删除消息队列。当cmd
设置为IPC_RMID
时,msgctl
函数会将标识符为msqid
的消息队列标记为删除。消息队列被标记为删除后,不能再向这个队列发送消息,但已经在队列中的消息仍然可以被接收,直到最后一个接收进程完成接收操作后,消息队列才会被真正删除,系统会回收相关的资源。例如:
int msgid = msgget(key, IPC_CREAT | 0666); // 其他操作... if (msgctl(msgid, IPC_RMID, NULL) == -1) {perror("msgctl - delete queue"); }
IPC_SET
:用于设置消息队列的属性。当cmd
设置为IPC_SET
时,msgctl
函数会根据buf
指向的msqid_ds
结构体中的信息来设置消息队列的属性。msqid_ds
结构体包含了消息队列的各种属性信息,如权限(msg_perm
字段)等。不过,并不是msqid_ds
结构体中的所有字段都可以通过IPC_SET
命令来设置,具体哪些字段可以设置取决于系统实现。例如,要修改消息队列的权限,可以这样做:
struct msqid_ds buf; // 获取消息队列的当前状态信息 if (msgctl(msgid, IPC_STAT, &buf) == -1) {perror("msgctl - get queue status");return 1; } buf.msg_perm.mode = 0644; // 修改权限 if (msgctl(msgid, IPC_SET, &buf) == -1) {perror("msgctl - set queue mode");return 1; }
IPC_STAT
:用于获取消息队列的状态信息。当cmd
设置为IPC_STAT
时,msgctl
函数会将消息队列标识符为msqid
的状态信息填充到buf
指向的msqid_ds
结构体中。这些状态信息包括消息队列中的消息数量、字节数限制、最后一次发送和接收消息的进程ID等。例如:
struct msqid_ds buf; if (msgctl(msgid, IPC_STAT, &buf) == -1) {perror("msgctl - get queue status");return 1; } printf("Number of messages in queue: %ld\n", buf.msg_qnum);
- 除了上述常见的命令外,还有其他一些命令用于特定的系统操作或调试目的,但在一般的应用场景中较少使用。
- 这个参数指定了要对消息队列执行的操作命令,它有以下几种常见的取值:
buf
参数:- 这是一个指向
msqid_ds
结构体的指针。这个结构体用于存储消息队列的状态信息(当cmd
为IPC_STAT
时),或者提供要设置的消息队列属性信息(当cmd
为IPC_SET
时)。msqid_ds
结构体的定义可能因系统而异,但通常包含以下一些重要的字段:msg_perm
:这是一个struct ipc_perm
类型的字段,用于存储消息队列的权限信息,包括所有者用户ID、所有者组ID、权限模式(如读/写权限)等。msg_qnum
:表示消息队列中当前的消息数量。msg_qbytes
:表示消息队列可以容纳的最大字节数限制。msg_lspid
和msg_lrpid
:分别表示最后一次发送消息的进程ID和最后一次接收消息的进程ID。
- 这是一个指向
-
返回值
- 如果
msgctl
函数调用成功,它会返回0
,表示操作顺利完成。 - 如果调用失败,它会返回
-1
,并且会设置全局变量errno
来指示具体的错误原因。常见的错误原因包括:EACCESS
:当前进程没有足够的权限来执行指定的cmd
操作。例如,没有权限删除消息队列或者设置消息队列的属性。EFAULT
:buf
指向的地址无效,可能是因为指针未正确初始化或者指向了非法的内存区域。EIDRM
:消息队列的标识符msqid
所对应的消息队列已经被删除。EINVAL
:参数msqid
无效,或者cmd
参数的取值不合法(例如,指定了一个不被支持的操作命令)等情况会返回这个错误。
msqid_ds结构体定义在<sys/msg.h>头文件中,它包含以下字段:
- 如果
获取消息队列的状态
#include <sys/msg.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main(){key_t key;int msgid;struct msqid_ds msq_ds;int result;//生成keykey =ftok("/home/gwq/code/lesson13",5);//获取消息队列标识符msgid=msgget(key,0666|IPC_CREAT);if(msgid==-1){perror("msgget failed");exit(1);}result=msgctl(msgid,IPC_STAT,&msq_ds);if(result==-1){perror("msgctl failed");exit(1);}printf("Message queue status:\n");printf("msg_perm.uid=%d\n",msq_ds.msg_perm.uid);printf("msg_perm.gid=%d\n",msq_ds.msg_perm.gid);printf("msg_qnum=%ld\n",msq_ds.msg_qnum);printf("msg_qbytes=%ld\n",msq_ds.msg_qbytes);printf("msg_lspid=%ld\n",msq_ds.msg_lspid);printf("msg_lrpid=%ld\n",msq_ds.msg_lrpid);printf("msg_stime=%ld\n",msq_ds.msg_stime);printf("msg_rtime=%ld\n",msq_ds.msg_rtime);printf("msg_ctime=%ld\n",msq_ds.msg_ctime);return 0;
}
设置消息队列状态
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>int main(){sleep(2);key_t key;int msgid;struct msqid_ds msq_ds;int result;//生成 keykey=ftok("/home/gwq/code/lesson13",65);//获取消息队列标识符msgid=msgget(key,0666|IPC_CREAT);if(msgid==-1){perror("msgget failed");exit(1);}//设置消息队列属性memset(&msq_ds,0,sizeof(msq_ds));msq_ds.msg_perm.uid=getuid();msq_ds.msg_perm.gid=getgid();msq_ds.msg_qbytes=1024;result=msgctl(msgid,IPC_SET,&msq_ds);if(result==-1){perror("msgctl failed");exit(1);}printf("Message queue attributes set successfully.\n");return 0;
}