关于UNIX环境高级编程和UNIX网络编程的学习记录和总结
前言
管道是UNIX IPC(InterProcess Communication)的最老形式,并且所有UNIX系统都提供此种通信机制,管道有两种限制;
(1) 它们是半双工的。数据只能在一个方向上流动。
(2) 它们只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该
进程调用fork,此后父、子进程之间就可应用该管道。
流管道没有第一种限制, FIFO和命名流管道则没有第二种限制。
管道基本是最简单的进程间通信方式
一、pipe()
管道:
在内核中借助环形队列机制,使用内核缓冲区实现。 管道是一种伪文件
管道常见的形式:
管道的特点:管道中的数据只能一次读取, 数据在管道中只能单向流动。 不能在一端同时读写。管道pipe()只用在血缘关系进程间。
pipe()原型:
int pipe(int pipefd[2]);
int pipe2(int pipefd[2], int flags); //If flags is 0, then pipe2() is the same as pipe().
参数: fd[0]: 读端。
fd[1]: 写端。
返回值: 成功: 0
失败: -1 设置errno
读管道:
1. 管道有数据,read返回实际读到的字节数。
2. 管道无数据: 1)无写端,read返回0 (类似读到文件尾)
2)有写端,read阻塞等待。
写管道:
1. 无读端, 异常终止。 (SIGPIPE导致的)
2. 有读端: 1) 管道已满, 阻塞等待
2) 管道未满, 返回写出的字节个数。
pipe()的使用
尽管管道是由单个进程创建的,却很少在单个进程间使用。管道的典型用途是为两个不同的进程提供进程间的通信。
管道的一端只能占用读或写,因此在使用时需在一端关闭读端(或写端),而在另一端关闭写端(或读端)。
如:父子进程使用pipe(),父写子读
在调用fork之前先创建一个管道。 fork之后父进程关闭其读端,子进程关闭其写端。
ret = pipe(fd); // 父进程先创建一个管道,持有管道的读端和写端
...
pid = fork(); // 子进程同样持有管道的读和写端
if (pid > 0) {
close(fd[0]); // 关闭读段
//sleep(3);
write(fd[1], str, strlen(str));
close(fd[1]);
} else if (pid == 0) {
close(fd[1]); // 子进程关闭写段
ret = read(fd[0], buf, sizeof(buf));
printf("child read ret = %d\n", ret);
write(STDOUT_FILENO, buf, ret); //输出到屏幕
close(fd[0]);
}
使用pipe()兄弟进程通信
:
父进程只需等待回收子进程———–关闭读写端————-wait(NULL),阻塞回收子进程
for(i = 0; i < 2; i++) { //创建子进程
pid = fork();
if (pid == -1) {
sys_err("fork error");
}
if (pid == 0) // 子进程,出口
break;
}
//父进程pid返回的是子进程id,所以父pid>0
if (i == 2) { // 父进程
close(fd[0]); // 关闭管道的 读端/写端.
close(fd[1]);
wait(NULL); // 回收子进程
wait(NULL);
} else if (i == 0) { //子1
close(fd[0]);
dup2(fd[1], STDOUT_FILENO); // 重定向stdout
execlp();
...
} else if (i == 1) { //子2
close(fd[1]);
dup2(fd[0], STDIN_FILENO); // 重定向 stdin
execlp();
...
也可以父进程完成读,两个子进程完成写。
二、有名管道fifo(有的书也叫命名管道)
FIFO有两种用途:
(1) FIFO由s h e l l命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时
文件。
(2) FIFO用于客户机-服务器应用程序中,以在客户机和服务器之间传递数据。
如可以用于无血缘关系的进程间通信。
有名管道是存在于文件系统中的文件节点,所以我们可以用建立文件节点的方式来建立有名管道。在 shell 中我们可以用下面的命令:
shell创建fifo:
mkfifo [OPTION]… NAME…
如
mkfifo("fifo1",0664);
//0664文件权限
mkfifo –m 0664 fifo1
//与上述等价 “ -m”选项指定所建立的有名管道的存取权限
mknod fifo2 p
//也是创建fifo,但mknod 则需要之后使用 chmod 来改变有名管道的存取权限
C中创建fifo:
int mknod( char *pathname, mode_t mode, dev_t dev);
返回值:成功 0
失败-1,设置errno
errno = EFAULT (pathname invalid)
EACCES (permission denied)
ENAMETOOLONG (pathname too long)
ENOENT (invalid pathname)
ENOTDIR (invalid pathname)
(see man page for mknod for others)
- 读端,open fifo O_RDONLY
- 写端,open fifo O_WRONLY
fifo的I/O使用
有名管道和管道的操作是相同的,只是要注意,在引用已经存在的有名管道时,首先
要用系统中的文件函数来打开它,才能接下来进行其他的操作。
因此我们可以像操作文件流一样来操作fifo
fifo非血缘进程间通信
//创建fifo
mkfifo("fifoname",0644);
/*---------------写端------------*/
fd=open(fifoname,O_WRONLY);
...
write(fd,buf,strlen(buf)); //向管道写
...
close(fd);
/*---------------读端----------------*/
fd=open(fifoname,O_RDONLY);
...
int len=read(fd,buf,sizeof(buf)); //从管道读
write(STDOUT_FILENO,buf,len); //输出到屏幕
...
close(fd);
管道和fifo的额外属性
有名管道自动支持进程阻塞, 一个文件描述符能以两种方式设置成非阻塞
-
writefd=open(FIFO1,O_WRONLY|O_NONBLOCK,0)
-
如果一个文件描述符已经打开,可以使用fcntl()设置非阻塞
原型:
int fcntl(int fd, int cmd, ...)
cmd 命令,决定了后续参数个数
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK 或等于,不管原来状态如何一定会添加上ONONBLOCK
fcntl(fd, F_SETFL, flgs);
获取文件状态: F_GETFL
设置文件状态: F_SETFL
使用fcntl将文件改为非阻塞读:
flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息
flags |= O_NONBLOCK;
int ret = fcntl(STDIN_FILENO, F_SETFL, flags);
if(ret == -1){
perror("fcntl error");
exit(1);
}
总结
管道可以用于一个读端和多个写端,或者多个读端一个写端,但是管道中的数据只能读一次,所以一般情况下应该是一对一模型,这样才能保证数据的顺序。
FIFO的真正优势表现在作为服务器,可以是一个长期运行的进程,构成单个服务器多个客户的模型,而且可以与客户无血缘关系,服务器的守护进程创建fifo读端,客户启动写端把自己的请求写入服务器。