管道是最初的UnixIPC形式,但是因为管道没有名字,所以它们只能用于有亲缘关系的进程使用;进而有名管道(FIFO)应运而生,有名管道有一个路径名与之关联,所以允许无亲缘关系的进程访问同一个FIFO。
以下具体介绍管道:
管道的创建:管道由函数 int pipe(int fd[2]) 创建,提供一个单向数据流,该函数返回两个文件描述符,fd[0]和fd[1],前者用来打开读,后者用来打开写。
管道的用途:其典型用途就是为父子进程提供进程间通信手段; 其过程是由一个进程(它将成为父进程)创建一个管道后调用fork派生一个自身的副本,如图所示,父进程关闭这个管道的读出端,子进程关闭该管道的写入端,这样就在父子进程间形成了一个单向数据流。
前面所述都是半双工即单向的,只提供一个方向的数据流的管道,若需要一个双向的数据流,就得创建两个管道,每个方向一个,具体的实现步骤如下:
其图示如下:
该过程的一个小小的示例如下:
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>
using namespace std;
int main()
{
const char* parent_talk[] = {"Hello",
"Can you tell me current data and time",
"I have to go, Bye",
NULL};
const char* child_talk[] = {"Hi",
"No problem:",
"Bye",
NULL};
int fd1[2], fd2[2];
int res1 = pipe(fd1);
int res2 = pipe(fd2);
if(res1 < 0){
printf("create pipe1 error\n");
exit(1);
}
if(res2 < 0){
printf("create pipe2 error\n");
exit(1);
}
pid_t pid;
pid = fork();
if(pid == 0){
close(fd1[1]);
close(fd2[0]);
char buffer[256];
int i = 0;
const char *child = child_talk[i];
while(child){
read(fd1[0], buffer, 256);
printf("Parent:> %s\n", buffer);
if(i == 1){
time_t t;
time(&t);
sprintf(buffer, "%s%s", child, ctime(&t));
write(fd2[1], buffer, strlen(buffer) + 1);
}else{
write(fd2[1], child, strlen(child) + 1);
}
++i;
child = child_talk[i];
}
close(fd1[0]);
close(fd2[1]);
}else if(pid > 0){
char buffer[256];
close(fd1[0]);
close(fd2[1]);
int i = 0;
const char *parent = parent_talk[i];
while(parent){
write(fd1[1], parent, strlen(parent) + 1);
read(fd2[0], buffer, 256);
printf("Child:> %s\n", buffer);
++i;
parent = parent_talk[i];
}
close(fd2[0]);
close(fd1[1]);
int status;
wait(&status);
}else{
printf("create child error\n");
}
return 0;
}
该过程是创建了两个管道,父进程用一个管道的一端进行写,子进程用该管道的另一端进行读;子进程用另外一个管道的一端进行写,父进程用该管道的另一端进行读;从而通过管道完成父子进程之间的通信。
有名管道:
有名管道的创建: 其由函数 int mkfifo(const char *pathname, mode_t mode) 创建,该函数隐含指定权限为O_CREAT | O_EXCL,即就是mkfifo要么创建一个新的fifo,要么返回错误(因为所指定名字的fifo已经存在),若不想创建新的fifo,就直接用open打开一个已经存在的fiffo。
FIFO不能打开既用来读又用来写,因为FIFO是半双工的。
关于该FIFO的一个小小的示例如下:
//头文件
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
using namespace std;
const char *write_fifo_name = "write_fifo";
const char *read_fifo_name = "read_fifo";
//makefile文件
all:ser cli
ser:ser.cpp
g++ ser.cpp -o ser
cli:cli.cpp
g++ cli.cpp -o cli
.PHONY:clean
clean:
rm ser cli
//服务器端的代码:
#include "utility.h"
int main()
{
int write_fd, read_fd;
int res = mkfifo(write_fifo_name, O_CREAT|O_EXCL|0600);
if(res == -1){
printf("mkfifo error.\n");
exit(1);
}
write_fd = open(write_fifo_name, O_WRONLY); //只写方式打开写管道
if(write_fd == -1){ //以只写方式打开文件失败
printf("open write fifo error.\n");
unlink(write_fifo_name); //解除文件的连接
exit(1);
}
printf("waiting Client Connecct.....\n");
while((read_fd = open(read_fifo_name, O_RDONLY)) == -1){ //一直等待,直到打开读文件成功
sleep(1);
}
printf("client connect ok.\n");
char sendbuf[256];
char recvbuf[256];
//进行服务器和客户端的通信
while(1){
printf("Ser:>");
scanf("%s", sendbuf); //输入服务器端的信息
if(strncmp(sendbuf, "quit", 4) == 0){ //比较是否要退出,若服务器端请求退出,则先解除文件再退出
unlink(write_fifo_name); //解除文件
write(write_fd, sendbuf, strlen(sendbuf) + 1); //告诉客户端服务器端要退出
break;
}
write(write_fd, sendbuf, strlen(sendbuf) + 1); //将服务器端的信息写入发送缓冲区
read(read_fd, recvbuf, 256); //读取接收缓冲区的客户端发送的数据
printf("Cli:>%s\n", recvbuf);
}
return 0;
}
//客户端的代码:
#include "utility.h"
int main()
{
int write_fd, read_fd;
int res = mkfifo(read_fifo_name, O_CREAT|O_EXCL|0600);
if(res == -1){
printf("make read fifo error.\n");
exit(1);
}
read_fd = open(write_fifo_name, O_RDONLY); //只读方式打开写管道
if(read_fd == -1){ //打开写管道失败,就无法读取服务器端发来的数据
printf("server error.\n");
unlink(read_fifo_name); //断开写连接
exit(1);
}
write_fd = open(read_fifo_name, O_WRONLY); //以只写方式打开读管道,准备写入客户端要发送的数据
if(write_fd == -1){
printf("client connect server error.\n");
exit(1);
}
char sendbuf[256];
char recvbuf[256];
while(1){
read(read_fd, recvbuf, 256); //客户端首先读取服务器端发来的数据
printf("Ser:>%s\n", recvbuf);
printf("Cli:>");
scanf("%s", sendbuf); //客户端写入自己要发送给服务器端的数据
if(strncmp(sendbuf, "quit", 4) == 0){ //若是客户端要退出,则解除读管道,发送消息告诉服务器端客户端要退出
unlink(read_fifo_name); //解除连接
write(write_fd, sendbuf, strlen(sendbuf) + 1); //写入要退出的数据
break;
}
write(write_fd, sendbuf, strlen(sendbuf) + 1); //将数据写入到要发送给服务器端的缓冲区中
}
return 0;
}
最后来简单的说一说管道和有名管道的区别:
(1)有名管道可以进行无亲缘关系的进程间通信,但是管道只能用于有亲缘关系的进程间通信。
(2)创建并打开一个管道只需要pipe函数就够了,但是创建并打开FIFO需要在调用mkfifo后再调用open函数。
(3)管道在所有进程最终都关闭它之后自动消失,但是FIFO只有在调用unlink后才能从文件系统中删除。
本文介绍了管道和FIFO(有名管道)的概念及其区别。管道是用于有亲缘关系进程间通信的单向数据流,由pipe()创建。FIFO允许无亲缘关系的进程通信,通过mkfifo()创建,需要额外的open()操作。两者都是半双工,但FIFO可以在文件系统中持久存在,可通过unlink()删除。
873

被折叠的 条评论
为什么被折叠?



