“解锁进程间高效沟通,Linux IPC是你的关键钥匙!“#Linux系统编程之进程间通信【上】

"解锁进程间高效沟通,Linux IPC是你的关键钥匙!"#Linux系统编程之进程间通信【上】

前言

  欢迎来到Linux系统编程之进程间通信【上篇】的精彩内容!本篇文章将全面概述进程间通信的重要性,深入解析管道通信的原理,并通过实战演示无名管道的编程应用。随后,我们将学习如何创建命名管道,并实战演示如何利用命名管道进行数据通信。此外,文章还将揭示消息队列的通信原理,详细介绍消息队列相关的API,并通过编程实例展示如何发送和接收消息队列中的数据。最后,我们还将探讨键值生成的方法以及消息队列的移除操作。希望这些内容能够激发您对Linux系统编程中进程间通信的浓厚兴趣。看到这篇博文的朋友们,不妨先点个赞,再细细品味其中的精彩内容吧!

预备知识

  一、C变量
  二、基本输入输出
  三、流程控制
  四、函数
  五、指针
  六、字符串
  七、结构体
  八、联合体
  九、Linux系统基本操作命令如mkdir,ls -l等。
  十、Linux系统编程之进程的知识

  如果以上知识不清楚,请自行学习后再来浏览。如果我有没例出的,请在评论区写一下。谢谢啦!

一、 进程间通信概述

1.1 理论介绍

  如下图
在这里插入图片描述

1.2 Linux进程间通信简图

  如下图
请添加图片描述

二、 管道通信原理

2.1 进程间通信(IPC)的方式

  进程间通信(IPC)的方式:管道(无名管道,命名管道),消息队列,信号量,共享存储,Scoket,Streams。其中Socket,Streams可用于两个主机上的进程通信。

2.2 无名管道

  管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

2.2.1 无名管道的特点

  1.它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
  2.它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
  3.它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中

2.2.2 无名管道的特点示意图

  如下图
请添加图片描述

2.2.3 无名管道的原型
1 #include <unistd.h>
2 int pipe(int fd[2]);    // 返回值:若成功返回0,失败返回-1

  当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
在这里插入图片描述

2.2.4 无名管道原理示例

  单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
在这里插入图片描述
  若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。
  以上内容来自大佬:"南宫凌霄"的博文
  点击这里

三、 无名管道编程实战

3.1 编程需求

  实现一个进程间通信的程序,其中父进程负责发送数据,子进程则负责接收这些数据并将其输出到标准输出。

3.2 程序代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

int main()
{
        int fd[2];              定义一个包含两个元素的整型数组用于承接pipe建立管道返回的文件描述符
        pid_t fk;               定义一个pid_t类型的变量fk用于承接fork创建新进程的返回值
        char *readBuf = NULL;   定义一个字符指针变量用于承接子进程读取到的数据

        if(pipe(fd) < 0)		创建无名管道并判断是否建立成功
        {
                puts("Creat pipe is fail\n");
        }
        else
        {
                puts("Creat pipe is successful");
        }
        fk = fork();           创建子进程
        if(fk < 0)             判断创建子进程是否成功
        {
                puts("Creat child process fail");
        }
        else if(fk > 0)        判断是否是父进程
        {
                printf("This is parent process; PID = %d\n",getpid());
                close(fd[0]);  关闭读通道
                write(fd[1],"This is parent process send data",strlen("This is parent process send data")); 向写通道写入数据
                wait(NULL);    等待子进程退出
        }
        else if(fk == 0)       判断是否是子进程
        {
                printf("This is child process; PID = %d\n",getpid());
                close(fd[1]);  关闭写通道
                readBuf = (char *)malloc(sizeof(char) * strlen("This is parent process send data"));   给readBuf分配空间
                read(fd[0],readBuf,strlen("This is parent process send data"));                       读读通道数据
                printf("This is child process read data : %s\n",readBuf);输出读通道的数据
                exit(0);       退出子进程
        }

        return 0;
}

3.3 程序运行结果

CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/01_Unnamed_Pipes$ gcc Linux_IPC_Programming.c -o Unnamepipe
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/01_Unnamed_Pipes$ ./Unnamepipe 
Creat pipe is successful
This is parent process; PID = 51637
This is child process; PID = 51638
This is child process read data : This is parent process send data

3.4 验证子进程先运行读取不到数据而阻塞的现象

3.4.1 程序代码

  程序代码只需要将父进程执行的时候睡眠3秒即可

 else if(fk > 0)        判断是否是父进程
        {
                printf("This is parent process; PID = %d\n",getpid());
                close(fd[0]);  关闭读通道
                write(fd[1],"This is parent process send data",strlen("This is parent process send data")); 向写通道写入数据
                wait(NULL);    等待子进程退出
        }
-------------------------------------------------------------------------
改为:
-------------------------------------------------------------------------
 else if(fk > 0)        判断是否是父进程
        {
        		sleep(3); 父进程睡眠3printf("This is parent process; PID = %d\n",getpid());
                close(fd[0]);  关闭读通道
                write(fd[1],"This is parent process send data",strlen("This is parent process send data")); 向写通道写入数据
                wait(NULL);    等待子进程退出
        }
3.4.2 程序运行结果
-------------------------------------------------------------------------
先出现
-------------------------------------------------------------------------
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/01_Unnamed_Pipes$ gcc Linux_IPC_Programming2.c -o Unnamepipe CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/01_Unnamed_Pipes$ ./Unnamepipe 
Creat pipe is successful
This is child process; PID = 51707
-------------------------------------------------------------------------
隔了3秒后出现(这里就在read阻塞)
This is parent process; PID = 51706
This is child process read data : This is parent process send data

四、 创建命名管道

4.1 FIFO介绍

  FIFO,也称为命名管道,它是一种文件类型。

4.1.1 FIFO特点

  FIFO可以在无关的进程之间交换数据,与无名管道不同。
  FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

4.1.2 FIFO的原型
1 #include <sys/stat.h>
2 // 返回值:成功返回0,出错返回-1
3 int mkfifo(const char *pathname, mode_t mode);

  其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
  当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
  若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
  以上内容来自大佬:"南宫凌霄"的博文
  点击这里

4.2 FIFO的创建

4.2.1 创建的FIFO

  程序代码

#include <stdio.h>
使用mkfifo函数创建fifo必须包含以下头文件
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
        mkfifo("./file",0600);		创建一个命名管道为file
        return 0;
}

  程序运行结果

CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ gcc Linux_IPC_Programming.c -o CreatPipe 
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ ./CreatPipe 
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ ls
CreatPipe 	     file       Linux_IPC_Programming.c
4.2.2 验证FIFO是否创建成功

  程序代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
        if(mkfifo("./file",0600)==-1)           FIFO创建失败会返回-1,成功返回0
        {
                printf("Creat name pipe fail\n");
        }
        else
        {

                printf("Creat name pipe successful\n");
        }

        return 0;
}

  程序运行结果

CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ gcc Linux_IPC_Programming2.c -o CreatPipe 
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ ./CreatPipe 
Creat name pipe successful                    第一次运行管道file还没有存在,所以成功
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ ls
CreatPipe             Linux_IPC_Programming2.c          file         Linux_IPC_Programming.c
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ ./CreatPipe 
Creat name pipe fail                           第二次运行管道file存在,所以失败
4.2.3 输出FIFO创建失败信息

  程序代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
        int ret = mkfifo("./file",0600);        

        if(ret == -1)
        {
                printf("Creat name pipe fail\n");
                perror("why");		采用perror输出创建FIFO失败信息
        }
        else if(ret == 0)
        {
                printf("Creat name pipe successful\n");
        }

        return 0;
}

  程序运行结果

CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ gcc Linux_IPC_Programming3.c -o CreatPipe 
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ ./CreatPipe 
Creat name pipe fail
why: File exists    失败原因为管道file存在
4.2.3 优化输出FIFO创建失败信息

  errno的作用
  在Linux系统中,errno 是一个全局变量,用于报告在C语言库中调用系统函数时发生的错误。当系统调用或库函数遇到错误时,它们不会返回错误代码作为函数的返回值(尽管有些函数也会返回特定的错误代码或错误状态),而是将 errno 设置为一个特定的错误代码,以指示发生了哪种类型的错误。
  程序代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
