.webp)
消息队列和共享内存
AI-摘要
Tianli GPT
AI初始化中...
介绍自己
生成本文简介
推荐相关文章
前往主页
前往tianli博客
消息队列和共享内存
1. 消息队列概述
- 消息队列中,每一个消息内容都有规定的数据类型/格式【协议】
- 消息队列采用【FIFO】 形式,先进先出,同时处理之后的消息,从当前队列中取出销毁,不在队列中存在。
- 消息队列可以进行随机查询,消息处理可以根据消息类型,消息相关数据进行指定消息,指定次序处理。
- 每一个消息都有一个唯一【消息标识符】,在整个队列中唯一
- 每一个消息队列同样存在对应的一个【队列标识符】,在整个系统唯一。
消息队列属于 System V 进程通信方式规范,在整个 IPC 通信过程中,系统会提供给当前消息队列一个唯一的 Key 值 --> 【队列标识符】
2. 消息队列相关函数
2.1 ftok 创建消息队列
函数文档
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
- 功能:
- 根据当前用户提供的文件/文件夹路径,以及指定的项目 ID 号,创建一个消息队列,返回值是消息队列的 key 值,key 值是当前消息队列访问的唯一标识符。
- 参数解释:
const char *pathname :
提供给当前函数的一个文件/文件夹路径,要求当前文件/文件夹必须存在,如果不存在,报错!!!创建消息队列失败
- 补充说明: ftok 是利用系统分配给当前文件/文件夹的 inode 信息, 参与生成对应的 消息队列 key 值
int proj_id :
用户指定当前项目的 id 号,参与整个消息队列生成过程,用于生成对应的消息队列 Key 值。proj_id 数据范围是 1 ~ 255 ,对应一个字节数据,后续代码中如果需要使用对应的 proj_id 是对应当前数据的【低 8 位有效】- 返回值类型
- 创建成功,返回是对应消息队列 Key 值
- 创建失败返回 -1
【注意】
- 采用相同文件/文件夹路径,以及 proj_id 得到 key 数据一致。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
/*
key_t ftok(const char *pathname, int proj_id);
*/
int main(int argc, char const *argv[])
{
key_t key = ftok("../share_dir", 6);
printf("key : %d\n", key);
key_t key2 = ftok("../share_file.txt", 6);
printf("key2 : %d\n", key2);
return 0;
}
2.2 消息队列数据格式【重点】
typedef struct _msg
{
long mtype; // message type 消息类型
char mtext[100]; // message text 字符数组,信息正文
/* 其他内容可以自行添加,正文内容,可以有多个字段数据,多个字段类型,多个字段形式 */
} MSG;
后续涉及到的消息队列相关函数,都是基于当前 MSG 类型完成。
【要求】结构体第一个字段必须是 long 类型,作为整个消息队列数据包的格式限制
2.3 消息队列获取 msgget
函数文档
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg);
- 函数功能:
- 根据消息队列对应的 key 值和设置消息队列创建的标记,获取到消息队列
- 函数参数
key_t key :
可以利用 ftok 得到对应的 key_t 值,作为当前消息队列唯一标识int msgflg :
获取当前消息队列方式,判断条件和权限情况
IPC_CREAT :
如果指定的消息队列不存在,则创建一个新的消息 队列。IPC_EXCL :
和IPC_CREAT
如果对应当前 Key_t 值的消息队列存在,创建失败,返回值为 -1,同时errno
设置为EEXIST
- 【常用模式】msgflg =
IPC_CREAT | 0666
- 返回值:
- 创建成功,返回对应的消息队列标识符
- 创建失败,返回 -1
2.4 消息队列数据发送 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);
- 函数功能:
- 在指定的消息队列中,发送目标数据包中的数据内容。
- 函数参数:
int msqid :
消息队列标识符,当前操作对应的是哪一个消息队列const void *msgp :
发送的消息数据包 (message package) , 要求满足消息队列对应的数据格式,对应消息队列要求的 【MSG 格式】size_t msgsz :
当前发送的数据包的字节个数。int msgflg :
控制消息发送行为的标志。可以是以下值:
0
:默认行为,如果消息队列已满,msgsnd
会阻塞,直到有空间可用。IPC_NOWAIT
:如果消息队列已满,不阻塞,立即返回-1
,并将errno
设置为EAGAIN
。- 返回值类型:
- 发送成功,返回 0
- 发送失败,并且 msgflg 设置为
IPC_NOWAIT
, 返回 -1
2.4 消息队列数据接收 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);
- 函数功能:
- 在指定的消息队列中,接收目标消息内容
- 函数参数:
int msqid :
消息队列标识符,当前操作对应的是哪一个消息队列void *msgp :
用于接收消息数据的数据包缓冲空间。size_t msgsz :
接收数据对应的字节个数**long msgtyp :
当前消息类型**
- msgtyp = 0 : 返回整个消息队列中的第一个消息
- msgtyp > 0 : 找到指定 消息结构体中的 mtype 对应的消息
- msgtyp < 0 : 找出整个消息队列中信息类型,小于等于当前 msgtyp 绝对值的所有消息内容,可能存在多个数据情况。
int msgflg :
控制消息接收行为的标志。可以是以下值:
0
:默认行为,如果消息队列中没有符合条件的消息,msgrcv
会阻塞,直到有符合条件的消息到达。IPC_NOWAIT
:如果消息队列中没有符合条件的消息,不阻塞,立即返回-1
,并将errno
设置为ENOMSG
。MSG_NOERROR
:如果接收到的消息长度超过msgsz
,截断消息,只返回msgsz
长度的数据,不会失败。- 返回值类型:
- 成功时返回接收到的消息数据部分的长度**(不包括
mtype
成员)。**- 失败时返回 -1 并设置 errno 来指示具体的错误原因,常见的错误包括:
EACCES
:没有权限从指定的消息队列接收消息。ENOMSG
:使用了IPC_NOWAIT
标志,且消息队列中没有符合条件的消息。EFAULT
:msgp
指针指向的内存区域不可访问。
消息结构体头文件
#ifndef _04_MSG_
#define _04_MSG_
// 满足消息队列所需的消息结构体
typedef struct _msg
{
long msg_type;
char msg_data[64];
} MSG;
#endif
消息队列读取
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include "04-msg.h"
int main(int argc, char const *argv[])
{
// 申请共享空间,得到对应 IPC 唯一标识符 key_t
key_t key = ftok(".", 88);
if (-1 == key)
{
perror("ftok failed");
}
// 获取已经存在/或者当前进程创建对应的消息队列内容
int msgid = msgget(key, IPC_CREAT | 0666);
if (-1 == msgid)
{
perror("msgget failed");
}
// 创建一个消息结构体变量
MSG msg;
/*
利用 msgrcv 函数接收在消息队列中的消息数据
*/
ssize_t size = msgrcv(msgid, &msg, sizeof(msg.msg_data), 0, 0);
printf("size : %ld\n", size);
printf("msg.msg_type : %ld\n", msg.msg_type);
printf("msg.msg_data : %s\n", msg.msg_data);
MSG msg2;
size = msgrcv(msgid, &msg2, sizeof(msg2.msg_data), 0, 0);
printf("size : %ld\n", size);
printf("msg.msg_type : %ld\n", msg2.msg_type);
printf("msg.msg_data : %s\n", msg2.msg_data);
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
消息队列发送数据
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include "04-msg.h"
int main(int argc, char const *argv[])
{
// 共享空间申请,得到对应 key_t 值,对应 IPC 唯一标识符
key_t key = ftok(".", 88);
if (-1 == key)
{
perror("ftok failed!");
}
// 获取消息队列,如果当前消息队列不存在,创建并且设置操作权限为 0666
int msgid = msgget(key, IPC_CREAT | 0666);
if (-1 == msgid)
{
perror("msgget failed!");
}
// 消息结构体变量
MSG msg;
// 给予当前消息数据结构体进行成员变量赋值操作
msg.msg_type = 1; // 信息类型为 1
strcpy(msg.msg_data, "Hello World!"); // 信息数据是一个字符串数据
/*
利用 msgsnd 函数对消息结构体数据进行发送操作,发送到当前消息队列中
将指定的 MSG 数据包发送到指定的消息队列中。
*/
int ret = msgsnd(msgid, &msg, sizeof(msg.msg_data), 0);
MSG msg2;
msg2.msg_type = 3; // 信息类型为 3
strcpy(msg2.msg_data, "你好!世界!"); // 信息数据是一个字符串数据
msgsnd(msgid, &msg2, sizeof(msg2.msg_data), 0);
return 0;
}
2.5 msgctl 消息队列控制函数
3. 共享内存
3.1 概述
共享内存是两个或者两个以上的进程,对于特定内存空间数据共享。
特点
- 共享内存是进程之间共享数据最快的方式,因为共享内存之后,对应内存空间,在任何一个进程中都可以通过对应的【内存地址/共享内存标识符】直接访问对应的内存空间
- 任何一个进程写入数据到共享内存,其他可以操作当前共享内存的进程,都可以获取到写入数据内容

3.2 获取共享内存标识符
函数文档
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
- 函数功能:
- 根据 ftok 申请的唯一 IPC key_t 数据,以及设置的共享内存字节数,和对应的共享内存设置参数得到对应的【共享内存标识符】
- 函数参数:
key_t key :
通过 ftok 申请的 IPC Key_t 数据,在整个 IPC 管理中唯一size_t size :
申请的共享内存字节数int shmflg :
共享内存权限和创建状态设置
IPC_CREAT
:如果对应共享内存不存在,创建对应共享内存,如果存在,无任何操作IPC_EXCL
:和IPC_CREAT
一起使用,如果共享内存存在,当前函数执行失败- 常见写法
IPC_CREAT | 0666
- 返回值:
- 获取成功,得到对应的共享内存标识符
- 获取失败,返回 -1
共享内存申请
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
/*
int shmget(key_t key, size_t size, int shmflg);
*/
// 申请当前项目在 IPC (inter process communication) 中的唯一 key_t
key_t key = ftok(".", 66);
if (-1 == key)
{
perror("ftok failed!");
}
size_t shmid = shmget(key, 128, IPC_CREAT | 0666);
if (-1 == shmid)
{
perror("shmget failed!");
}
sleep(10);
return 0;
}
利用 Linux 命令查看当前内存状态
qf@qf:~$ ipcs -m
数据结果
------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 连接数 状态 0x42011df2 2 qf 666 128 0
也可以利用 Linux 命令对共享内存空间进行删除操作
qf@qf:~$ # ipcrm -m shmid qf@qf:~$ ipcrm -m 2
3.3 共享内存映射【attach】
函数文档
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
- 函数功能:
- 将共享内存映射到进程的地址空间,使进程能够通过指针直接访问共享数据。
- 函数参数:
int shmid
: 共享内存标识符const void *shmaddr :
共享内存地址,【通常情况参数为 NULL,由系统自行管理分配】int shmflg
:共享内存参数配置
- 推荐参数 0 表示当前共享内存可读可写
SHM_RDONLY
当前共享内存只读- 返回值
- 返回值为 void *,可以通过强制类型转换,将当前共享内存中的数据指定处理为目标数据类型。映射成功,返回值是当前内存空间首地址
- 映射失败,返回 (void *) -1, -1 如果按照地址方式考虑,是一个非法地址。
共享内存写入和读取操作
03-shmat_write.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#define SHM_SIZE 128
int main(int argc, char const *argv[])
{
// 获取 IPC 唯一进程间通信标识符
key_t key = ftok(".", 68);
if (-1 == key)
{
perror("ftok failed!");
}
// 获取/申请共享内存
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (-1 == shmid)
{
perror("shmget failed!\n");
}
/*
【内存映射】当前进程获取使用对应共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr 推荐使用 NULL ,系统默认分配内存地址
shmflg 推荐 0,表示当前共享内存可读可写
*/
char * shmaddr = shmat(shmid, NULL, 0);
bzero(shmaddr, SHM_SIZE);
char * data = "陈陈你的 open 函数呢?";
memcpy(shmaddr, data, strlen(data));
return 0;
}
03-shmat_read.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SHM_SIZE 128
int main(int argc, char const *argv[])
{
// 获取 IPC 唯一进程间通信标识符
key_t key = ftok(".", 68);
if (-1 == key)
{
perror("ftok failed!");
}
// 获取/申请共享内存
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (-1 == shmid)
{
perror("shmget failed!\n");
}
/*
【内存映射】当前进程获取使用对应共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr 推荐使用 NULL ,系统默认分配内存地址
shmflg 推荐 0,表示当前共享内存可读可写
*/
char *shmaddr = shmat(shmid, NULL, 0);
printf("data = %s\n", shmaddr);
return 0;
}
3.4 共享内存取消映射【detach】
函数文档
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
- 函数功能:
- 解除当前进程和指定地址共享内存的联系,后续代码中当前进程无法使用共享内存。如果进程继续使用直接会导致【段错误,核心已转储】
- 函数参数:
- 目标分离的共享内存空间首地址
- 返回值
- 成功返回 0
- 失败返回 -1
shmat.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SHM_SIZE 128
int main(int argc, char const *argv[])
{
// 获取 IPC 唯一进程间通信标识符
key_t key = ftok(".", 68);
if (-1 == key)
{
perror("ftok failed!");
}
// 获取/申请共享内存
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (-1 == shmid)
{
perror("shmget failed!\n");
}
/*
【内存映射】当前进程获取使用对应共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr 推荐使用 NULL ,系统默认分配内存地址
shmflg 推荐 0,表示当前共享内存可读可写
*/
char *shmaddr = shmat(shmid, NULL, 0);
printf("data = %s\n", shmaddr);
/*
分离当前进程和共享内存
*/
int ret = shmdt(shmaddr);
if (-1 == ret)
{
perror("shmdt failed");
}
// 分离之后再次使用原本的共享内存,直接错误!
printf("data = %s\n", shmaddr);
sleep(30);
return 0;
}
3.5 共享内存控制 shmctl
函数文档
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 函数功能
- 指定共享内存的控制函数,可以记录共享内存状态,也可以对共享内存状态进行获取,另外可以利用当前函数对共享内存进行关闭操作。
- 函数参数
int shmid :
共享内存标识符cmd
:这是一个命令参数,用于指定要对共享内存段执行的操作。常见的命令有以下几种:
IPC_STAT
:将共享内存段的当前状态信息复制到buf
所指向的struct shmid_ds
结构体中。这个结构体包含了共享内存段的各种属性,如权限、大小、创建时间等。IPC_SET
:将buf
所指向的struct shmid_ds
结构体中的某些属性值设置到共享内存段中。可以设置的属性包括共享内存段的权限、所有者等。IPC_RMID
:删除指定的共享内存段。一旦使用该命令删除共享内存段,该共享内存段将被标记为待删除状态,当所有连接到该共享内存段的进程都与之分离后,系统会自动释放该共享内存段所占用的资源。buf :
- 当
cmd
为IPC_STAT
时,系统会将共享内存段的状态信息填充到buf
所指向的结构体中。- 当
cmd
为IPC_SET
时,系统会从buf
所指向的结构体中读取相关属性值并设置到共享内存段中。- 当
cmd
为IPC_RMID
时,buf
参数通常被忽略,可以设置为NULL
。
struct shmid_ds 结构体内容, 不同的操作系统情况下,结构体内容不一致
struct shmid_ds { struct ipc_perm shm_perm; /* 权限和所有者信息 */ size_t shm_segsz; /* 共享内存段的大小(以字节为单位) */ time_t shm_atime; /* 最后一次附加到共享内存段的时间 */ time_t shm_dtime; /* 最后一次分离共享内存段的时间 */ time_t shm_ctime; /* 最后一次更改共享内存段状态的时间 */ pid_t shm_cpid; /* 创建共享内存段的进程 ID */ pid_t shm_lpid; /* 最后一次执行 shmat 或 shmdt 操作的进程 ID */ shmatt_t shm_nattch; /* 当前附加到共享内存段的进程数量 */ ... };
shmctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SHM_SIZE 128
int main(int argc, char const *argv[])
{
// 获取 IPC 唯一进程间通信标识符
key_t key = ftok(".", 68);
if (-1 == key)
{
perror("ftok failed!");
}
// 获取/申请共享内存
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (-1 == shmid)
{
perror("shmget failed!\n");
}
/*
【内存映射】当前进程获取使用对应共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr 推荐使用 NULL ,系统默认分配内存地址
shmflg 推荐 0,表示当前共享内存可读可写
*/
char *shmaddr = shmat(shmid, NULL, 0);
printf("data = %s\n", shmaddr);
sleep(15);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载必须注明来自 卡卡罗特
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果