管道和FIFO

管道是最初的Unix IPC形式,FIFO有时也称为有名管道


客户-服务器模型:客户提供文件路径名,并把它写入到IPC管道,服务器从该管道读出路径名,并尝试打开文件,若可以打开,他就能读出其中的内容,并写入另一个IPC管道作为对客户端的响应,若找不到文件,就响应一个错误消息,客户端从该管道读出响应,并输出到标准输出stdout


所有的Unix都提供管道,由pipe函数创建,提供一个单向数据流

 
 
  1. int pipe(int fd[2]); //返回两个文件描述符,一个用来读,一个写

管道在父子进程间的通信:

1)父进程创建一个管道并fork出一个子进程

2)父进程关闭管道读出端,子进程关闭该管道的写入端,也就是父进程往管道写东西,子进程从管道读取父进程的写入


当我们执行  who | sort | ip 时,实际上就创建了2个管道,3个进程

客户端-服务器端的过程实际上是通过内核运作的 


eg:用管道来实现客户--服务端的例子

1)main函数创建2个管道并fork出一个子进程作为服务端,第一个管道用于父进程(客户端)向子进程发送文件路径名,第二个管道用子进程(服务端)想父进程发送文件内容

2)为子进程waitpid,子进程在往管道写入最终数据后调用exit首先终止,变成僵尸进程(zombie,自身先终止,父进程仍在运行,且尚未等待该子进程的进程),子进程终止时,给父进程产生一个sigchld的信号(被默认忽略)。此后,父进程的client函数在从管道读入最终数据后返回,父进程随后调用waitpid取得已终止子进程的终止状态,要是父进程没有调用waitpid,而是直接终止,那么子进程将成为托孤给init进程的孤儿进程,内核为此向init进程发送另一个sigchld信号,init进程将取得该僵尸进程的终止状态

3)从标准输入读进文件路径,删除其中有fgets写入的换行符

4)从管道复制到标准输出

5)从管道读出路径名

6)打开文件,或者处理错误

7)把文件内容复制到管道


全双工通道:整个管道只有一个缓冲区,写入管道的任何数据都是添加到缓冲区的尾部,从管道读出的数据都是取自缓冲区的开头


全双工通道是由2个半双工通道组成的


popen和pclose是标准函数库IO提供的,它创建一个管道并启动另外一个进程,该进程要么从管道读出,要么从管道写入

 
 
  1. #include <stdio.h>
  2. FILE *popen(const char *command,const char *type); //成功返回文件指针,失败返回null
  3. int pclose(FILE *stream); //成功返回shell的终止状态,失败返回-1



####  FIFO

管道最大的劣势就是没用名称,所以只能用于有一个父进程的各个进程之间,无法在无亲缘关系的两个进程之间建立管道并用作IPC通信

Unix中的FIFO类似于管道,是一个单向数据流,每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程间通信,FIFO有时也称为有名管道,由mkfifo函数创建

 
 
  1. int mkfifo(const chat *pathname,mode_t mode);//成功返回0,失败-1
  2. #pathname是路径名,也是fifo的名字,mode是文件权限位,类似open的第二个参数
  3. #FIFO是半双工的,不能同时读写


eg:用FIFO改写上文中的客户-服务端程序

 
 
  1. 1)创建2个FIFO
  2. 2)fork,调用fork后子进程调用我们的server函数,父进程调用我们的client函数,执行调用前,父进程打开第一个FIFO来写,打开第二个FIFO来读,子进程打开第一个FIFO来读,第二个FIFO来写(注:FIFO的名字只有调用unlink才会从文件系统删除,儿之前创建的管道在所有进程关闭后自动消失)
  3. 3)父进程调用client,子进程调用server

  4. 4)关闭文件描述

  5. 5)删除FIFO(用的是客户端)

  6. 内核为管道和FIFO维护了一个访问计数器,但对于system V这样的计数器并不存在


#### 一点补充:

1)请求读出的数据量多余当前管道或者FIFO中的数据量时,只返回这些可以用的数据量

2)若请求写入额数据量小于等于PIPE_BUF,写入操作是原子的,否则不能保证是原子操作,

若两个进程同时写入,会先处理完一个进程的请求,再处理另一个进程的请求

3)O_NONBLOCK标识的设置不会影响到write操作的原子性,原子性完全是由所请求的字节数是不是小于等于PIPE_BUF来决定的

4)如果想一个没有为读打开着的管道或者FIFO写入,那么内核将产生一个SIGNPIPE信号

5)如果调用进程既没有捕获也没有忽略改SIGPIPE信号,采取的默认行动就是终结改进程

6)若调用进程忽略改信号,或者捕获并从信号处理程序中返回,那mewrite返回一个EPIPE错误


### 单个服务器,多个客户

1  服务端

1)创建众所周知FIFO,打开两次一次只读,一次只写

2)读出客户请求

3)分析客户请求

4)打开客户请求的文件,将它发送到客户的FIFO


2 客户端

1)创建FIFO,以自身进程ID作为最后一部分的路径名创建

2)构建客户请求行,客户的请求由其进程ID,一个空格,一个路径名和一个换行符构成,这个请求行在字符数组buff中构建,路径名从标准输入读入

3)打开服务器的FIFO,写入请求

4)读出文件内容或者出错消息


### 并发服务器:

1)创建一个子进程池,让池中的某个空闲子进程为一个新的客户服务

2)为每一个客户创建一个线程

3)创建一个线程池,让池中的某个空闲线程为一个新的客户服务


### 拒绝服务型(DOS)攻击

1)迭代服务器:发送一个请求行,但是不打开自己的FIFO来读,使进程阻塞

要留意代码可能在哪里阻塞,可能阻塞多久,可以在特定的操作室设置超时时钟

2)并发服务器:一个恶意客户可能发送大量独立请求,导致服务器达到最大子进程数,使后续fork失败




字节流:读进程无法知道从FIFO拿到的数据是一个还是多个进程写入的,可以通过:

1)带内终止序列:用换行分隔每个消息

2)显式指定消息的长度

3)通过关闭与其对端的连接来指示一个记录的结束,这需要为每一个记录创建一个新的连接,http1.0是这么做的



管道和FIFO限制:

OPEN_MAX:一个进程可在任意时刻打开的最大的描述符的数量(posix要求至少是16)

PIPE_BUF:可原子的写入一个管道或者FIFO的最大数据量(posix要求必须大于512)

open_max的值可以通过调用sysconf函数查询,可执行ulimie或者limit从shell中修改或者setrlimit从进程中修改


pipe_buf在<limits.h>中定义,但posix认为它是一个路径名变量,可在运行时通过pathconf或者fpathconf来获取







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值