"解锁进程间高效沟通,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); 父进程睡眠3秒
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); 等待子进程退出
}
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
: 控制消息发送行为的标志。它可以是0
或IPC_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
: 指定要接收的消息类型。如果msgtyp
为0
,则接收队列中的第一个消息。如果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
结构体的指针,该结构体用于传递或接收消息队列的状态信息。如果cmd
是IPC_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 程序运行结果
如下图
结束语
非常感谢您的耐心阅读!在您即将离开之际,如果您觉得内容有所收获或启发,不妨动动手指,点个赞再走,这将是对我莫大的鼓励和支持。衷心感谢您的点赞与关注,期待未来能继续为您带来更多有价值的内容!谢谢您!