UNP卷二 chapter4 管道和FIFO

1、概述和一个简单的客户-服务器例子

管道是最初的Unix IPC的形式,其局限性在于没有名字,从而只能由有亲缘关系的进程使用。FIFO有时又称有名管道,其最大好处可用于无亲缘关系的进程间通信。

利用客户与服务器利用IPC通道通信样例


2、管道(一般指半双工模式)

a、pipe函数及实现方式

#include<unistd.h>
int pipe(int fd[2]);//返回:若成功则为0,若出错则为-1

pipe函数返回两个文件描述符:fd[0]和fd[1],前者打开来读,后者打开来写。


上图中由左边到右边的实际步骤如下:

i、创建管道1(fd1[0]和fd1[1])和管道2(fd2[0]和fd2[1]);

ii、fork;

iii、父进程关闭管道1的读入端(fd1[0]);

iv、父进程关闭管道2的写入端(fd2[1]);

v、子进程关闭管道1的写入端(fd2[1]);

vi、子进程关闭管道2的读入端(fd2[0]);

实际代码参见如下,

#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]);//关闭管道1的写出
		Close(pipe2[0]);//关闭管道2的读入

		server(pipe1[0], pipe2[1]);
		exit(0);
	}
	/* 4parent */
	Close(pipe1[0]);//关闭管道1的读入
	Close(pipe2[1]);//关闭管道2的写出

	client(pipe2[0], pipe1[1]);

	Waitpid(childpid, NULL, 0);		/* wait for child to terminate */
	exit(0);                   //waitpid调用是防止出现僵尸进程占用资源
}

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);
}

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);
	}
}

b、全双工管道


测试全双工管道的双向通信能力举例,

#include	"unpipc.h"

int
main(int argc, char **argv)
{
	int		fd[2], n;
	char	c;
	pid_t	childpid;

	Pipe(fd);		/* assumes a full-duplex pipe (e.g., SVR4) */
	if ( (childpid = Fork()) == 0) {		/* child */
		sleep(3);//先睡3s,让父进程先写入数据
		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);
}

3、FIFO

FIFO指代先进先出,Unix中的FIFO类似于管道。它是一个单向(半双工)数据流。每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。FIFO也称为有名管道。

FIFO创建函数如下,

#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char* pathname, mode_t mode);//返回:若成功则为0,若出错则为-1

mode参数指定文件权限位,如S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH。

mkfifo函数已隐含指定O_CREAT | O_EXCL。也就是说该函数要么创建一个新的FIFO,要么返回一个EEXIST错误(可以忽略)。mkfifo函数调用完后,再就是调用open创建FIFO描述符。具体举例如下,

以下是FIFO运用,举例,


#include	"unpipc.h"

#define	FIFO1	"/tmp/fifo.1"
#define	FIFO2	"/tmp/fifo.2"

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

int
main(int argc, char **argv)
{
	int		readfd, writefd;
	pid_t	childpid;

	/* 4create two FIFOs; OK if they already exist */
	if ((mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST))//此处FILE_MODE(即权限)设为644
		err_sys("can't create %s", FIFO1);
	if ((mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST)) {
		unlink(FIFO1);
		err_sys("can't create %s", FIFO2);
	}

	if ((childpid = Fork()) == 0) {		/* child */
		readfd = Open(FIFO1, O_RDONLY, 0);//先读后写
		writefd = Open(FIFO2, O_WRONLY, 0);

		server(readfd, writefd);
		exit(0);
	}
	/* 4parent */
	writefd = Open(FIFO1, O_WRONLY, 0);//先写后读,预防死锁
	readfd = Open(FIFO2, O_RDONLY, 0);//因为没有任何进程打开某FIFO写,则打开该FIFO读的进程将阻塞

	client(readfd, writefd);

	Waitpid(childpid, NULL, 0);		/* wait for child to terminate */

	Close(readfd);
	Close(writefd);

	Unlink(FIFO1);//删除管道FIFO的名字
	Unlink(FIFO2);
	exit(0);
}

4、利用FIFO实现单个服务器与多个客户


上图的代码实现,

服务器程序:

#include	"unpipc.h"

#define	SERV_FIFO	"/tmp/fifo.serv"
void	server(int, int);