使用errno必须包含以下头文件
#include <errno.h>

int main()
{

        if(mkfifo("./file",0600)==-1 && errno == EEXIST)
        {
                printf("Creat name pipe fail\n");
                perror("why");
        }
        else
        {
                if(errno == EEXIST)
                {
                        printf("file you\n");
                }
                else
                {
                        printf("Creat name pipe successful\n");
                }
        }

        return 0;
}

  程序运行结果

CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ gcc Linux_IPC_Programming4.c -o CreatPipe 
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$ ./CreatPipe 
Creat name pipe fail
why: File exists
CLC@Embed_Learn:~/Linux_System_Programming/03_Linux_IPC/02_Name_Pipe$

五、 命名管道的数据通信编程实现

5.1 验证open打开管道阻塞性

5.1.1 读程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
        open("./file",O_RDONLY);                 这里直接以只读的方式打开管道是因为之前就已经创建了管道
        printf("Read open fifo successful!\n\n");

        return 0;
}
5.1.2 写程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
        open("./file",O_WRONLY);                用open以只写的方式打开管道file
        printf("Write open fifo successful!\n\n");

        return 0;
}
5.1.3 两程序配合运行结果

  如下图
在这里插入图片描述

5.2 读写两进程单次通信

5.2.1 读程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
        int fd = 0;             定义文件描述符
        char readBuf[32] = {0}; 定义读取数据缓冲区
        int n_read = 0;         定义read返回值变量
  
        fd = open("./file",O_RDONLY); 已只读的方式打开管道
        n_read = read(fd,readBuf,33); 从管道中读取33字节数据进入readBuf
        if(n_read > 0)                
        {		成功读取到数据
                printf("From pipe read data successful\n");
                printf("n_read = %d,readBuf = %s\n",n_read,readBuf);
                输出读取到的字节数以及内容
        }
        else
        {		没读取到数据输出失败信息
                printf("From pide read data fail\n");
        }
        close(fd); 关闭管道

        return 0;
}
5.2.2 写程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main()
{
        int fd = 0;                  定义文件描述符
        int n_write = 0;             定义write函数返回值变量
        fd = open("./file",O_WRONLY); 已只写的方式打开管道
        n_write = write(fd,"This is write process wtite data",strlen("This is write process wtite data")+ 1); 向管道中写入数据
        if(n_write > 0)   
        {       成功写入
                printf("Write data to pipe successful\n");
                printf("n_write = %d\n",n_write); 输出写入数据的字节数
        }
        else
        {       写入失败,输出失败信息
                printf("Write data to pipe fail\n");
        }
        close(fd);     关闭管道

        return 0;
}
5.2.3 两程序配合运行结果

  如下图
在这里插入图片描述

5.3 读写两进程多次通信

5.3.1 读程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main()
{
        int fd = 0;
        char readBuf[33] = {0};
        int n_read = 0;

        fd = open("./file",O_RDONLY);
        while(1)while(1)一直死循环读取数据
        {
                sleep(1);                     睡眠一秒实现每隔1秒读取一次
                n_read = read(fd,readBuf,33);
                if(n_read > 0)
                {
                        printf("From pipe read data successful\n");
                        printf("n_read = %d,readBuf = %s\n",n_read,readBuf);
                }
                else
                {
                        printf("From pide read data fail\n");
                }
        }
        close(fd);

        return 0;
}
5.3.2 写程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

int main()
{
        int fd = 0;
        int n_write = 0;
        fd = open("./file",O_WRONLY);

        while(1)while(1)一直死循环写数据    
        {
                sleep(1); 睡眠一秒实现每隔1秒写一次
                n_write = write(fd,"This is write process wtite data",strlen("This is write process wtite data") +1);
                if(n_write > 0)
                {
                        printf("Write data to pipe successful\n");
                        printf("n_write = %d\n",n_write);
                }
                else
                {
                        printf("Write data to pipe fail\n");
                }
        }

        close(fd);
        
        return 0;
}
5.3.3 两程序配合运行结果

  如下图
