管道是 Linux 支持的最初 Unix IPC 形式之一,我们可以使用管道使得两个进程之间进行通信。
管道通信实现的本质:控制同一文件在两个进程中仅打开读端或写端进行收发数据。
1.匿名管道
顾名思义,匿名管道是我们在进程里创建出来并且没有给其赋予指定文件名的一类管道,它可用于两个具有亲属关系的进程之间的通信。
在我们创建子进程时,子进程会创建自己的地址空间但是会复制父进程的文件描述符表,所以在子进程和父进程里我们使用同样的文件描述符指向的是同一个文件,并且,在我们打开文件时分别使用读方法和写方法打开我们找到的是同一个文件但是我们得到的描述符是不一样的。如图示
在上图里面,我们不难看出,管道文件被父子进程共享,这就说明了进程间通信的本质是共享数据。
匿名管道的创建:
使用#include
#include
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd) < 0)
{
perror("pipe");
return 1;
}
pid_t id = fork();
//child
int i = 0;
if(id == 0)
{
close(pipe_fd[0]);
const char*msg = "I am child pro...\n";
int count = 5;
while(1)
{
write(pipe_fd[1],msg,strlen(msg));
if(count -- == 0)
break;
printf("child write data :%d\n",++i);
}
//close(pipe_fd[1]);
}
//father
else
{
close(pipe_fd[1]);
char buf[1024];
int count = 5;
while(count -- > 0)
{
memset(buf,'\0',sizeof(buf));
ssize_t _s = read(pipe_fd[0],buf,sizeof(buf)-1);
if(_s >0)
printf("client -> server: %s\n",buf);
else if(_s == 0)
{
printf("read pipe EOF...\n");
break;
}
else
break;
}
//close(pipe_fd[0]);
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret == id)
{
printf("wait success...\n");
printf("get sig: %d",status&0xff);
printf("exit code: %d",status >> 8&0xff);
}
}
return 0;
}
**我们使用status来接收程序退出的状态码,而它的低八位是退出信号,高八位是退出码
2.命名管道
与匿名管道不同,命名管道不仅可以用于具有亲属关系的进程之间的通信,还可以用于完全无关系的两个管道的通信。我们使一个进程负责创建管道文件另一个进程只需要打开该管道文件就可以在两个进程之间进行通信.
我们仍然使用一个例子来说明命名管道的通信:
client端程序
#include
#include
#include
#include
#include
int main()
{
int fd = open("./myfifo",O_WRONLY);
if(fd < 0)
{
perror("open error...");
return 1;
}
char buf[1024];
while(1)
{
memset(buf,'\0',sizeof(buf));
printf("Please enter# ");
fflush(stdout);
ssize_t _s = read(1,buf,sizeof(buf));
if(_s > 0)
{
buf[_s-1]='\0';
write(fd,buf,strlen(buf));
}
}
close(fd);
return 0;
}
server端程序:
#include
#include
#include
#include
#include
int main()
{
if(mkfifo("./myfifo",S_IFIFO | 0644) < 0)
{
perror("mkfifo");
return 1;
}
int fd = open("./myfifo",O_RDONLY);
if( fd < 0 )
{
perror("open error...");
return 2;
}
char buf[1024];
while(1)
{
memset(buf,'\0',sizeof(buf));
ssize_t _s = read(fd,buf,sizeof(buf)-1);
if(_s > 0 )
{
printf("Client->Server# %s\n",buf);
}
else
{
break;
}
}
close(fd);
return 0;
}
一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
管道有容量,它的容量大小存储于一个叫做PIPE_BUF的字段里,它规定了管道缓冲区的大小。其位于 include/linux/limits.h中,我们可以自行查看。不同的内核版本可能会有所不同。Posix.1 要求 PIPE_BUF 至少为 512 字节,red hat 7.2 中为 4096。
管道的原理:管道实际上是在内核创建了一个inode和一个指向它的固定大小的内核缓冲区,读写的文件描述符不同但指向了同一块内核和缓冲区。管道pipe是一类特殊的文件pipefs,在磁盘里没有映像,只存在于内存中,这样设计的原因是处于对管道文件读写速度的优化。
如果我们需要实现全双工的通信方式就在两个进程之间创建两个管道,一个正向通信另一个反之。