int
main(int argc, char **argv)
{
	int		readfifo, writefifo, dummyfd, fd;
	char	*ptr, buff[MAXLINE], fifoname[MAXLINE];
	pid_t	pid;
	ssize_t	n;

	/* 4create server's well-known FIFO; OK if already exists */
	if ((mkfifo(SERV_FIFO, FILE_MODE) < 0) && (errno != EEXIST))
		err_sys("can't create %s", SERV_FIFO);

	/* 4open server's well-known FIFO for reading and writing */
	readfifo = Open(SERV_FIFO, O_RDONLY, 0);
	dummyfd = Open(SERV_FIFO, O_WRONLY, 0);		/* never used *///由于此描述符存在,当不再有客户存在时,服务器一定不会返回0以指示读到一个EOF

	while ((n = Readline(readfifo, buff, MAXLINE)) > 0) {
		if (buff[n - 1] == '\n')
			n--;			/* delete newline from readline() */
		buff[n] = '\0';		/* null terminate pathname */

		if ((ptr = strchr(buff, ' ')) == NULL) {//strchr函数返回赋给ptr的指针指向客户请求行中的空格
			err_msg("bogus request: %s", buff);
			continue;
		}

		*ptr++ = 0;			/* null terminate PID, ptr = pathname */
		pid = atol(buff);//转换PID号到整型
		snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long)pid);//提取出客户的fifo名称
		if ((writefifo = open(fifoname, O_WRONLY, 0)) < 0) {//以只写打开客户端的fifo
			err_msg("cannot open: %s", fifoname);
			continue;
		}

		if ((fd = open(ptr, O_RDONLY)) < 0) {
			/* 4error: must tell client */
			snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n",
				strerror(errno));
			n = strlen(ptr);
			Write(writefifo, ptr, n);
			Close(writefifo);

		}
		else {
			/* 4open succeeded: copy file to FIFO */
			while ((n = Read(fd, buff, MAXLINE)) > 0)//读入文个把信息,当FIFO变为空时,服务器的read返回0,表示是个EOF
				Write(writefifo, buff, n);//往writefifo管道中写入文件信息
			Close(fd);
			Close(writefifo);
		}
	}
}
客户端程序:
#include	"unpipc.h"

#define	SERV_FIFO	"/tmp/fifo.serv"

int
main(int argc, char **argv)
{
	int		readfifo, writefifo;
	size_t	len;
	ssize_t	n;
	char	*ptr, fifoname[MAXLINE], buff[MAXLINE];
	pid_t	pid;

	/* 4create FIFO with our PID as part of name */
	pid = getpid();
	snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long)pid);
	if ((mkfifo(fifoname, FILE_MODE) < 0) && (errno != EEXIST))
		err_sys("can't create %s", fifoname);

	/* 4start buffer with pid and a blank */
	snprintf(buff, sizeof(buff), "%ld ", (long)pid);//往buf中写入pid号
	len = strlen(buff);
	ptr = buff + len;

	/* 4read pathname */
	Fgets(ptr, MAXLINE - len, stdin);//从标准输入中获取需要打开的文件
	len = strlen(buff);		/* fgets() guarantees null byte at end */

							/* 4open FIFO to server and write PID and pathname to FIFO */
	writefifo = Open(SERV_FIFO, O_WRONLY, 0);//打开服务器众所周知的FIFO管道(只写)
	Write(writefifo, buff, len);//写入进程号及需要打开的文件名+路径名

	/* 4now open our FIFO; blocks until server opens for writing */
	readfifo = Open(fifoname, O_RDONLY, 0);

	/* 4read from IPC, write to standard output */
	while ((n = Read(readfifo, buff, MAXLINE)) > 0)
		Write(STDOUT_FILENO, buff, n);//写出标准输出

	Close(readfifo);//关闭描述符
	Unlink(fifoname);//删除该FIFO
	exit(0);
}

5、字节流和消息

本章使用管道和FIFO的例子都使用了节流I/O模型,也即Unix的原生I/O模型。

当数据由长度可变消息构成,且读出者必须知道这些消息的边界以判定何时已读出单个消息时,便需要对所传送的数据加上某种结构而实现,三种技巧常用于此目的。

i、带内特殊终止序列:许多Unix应用程序使用换行符来分隔每个消息。写进程会给每个消息添加一个换行符,读进程则每次读出一行。

ii、显式长度:每个记录前冠以它的长度。其优势是不再需要通过转义出现在数据中的分隔符,因为接收者不必扫描整个数据以寻找每个记录的结束位置。(具体代码实现见p52-p55)

iii、每次连接一个记录:应用通过关闭与其对端的连接(网络应用时为tcp连接,IPC时为IPC连接)来指示一个记录的结束。

以上知识点来均来自steven先生所著UNP卷二(version2),刚开始学习网络编程,如有不正确之处请大家多多指正。

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80878607