Linux系统编程_进程间通信第1天:IPC、无名管道pipe和命名管道mkfifo(半双工)、消息队列msgget(全双工)

本文主要是介绍Linux系统编程_进程间通信第1天:IPC、无名管道pipe和命名管道mkfifo(半双工)、消息队列msgget(全双工),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 进程间通信概述(427.1)

在这里插入图片描述

2. 管道通信原理(428.2)

  • 进程间的五种通信方式介绍
    • https://blog.csdn.net/wh_sjc/article/details/70283843
  • 进程间通信(IPC,InterProcess Communication):在不同进程之间传播或交换信息(*面试会问)
    • IPC的方式通常有管道(无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

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

2.1 特点

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

2.2 原型

#include <unistd.h>
int pipe(int fd[2]);    // 返回值:若成功返回0,失败返回-1
  • 当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
  • 要关闭管道只需将这两个文件描述符关闭即可。
    在这里插入图片描述

2.3 例子

  • 单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
  • 若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>int main(){int fd[2];  // 两个文件描述符pid_t pid;char buff[20];if(pipe(fd) < 0)  // 创建管道printf("Create Pipe Error!\n");if((pid = fork()) < 0)  // 创建子进程printf("Fork Error!\n");else if(pid > 0){  // 父进程close(fd[0]);  // 关闭读端write(fd[1],"hello world\n",12);}else{			   //子进程close(fd[1]);  // 关闭写端read(fd[0],buff,20);printf("%s",buff);}return 0;
}

在这里插入图片描述

3. 管道编程实战(429.3)(无名管道)

  • IPC/demo1.c(无名管道,父进程向子进程写入字符串)
#include <stdio.h>
#include <unistd.h>//标准c库 pipe fork
#include <string.h>
#include <stdlib.h>//标准c库 exit
#include <sys/types.h>
#include <sys/wait.h>//waitint main(){int fd[2];pid_t pid;char buf[128];
//	int pipe(int pipefd[2]);if(pipe(fd) == -1){   //创建管道printf("creat pipe failed\n");}pid = fork();         //创建子进程if(pid<0){printf("creat child failed\n");}else if(pid > 0){     //父进程sleep(3);		  //休眠确保子进程先运行,到read时阻塞,3s后父进程往下走printf("this is father\n");close(fd[0]); //关闭读端write(fd[1],"hello from father",strlen("hello form father"));wait(NULL);}else{		      //子进程printf("this is child\n");close(fd[1]); //关闭写端read(fd[0],buf,128);printf("read from father: %s\n",buf);exit(0);}return 0;
}

在这里插入图片描述

4. 创建命名管道(4)

FIFO,也称为命名管道,它是一种文件类型(半双工)

4.1 特点

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

4.2 原型

#include <sys/stat.h>
// 返回值:成功返回0,出错返回-1
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.3 例子

  • FIFO 的通信方式类似于在进程中使用文件来传输数据,只不过 FIFO 类型文件同时具有管道的特性。在数据读出时,FIFO 管道中同时清除数据,并且“先进先出”。下面的例子演示了使用 FIFO 进行 IPC 的过程:
  • IPC/demo2.c(仅创建)
#include <sys/types.h>
#include <sys/stat.h>//mkfifo的int main(){//int mkfifo(const char *pathname, mode_t mode);mkfifo("./file",0600);//创建管道,权限为可读可写return 0;
}
  • IPC/demo3.c(简单调试:文件已存在,错误)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>int main(){//int mkfifo(const char *pathname, mode_t mode);int ret = mkfifo("./file",0600);if(ret == 0){printf("mkfifo suscceess\n");}if(ret == -1){printf("mkfifo failed\n");perror("why");}		return 0;
}

在这里插入图片描述

  • IPC/demo4.c(效果同demo3)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include<errno.h>int main(){
//int mkfifo(const char *pathname, mode_t mode);if( (mkfifo("./file",0600) == -1) && errno==EEXIST){printf("mkfifo failed\n");perror("why");}else{if(errno= =EEXIST){printf("file exists\n");}elseprintf("mkfifo suscceess\n");}		return 0;
}

在这里插入图片描述

  • IPC/demo5.c(创建管道,且只有在错误不是 EEXIST 时才打印错误消息)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include<errno.h>int main(){
//int mkfifo(const char *pathname, mode_t mode);if( (mkfifo("./file",0600) == -1) && errno!=EEXIST){//只有在错误不是 EEXIST 时才打印错误消息perror("mkfifo failed");}return 0;
}

5. 命名管道的数据通信编程实现(430.5)

  • IPC/demo6.c(没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include<errno.h>
#include <fcntl.h>int main(){
//int mkfifo(const char *pathname, mode_t mode);if( (mkfifo("./file",0600) == -1) && errno!=EEXIST){printf("mkfifo failuer\n");perror("why");}int fd = open("./file",O_RDONLY);//若没有指定O_NONBLOCK(默认)printf("open success\n");//只读 open 要阻塞到某个其他进程为写而打开此 FIFO//close(fd);return 0;
}
  • IPC/writedemo6.c(没有指定O_NONBLOCK(默认),只写 open 要阻塞到某个其他进程为读而打开它)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include<errno.h>
