管道是最初的Unix IPC形式,它们的最大局限是没有名字,所以,管道只能用于有亲缘关系的进程之间使用。之后,慢慢随着FIFO的加入,这点才有所改观。FIFO也成为有名管道。管道和FIFO的共同点就是它们都是通过read和write函数进行访问的。
管道:
管道是由pipe函数创建,提供一个单路数据流。也就是说,所有的管道都是半双工。
管道创建方法:
#include <unistd.h>
int pipe(int fd[2]);
该函数返回两个文件描述符:fd[0] (用来打开读)、fd[1] (用来打开写)。管道只是它形象的叫法,它的本质实际上就是文件。
单个进程中的管道模式:
一般管道很少只在单个进程中进行使用。管道最常用于两个不同但有亲缘关系的进程(一个父进程,一个子进程。或两个有共同祖先的进程)中,提供进程间的通信。
进程间通信模式:
只要有亲缘关系的两个进程都可以用管道进行通信。这里我们用父进程和子进程进行介绍。
首先,我们有主进程创建一个管道后,调用fork()函数派生一个自身的副本。此时主进程将成为父进程,它的副本将成为子进程。完成这些预备操作后,父进程将关闭相应管道的读出端(fd[0]),子进程将关闭该管道的写入端(fd[1])。这样父进程可以通过write函数写入数据,而子进程通过read函数读出数据【必须先写入数据才能读出】。
进程间双向数据流:
双向数据与进程间单向数据流十分相似。只是它是创建了两个管道。父进程关闭了管道1的读端口(fd1[0])和管道2的写端口(fd2[1]),子进程则恰好相反,它关闭的是管道2的读端口(fd2[0])和管道1的写端口(fd1[1])。这样,两个管道可以保证数据的双向流动。父进程由管道2进行读数据,由管道1进行写数据,而子进程则由管道2写数据,由管道1读数据。
下面是进程间双向数据流的实现代码:
步骤:
(1)、创建管道1和管道2(利用pipe函数)
(2)、fork一个子进程
(3)、父进程关闭管道1的读端口(fd1[0])和管道2的写端口(fd2[1])
(4)、子进程关闭管道1的写端口(fd1[1])和管道2的读端口(fd2[1])
#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
using namespace std;
int main()
{
//分别定义一个字符串数组记录父进程和子进程所传数据,最后一个为NULL
char* parent_talk[] = {"Hello",
"can you tell me current data and time?",
"I have to go, Bye",
NULL};
char* child_talk[] = {"Hi",
"No problem:",
"Bye.",NULL};
int fd1[2], fd2[2]; //创建两个管道
//检测管道是否创建成功,如果创建成功会返回0,否则返回-1
if(pipe(fd1)<0)
{
printf("create pipe1 error.\n");
exit(1);
}
if(pipe(fd2)<0)
{
printf("create pipe2 error.\n");
exit(1);
}
pid_t pid;
pid = fork(); //fork一个子进程,并将子进程的id号符给父进程的pid
if(pid == 0) //子进程没有自己的子进程,所以子进程pid = 0
{
char buffer[256];
//关闭子进程需要关闭的端口
close(fd1[1]);
close(fd2[0]);
int i=0;
char *child = child_talk[i];
while(child != NULL)
{ //只要子进程字符串数组不为NULL,就说明通信为及未结束
//从管道1中读出数据,并打印出来
read(fd1[0],buffer,256);
printf("Parent:>%s\n",buffer);
//给管道2中写入数据
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;
char *parent = parent_talk[i];
//父进程的字符串数组中数据不为NULL时,继续写入数据
while(parent != NULL)
{
//将数据写入管道1
write(fd1[1],parent,strlen(parent)+1);
//从管道2中读出子进程发送的数据
read(fd2[0],buffer,256);
printf("Child:>%s\n",buffer);
i++;
parent = parent_talk[i];
}
//通信结束后,关闭所有端口
close(fd1[1]);
close(fd2[0]);
//等待子进程结束,然后回收它的空间,防止它成为孤儿进程
int status;
wait(&status);
}
//如果pid不满足上述条件,则说明fork子进程失败
else
{
printf("Create child process error!\n");
}
return 0;
}
FIFO(有名管道):
FIFO即先进先出,每个FIFO有一个路径名与之相关联,所以它可以实现无亲缘关系的进程之间进行通信访问同一个FIFO。FIFO又称为有名管道。与管道不同的是,FIFO是由mkfifo函数创建,创建成功则返回0,失败则返回1。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
其中pathname是一个普通的路径名,它将是该FIFO的名字;mode则指定文件权限位,一般使用的权限位参数为:O_CREAT|O_EXCL,意思为,它要么创建一个新的FIFO,要么返回一个EEXIST(已存在错误)。在使用mkfifo函数时,它会检测是否返回EEXIST错误,如果返回该错误,则直接调用open函数打开即可。
在创建出一个FIFO后,必须打开读或写,但不能同时打开读和写,因为它和管道一样也是半双工。
对于管道和FIFO而言,write是往末尾添加数据,而read则是从头部返回数据。
用两个FIFO实现客户-服务器:
它的原理和用管道实现双向数据流相似,它是用FIFO1来进行服务器给客户端发送数据,而用FIFO2来实现客户端给服务器传送数据。
实现程序:
utili.h 头文件:
#pragma once
#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include <sys/stat.h>
using namespace std;
//创建两个路径名(mkfifo函数中pathname参数)
const char *write_fifo_name = "write_fifo";
const char *read_fifo_name = "read_fifo";
ser.cpp:服务端程序:
#include"utili.h"
int main()
{
int write_fd;
int read_fd;
//创建一个write_fifo_name的FIFO
int res = mkfifo(write_fifo_name,O_CREAT|O_EXCL|S_IRUSR|S_IWUSR);
if(res == -1) //如果返回值为-1,则创建FIFO失败
{
printf("make write fifo error.\n");
exit(1);
}
//创建成功后,以只写方式打开write_fifo_name管道
write_fd = open(write_fifo_name,O_WRONLY);
//如果返回-1则表明打开失败
if(write_fd == -1)
{
printf("open write fifo error.\n");
unlink(write_fifo_name);
exit(1);
}
//打开成功后等待客户端
printf("Wait Client Connect......\n");
//以只读方式打开read_fifo_name,并等待客户端的连接
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)
{
//服务器从write_fifo_name写入数据
printf("Ser:>");
scanf("%s",sendbuf);
write(write_fd,sendbuf,strlen(sendbuf)+1);
//服务器从read_fifo_name读出来自客户端的数据
read(read_fd,recvbuf,256);
printf("Cli:>%s\n",recvbuf);
}
return 0;
}
cli.cpp客户端程序:
#include"utili.h"
int main()
{
int write_fd, read_fd;
//创建一个名为read_fifo_name的FIFO,如果创建失败则返回-1,成功则返回0
int res = mkfifo(read_fifo_name, O_CREAT|O_EXCL|S_IRUSR|S_IWUSR);
if(res == -1)
{
printf("make read fifo error.\n");
exit(1);
}
//客户端以只读的形式打开write_fifo_name
read_fd = open(write_fifo_name, O_RDONLY);
if(read_fd == -1) //如果返回值为-1则表明打开失败
{
printf("Server Error.\n");
unlink(read_fifo_name);
exit(1);
}
//客户端以只写方式打开read_fifo_name
write_fd = open(read_fifo_name,O_WRONLY);
if(write_fd == -1) //如果返回值为-1,则打开失败
{
printf("Client Connect Server Error.\n");
exit(1);
}
//定义两个字符串数组,分别用来存放客户端发送数据和接收的数据
char sendbuf[256];
char recvbuf[256];
while(1)
{
//客户端通过write_fifo_name来读取来自服务器的数据
read(read_fd,recvbuf,256);
printf("Ser:>%s\n",recvbuf);
//客户端通过write_fifo_name写入数据
printf("Cli:>");
scanf("%s",sendbuf);
write(write_fd,sendbuf,strlen(sendbuf)+1);
}
return 0;
}