#include <unistd.h>
int pipe(int fd[2]);
功能:创建一无名管道原型
参数
fd:文件描述符数组
其中fd[0]表示读端, fd[1]表示写端(我们可以将0看作一张嘴代表读,1看作一支笔代表写)
返回值:成功返回0,失败返回错误代码
上图可以更好的帮助理解pipe函数的功能,当调用pipe函数时,向系统传递一个fd文件描述符数组,其中fd[1]对应写端,将数据塞入管道,fd[0]代表读端,从管道中读取数据
#include<stdio.h>
#include<unistd.h>
int main()
{
int fd[2] = {0};
int ret = pipe(fd);
printf("ret:%d\n",ret);
printf("fd[0]:%d\n",fd[0]);
printf("fd[1]:%d\n",fd[1]);
return 0;
}
首先我们先了解管道的两个特性:
1. 只能用于具有共同祖先的进程(**具有亲缘关系的进程**)之间进行通信;通常,一个管道由一个进程创
建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
2. **管道是半双工的,也就是说管道只能进行单向通信,即只能一端写一端读**,但我们调用fork函数时,如果父进程写入i am father,那么父进程就需要关闭读端,而子进程读取父进程写入的信息时,子进程就需要关闭写端,如下图所示
代码实例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
int pipefd[2] = {0};
int ret = pipe(pipefd);
if(ret==-1)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
//父进程读取
if(id > 0)
{
//父进程关闭写文件描述符
close(pipefd[1]);
char buf[64];
while(1)
{
ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("father get a msg:%s\n",buf);
sleep(1);
}
}
}
//子进程写入i am child
if(id == 0)
{
//子进程关闭读文件描述符
close(pipefd[0]);
const char *msg = "i am child";
while(1)
{
write(pipefd[1],msg,strlen(msg));
sleep(1);
}
}
return 0;
}
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLlzXFd6-1617778502369)(https://gitee.com/Kyrie-leon/blog-image/raw/master/Linux//20210406092524.png]
上述程序子进程每次向管道写入信息,父进程从管道读取并打印
父进程调用pipe()创建管道,假设系统分配文件描述符3给fd[0]用于读,文件描述符4给fd[1]用于写
父进程调用fork()函数创建子进程,子进程具有和父进程同样的数据,文件描述符的指向也是相同的
但是管道具有半双工特征,即只能一端读一端写,因此父子进程需要关闭各自不需要的文件描述符,假设父进程写子进程读,那么父进程关闭fd[0]读端,子进程关闭fd[1]写端
当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
int pipefd[2] = {0};
int ret = pipe(pipefd);
if(ret==-1)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
//父进程读取
if(id > 0)
{
//父进程关闭写文件描述符
close(pipefd[1]);
char buf[64];
while(1)
{
ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("father get a msg:%s\n",buf);
}
}
}
//子进程写入i am child
if(id == 0)
{
//子进程关闭读文件描述符
close(pipefd[0]);
const char *msg = "i am child";
int count = 0;
while(1)
{
write(pipefd[1],msg,strlen(msg));
printf("write a msg:%d\n",count++);
sleep(5);
}
}
return 0;
}
当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
int pipefd[2] = {0};
int ret = pipe(pipefd);
if(ret==-1)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
//父进程读取
if(id > 0)
{
//父进程关闭写文件描述符
close(pipefd[1]);
char buf[64];
while(1)
{
ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("father get a msg:%s\n",buf);
sleep(5);
}
}
}
//子进程写入i am child
if(id == 0)
{
//子进程关闭读文件描述符
close(pipefd[0]);
const char *msg = "i am child";
int count = 0;
while(1)
{
write(pipefd[1],msg,strlen(msg));
printf("write a msg:%d\n",count++);
}
}
return 0;
}
如果所有管道写端对应的文件描述符被关闭,则read返回0
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
int pipefd[2] = {0};
int ret = pipe(pipefd);
if(ret==-1)
{
perror("pipe");
return 1;
}
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
//父进程读取
else if(id > 0)
{
//父进程关闭写文件描述符
close(pipefd[1]);
char buf[64];
while(1)
{
ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("father get a msg:%s\n",buf);
sleep(1);
}
printf("father get a msg:%d\n",s);
sleep(1);
}
}
//子进程写入i am child
else
{
//子进程关闭读文件描述符
close(pipefd[0]);
const char *msg = "i am child\n";
int count = 0;
while(1)
{
write(pipefd[1],msg,strlen(msg));
printf("write a msg:%d\n",count++);
//读10次后关闭写端
if(count == 10)
{
close(pipefd[1]);
break;
}
}
exit(2);
}
return 0;
}
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程 退出
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
管道道的四个特性:
管道特点:
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创
建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
管道提供流式服务
一般而言,进程退出,管道释放,所以管道的生命周期随进程,管道文件只是标识,删除后依然可以通信
一般而言,内核会对管道操作进行同步(没有数据读阻塞,缓冲区写满写阻塞)与互斥
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
mkfifo filename
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename, mode_t mode);
匿名管道由pipe函数创建并打开。
命名管道由mk?fo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义。
构建Makefile文件
.PHONY:all
all:server client
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm client server fifo
服务器代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_FILE "./fifo"
int main()
{
umask(0);
if(-1 == mkfifo(FIFO_FILE,0666))
{
perror("mkfifo");
return 1;
}
int fd = open(FIFO_FILE, O_RDONLY);
if(fd < 0)
{
perror("open");
return 1;
}
else
{
while(1)
{
char buf[1024];
ssize_t s = read(fd,buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("#############################\n");
printf("client#:%s\n",buf);
}
else
{
close(fd);
printf("server offline!\n");
break;
}
}
}
return 0;
}
客户端代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_FILE "./fifo"
int main()
{
int fd = open(FIFO_FILE,O_WRONLY);
if(fd < 0)
{
perror("open");
return 1;
}
else
{
while(1)
{
printf("Please Input Your Message:");
fflush(stdout);
char msg[1024];
//从键盘读取信息
ssize_t s = read(0,msg,sizeof(msg)-1);
if(s > 0)
{
msg[s] = 0;
write(fd,msg,strlen(msg));
}
}
}
return 0;
}
通过命名管道可以发现管道的本质就是一块缓存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到 内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
进程间能够实现通信必然需要看到同一份资源,而共享内存就是通过让进程A和B能够同时看到同一块物理内存而实现的进程间通信。而共享内存是将同一块物理内存映射到各个进程虚拟地址空间,可以直接通过虚拟地址访问,相较于其它方式少了两步内核态与用户态之间的数据拷贝因此速度最快,对于一份数据想要通过A传递给B,只要拷贝到进程A的地址空间,共享内存再将这份资源拷贝过来,然后再拷贝给进程B,这样减少了诸多的步骤就可以做到高速高效了。
说明
.PHONY:all
all:client server
clinet:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm client server
#pragma once
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
#define SIZE 4096
#include"comm.h"
int main()
{
//获取一个唯一标识内存的key值
key_t key = ftok(PATHNAME, PROJ_ID);
if(key < 0)
{
perror("ftok");
return 1;
}
//创建共享内存
int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
//关联共享内存
char * addr = shmat(shmid, NULL, 0);
sleep(2);
int count = 0;
while(count++ < 26)
{
printf("client#%s\n",addr);
sleep(1);
}
//取消共享内存
shmdt(addr);
sleep(5);
//删除
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#include"comm.h"
int main()
{
//获取一个唯一标识内存的key值
key_t key = ftok(PATHNAME, PROJ_ID);
if(key < 0)
{
perror("ftok");
return 1;
}
//创建共享内存
int shmid = shmget(key,SIZE,0);
//关联共享内存
char * addr = shmat(shmid, NULL, 0);
int i = 0;
while(i < 26)
{
addr[i] = 'A' + i;
i++;
addr[i] = 0;
sleep(1);
}
//取消共享内存
shmdt(addr);
sleep(5);
return 0;
}
在 2021 年,人们喜欢 Linux 的理由比以往任何时候都多。在这个系列中,我将分享...
文章目录 关系数据库 关系数据库简介 关系数据结构及形式化定义 关系 关系模式 ...
php实现微信支付 微信支付文档地址: https://pay.weixin.qq.com/wiki/doc/api/i...
本文转载自微信公众号「程序员历小冰」,转载本文请联系程序员历小冰公众号。 疫...
六、XML展望 任何一项新技术的产生都是有其需求背景的,XML的诞生是在HTML遇到不...
struts json 类型异常返回到js弹框问题解决办法 当struts 框架配置了异常时 例如...
前言 我们在使用ajax异步的提交多选框得到需要操作的对象的id,这时我们可以把每...
下面是ajax代码和Controller层代码,期初以为是后台程序写错了。 $("#sourcefile...
微软官方博客于 2 月初再次发布提示,将会在 3 月 9 日停止对经典版 Edge 浏览器...
背景 该问题来自某客户,据描述,他们在部署 MySQL 主从复制时,有时候仅在主库...