赵老师笔记----管道(无名管道)、有名管道
一、管道(无名管道)
1.简单应用,linux命令的复合
man pipe | more //管道的应用,就是man pipe的输出作为more的输入
2.联想生活中的管道
有进有出,中间要传输物质(水),单向流动
3.进程间通信的管道的特点
①管道是单向的,是一种半双工通信,具有固定的读端和写端
②管道是一种只存在于内存中文件,不是普通文件,不属于文件系统,可用read(),write(),close()等函数,但是不能使用标准文件指针操作函数,也不能定位lseek()。
③如果管道中无数据可读,用read()读取时会阻塞,写时如果write()缓冲区满,写也阻塞。
④管道中数据遵循先进先出(FIFO)原则读写
⑤管道中数据是一次性操作,被读取后即被抛弃,管道就是一个小空间的缓冲区,大小有限
⑥无名管道,没有名字,只能在具有公共祖先的进程间进行通信
⑦管道中数据无格式,通信双方互相约定。
4.管道的作用
父子进程通过管道实现数据的传输
5.管道的创建
#include <unistd.h>
int pipe(int fd[2]);
功能:经由参数fd返回两个文件描述符
参数:
fd为int型数组的首地址,其存放了管道的文件描述符fd[0]、fd[1]。
fd[0]为读而打开,fd[1]为写而打开管道,fd[0]的输出是fd[1]的输入。
返回值:
成功:返回 0
失败:返回-1
6.不同进程间利用管道传输数据的方式
①父进程传数据到子进程
在父进程里关闭fd[0],子进程关闭fd[1]
父进程写数据到fd[1],子进程从fd[0]读取数据
②子进程传数据到父进程
在子进程里关闭fd[0],父进程关闭fd[1]
子进程写数据到fd[1],父进程从fd[0]读取数据
例1
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd[2];
if (pipe(fd)<0)
{
perror("pipe");
return 0;
}
pid_t pid=fork();
if (pid<0)
{
perror("fork");
return 0;
}
if (0==pid)
{//子进程
close(fd[1]);//关闭子进程的管道写端
char buf[50]={0};
while(1)
{
printf("in child process now\n");//提示目前在子进程中
bzero(buf,50);
read(fd[0],buf,49);//从键盘输入信息
printf("child:%s\n",buf);//打印从管道里读入的信息
if (!strncasecmp(buf,"quit",strlen("quit")))
break;//用户输入了,quit
}
close(fd[0]);//关闭子进程的管道读端
exit(0);
}else
{//父进程
close(fd[0]);//关闭父进程的管道的读端
char buf[50]={0};
while(1)
{
bzero(buf,50);
read(0,buf,49);//从键盘输入信息
printf("father:%s\n",buf);//打印从键盘读入的信息
write(fd[1],buf,49);//将读入的信息写入到无名管道
if (!strncasecmp(buf,"quit",strlen("quit")))
break;//用户输入了,quit
}
sleep(1);//确保子进程可以读到quit,并退出。
close(fd[1]);//关闭父进程的管道的写端
}
return 0;
}
思考:如何退出死循环??
任务1(自己完成):从子进程里读入小写字母写入管道,父进程读取管道中数据,转成大写字母输出
任务2:双管道双向通信,父进程从键盘读入小写字母,写入管道1,子进程读取管道1中数据,转成大写字母,再写入管道2中,父进程中读取管道2中大写字母打印输出
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd1[2];
int fd2[2];
if (pipe(fd1)<0)//这个管道用于父进程向子进程写数据通信
{
perror("pipe");
return 0;
}
if (pipe(fd2)<0)//这个管道用于子进程向父进程写数据通信
{
perror("pipe");
return 0;
}
pid_t pid=fork();
if (pid<0)
{
perror("fork");
return 0;
}
if (0==pid)
{//子进程
close(fd1[1]);//关闭发fd1写端
close(fd2[0]);//关闭发fd2读端
char ch;
while(1)
{
if (read(fd1[0],&ch,1)>0)//从fd1读入小写字母
{
if (ch>'a'-1)
{
ch=ch-32;//将小写字母
}
write(fd2[1],&ch,1);//写入管道fd2
}
}
close(fd1[0]);//关闭fd1读端
close(fd2[1]);//关闭fd2写端
exit(0);
}else
{//父进程
close(fd1[0]);//关闭发fd1读端
close(fd2[1]);//关闭发fd2写端
char ch;
while(1)
{
read(0,&ch,1);//从键盘读入小写字母
write(fd1[1],&ch,1);//将小写字母写入到管道fd1
if (read(fd2[0],&ch,1)>0)//从管道fd2读取数据,并打印
printf("%c\n",ch);
}
sleep(1);
close(fd1[1]);//关闭fd1写端
close(fd2[0]);//关闭fd2读端
}
return 0;
}
7.从管道中读数据的特点
①默认用read函数从管道中读数据是阻塞的。
②调用write函数向管道里写数据,当缓冲区已满时write也会阻塞。
③通信过程中,读端口全部关闭后,写进程向管道内写数据时,写进程会(收到SIGPIPE信号)退出。
8.fcntl函数
编程时可通过fcntl函数设置文件的阻塞特性。
设置为阻塞:
fcntl(fd, F_SETFL, 0);
设置为非阻塞:
fcntl(fd, F_SETFL, O_NONBLOCK);
9.文件描述符的复制
dup和dup2是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件。
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup和dup2经常用来重定向进程的stdin、stdout和stderr。
#include <unistd.h>
int dup(int oldfd);
功能:
复制oldfd文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符。
参数:要复制的文件描述符oldfd。
返回值:
成功:新文件描述符。
失败:返回-1,错误代码存于errno中
#include <fcntl.h>
int main(void)
{
int fd1;
int fd2;
fd1 = open("test", O_CREAT|O_WRONLY, S_IRWXU);
if (fd1 < 0)
{
perror("open");
exit(-1);
}
close(1);
fd2 = dup(fd1);
printf("fd2=%d\n", fd2);
return 0;
}
dup2函数
#include <unistd.h>
int dup2(int oldfd, int newfd)
功能:
复制一份打开的文件描述符oldfd,并分配新的文件描述符newfd,newfd也标识oldfd所标识的文件。
注意:
newfd是小于文件描述符最大允许值的非负整数,如果newfd是一个已经打开的文件描述符,则首先关闭该文件,然后再复制。
参数:
要复制的文件描述符oldfd
分配的新的文件描述符newfd
返回值:
成功:返回newfd
失败:返回-1,错误代码存于errno中
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd1;
int fd2 = 3;
int err = 0;
err = dup2(1,fd2);//save 1
if(err<0)
{
perror("dup2");
}
printf("fd2=%d,err=%d\n", fd2, err);
fd1 = open("test", O_CREAT|O_RDWR, S_IRWXU);
dup2(fd1,1);//1---> test
printf("hello world\n");
dup2(fd2,1);//reset 1--->stdout
printf("I love you \n");
return 0;
}
10.exec前后文件描述符的特点
close_on_exec标志决定了文件描述符在执行exec后文件描述符是否可用。
文件描述符的close_on_exec标志默认是关闭的, 即文件描述符在执行exec后文件描述符是可用的。
若没有设置close_on_exec标志位,进程中打开的文件描述符,及其相关的设置在exec后不变,可供新启动的程序使用。
exec前后文件描述符的特点
设置close_on_exec标志位的方法:
int flags;
flags = fcntl(fd, F_GETFD);//获得标志
flags |= FD_CLOEXEC; //打开标志位
flags &= ~FD_CLOEXEC; //关闭标志位
fcntl(fd, F_SETFD, flags);//设置标志
任务1(自己完成):从子进程里读入小写字母写入管道,父进程读取管道中数据,转成大写字母输出
上午练习:
题目:借用外部命令,实现计算器功能
提示:
expr是个外部命令,它向标准输出打印运算结果。
创建一个管道以便让expr 4 + 5的输出到管道中
子进程exec执行expr 4 + 5命令之前重定向“标准输出”到“管道写端”。
父进程从管道读端读取数据,并显示运算结果
二、有名管道
1.命名管道(FIFO)和管道(pipe)之间的异同
命名管道(FIFO)和管道(pipe)基本相同,相同点是:
①半双工,数据在同一时刻只能在一个方向上流动。
②写入FIFO中的数据遵循先入先出的规则。
③FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
④管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
⑤从FIFO读数据是一次性操作,数据一旦被读,它就从FIFO中被抛弃,释放空间以便写更多的数据。
不同点:
①FIFO在文件系统中作为一个特殊的文件而存在,但FIFO中的内容却存放在内存中。
②当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
③FIFO有名字,不相关的进程可以通过打开命名管道进行通信。无名管道一定是存在继承关系的进程间才可以通信。
2.如何创建有名管道
①通过函数mkfifo()去创建
FIFO文件的创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo( const char *pathname, mode_t mode);
参数:
pathname:FIFO的路径名+文件名。
mode:mode_t类型的权限描述符。
O_RDONLY
O_WRONLY
O_RDWR
O_CREAT
还要或上读写执行权限,例如O_RDWR|O_CREAT|0666
返回值:
成功:返回 0
失败:如果文件已经存在,则会出错且返回-1。
②通过linux命令mkfifo创建
用法:mkfifo [选项]... 名称...
以指定的名称创建先进先出文件(FIFO)。
长选项必须使用的参数对于短选项时也是必需使用的。
-m, --mode=模式 设置权限模式(类似chmod),而不是rwxrwxrwx 减umask
-Z, --context=CTX 将每个创建的目录的SELinux 安全环境设置为CTX
--help 显示此帮助信息并退出
--version 显示版本信息并退出
举例:mkfifo -m 0666 ./myfifo.pipe
3.对有名管道的操作
open()
read()
write()
close()
但是不能lseek()
举例:
fifo_read.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd=open("myfifo.pipe",O_RDONLY);
if (fd<0)
{
perror("open");
return 0;
}
char buf[50];
while(1)
{
bzero(buf,50);
read(fd,buf,50-1);
printf("read:%s\n",buf);
}
close(fd);
return 0;
}
fifo_write.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd=open("myfifo.pipe",O_WRONLY);
if (fd<0)
{
perror("open");
return 0;
}
char buf[50];
while(1)
{
bzero(buf,50);
read(0,buf,50-1);//从标准输入读入数据
write(fd,buf,50-1);
}
close(fd);
return 0;
}
4.如何检查文件是否存在?
#include <unistd.h>
int access( const char *pathname, int mode);
pathname:文件的路径名+文件名。
mode:权限描述符。
R_OK 写权限
W_OK 读权限
X_OK 执行权限
F_OK 文件是否存在
返回值:mode满足则返回值为0,否则为-1
举例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
if (access("myfifo.pipe",F_OK)!=0)
{
int ret =mkfifo("myfifo.pipe",O_RDWR|O_CREAT|0666);
if (ret!=0)
{
perror("mkfifo");
return 0;
}
}
int fd=open("myfifo.pipe",O_RDONLY);
if (fd<0)
{
perror("open");
return 0;
}
char buf[50];
while(1)
{
bzero(buf,50);
read(fd,buf,50-1);
printf("read:%s\n",buf);
}
close(fd);
return 0;
}
练习:将上午的例1改写成两个独立的程序,用有名管道通信,当输入quit时退出。
5.非阻塞标志(O_NONBLOCK)产生影响
①特点一:不指定O_NONBLOCK(即open没有位或O_NONBLOCK)
1、open以只读方式打开FIFO时,要阻塞到某个进程为写而打开此FIFO
2、open以只写方式打开FIFO时,要阻塞到某个进程为读而打开此FIFO。
3、open以只读、只写方式打开FIFO时会阻塞,调用read函数从FIFO里读数据时read也会阻塞。当没有读到东西时会停在read函数这里,直到读到数据才会继续执行。
4、通信过程中若写进程先退出了,则调用read函数从FIFO里读数据时不阻塞;若写进程又重新运行,则调用read函数从FIFO里读数据时又恢复阻塞。
5、通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到SIGPIPE信号)退出。????
6、调用write函数向FIFO里写数据,当缓冲区已满时write也会阻塞。
②指定O_NONBLOCK(即open位或O_NONBLOCK)
1、先以只读方式打开:如果没有进程已经为写而打开一个FIFO, 只读open成功,并且open不阻塞。
2、先以只写方式打开:如果没有进程已经为读而打开一个FIFO,只写open将出错返回-1。
3、read、write读写命名管道中读数据时不阻塞。
4、通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到SIGPIPE信号)退出。?????????
③open函数以可读可写方式打开FIFO文件时的特点:
1、open不阻塞。
2、调用read函数从FIFO里读数据时read会阻塞。
3、调用write函数向FIFO里写数据,当缓冲区已满时write也会阻塞。
晚上作业:利用管道,实现一个简单单机QQ聊天模拟程序(FIFO有名管道实现)