在这里插入图片描述

5.4 读写程序多次通信退出补充

5.4.1 问题描述

  在上述的多次通信的读写程序中,当我使用Ctrl + C的方式关闭读程序时,随后写程序也随之关闭了。然而,如果我先以同样的方式关闭写程序,读程序却能正常运行。

5.4.2 原因分析

  当读程序退出时,它通常会关闭其文件描述符,包括用于读取数据的程序。一旦管道的读端被关闭,如果写端尝试向管道写入数据,它会收到一个SIGPIPE信号(除非该信号已被进程忽略)。默认情况下,SIGPIPE信号会导致进程终止。这就是为什么在读程序退出后,写程序也跟着退出的原因。

5.4.3 解决办法

   写程序在尝试写入之前检查了管道的状态(例如,使用poll()或select()函数来检查管道是否仍然可读或可写),或者它忽略了SIGPIPE信号(通过signal(SIGPIPE, SIG_IGN);实现),那么即使读程序退出,它也不会自动退出。

5.4.3 解决问题后的写程序代码和运行结果

  程序代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

int main()
{
        int fd = 0;
        int n_write = 0;
        fd = open("./file",O_WRONLY);
        signal(SIGPIPE,SIG_IGN);        忽略SIGPIPE信号
        while(1)
        {
                sleep(1);
                n_write = write(fd,"This is write process wtite data",strlen("This is write process wtite data") +1);
                if(n_write > 0)
                {
                        printf("Write data to pipe successful\n");
                        printf("n_write = %d\n",n_write);
                }
                else
                {
                        printf("Write data to pipe fail\n");
                }
        }

        close(fd);

  程序运行结果:如下图
在这里插入图片描述

5.5 采用非阻塞打开管道读取数据的现象

  当采用非阻塞方式打开管道进行读取时,可能会遇到读取程序持续尝试读取而阻塞不前的情况,即使此时没有数据可读。这种情况下,如果随后启动的写程序尝试写入数据,也可能因为读取程序持续占用读取操作而无法及时获取到数据。因此,建议谨慎使用非阻塞模式打开管道,以避免此类潜在的同步问题。

六、 消息队列的通信原理

6.1 消息队列的定义

  消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

6.2 消息对了的特点

  1.消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  2.消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  3.消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

6.3 消息队列的通信原理简图

请添加图片描述

七、 消息队列相关API

7.1 进程通过消息队列通信的步骤

  如下图
请添加图片描述

7.2 消息队列相关API原型

1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

  在以下两种情况下,msgget将创建一个新的消息队列:
  如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
  key参数为IPC_PRIVATE。
  函数msgrcv在读取消息队列时,type参数有下面几种情况:
  type == 0,返回队列中的第一个消息;
  type > 0,返回队列中消息类型为 type 的第一个消息;
  type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
  可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。

八、 消息队列编程收发数据

8.1 消息队列单向通信

8.1.1 发送程序程序代码
#include <stdio.h>
使用消息队列相关API需要包含以下三个头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include <string.h>

//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
建立一个结构体,用于消息队列进行收发消息的缓冲区
struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
        int msgId = 0;           定义消息队列的ID变量
        int sendMark = 0;        定义消息队列发送标志位
        struct msgbuf sendBuf = {888,"This is send data"}; 建立消息队列发送的内容,消息队列节点编号为888,节点内容为"This is send data"//int msgget(key_t key, int msgflg);
        msgId = msgget(0x1235,IPC_CREAT|0777);   使用msgget函数获取消息队列的ID,如果没有队列就创建它。 
        if(msgId == -1)		msgId = -1 表示获取消息队列ID失败或者创建消息队列失败	
        {
                puts("Get msgId fail");
        }
        else
        {
                puts("Get msgId successful");
        }
        sendMark = msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0); 向消息队列中发送节点信息
                if(sendMark== 0)           sendMark=0 代表发送成功
        {
                puts("Send data successful!");

        }
        else
        {
                puts("Send data fail ");
        }

        return 0;
}
8.1.2 发送程序代码重要函数解读

  msgget函数
  函数原型