#include <fcntl.h>int main(){int fd = open("./file",O_WRONLY);//若没有指定O_NONBLOCK(默认)printf("write open success\n");//只写 open 要阻塞到某个其他进程为读而打开它//close(fd);return 0;
}
  • IPC/read.c(读5次)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h> // O_WRONLY open
#include <string.h>//strlen
#include <unistd.h>//write
//int mkfifo(const char *pathname, mode_t mode);
int main(){char buf[30] = {0};//int nread = 0;int cnt = 0;if( (mkfifo("./file",0600) == -1) && errno!=EEXIST){//创建fifo管道printf("mkfifo failed\n");perror("why");}int fd = open("./file",O_RDONLY);printf("open success\n");while(1){int nread = read(fd,buf,30);//读内容printf("read %d byte from fifo,context:%s\n",nread,buf);cnt++;if(cnt == 5){break;	}}close(fd);return 0;
}
  • IPC/write.c(写5次)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h> // O_WRONLY open
#include <string.h>//strlen
#include <unistd.h>//write
//int mkfifo(const char *pathname, mode_t mode);
int main(){int cnt = 0;char *str = "I'm Message from fifo_write";int fd = open("./file",O_WRONLY);printf("write open success\n");while(1){write(fd, str, strlen(str));//写内容cnt++;sleep(1);if(cnt == 5){break;	}}close(fd);return 0;
}

在这里插入图片描述

6. 消息队列的通信原理(6)

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

特点

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

7. 消息队列相关api(431.7)

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

原型

#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);//通过key(非负数 索引值)找到对应消息队列,打开队列的方式//返回值是消息队列的标识符
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);//flag为0是默认阻塞的方式添加消息
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);//long type消息类型,用于找到对应消息,flag为0是默认阻塞的方式读取消息
// 控制消息队列:成功返回0,失败返回-1
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 看做优先级的权值。(其他的参数解释,请自行Google之)
    在这里插入图片描述

8. 消息队列编程收发数据(432.8)

在这里插入图片描述

  • IPC/msgGet.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//       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[256];    /* message data */
};int main(){struct msgbuf readBuf;	int msgId = msgget(0x1234,IPC_CREAT|0777);//创建/打开消息队列,通过key找到对应消息队列,可读可写可执行if(msgId == -1 ){printf("get que failed\n");//创建/打开失败}msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),888,0);//读取消息	printf("read from que:%s\n",readBuf.mtext);struct msgbuf sendBuf = {988,"thank you for reach"};msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//添加消息return 0;
}
  • IPC/msgSend.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//       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[256];    /* message data */
};int main(){struct msgbuf sendBuf = {888,"this is message from quen"};	int msgId = msgget(0x1234, IPC_CREAT|0777);//创建/打开消息队列,通过key找到对应消息队列,可读可写可执行if(msgId == -1 ){printf("get que failed\n");}msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//添加消息printf("send over\n");struct msgbuf readBuf;msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);//读取消息printf("reaturn from get:%s\n",readBuf.mtext);return 0;
}

9. 键值生成及消息队列移除(9)

在这里插入图片描述

  • IPC/msgGet.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//       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);
//int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf{long mtype;       /* message type, must be > 0 */char mtext[256];    /* message data */
};int main(){struct msgbuf readBuf;	key_t key;key = ftok(".",'m');printf("key=%x\n",key);int msgId = msgget(key,IPC_CREAT|0777);//创建/打开消息队列,通过key找到对应消息队列,可读可写可执行if(msgId == -1 ){printf("get que failuer\n");}//memset(&readBuf,0,sizeof(struct msgbuf));msgrcv(msgId, &readBuf,sizeof(readBuf.mtext),888,0);//读取消息	printf("read from que:%s\n",readBuf.mtext);struct msgbuf sendBuf = {988,"thank you for reach"};msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//添加消息msgctl(msgId,IPC_RMID,NULL);//控制消息队列,将消息队列从内核中删除return 0;
}
  • IPC/msgSend.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
//       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);
//int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf {long mtype;       /* message type, must be > 0 */char mtext[256];    /* message data */
};int main(){struct msgbuf sendBuf = {888,"this is message from quen"};	key_t key;key = ftok(".",'m');printf("key=%x\n",key);int msgId = msgget(key, IPC_CREAT|0777);//创建/打开消息队列,通过key找到对应消息队列,可读可写可执行if(msgId == -1 ){printf("get que failuer\n");}msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//添加消息printf("send over\n");struct msgbuf readBuf;//memset(&readBuf,0,sizeof(struct msgbuf));msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);//读取消息printf("reaturn from get:%s\n",readBuf.mtext);msgctl(msgId,IPC_RMID,NULL);//控制消息队列,将消息队列从内核中删除return 0;
}

这篇关于Linux系统编程_进程间通信第1天:IPC、无名管道pipe和命名管道mkfifo(半双工)、消息队列msgget(全双工)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/240821

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

Linux命令rm如何删除名字以“-”开头的文件

《Linux命令rm如何删除名字以“-”开头的文件》Linux中,命令的解析机制非常灵活,它会根据命令的开头字符来判断是否需要执行命令选项,对于文件操作命令(如rm、ls等),系统默认会将命令开头的某... 目录先搞懂:为啥“-”开头的文件删不掉?两种超简单的删除方法(小白也能学会)方法1:用“--”分隔命