一、管道:
mkfifo 1.pipe //创建管道文件。通过ll可以看到管道文件的文件类型是"p".ll还可以看到管道文件的文件大小为0.因为管道文件不是用
来存储数据的。
管道的特点:
1.管道式半双工的,一端读,另一端只能写。
2.双方使用同一个管道(即一方读,另一方写),一方打开管道之后,需要等待另一方打开,才能继续执行。
3.读端读取管道时,如果管道内没有数据,read就会阻塞,一直等着。
4.如果管道写端关闭,读端read时,会返回0.
如果读端关闭,写端还继续向管道写数据,则写端程序会直接崩溃。
通过管道实现两个进程的通信:
步骤一:首先建立两个管道文件:
mkfifo 1.pipe;
mkfifo 2.pipe;
步骤二:为了同时编译两个进程,makefile可以这样写:
a:a.c b.c
gcc a.c -o a
gcc b.c -o b
.PHONY:clean
clean:
rm -f a b
步骤三:编写a.c 与b.c,如下:
a.c
#include "func.h"
int main(int argc,char* argv[]){
if(argc !=3){
printf("error args\n");
return -1;
}
int
fdw=open(
argv[1],O_WRONLY);
if(-1==fdw){
perror("open");
return -1;
}
int
fdr=open(
argv[2],O_RDONLY);
if(-1==fdr){
perror("open1");
return -1;
}
// printf("fdr=%d,fdw=%d\n",fdr,fdw);
char buf[128];
while(1){
bzero(buf,sizeof(buf));
read(0,buf,sizeof(buf));
//
读取标准输入
write(fdw,buf,strlen(buf)-1);
//写
入管道,回车不要
bzero(buf,sizeof(buf));
read(fdr,buf,sizeof(buf));
//读取管道
printf("%s\n",buf);
//把读到的内容写到屏幕
}
return 0;
}
|
b.c
#include "func.h"
int main(int argc,char* argv[]){
if(argc !=3){
printf("error args\n");
return -1;
}
int
fdr=open(
argv[1],O_RDONLY);
if(-1==fdr){
perror("open");
return -1;
}
int
fdw=open(
argv[2],O_WRONLY);
if(-1==fdw){
perror("open1");
return -1;
}
// printf("fdr=%d,fdw=%d\n",fdr,fdw);
char buf[128];
while(1){
bzero(buf,sizeof(buf));
read(fdr,buf,sizeof(buf));
//读取管道
printf("%s\n",buf);
bzero(buf,sizeof(buf));
read(0,buf,sizeof(buf));
//读取标准输入
write(fdw,buf,strlen(buf)-1);
//写入管道
}
return 0;
}
|
注意1:要防止死锁的情况。比如1号先打开管道1,再打开管道2,那么2号也要先打开管道1,再打开管道2。如果反了,就会死锁。
注意2:因为从标准输入缓冲区输入的时候会按回车,这个回车键不需要传送。所以write的长度是strlen(buf)-1
以上的代码只能够实现,自己说一句话之后等待对方再说一句话,然后才能自己再说一句。为了实现自己可以一次说好几句话,可以使用下面的select函数进行改进。因为现在程序卡在两个地方(标准输入和管道),可以使用select来监控,帮我们判断哪一个描述符可用。
二、select函数(后面学习了epoll之后就不再使用select):
select多路复用。虽然现在不用select了,因为又出现了升级版。但是还是要学,为了理解升级版的时候更好理解。
用select是为了监控多个读描述符,然后告诉我们。
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset,fd_set *writeset, fd_set *exceptionset, const struct timeval * timeout);
参数:
maxfd:最大的描述符+1.
timeout:NULL表示永远等下去。
fd_set readset;
FD_ZERO(&readset); //将集合清零。
FD_SET(0,&readset); //增加一个fd
FD_SET(fdr,&readset);
FD_CLR(fdr,&readset); //删除一个id
int ret=select(fdr+1,&readset,NULL,NULL,NULL); //执行时select会将readset中的数据读走,清空readset,等到fdr或者0可读了,
会把fdr或0填入readset。
对步骤三的改进(通过select函数对以上进程通信的改进):
a.c
#include "func.h"
int main(int argc,char* argv[]){
if(argc !=3){
printf("error args\n");
return -1;
}
int
fdw=open(
argv[1],O_WRONLY);
if(-1==fdw){
perror("open");
return -1;
}
int
fdr=open
(argv[2],O_RDONLY);
if(-1==fdr){
perror("open1");
return -1;
}
printf("fdr=%d,fdw=%d\n",fdr,fdw);
char buf[128];
fd_set rdset;
int ret;
int ret1;s
while(1){
FD_ZERO(&rdset);
//将集合中rsf所有fd清零
FD_SET(0,&rdset);
//增加一个fd
FD_SET(fdr,&rdset);
ret=
select(fdr+1,&rdset,NULL,NULL,NULL);
if(ret >0){
/
/说明有可读的
if(
FD_ISSET(0,&rdset)){
//判断该fd是否有设置
bzero(buf,sizeof(buf));
read(0,buf,sizeof(buf));//读取标准输入
write(fdw,buf,strlen(buf)-1);//写入管道
}
if(
FD_ISSET(fdr,&rdset)){
bzero(buf,sizeof(buf));
ret1=read(fdr,buf,sizeof(buf));//读取管道
if(ret1 == 0){
//防止写端终止,一直刷0
printf("b is over\n");
break;
}
printf("%s\n",buf);
}
}
}
return 0;
}
|
b.c
#include "func.h"
int main(int argc,char* argv[]){
if(argc !=3){
printf("error args\n");
return -1;
}
int
fdr=open(
argv[1],O_RDONLY);
if(-1==fdr){
perror("open");
return -1;
}
int
fdw=open(
argv[2],O_WRONLY);
if(-1==fdw){
perror("open1");
return -1;
}
printf("fdr=%d,fdw=%d\n",fdr,fdw);
char buf[128];
fd_set rdset;
int ret;
int ret1;
while(1){
FD_ZERO(&rdset);
FD_SET(0,&rdset);
FD_SET(fdr,&rdset);
ret=
select(fdr+1,&rdset,NULL,NULL,NULL);
if(ret >0){
if(
FD_ISSET(0,&rdset)){
bzero(buf,sizeof(buf));
read(0,buf,sizeof(buf));//读取标准输入
write(fdw,buf,strlen(buf)-1);//写入管道
}
if(
FD_ISSET(fdr,&rdset)){
bzero(buf,sizeof(buf));
ret1=read(fdr,buf,sizeof(buf));//读取管道
if(0==ret1){
printf("a is over\n");
break;
}
printf("%s\n",buf);
}
}
}
return 0;
}
|
注:如果写端终止了,读端会一直返回0.为了防止一直在刷0,需要有个判断。
while(1){
FD_ZERO(&readset);
FD_SET(0,&readset);
FD_SET(fdr,&readset); //每次循环的时候都要重新设置,因为上一次返回的不知道是什么了。
bzero(&t,sizeof(t));
t.tv_sec=3; //监控三秒钟,会打印出"--------".一定要在这里重新定义3秒钟。因为执行完了之后,时间会清零
ret=select(fdr+1,&readset,NULL,NULL,&t); //最后的&t一般写NULL,很少会用到写时间的情况。
if(ret>0){
if(FD_ISSET(0,&readset)){
}
if(FD_ISSET(fdr,&readset)){
bzero(buf,sizeof(buf));
ret1=read(fdr,buf,sizeof(buf));
if(0==ret1){
printf("b is over");
break;
}
printf("%s\n",buf);
}
}
printf("-----------------------------");
}