int msgget(key_t key, int msgflg);

  函数参数
  key: 这是一个 key_t 类型的值,用于标识消息队列。如果 key 是 IPC_PRIVATE,则创建一个新的、唯一的消息队列。否则,它通常是通过 ftok 函数从文件路径和ID生成的。
  msgflg: 这是一个标志位,用于控制 msgget 的行为。它可以是以下值的组合(使用按位或操作符 |):

  • IPC_CREAT: 如果不存在具有给定键的消息队列,则创建一个新的消息队列。
  • IPC_EXCL: 与 IPC_CREAT 一起使用时,如果已存在具有给定键的消息队列,则调用失败。
  • 0666 或其他权限模式(通过 S_IRUSR、S_IWUSR等宏定义):指定新创建的消息队列的权限。这些权限会被进程的 umask 掩码修改。
      函数返回值
      成功时,msgget 返回一个非负整数,即消息队列的标识符(msgid)。出错时,返回 -1,并设置 errno以指示错误类型。
      msgsnd函数
      函数原型
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

  函数参数

  • msqid: 消息队列的标识符,这是之前通过 msgget 函数获得的。
  • msgp: 指向要发送的消息的指针。这个消息应该是一个 struct msgbuf 类型的结构体,该结构体通常定义如下:
struct msgbuf {  
    long mtype;   // 消息类型,必须大于 0  
    char mtext[1]; // 消息正文,实际大小由 msgsz 指定  
};

注意:mtext字段的大小是灵活的,实际使用时需要根据需要分配足够的空间。

  • msgsz: 消息正文(mtext)的字节大小。
  • msgflg: 控制消息发送行为的标志。它可以是 0IPC_NOWAIT。如果设置了 IPC_NOWAIT,并且消息队列已满(即达到了系统限制或队列的最大消息数),则 msgsnd 会立即返回一个错误(EAGAIN)。如果没有设置 IPC_NOWAIT,则 msgsnd 会阻塞调用进程,直到消息被发送到队列中。
      函数返回值
  • 成功时,msgsnd 返回 0。
  • 出错时,返回 -1,并设置 errno 以指示错误类型。
8.1.3 接收程序代码
#include <stdio.h>
使用消息队列相关API需要包含以下三个头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
建立一个结构体,用于消息队列进行收发消息的缓冲区
struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
        int msgId = 0;          		定义消息队列的ID变量
        int n_read;             		定义消息队列接收字节变量
        struct msgbuf readBuf;  		定义消息队列接收缓冲区
//int msgget(key_t key, int msgflg);
        msgId = msgget(0x1235,IPC_CREAT|0777); 使用msgget函数获取消息队列的ID,如果没有队列就创建它。 
        if(msgId == -1)                        msgId = -1 表示获取消息队列ID失败或者创建消息队列失败
        {
                puts("Get msgId fail");
        }
        else
        {
                puts("Get msgId successful");
        }
        n_read = msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0); 向消息队列中节点读取信息
        if(n_read > 0)    如果读取到数据,就输出读取到数据的字节数和内容
        {
                puts("Receive data successful!");
                printf("n_read = %d; readBuf.mtext = %s\n",n_read,readBuf.mtext);
        }
        else
        {
                puts("Receive data fail ");
        }

        return 0;
}
8.1.4 接收程序代码重要函数解读

  msgrcv函数
  函数原型

ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg);

  函数参数

  • msqid: 消息队列的标识符,这是通过msgget函数获得的。
  • msgp: 指向msgbuf结构体的指针,该结构体用于存储接收到的消息。msgbuf结构体通常定义如下:
struct msgbuf {  
    long mtype;   // 消息类型  
    char mtext[1]; // 消息正文,实际大小由msgsz指定  
};

