Linux应用编程—6.无名管道
6.1 pipe()函数使用详情
管道是进程之间进行通讯的一种方式。管道有两种,分别是:无名管道和有名管道。先看无名管道。管道创建函数是pipe(),在Linux终端输入man pipe。
NAME
pipe, pipe2 - create pipe
SYNOPSIS
#include <unistd.h>
/* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES */
struct fd_pair {
long fd[2];
};
struct fd_pair pipe();
/* On all other architectures */
int pipe(int pipefd[2]);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
pipe()与pipe2()函数用来创建管道。调用这两个函数需要包含unistd.h这个头文件。pipe()函数的入参是一个有两个元素的整形数组。
ESCRIPTION
pipe() creates a pipe, a unidirectional data channel that can be used for interprocess communication. The array pipefd is used to return two file descriptors referring
to the ends of the pipe. pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe. Data written to the write end of the pipe is
buffered by the kernel until it is read from the read end of the pipe. For further details, see pipe(7).
pipe()函数创建管道,管道是一个单向的数据通道被用于进程间通讯。这个数组pipefd被用于返回两个文件描述符。 pipefd[0] 数组第一个元素是管道读文件描述符,pipefd[1]数组第二个元素是管道写 文件描述符。写入管道写端的数据由内核进行缓存,直到管道读端读取。
RETURN VALUE
On success, zero is returned. On error, -1 is returned, errno is set appropriately, and pipefd is left unchanged.
管道创建成功的话,函数返回0,如果失败,则返回-1。
6.2 pipe()编程实战
创建父子进程,父子进程之间通过管道传递数据,比如字符串。如何给父子进程创建管道,因为调用fork()函数创建子进程时,子进程是父进程的拷贝,所以,父进程先创建了管道,子进程也拥有了管道。但是,对于父子进程的管道来说,这个管道都有一个数据的读取端与写入端。如何使数据单向传递呢,我们在父进程中,将管道的读取端关闭,只使用管道的写入端,来向子进程传递数据。在子进程的管道中,我们关闭管道的写入端,只使用管道的读取端,来接收父进程的数据。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main(void)
{
int fd[2];
int ret = 0;
pid_t pid;
ret = pipe(fd);
if(0 != ret)
{
perror("pipe.");
}
pid = fork();
if(pid > 0) // Parent process.
{
close(fd[0]);
sleep(5); // Delay 5 seconds.
write(fd[1], "123456789", 9); // Write data:vae, length = 3.
while(1); // Keep parent process running.
}
else if(pid == 0) // child process.
{
char str[9]; // Read data buffer.
printf("Waitting parent process data...\n");
close(fd[1]); // In child process, close write end of the pipe.
read(fd[0], str, 9); // Read data from fd[0];
printf("The data from Parent process is %s.\n", str);
}
else
{
perror("fork.");
}
return 0;
}
运行结果:
子进程先打印提示信息,5秒钟后(父进程在关闭管道读取端后,延时5秒发送数据),子进程管道读取端读取数据,并且打印了出来。
疑问:邴老师留下了问题,管道能传递的数据有限制嘛?如果有,是多少。编写一个测试代码。
6.3 测试无名管道的大小
编写一段代码,用来测试管道大小。原理就是,在子进程中一直往管道的写入端写数据,并且对写操作进行计数,当管道内无法写入时,代码就阻塞了。此时,父进程调用waitpid()函数等待子进程的结束。
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
int main(void)
{
int ret = 0;
int fd[2];
pid_t pid;
ret = pipe(fd);
if(-1 == ret)
{
perror("pipe");
}
pid = fork();
if(pid == 0) // Child process: write data to the end of pipe.
{
char ch = '!';
int count = 0;
close(fd[0]);
while(1)
{
write(fd[1], &ch, 1); // 每次写入1个字节。
printf("Write data count = %d.\n", ++count);
}
}
else if(pid > 0) // Parent process: wait until child process is over.
{
waitpid(pid, NULL, 0);
}
return 0;
}
运行结果:
可以看出,当子进程往管道写数据写到65536时,无法在继续写入。说明管道的大小是65536个字节。
6.4 管道应用编程
编写一段代码,创建一个父子进程,子进程不断接收用户键盘输入的字符串数据,并通过管道传递到父进程,父进程收到数据就要打印出来。
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main(void)
{
int ret = 0;
int fd[2];
pid_t pid;
ret = pipe(fd);
if(-1 == ret)
{
perror("pipe.");
}
pid = fork();
if(pid == 0)
{
char str[32];
close(fd[0]);
while(1)
{
scanf("%s", str);
write(fd[1], &str, sizeof(str));
}
}
else if(pid > 0)
{
char str[32];
close(fd[1]);
while(1)
{
printf("Waitting data from child process...\n");
read(fd[0], &str, sizeof(str));
printf("Data from child process is %s.\n", str);
}
}
else
{
perror("fork.");
}
return 0;
}
运行结果:
现在可以实现父子进程之间连续通过管道传递数据。
6.5 双向管道在父子进程之间传递数据
之前的代码,父子进程通过管道单向传递数据。有点类似半双工通讯,只能一方传递给另外一方。现在代码实现这样一个场景,父进程通过管道向子进程传递一串不定长的字符串,子进程接收到字符串后,将其修改为大写,然后在传递到父进程,供父进程打印。这里父进程有写入数据的操作,也有读取数据的操作。子进程有读取数据的操作,也有写入数据的操作。由于管道是单向的,需要创建2根管道。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(void)
{
int ret = 0;
int fd1[2];
int fd2[2];
pid_t pid;
ret |= pipe(fd1);
if(0 != ret)
{
perror("pipe.");
}
ret |= pipe(fd2);
if(0 != ret)
{
perror("pipe.");
}
pid = fork();
if(pid > 0)
{
char buff[64];
close(fd1[0]);
close(fd2[1]);
while(1)
{
memset(buff, '\0', sizeof(buff));
gets(buff);
write(fd1[1], buff, sizeof(buff));
memset(buff, '\0', sizeof(buff));
read(fd2[0], buff, sizeof(buff));
printf("After change is : %s.\n", buff);
}
}
else if(pid == 0)
{
int i = 0;
char buff[64];
close(fd1[1]);
close(fd2[0]);
while(1)
{
memset(buff, '\0', sizeof(buff));
read(fd1[0], buff, sizeof(buff));
for(i = 0; i < sizeof(buff); i++)
{
buff[i] = toupper(buff[i]);
}
write(fd2[1], buff, sizeof(buff));
}
}
else
{
perror("fork.");
}
return 0;
}
运行结果:
示意图如下:
6.6 总结
管道用于进程之间通讯,管道又分为有名管道和无名管道。管道是单向的,创建管道用函数pipe(),需要传入一个数组int fd[2],有2个元素,元素类型是整形。其中,fd[0]是管道读取端, fd[1]是管道写入端。它两叫做文件通配符,可以使用函数close()与write()进行操作。