一、什么是管道
Linux下一切皆文件,我们可以创建一个管道文件进行通信,实际上是调用pipe函数在内核中开辟一块缓冲区(称为管道)用于通信,管道是一种最基本的IPC机制,由pipe函数创建 。
它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调⽤用成功返回0,调⽤用失败返回-1。
管道的种类 :
1、普通管道:pipe也叫匿名管道
a.单向通信
b.只有在具有亲缘关系的进程间通信
c.具有同步机制
d.它是一种面向字节流的通信服务
e.生命周期随进程
2、流管道:s_pipe
a.进行双向传输,其他同上
b.只有在具有亲缘关系的进程间通信
c.具有同步机制
d.它是一种面向字节流的通信服务
e.生命周期随进程
3、命名管道:name_pipe
可以使毫不相干的进程进行通信,其他同匿名管道
二、使用管道进行通信
单向通信:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
int _pipe[2];
int ret = pipe(_pipe);
if(ret == -1){
printf("creat pipe error!errno code is:%d\n",errno);
return 1;
}
pid_t id = fork();
if(id < 0)
{
printf("fork error!");
return 2;
}else if(id==0){//child
close(_pipe[0]);//关掉读
int i = 0;
char* _mesg_c = NULL;
while(i<20){
if(i<10){
_mesg_c = " i am child!";
write(_pipe[1], _mesg_c,strlen(_mesg_c)+1);//写入
}
sleep(1);
i++;
}
// close(_pipe[1]);
}else{//father
close(_pipe[1]);//关掉写
char _mesg[100];
int j = 0;
while(j<3)
{
memset(_mesg,'\0',sizeof(_mesg));
int ret = read(_pipe[0],_mesg,sizeof(_mesg));//读出
printf("%s:code is %d\n",_mesg,ret);
j++;
}
close(_pipe[0]);
sleep(10);
if(waitpid(id,NULL,0)< 0)
{
return 3;
}
}
return 0;
}
分析:父子进程进行单向通信,子进程写,父进程读
使用管道需要注意以下四种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志)
1、如果所有指向管道写端的文件描述符都关闭了,(管道写端的引用计数为0),而仍然有进程从管道的读端读取数据,那么管道中剩余的数据都被读取之后,再次read将会返回0,就像读到文件结尾一样。也就是说,写端不会写,读端读完之后就会再等着写端去写,但是写端关闭了啊,不会写了,所以就出现上面说的情况。这就体现出了管道的同步机制。
2、如果有指向管道写端的文件描述符没有关闭,(管道写端的引用计数大于0)而持有管道写端的进程也没有向管道中写数据,这时有进程管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。通俗讲就是,读端读数据,一直读,但是写端不写了,而且写端并没有关闭,所以这时读端就会一直等着写端去写。这就造成了阻塞式等待。
3、如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数为0),这时有进程向管道的写端写数据,那么该进程会收到SIGPIPE,通常会导致进程异常终止。所以进程就会异常退出了。
4、如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0)而持有管道读端的进程也没有从管道中读取数据,这时有进程向管道写端写数据,那么在管道写满时再写将会阻塞,直到管道中有了空位置才写入并返回,也就是管道的同步机制。
双向通信
命名管道:
命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。而且,FIFO总是按照先进先出的原则工作,第一个被写入的数据首先从管道中读出。
创建:
#include<sys/types.h>
#include<sys/stat.h>
int mknod(const char*path,mode_t mod,dev_t dev);
int mkfifo(const char*path,mode_t mode);
参数:
path为创建的命名管道的路径名,mod为创建命名管道的模式,指明其存取权限,dev为设备值,该值文件创建的种类,它只在创建设备文件时才会用到。这两个函数掉用成功返回0,失败都返回-1.
命名管道实现通信的代码:
这里写代码片.PHONY:all
all:server client
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f server client
common.h
#ifndef __COMM_H_
#define __COMM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define ONEPATH "./fifo_one.c"
#define TWOPATH "./fifo_two.c"
#define SIZE 1000
#endif
client.c
#include "comm.h"
int main()
{
int _ret = mkfifo(ONEPATH,S_IFIFO|0666);
if(_ret == -1)
{
printf("mkfifo ONEPATH error\n");
return 1;
}
int fdone = open(ONEPATH,O_WRONLY);
int fdtwo = open(TWOPATH,O_RDONLY);
if((fdone<0)||(fdtwo<0))
{
printf("open file error\n");
return 1;
}
char buf[SIZE];
memset(buf,'\0',sizeof(buf));
while(1)
{
printf("guangyuan->>>");
scanf("%s,buf");
int retone = write(fdone,buf,strlen(buf)+1);
if(retone<0)
{
printf("write error\n");
break;
}
printf("guangyuan<<<-");
int rettwo = read(fdtwo,buf,sizeof(buf));
if(rettwo<0)
{
printf("write error\n");
break;
}
printf("%s\n",buf);
}
close(fdone);
close(fdtwo);
return 0;
}
server.c
#include "comm.h"
int main()
{
int ret = mkfifo(TWOPATH,S_IFIFO|0666);
if(ret == -1)
{
printf("mkfifo TWOPATH error\n");
return 1;
}
int fdone = open(ONEPATH,O_RDONLY);
int fdtwo = open(TWOPATH,O_WRONLY);
if((fdone<0)||(fdtwo<0))
{
printf("open file error\n");
return 1;
}
char buf[SIZE];
memset(buf,'\0',sizeof(buf));
while(1)
{
printf("mengnan<<<-");
int retone = read(fdone,buf,sizeof(buf));
if(retone<0)
{
printf("read error\n");
break;
}
printf("%s\n",buf);
printf("megnnan->>>");
scanf("%s",buf);
int rettwo = write(fdtwo,buf,strlen(buf)+1);
if(rettwo<0)
{
printf("write error\n");
break;
}
}
close(fdone);
close(fdtwo);
return 0;
}
注意 :
命名管道的使用和匿名管道基本相同,只是在使用命名管道之前首先要使用open函数打开,因为命名管道是存在于硬盘上的文件,而管道是存在于内存中的特殊文件。
需要注意,使用open的几点:
1、调用open()打开命名管道可能会被阻塞,但是如果同时用读写方式(O_RDWR)打开,则一定不会造成阻塞。
2、 如果以只读方式(O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写才能打开管道。
/3、 同样,以写方式(O_WRONLY)打开也会阻塞直到有读方式打开管道。