注意:mtext字段的大小是灵活的,但在分配msgbuf结构体时,你需要根据实际需要分配足够的空间。

  • msgsz: 指定msgp->mtext数组的最大字节数,即消息正文的最大长度。
  • msgtyp: 指定要接收的消息类型。如果msgtyp0,则接收队列中的第一个消息。如果msgtyp大于0,则接收类型等于msgtyp的第一个消息。如果msgtyp小于0,则接收类型绝对值等于msgtyp绝对值的最小类型的消息。
  • msgflg: 控制接收行为的标志。它可以包含以下值:
  • 0:阻塞等待消息。
  • IPC_NOWAIT:非阻塞方式接收消息,如果没有消息可接收,则立即返回EAGAIN错误。
  • MSG_EXCEPT(与msgtyp小于0一起使用):接收除指定类型外的第一个消息。
      函数返回值
  • 成功时,msgrcv返回接收到的消息数据(不包括消息类型字段)的字节数
  • 出错时,返回-1,并设置errno以指示错误类型。
8.1.5 消息队列单向通信程序运行结果

  如下图
在这里插入图片描述

8.2 消息队列双向通信

8.2.1 发送程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
        int msgId = 0;
        int sendMark = 0;
        int n_read;
        struct msgbuf readBuf;
        struct msgbuf sendBuf = {888,"This is send data"};
//int msgget(key_t key, int msgflg);
        msgId = msgget(0x1235,IPC_CREAT|0777);
        if(msgId == -1)
        {
                puts("Get msgId fail");
        }
        else
        {
                puts("Get msgId successful");
        }
        sendMark = msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);   发送数据
        if(sendMark== 0)
        {
                puts("Send data successful!");

        }
        else
        {
                puts("Send data fail ");
        }
        n_read = msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),999,0); 接收数据
        if(n_read > 0)
        {
                puts("Receive data successful!");
                printf("n_read = %d; readBuf.mtext = %s\n",n_read,readBuf.mtext);
        }
        else
        {
                puts("Receive data fail ");
        }

        return 0;
}
8.2.2 接收程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
        int msgId = 0;
        int n_read;
        int sendMark = 0;
        struct msgbuf readBuf;
        struct msgbuf receBuf = {999,"Received message!"};
//int msgget(key_t key, int msgflg);
        msgId = msgget(0x1235,IPC_CREAT|0777);
        if(msgId == -1)
        {
                puts("Get msgId fail");
        }
        else
        {
                puts("Get msgId successful");
        }
                n_read = msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);    接收数据
        if(n_read > 0)
        {
                puts("Receive data successful!");
                printf("n_read = %d; readBuf.mtext = %s\n",n_read,readBuf.mtext);
        }
        else
        {
                puts("Receive data fail ");
        }
        sendMark = msgsnd(msgId,&receBuf,strlen(receBuf.mtext),0); 回数据
        if(sendMark== 0)
        {
                puts("Send data successful!");

        }
        else
        {
                puts("Send data fail ");
        }
        return 0;
}
8.2.3 程序运行结果

  如下图
在这里插入图片描述

九、 键值生成及消息队列移除

9.1 键值生成函数ftok介绍

9.1.1 ftok函数原型
key_t ftok(const char *pathname, int proj_id);
9.1.2 ftok函数参数
  • pathname是用于生成键值的文件的路径名。
  • proj_id是一个项目的ID,其值必须在0到255之间(包含0和255)。
9.1.3 ftok函数返回值
  • 返回值是一个 key_t 类型的值,它是根据路径名和项目ID生成的唯一键值。如果调用失败,则返回 (key_t)-1
      返回值的构成
      如指定文件的索引节点(pathname)号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002
      在程序中我将ID值设置为16,索引节点值通过ls -ai即可查询当前目录下的索引节点值。如下图
    在这里插入图片描述
    那么组合起来的key值为:1005434a

9.2 消息队列移除函数msgctl介绍

