Linux进阶-ipc消息队列

目录

[system-V IPC](#system-V IPC)

消息队列

消息队列和信号管道的对比

消息队列和信号的对比

消息队列和管道的对比

消息队列函数API

msgget():打开或创建消息队列

msgsnd():发送消息

msgrcv():接收消息

msgctl():控制消息队列

msgsnd.c文件

msgrcv.c文件

Makefile文件

执行过程


system-V IPC

system-V IPC:消息队列、共享内存、信号量。

这些对象的操作接口都类似,在系统中都使用key的键值来唯一标识,而且都是持续性资源(即它们被创建后不会因为进程的退出而消失,而会持续性存在,除非调用特殊的函数或命令删除它们)。

Linux的IPC对象在内核内部使用链表维护,不同的对象使用IPC标识符来标识(如消息队列标识符msqid、共享内存标识符shmid、信号量标识符semid)。

对于用户而言,内核提供了简洁的接口,不同的进程通过IPC关键字(key)即可访问具体的对象。
ipcs:查看系统当前IPC对象。

消息队列

信号队列:从一个进程发送一个数据块到另一个进程 。每个数据块含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。

消息队列和信号管道的对比

消息队列和信号的对比

信号承载的信息量少,而消息队列可以承载大量自定义的数据。

消息队列和管道的对比

消息队列和有名管道都可以在不相关的进程间通信,同时都是通过发送和接收的方式来传递数据。

有名管道:发送数据使用write()函数、接收数据使用read()函数。

消息队列:发送数据使用msgsnd()函数、接收数据使用msgrcv()函数。消息队列对每个数据都有一个最大长度的限制。

消息队列也可以独立于发送和接收进程而存在,在进程终止时,消息队列及其内容并不会被删除。

管道只能承载无格式字节流,而消息队列提供有格式的字节流,可以减少开发人员的工作量。

消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级,接收程序可以通过消息类型有选择地接收数据,而不像有名管道只能默认接收。

消息队列可以实现消息的随机查询,消息不一定要以先进先出的顺序接收,也可以按消息的类型接收。

消息队列函数API

msgget():打开或创建消息队列

收发消息需要具体的消息队列对象,函数作用就是创建或获取一个消息队列对象,并返回消息队列标识符。创建的消息队列的数量受到系统可支持的消息队列数量的限制。

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
/*
key:消息队列的关键字值,多个进程可以通过它访问同一个消息队列。例如收发进程都使用。有个特殊值IPC_PRIVATE,用于创建当前进程的私有消息队列。
msgflg:表示创建的消息队列的模式标志参数,主要有IPC_CREAT,IPC_EXCL和权限mode。
    如果IPC_CREAT为真:如果内核中不存在关键字与key相等的消息队列,则新建一个消息队列;如果存在,返回此消息队列的标识符
    如果IPC_CREAT | IPC_EXCL为真:如果内核中不存在关键字与key相等的消息队列,则新建一个消息队列;如果存在,则报错
    mode指IPC对象存取权限。如0666等
返回值:
    执行成功:队列ID
    执行失败:-1,并且错误原因存于error
*/

权限只有读写,没有执行。

当key被指定为IPC_PRIVATE,系统会自动产生一个未用的key来对应一个新的消息队列对象,这个消息队列一般用于进程内部间通信。

该函数可能返回以下错误代码error:

EACCES:指定的消息队列已存在,但调用进程没有权限访问它

EEXIST:key指定的消息队列已存在,而msgflg同时指定IPC_CREAT | IPC_EXCL标志

ENOENT:key指定的消息队列不存在,同时msgflg没有指定IPC_CREAT标志

ENOMEM:需要建立消息队列,但内存不足

ENOSPC:需要建立消息队列,但已达到系统的限制

msgsnd():发送消息

msgsnd()函数把消息发送到已打开的消息队列的末尾。

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
/*
msqid:消息队列标识符
msgp:发送给队列的消息。可以是任何类型的结构体,但第一个字段必须为long类型(即表明此发送消息的类型),如下结构体
msgsz:待发送消息的大小,不包含消息类型占用的4个字节(即下面结构体的mtext的长度)
msgflg:
    0:当消息队列满时,该函数会阻塞,直到消息能写入消息队列
    IPC_NOWAIT:当消息队列满时,该函数不等待立即返回
    IPC_NOERROR:要发送的消息大于size字节,则把消息截断,截断部分将被丢弃,且不通知发送进程
返回值:
    执行成功:0
    执行失败:-1,并且错误原因存于error
*/
cpp 复制代码
/* msgp定义的参数格式 */
struct s_msg{
    long type;        /* 消息类型,必须大于0 */
    char mtext[1];    /* 消息正文,可以是其他任何类型 */
}msgp;

msgsnd()为阻塞函数,解除阻塞的条件:

消息队列有容乃该消息的空间

msqid代表的消息队列被删除

调用msgsnd函数的进程被信号中断

该函数可能返回以下错误代码error:

EAGAIN:参数msgflg设为IPC_NOWAIT,而消息队列已满

EIDRM:msqid标识符的消息队列已被删除

EACCESS:无权限写入消息队列

EFAULT:参数msgp指向无效的内存地址

EINTR:队列已满而处于等待情况下被信号中断

EINVAL:无效的参数msqid、msgsz或参数消息类型type小于0

msgrcv():接收消息

msgrcv()函数把消息从消息队列取出(可以指定取出某一条消息),即从msqid标识符的消息队列读取消息并将消息存储在msgp中,读取后把此消息从消息队列中删除。

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
/*
msqid:消息队列标识符
msgp:发送给队列的消息。可以是任何类型的结构体,但第一个字段必须为long类型(即表明此发送消息的类型)
msgsz:待发送消息的大小,不包含消息类型占用的4个字节
msgtyp:
    0:接收第一个消息
    >0:接收类型等于msgtyp的第一个消息
    <0:接收类型等于或小于msgtyp绝对值的第一个消息
msgflg:
    0:阻塞式接收消息,没有该类型的消息时该函数一直会阻塞等待
    IPC_NOWAIT:若消息队列中没有相应类型的消息可以接收,则函数error为ENOMSG
    IPC_EXCEPT:与msgtype配合使用返回队列第一个类型不为msgtype的消息
    IPC_NOERROR:若队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃
返回值:
    执行成功:实际读取到的消息数据长度
    执行失败:-1,并且错误原因存于error
*/

msgrcv()为阻塞函数,解除阻塞的条件:

消息队列中有了满足条件的消息

msqid代表的消息队列被删除

调用msgrcv()函数的进程被信号中断

该函数可能返回以下错误代码error:

E2BIG:消息数据长度大于msgsz而msgflag没有设置IPC_NOERROR

EIDRM:msqid标识符的消息队列已被删除

EACCESS:无权限读取消息队列

EFAULT:参数msgp指向无效的内存地址

ENOMSG:参数msgflg设为IPC_NOWAIT,而消息队列中无消息可读

EINTR:等待读取队列内的消息情况下被信号中断

msgctl():控制消息队列

msgctl()可以操作消息队列,如设置或者获取消息队列的相关属性。

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
/*
msqid:消息队列标识符
cmd:
    IPC_STAT:获取该msg的信息,保存在结构体msqid_ds类型的buf中
    IPC_SET:设置消息队列的属性,要设置的属性需先存储在结构体msqid_ds类型的buf中,可设置的属性包括msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_qbytes。
    IPC_RMID:立即删除该msg,并且唤醒所有阻塞在该msg上的进程,同时忽略第三个参数
    IPC_INFO:获得关于当前系统中msg的限制值信息
    MSG_INFO:获得关于当前系统中msg的相关资源消耗信息
    MSG_STAT:同IPC_STAT,但msgid为该消息队列在内核中记录所有消息队列信息的数组的下标,因此通过迭代所有的下标可以获得系统中所有消息队列的相关消息
返回值:
    执行成功:0
    执行失败:-1,并且错误原因存于error
*/

该函数可能返回以下错误代码error:

EACCESS:cmd参数为IPC_STAT,却无权限读取该消息队列

EFAULT:buf参数指向无效的内存地址

EIDRM:msqid标识符的消息队列已被删除

EINVAL:无效的参数msqid、cmd

EPERM:cmd参数为IPC_SET | IPC_RMID,却无权限执行

msgsnd.c文件

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE     512

typedef struct message{
        long msg_type;
        char msg_text[BUFFER_SIZE];
}message;

int main(void)
{
        int qid;
        message msg;

        if((qid = msgget((key_t)1234, IPC_CREAT|0666)) == -1){
                perror("msgget\n");
                exit(1);
        }

        printf("open queue %d\n", qid);

        while(1){
                printf("Enter some message to the queue:");
                if((fgets(msg.msg_text, BUFFER_SIZE, stdin)) == NULL){
                        printf("get message end\n");
                        exit(1);
                }

                msg.msg_type = getpid();

                if((msgsnd(qid, &msg, strlen(msg.msg_text), 0)) < 0){
                        perror("msgsnd");
                        exit(1);
                }else{
                        printf("send message\n");
                }

                if(strncmp(msg.msg_text, "quit", 4) == 0){
                        printf("quit get message\n");
                        break;
                }
        }

        exit(0);
}

msgrcv.c文件

cpp 复制代码
include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFFER_SIZE     512

typedef struct message{
        long msg_type;
        char msg_text[BUFFER_SIZE];
}message;

int main(void)
{
        int qid;
        message msg;

        if((qid = msgget((key_t)1234, IPC_CREAT|0666)) == -1){
                perror("msgget\n");
                exit(1);
        }

        printf("open queue %d\n", qid);

        do{
                memset(msg.msg_text, 0, BUFFER_SIZE);

                if(msgrcv(qid, (void *)&msg, BUFFER_SIZE, 0, 0) < 0){
                        perror("msgrcv");
                        exit(1);
                }

                printf("the message from process %ld:%s", msg.msg_type, msg.msg_text);
        }while(strncmp(msg.msg_text, "quit", 4));

        if((msgctl(qid, IPC_RMID, NULL)) < 0){
                perror("msgctl");
                exit(1);
        }else{
                printf("Delete msg qid:%d\n", qid);
        }

        exit(0);
}

Makefile文件

为两个工程,但都类似,照旧。

执行过程

相关推荐
夜听莺儿鸣17 小时前
502-003_Linux 中断与异常(一):从Linux角度理解中断与异常
linux·中断与异常·linux中断
杨云龙UP18 小时前
TDengine 3.4.2.8 Community 三节点三副本生产集群部署实战(DNode/MNode/taosAdapter/Explorer)
大数据·linux·运维·数据库·tdengine·时序库
向成科技19 小时前
XC3576H工控主板|深度适配Ubuntu 26.04 LTS,释放边缘AI与工业开发新潜能
linux·人工智能·ubuntu·机器人·硬件·主板·边缘ai
Fcy64819 小时前
Linux下 进程间关系与守护进程
linux·运维·服务器·守护进程
码农小韩20 小时前
Linux驱动理论(二)——Linux字符设备驱动
linux·嵌入式软件开发·linux操作系统·linux应用开发·linux驱动理论
well061220 小时前
Linux粘滞位与Makefile机制深度解析
linux·运维·服务器
再写一行代码就下班20 小时前
linux sh脚本在windows修改导致无法使用解决方式
java·linux·centos
额额额对了20 小时前
SPI通信
linux·c语言·汇编·单片机·嵌入式硬件·arm
子木HAPPY阳VIP21 小时前
Ubuntu 关闭防火墙操作步骤
linux·运维·ubuntu
王振超wzc21 小时前
嵌入式开发环境搭建--WM软件安装,Ubuntu操作系统安装
linux·运维·ubuntu