无名管道(pipe)用来具有亲缘关系的进程之间进行单向通信。半双工的通信方式,数据只能单向流动。

管道以字节流的方式通信,数据格式由用户自行定义。

无名管道多用于父子进程间通信,也可用于其他亲缘关系进程间通信。

因为父进程调用fork函数创建子进程,子进程拷贝父进程的文件表,由于父子进程文件表内容相同,指向的file相同,所有最终父子进程操作的pipe管道相同。

父子进程都能看到pipe管道内存空间,所以父子进程能正常通信。进程通信的本质就是进程之间能够访问同一块内存。

进程通信(1):无名管道(pipe)_父子进程

父进程调用fork函数后,父子进程不能同时保留读写文件描述符,需要关闭读或者写文件描述符,形成单向数据流防止父子进程同时读写引发数据错误。

如果需要双向数据流,需要创建两个管道,然后关闭一个管道的读端和另一个管道的写端。

(1)创建管道1(fdl[0]和fdl[1])和管道2(fd2[0]和fd2[1]);

(2) fork;

(3)父进程关闭管道1的读出端(fd[0]);

(4)父进程关闭管道2的写入端(fd2[l]);

(5)子进程关闭管道1的写入端(fd[1]);

(6)子进程关闭管道2的读出端(fd2[0]);

进程通信(1):无名管道(pipe)_linux_02

 在下面程序中,父进程作为client,子进程作为server,使用两个匿名管道进行ipc通信。

首先父进程使用pipe创建两个管道pipe1和pipe2,然后使用fork创建子进程,在子进程中关闭pipe1的写端和pipe2的读端。在子进程中调用server函数,在父进程中调用client函数。

client函数读取标准输入的文件路径,然后使用write发送给管道;server使用read读取对应管道获取文件路径,然后打开文件,读取内容后使用write发送给另一个管道,然后client读取对应管道的文件内容输出到终端。

最后父进程调用waitpid回收结束的子进程。在调用waitpid之前,子进程处于僵尸状态,虽然没有占用内存空间,但是占用一定的资源。子进程结束后发送SIGCHLD信号给父进程,父进程默认是忽略这个信号,所以需要手动回收子进程。

#include	"unpipc.h"

void	client(int, int), server(int, int);

int main(int argc, char **argv)
{
	int		pipe1[2], pipe2[2];
	pid_t	childpid;
	Pipe(pipe1);	/* create two pipes */
	Pipe(pipe2);
	if ( (childpid = Fork()) == 0) {		/* child */
		Close(pipe1[1]);
		Close(pipe2[0]);
		server(pipe1[0], pipe2[1]);
		exit(0);
	}
		/* 4parent */
	Close(pipe1[0]);
	Close(pipe2[1]);
	client(pipe2[0], pipe1[1]);
	Waitpid(childpid, NULL, 0);		/* wait for child to terminate */
	exit(0);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

server.c 

#include	"unpipc.h"

void
server(int readfd, int writefd)
{
	int		fd;
	ssize_t	n;
	char	buff[MAXLINE+1];
		/* 4read pathname from IPC channel */
	if ( (n = Read(readfd, buff, MAXLINE)) == 0)
		err_quit("end-of-file while reading pathname");
	buff[n] = '\0';		/* null terminate pathname */

	if ( (fd = open(buff, O_RDONLY)) < 0) {
			/* 4error: must tell client */
		snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n",
				 strerror(errno));
		n = strlen(buff);
		Write(writefd, buff, n);
	} else {
			/* 4open succeeded: copy file to IPC channel */
		while ( (n = Read(fd, buff, MAXLINE)) > 0)
			Write(writefd, buff, n);
		Close(fd);
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

client.c

#include "unpipc.h"
void client(int readfd, int writefd)
{
	size_t	len;
	ssize_t	n;
	char	buff[MAXLINE];
	/*4read pathname*/
	Fgets(buff, MAXLINE, stdin);
	len = strlen(buff);		/* fgets() guarantees null byte at end */
	if (buff[len-1] == '\n')
		len--;				/* delete newline from fgets() */

		/* 4write pathname to IPC channel */
	Write(writefd, buff, len);

		/* 4read from IPC, write to standard output */
	while ( (n = Read(readfd, buff, MAXLINE)) > 0)
		Write(STDOUT_FILENO, buff, n);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

全双工管道

linux中可以使用socketpair创建一个全双工管道,但是在fd[0]写的数据只能在fd[1]读,在fd[1]写的数据只能在fd[0]读。

进程通信(1):无名管道(pipe)_父子进程_03

全双工管道是由两个半双工管道实现的,但是具体原理这本书上没有说(以后有机会找一下)

下面程序使用了全双工管道进行父子进程的通信:

父进程使用socketpair创建一个全双工管道,然后使用fork创建子进程。父进程向管道fd[1]端写入字符p,然后读取管道fd[1]的字符。子进程睡3秒后读取管道fd[0]的字符,然后向管道fd[1]写入字符c。

#include	"unpipc.h"
#include <sys/types.h>
#include <sys/socket.h>
int
main(int argc, char **argv)
{
	int		fd[2], n;
	char	c;
	pid_t	childpid;
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1){
		err_quit("create error");
	}
	Pipe(fd);		/* assumes a full-duplex pipe (e.g., SVR4) */
	if ( (childpid = Fork()) == 0) {		/* child */
		sleep(3);
		if ( (n = Read(fd[0], &c, 1)) != 1)
			err_quit("child: read returned %d", n);
		printf("child read %c\n", c);
		Write(fd[0], "c", 1);
		exit(0);
	}
		/* 4parent */
	Write(fd[1], "p", 1);
	if ( (n = Read(fd[1], &c, 1)) != 1)
		err_quit("parent: read returned %d", n);
	printf("parent read %c\n", c);
	exit(0);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.