9.2.1 msgctl函数原型
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
9.2.2 msgctl函数参数
  • msqid:消息队列的标识符,这是通过msgget函数获得的。
  • cmd:指定要执行的操作,它可以是以下值之一:
    • IPC_STAT:获取消息队列的状态,并将状态信息复制到buf指向的msqid_ds结构体中。
    • IPC_SET:设置消息队列的属性。buf应指向一个包含新属性的msqid_ds结构体。只有消息队列的所有者或超级用户才能更改消息队列的权限。
    • IPC_RMID:删除消息队列。
  • buf:指向msqid_ds结构体的指针,该结构体用于传递或接收消息队列的状态信息。如果cmdIPC_STAT,则它用于接收状态信息;如果cmd是IPC_SET,则它包含要设置的新属性。对于IPC_RMID操作,buf可以是NULL
9.2.3 msgctl函数返回值
  • 成功时,msgctl返回0
  • 出错时,返回-1,并设置errno以指示错误类型。

9.3 键值生成及消息队列移除优化消息队列双向通信程序

9.3.1 发送程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

struct msgbuf {
	long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
	int msgId = 0;
	int sendMark = 0;
	int n_read;
	int key = 0;
	int remove = 0;
	struct msgbuf readBuf;
	struct msgbuf sendBuf = {888,"This is send data"};
//int msgget(key_t key, int msgflg);
	key = ftok(".",16);                获取key值
	printf("key = %x\n",key);          输出key值
	msgId = msgget(key,IPC_CREAT|0777);采用key值创建消息队列/获取消息队列的ID
	if(msgId == -1)
	{
		puts("Get msgId fail");
	}
	else
	{ 
		puts("Get msgId successful");
	}
	sendMark = msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
	if(sendMark== 0)
	{
		puts("Send data successful!");
		
	}
	else
	{
		puts("Send data fail ");
	}
	n_read = msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),999,0);
	if(n_read > 0)
        {
                puts("Receive data successful!");
                printf("n_read = %d; readBuf.mtext = %s\n",n_read,readBuf.mtext);
        }
        else
        {
                puts("Receive data fail ");
        }
	//int msgctl(int msqid, int cmd, struct msqid_ds *buf);
        remove = msgctl(msgId,IPC_RMID,NULL);     移除消息队列
        if(remove == 0)                           移除消息队列成功,返回0
        {
                puts("Successful remove message queue");
        }
        else
        {
                puts("Fail remove message queue");
        }
	return 0;
}
9.3.2 接收程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

struct msgbuf {
	long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
	int msgId = 0;
	int n_read;
	int key = 0;
	int sendMark = 0;
	int remove = 0;
	struct msgbuf readBuf;
	struct msgbuf receBuf = {999,"Received message!"};
	//key_t ftok(const char *pathname, int proj_id);
	key = ftok(".",16);                     获取key值
	printf("key = %x\n",key);               输出key值
	//int msgget(key_t key, int msgflg); 
	msgId = msgget(key,IPC_CREAT|0777);     采用key值创建消息队列/获取消息队列的
	if(msgId == -1)
	{
		puts("Get msgId fail");
	}
	else
	{ 
		puts("Get msgId successful");
	}
	n_read = msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
	if(n_read > 0)
	{
		puts("Receive data successful!");
		printf("n_read = %d; readBuf.mtext = %s\n",n_read,readBuf.mtext);
	}
	else
	{
		puts("Receive data fail ");
	}
	sendMark = msgsnd(msgId,&receBuf,strlen(receBuf.mtext),0);
	if(sendMark== 0)
        {
                puts("Send data successful!");

        }
        else
        {
                puts("Send data fail ");
        }
	//int msgctl(int msqid, int cmd, struct msqid_ds *buf);
	remove = msgctl(msgId,IPC_RMID,NULL);      移除消息队列
	if(remove == 0)                            移除消息队列成功,返回0
	{
		puts("Successful remove message queue");
	}
	else
	{
		puts("Fail remove message queue");
	}
	return 0;
}
9.2.3 程序运行结果

  如下图
在这里插入图片描述

结束语

  非常感谢您的耐心阅读!在您即将离开之际,如果您觉得内容有所收获或启发,不妨动动手指,点个赞再走,这将是对我莫大的鼓励和支持。衷心感谢您的点赞与关注,期待未来能继续为您带来更多有价值的内容!谢谢您!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值