匿名管道:
什么是管道?
管道是unix中最古老的进程间通讯方式。
具有亲缘关系的两个进程间连接的数据流称为一个管道。
注:yum进程与grep进程同为shell进程创建,所以为兄弟进程。
#include <unistd.h>
int pipe(int pipefd[2]);
说明:
此函数用于创建一个匿名管道。
参数:
pipefd[2]:输入输出型参数,pipe()调用成功后,pipefd[0]用于读管道、pipefd[1]用于写管道。
返回值:
成功:返回0。
失败:返回-1,并设置errno。
利用fork()共享管道原理
在父进程中打开的文件描述符会被fork()创建的子进程所继承,所以我们可以利用这个特性,在父子进程中关闭各自多余不用的描述符,达到进程间单向通讯
父进程读,子进程写
父进程写,子进程读
代码:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main(){
int fds[2];
if(pipe(fds)){
perror("pipe");
return -1;
}
pid_t id = fork();
if(id < 0){
perror("fork");
return -2;
}else if(id == 0){
//子进程
close(fds[0]);
char buf[100] = {0};
printf("child write:$");
fgets(buf,100,stdin);
int len = strlen(buf);
write(fds[1],buf,len);
return -3;
}else{
//父进程
wait(NULL);
close(fds[1]);
char buf[100] = {0};
read(fds[0],buf,100);
printf("father read:$%s",buf);
}
return 0;
}
结果:
管道的读写规则:
1、当管道中没有数据可读时:
阻塞方式:read()调用阻塞,直到有数据到来。
非阻塞方式:read()返回-1,errno值为EAGAIN。
2、当管道中数据满时:
阻塞方式:write()调用阻塞,直到有进程读走数据。
非阻塞方式:write()返回-1,errno值为EAGAIN。
3、如果管道所有写端被关闭,read()返回0。
4、如果管道所有读端被关闭,调用write()产生SIGPIPE信号,可能导致进程退出。
5、当每次写入的数据量不超过PIPE_BUF时,Linux保证写入的原子性,否则,不保证。
注:非阻塞方式可使用pipe2()函数创建并打开管道。
#include <fcntl.h>
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
//flags传为O_NONBLOCK为非阻塞方式创建并打开管道。
管道总结:
1、管道只能用于具有亲缘关系的进程间通讯,通常,是由父进程创建管道后,再调用fork()函数创建子进程,以达到共享管道从而通讯的目的。
2、管道提供流式服务。
3、管道的生命周期随进程。
4、内核会对管道的操作进行同步与互斥。
5、管道是半双工通讯,需要双向通讯时,需要建立两个管道。
命名管道:
命名管道是一种FIFO文件,此类文件是一种特殊类型的文件,可以用于没有亲缘关系的进程间通讯工作,因为匿名管道只能用于有亲缘关系的进程间通讯,所以便有了此类文件的产生,常称为命名管道。
命名管道的创建:
命名管道的创建有两种方式:一、在命令行通过mkfifo
命令创建,二、在程序中通过mkfifo()
函数创建。
通过命令创建:
mkfifo fifoname
通过函数创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname:
命名管道的创建路径和名字。
mode:
命名管道的权限。
返回值:成功返回0,失败返回-1,并设置errno。
命名管道与匿名管道的区别:
命名管道与匿名管道的唯一区别就是创建和打开方式不同,其余均相同。
命名管道:
创建用mkfifo()
函数,打开用open()
函数。
匿名管道:
用pipe()
函数创建并打开。
命名管道的打开规则:
读的方式打开:
阻塞方式:阻塞,直到有进程以写的方式打开。
非阻塞方式:直接返回成功。
写的方式打开:
阻塞方式:阻塞,直到有进程以读的方式打开。
非阻塞方式:直接返回失败,errno为ENXIO。
用命名管道实现服务器与客户端通讯(本地通讯):
代码:
client.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#define ERR_EXIT(m) \
do{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(){
//以写的方式打开命名管道
int fifofd = open("tmp",O_WRONLY);
if(fifofd == -1){
ERR_EXIT("client:open");
}
char buf[1024];
ssize_t len = 0;
while(1){
printf("please enter$");
fflush(stdout);
//读标准输出
len = read(0,buf,1024);
if(len <= 0){
ERR_EXIT("client read");
}else{
//写入文件命名管道
len = write(fifofd,buf,len);
if(len <= 0){
ERR_EXIT("client write");
}
}
}
return 0;
server.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
//此宏作用在于,可以执行多条语句,还可防止;问题
#define ERR_EXIT(m) \
do{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(){
//创建命名管道
if(mkfifo("tmp",0644)){
ERR_EXIT("mkfifo");
}
printf("please wait\n");
//以读的方式打开命名管道
int fifofd = open("tmp",O_RDONLY);
if(fifofd == -1){
ERR_EXIT("server:open");
}
char buf[1024];
ssize_t len = 0;
while(1){
//读管道
len = read(fifofd,buf,1024-1);
if(len == -1){
ERR_EXIT("server:read");
}else if(len == 0){
printf("client close! Disconnect\n");
break;
}else{
buf[len] = '\0';
printf("client:$%s",buf);
}
}
//关闭文件描述符
close(fifofd);
//删除命名管道
unlink("tmp");
return 0;
}
结果:
注:启用时,必须先启用server
,因为在server
中创建命名管道。一个server
可被多个client
连接,server
可收到每一个client
发送的数据,关闭时,关闭所有client
后,server
端自动关闭。也可一个client
连接多个server
,连接后,client
发送的数据随机的被多个server
中的一个接收,关闭时,一旦关闭client
,所有server
均都关闭。