Linux系统编程 | IPC对象---消息队列
在Linux系统编程的进程间通信部分中,前面几篇博客已经对管道通信、signal信号做了较为完善的梳理总结,接下来将对进程间通信部分的IPC对象进行梳理,初步预计分为IPC对象的消息队列、共享内存、信号量三个小部分进行讲解梳理。如果对博主前面的文章感兴趣的网友,欢迎访问我的Linux系统编程专栏进行查阅交流。
系统编程_奔跑的蜗牛!的博客-CSDN博客https://blog.csdn.net/weixin_49337111/category_12952742.html?spm=1001.2014.3001.5482
1、IPC对象
(1)、什么是IPC对象
IPC对象是活动在内核级别的一种进程间通信的工具。各种不同的IPC其实是在不同时期逐步引入的,在UNIX伯克利版本system-V(念作系统五,V是罗马数字,是Unix伯克利分支的版本号)中引入的三种通信方式(消息队列、共享内存和信号量组)被称为IPC对象,它们有较多共同的特性:
- 在系统中使用键值(KEY)来唯一确定,类似于文件系统中的文件路径(绝对路径)。
- 当某个进程创建(或打开)一个IPC对象时,将会获得一个整型ID,类似于文件描述符。
- IPC对象属于系统,而不是进程,因此在没有明确删除操作的情况下,IPC对象不会因为进程的退出而消失。
如果需要使用IPC对象实现进程之间的通信,首先必须为IPC对象申请对应的资源。比如,如果要使用消息队列来通信,那么就必须先申请消息队列对应的 key 值 和 ID 号。
(2)、查看IPC对象
ipcs -a
Snail@ubuntu:~/Desktop/process$ ipcs -a------ Message Queues -------- //消息队列
key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- //共享内存
key shmid owner perms bytes nattch status
0x00000000 9 Snail 600 524288 2 dest ------ Semaphore Arrays --------//信号量
key semid owner perms nsems Snail@ubuntu:~/Desktop/process$
key值:类似于 文件的路径名
ID号:类似于文件描述符
(3)、删除IPC对象
删除消息队列:
ipcrm -q 消息队列的key值
ipcrm -q 消息队列的ID值
删除共享内存:
ipcrm -m 共享内存的key值
ipcrm -m 共享内存的ID值
删除信号量:
ipcrm -s 信号量的key值
ipcrm -s 信号量的ID值
2、消息队列基础
消息队列是system-V三种IPC对象之一,其最主要的特征是允许发送的数据携带类型,具有相同类型的数据在消息队列内部排队,读取的时候也要指定类型,然后依次读出数据。这使得消息队列用起来就像一个多管道集合,如下图所示:
由于每个消息都携带有类型,相同的类型自成一队,因此读取方可以根据类型来“挑选”不同的队列,也因此MSG适用于所谓“多对一”的场景,经典案例是系统日志:多个不同的、不相关的进程向同一管道输入数据。
3、消息队列API
(1)、ftok
//函数原型
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);//函数功能
生成一个唯一的 key 值,用于标识 IPC 资源//函数参数
pathname:一个已经存在的文件路径,用于计算 key 值
proj_id:项目 ID,用于区分不同的 IPC 资源//函数返回值
成功返回key值
失败返回-1,并设置errno
(2)、msgget
//函数原型
#include <sys/msg.h>
int msgget(key_t key, int msgflg);//函数功能
创建或获取一个消息队列,用于进程间通信;
如果指定的消息队列不存在且 msgflg 包含 IPC_CREAT,则创建一个新的消息队列;
如果消息队列已存在,则直接返回其标识符(msgid)。//函数参数
key:消息队列的唯一标识符
msgflg:控制消息队列的创建和访问权限//函数返回值
成功返回消息队列的标识符(msgid)
失败返回-1,并设置errno
(3)、msgsnd
//函数原型
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//函数功能
向消息队列中发送一条消息。
消息队列由 msqid 标识,消息内容由 msgp 指向的结构体提供。
如果消息队列已满,且 msgflg 包含 IPC_NOWAIT,则立即返回错误;否则阻塞等待队列有空闲空间。//函数参数
msqid:消息队列标识符
msgp:指向要发送的消息结构体的指针,结构体必须包含 long mtype 成员(消息类型)
msgsz:消息正文的大小(不包括 mtype 的大小)
msgflg:控制发送行为: 0:默认,如果队列满则阻塞等待,IPC_NOWAIT:如果队列满则立即返回错误(EAGAIN)//函数返回值
成功返回0
失败返回-1,并设置errno
(4)、msgrcv
//函数原型
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);//函数功能
从消息队列中读取数据(接收数据)//函数参数
msqid:消息队列标识符
msgp:指向接收消息的缓冲区(必须是 struct msgbuf 或兼容结构)
msgsz:要接收的消息正文的最大大小(不包括 mtype)
msgtyp:消息类型筛选条件> 0:接收第一条类型为 msgtyp的消息。= 0:接收队列中的第一条消息(忽略类型)。< 0:读取队列中类型值 ≤ |msgtyp| 的最小类型的消息(优先级队列)。
msgflg:控制接收行为:0:默认,如果队列为空则阻塞等待。IPC_NOWAIT:如果队列为空则立即返回错误(ENOMSG)。 MSG_NOERROR:如果消息正文超过 msgsz,则截断并返回实际大小(否则返回错误 E2BIG)。MSG_EXCEPT:接收第一条类型 不等于 msgtyp 的消息(仅当 msgtyp > 0 时有效)。//函数返回值
返回接收到的消息正文的实际大小
失败返回-1,并设置errno
(5)、msgctl
//函数原型
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//函数功能
控制消息队列的行为,执行各种管理操作,如获取/设置队列属性、删除队列等。//函数参数
msqid:消息队列标识符
cmd:指定要执行的操作命令IPC_STAT:获取消息队列的当前状态(填充 buf)IPC_SET:设置消息队列的属性(需提供 buf)IPC_RMID:删除消息队列(buf 可为 NULL)
buf:指向 msqid_ds 结构体的指针,用于读取或设置队列属性。//函数返回值
成功返回0
失败返回-1,并设置errno
4、程序举例
消息队列实现两个进程间互发互收。
(1)、消息队列进程1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>//进程1 : 往 消息队列 中 发送数据//自定义的消息队列的数据结构体
struct msgbuf{long mtype; //数据的编号/类型char mtext[1024]; //数据的正文
};void signalHandle(int signum)
{printf("进程[%d] 收到信号%d\n", getpid(), signum);exit(0); //退出进程
}int main(int argc, char **argv)
{signal(SIGUSR1,signalHandle);//1、确定 文件路径名 ---获取消息队列的key值key_t key = ftok(".", 100);//2、根据文件路径名,打开文件 文件不存在则创建 得到fd ---根据key值 获取消息队列的ID,如果消息队列不存在则创建int msgid = msgget(key,IPC_CREAT|0666);if(msgid == -1){perror("msgget error");return -1;}printf("消息队列 key:%#x msgid:%d\n",key,msgid); //0xpid_t id = fork();if(id>0)//父进程 ---发送数据 10{//3、往文件中写入数据 --------往消息队列中 发送数据struct msgbuf data;while(1){memset(&data,0,sizeof(data));scanf("%s",data.mtext); //数据的正文data.mtype = 10; //数据的编号/类型int ret = msgsnd(msgid, &data,strlen(data.mtext),0);if(ret == -1){perror("msgsnd error");return -1;}//退出处理if(!strcmp(data.mtext,"exit")){//退出之前,通知子进程也退出 kill(id,SIGUSR1);break;}} }else if(id == 0)//子进程 ---接收数据 100{struct msgbuf data;while(1){memset(&data, 0, sizeof(data));msgrcv(msgid, &data, sizeof(data.mtext), 100,0);printf("pid:[%d]\tmtype:[%ld]\trecv:%s\n", getpid(), data.mtype, data.mtext);//接收到退出,告诉父进程 也要退出if(!strcmp(data.mtext,"exit")){kill(getppid(), SIGUSR1);break;}}exit(0);}waitpid(-1,NULL, 0);return 0;
}
(2)、消息队列进程2
#include<stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <sys/types.h>//进程2 //自定义的消息队列的数据结构体
struct msgbuf{long mtype; //数据的编号/类型char mtext[1024]; //数据的正文
};void signalHandle(int signum)
{printf("进程[%d] 收到信号%d\n", getpid(), signum);exit(0); //退出进程
}int main(int argc, char **argv)
{signal(SIGUSR1,signalHandle);//1、确定 文件路径名 ---获取消息队列的key值key_t key = ftok(".", 100);//2、根据文件路径名,打开文件 文件不存在则创建 得到fd ---根据key值 获取消息队列的ID,如果消息队列不存在则创建int msgid = msgget(key, IPC_CREAT|0666);if(msgid == -1){perror("msgget error");return -1;}printf("消息队列 key:%#x msgid:%d\n", key, msgid); //0xpid_t id = fork();if(id>0)//父进程 ---发送数据 100{//3、往文件中写入数据 --------往消息队列中 发送数据struct msgbuf data;while(1){memset(&data, 0, sizeof(data));scanf("%s", data.mtext); //数据的正文data.mtype = 100; //数据的编号/类型int ret = msgsnd(msgid, &data, strlen(data.mtext), 0);if(ret == -1){perror("msgsnd error");return -1;}//退出处理if(!strcmp(data.mtext,"exit")){//退出之前,通知子进程也退出 kill(id,SIGUSR1);break;}}}else if(id == 0)//子进程 --接收数据 10{struct msgbuf data;while(1){memset(&data, 0, sizeof(data));msgrcv(msgid, &data, sizeof(data.mtext), 10, 0);printf("pid:[%d]\tmtype:[%ld]\trecv:%s\n", getpid(), data.mtype, data.mtext);//接收到退出,告诉父进程 也要退出if(!strcmp(data.mtext, "exit")){kill(getppid(), SIGUSR1);break;}}exit(0);}waitpid(-1, NULL, 0);return 0;
}