消息队列和共享内存

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 标志,且消息队列中没有符合条件的消息。
      • EFAULTmsgp 指针指向的内存区域不可访问。

消息结构体头文件

#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 :
      • cmdIPC_STAT 时,系统会将共享内存段的状态信息填充到 buf 所指向的结构体中。
      • cmdIPC_SET 时,系统会从 buf 所指向的结构体中读取相关属性值并设置到共享内存段中。
      • cmdIPC_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;
}