第1章 管道和FIFO
1.1 pipe
int pipe(int fd[2]);
创建一个单向、半双工管道,其中fd[0]用于读,fd[1]用于写。
1.2 fork
管道很少在单个进程内使用,一般用在两个有亲缘关系的进程间,父进程在pipe后fork,然后和子进程或子进程的后裔利用这个管道通信。
pipe(fd);
if ((pid = fork()) == 0)
{
close(fd[0]);
/*用fd[1]向父进程传递数据*/
}
close(fd[1]);
/*用fd[0]接收子进程传递的数据*/
1.3 全双工管道
可移植的创建全双工管道的方法是创建两个管道,分别负责一个方向的通信。
Linux中可以用socketpair创建一对Unix域套接字来模拟全双工管道的功能。
1.4 popen和pclose
FILE *popen(const char *cmd, const char *type);
int pclose(FILE *stream);
管道的一种常见应用模式是父进程pipe+fork后,子进程重定向标准输入和输出,再exec执行一个目标程序。
popen用来简化这一系列操作,其中type用来指定读写。pclose会先关闭文件流,等到子进程执行结束后再返回子进程的退出状态。
1.5 shell中创建管道
当在shell中输入这样的命令时:
who | sort | lp
该shell将创建三个进程和其间的两个管道,还把每个管道的fd[0]复制到相应进程的stdin,fd[1]复制到相应进程的stdout上。
1.6 mkfifo
int mkfifo(const char *pathname, mode_t mode);
mkfifo用来创建一个FIFO文件,它的mode隐含了O_CREAT | O_EXCL。FIFO允许无亲缘关系的任意进程访问,这是和管道的最大区别。
1.7 打开FIFO
创建FIFO后,可用open或fopen打开FIFO,打开时只能指定读或写,不能同时指定。
默认的阻塞模式下,以读或写模式打开一个FIFO时,若当前没有进程以对应的写或读模式打开FIFO,这个open将会阻塞,直到另一个进程以对应模式打开FIFO才返回。不正确的FIFO操作可能会导致死锁。
非阻塞模式下,读模式打开一个没有进程写打开的FIFO会成功返回,相反则会返回错误。
所有打开FIFO的进程都关闭FIFO后,FIFO中的剩余数据会被丢弃。
1.8 设置属性
open打开FIFO时可指定O_NONBLOCK标志,如:
writefd = open(FIFO1, O_WRONLY | O_NONBLOCK, 0);
对描述符可以用fcntl启用O_NONBLOCK标志。管道不能用open,因此必须用这一方法:
flags = fcntl(fd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
1.9 读写管道和FIFO
对管道和FIFO的write总从末尾写,read总从开头读,lseek会返回错误。
阻塞模式下,read时若管道中有数据则返回,可能返回的数据比请求的少;若管道为空,如果有进程写打开该管道,则阻塞,否则返回0,表示遇到了EOF。
write时写入的数据会分成若干个最大为PIPE_BUF字节的段,每段都是原子写入,但多个进程的多段数据写入不保证原子性。写一个没有进程读打开的FIFO会产生SIGPIPE信号。
非阻塞read和write不会影响操作的原子性,但会在需要阻塞的时候返回错误,包括:
1) read空管道。
2) write的字节数不大于PIPE_BUF,但管道中剩余空间不足时,为了保证原子操作。
3) write的字节数大于PIPE_BUF,管道中还有空间则写入那么多字节,否则返回错误。
1.10 FIFO的单服务器多客户
服务器需要一个公开的FIFO路径接收客户的请求,客户为了能接收服务器的回应,应该在请求中附上客户自己创建的FIFO路径。
并发服务器可以创建一个子进程池或子线程池来提高响应速度。
对迭代服务器的拒绝服务器型攻击是指故意发送一个不完整的请求,服务器就会陷入对剩下部分的等待中。对并发服务器的攻击则是发送大量独立请求,导致服务器fork失败。
1.11 字节流和消息
管道和FIFO发送的是无边界的字节流,有三种技巧用于判定消息边界:
1) 约定的终止符。
2) 显式指定长度。
3) 每次连接一个记录。
1.12 管道和FIFO的限制
系统的限制:
1) OPEN_MAX 打开的最大描述符数。
2) PIPE_BUF 原子写入的最大数据量。
FIFO是单主机上的IPC形式,不能用在NFS文件系统上。