读书笔记:第4章 管道和FIFO (6)

        《UNIX网络编程:卷2》P47:图4-23 处理多个客户请求的FIFO服务器程序

/*P47 mainserver.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#define MAXLINE 1024
#define FILE_MODE	(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define SERV_FIFO	"/tmp/fifo.serv"

ssize_t readline(int fd, void *vptr, size_t maxlex);

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

	// 创建服务器的众所周知FIFO,就算它已经存在也没问题
	if ((mkfifo(SERV_FIFO, FILE_MODE) < 0) && (errno != EEXIST)) {
		fprintf(stderr, "can't create %s: %s\n", SERV_FIFO, strerror(errno));
	}

	// 打开服务器的众所周知FIFO读
	if ((readfifo = open(SERV_FIFO, O_RDONLY, 0)) < 0) {
		fprintf(stderr, "open %s error: %s\n", SERV_FIFO, strerror(errno));
		exit(1);
	}

	// 打开服务器的众所周知FIFO写;不用
	if ((dummyfd = open(SERV_FIFO, O_WRONLY, 0)) < 0) {
		fprintf(stderr, "open %s error: %s\n", SERV_FIFO, strerror(errno));
		exit(1);
	}

	// 每个客户请求是由进程ID、一个空格再加上路径名构成的单行
	while ((n = readline(readfifo, buff, MAXLINE)) > 0) {
		if (buff[n-1] == '\n')
			n--;					// 删除换行符
		buff[n] = '\0';

		// 搜索空格
		if ((ptr = strchr(buff, ' ')) == NULL) {
			fprintf(stderr, "bogus request: %s\n", buff);
			continue;
		}

		*ptr++ = 0;					// ptr增1后指向后跟的路径名的首字符
		pid = atol(buff);			// 将字符串转换成long类型
		
		// 构建一个客户FIFO路径
		snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long)pid);
		// 只写方式打开该客户FIFO
		if ((writefifo = open(fifoname, O_WRONLY, 0)) < 0) {
			fprintf(stderr, "cannot open: %s\n", fifoname);
			continue;
		}

		// 打开客户请求的文件
		if ((fd = open(ptr, O_RDONLY)) < 0) {
			// 打开文件失败,构建出错字符串
			snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n", strerror(errno));
			n = strlen(ptr);
			// 通过客户的FIFO向客户返回一个出错消息
			if (write(writefifo, ptr, n) != n) {
				fprintf(stderr, "write error: %s\n", strerror(errno));
			}
			close(writefifo);
		} else {
			// 打开文件成功,把文件的内容复制到客户的FIFO中
			while ((n = read(fd, buff, MAXLINE)) > 0) {
				if (write(writefifo, buff, n) != n) {
					fprintf(stderr, "write error: %s\n", strerror(errno));
				}
				close(fd);			// 关闭打开的文件
				close(writefifo);	// 关闭客户的FIFO,使得客户的read返回0(文件结束符)
			}
		}
	}
	exit(0);
}

ssize_t readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t		n, rc;
	char		c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
again:
		if ((rc = read(fd, &c, 1)) == 1) {
			*ptr++ = c;
			if (c == '\n')			// 新的一行
				break;
		} else if (rc == 0) {
			*ptr = 0;				// 文件结束符,读取了(n-1)字节数据
			return(n - 1);
		} else {
			if (errno == EINTR)
				goto again;
			return(-1);				// 读取出错
		}
	}
	*ptr = 0;						// 
	return(n);
}

        打开FIFO来写的原因:要使我们不这么做,那么每当有一个客户终止时,该FIFO就变为空,于是服务器的read返回0,表示一个文件结束符。我们将不得不close该FIFO,并以O_RDONLY标志再次调用open,不过该调用会一直阻塞到下一个客户请求到达为止。然而如果我们总是有一个该FIFO的描述符打开着用于写,那么当不再有客户请求存在时,服务器的read一定不会返回0以指示读到一个文件结束符。相反,服务器只是阻塞在read调用中,等待下一个客户请求。

        服务器启动时,它的第一个open(使用O_RDONLY)将阻塞到第一个客户只写打开服务器的FIFO为止。它的第二个open(使用O_WRONLY)则立即返回,因为这时FIFO已经打开着用于读了。


        《UNIX网络编程:卷2》P48:图4-24 服务器程序协同工作的客户程序

/* P48 mainclient.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>

#define MAXLINE 1024
#define FILE_MODE	(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#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;

	pid = getpid();			// 获取进程ID
	// 构建客户FIFO路径
	snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long)pid);
	// 创建客户FIFO
	if ((mkfifo(fifoname, FILE_MODE) < 0) && (errno != EEXIST))
		fprintf(stderr, "can;t create %s\n", fifoname);

	// 客户的请求是由其进程ID、一个空格、一个路径名和一个换行符构成
	snprintf(buff, sizeof(buff), "%ld ", (long)pid);
	len = strlen(buff);
	ptr = buff + len;
	// 路径名指代请求服务器发给本客户的文件,从标准输入读入
	fgets(ptr, MAXLINE - len, stdin);
	len = strlen(buff);

	// 打开服务器的FIFO
	if ((writefifo = open(SERV_FIFO, O_WRONLY, 0)) < 0) {
		fprintf(stderr, "open %s error: %s\n", SERV_FIFO, strerror(errno));
	}
	// 往服务器FIFO中写入请求
	if (write(writefifo, buff, len) != len) {
		fprintf(stderr, "write error: %s\n", strerror(errno));
	}

	// 打开本客户的FIFO
	if ((readfifo = open(fifoname, O_RDONLY, 0)) < 0) {
		fprintf(stderr, "open %s error: %s\n", fifoname, strerror(errno));
	}
	// 从本客户的FIFO中读出服务器的应答,并写到标准输出
	while((n = read(readfifo, buff, MAXLINE)) > 0) {
		if (write(STDOUT_FILENO, buff, n) != n)
			fprintf(stderr, "write error: %s\n", strerror(errno));
	}

	close(readfifo);		// 关闭该客户的FIFO
	unlink(fifoname);		// 删除FIFO文件

	exit(0);
}


        Makefile文件:

mainserver:
    gcc mainserver.c -o mainserver -Wall
mainclient:
    gcc mainclient.c -o mainclient -Wall
clean:
    rm mainserver mainclient

        可以在一个窗口中启动服务器:

$ ./mainserver

        在另一个窗口中运行客户:

$ ./mainclient 
/etc/shadow					一个我们不能读的文件
/etc/shadow: can't open, Permission denied
$ ./mainclient 
Makefile					一个文本文件
mainserver:
	gcc mainserver.c -o mainserver -Wall
mainclient:
	gcc mainclient.c -o mainclient -Wall
clean:
	rm mainserver mainclient

        我们还可以从shell中与服务器交互,因为FIFO在文件系统中有名字:

$ Pid=$$									本shell的进程ID
$ mkfifo /tmp/fifo.$Pid						创建客户的FIFO
$ echo "$Pid Makefile" > /tmp/fifo.serv 	将命令写入服务器FIFO
$ cat < /tmp/fifo.$Pid						读出服务器的应答
mainserver:
	gcc mainserver.c -o mainserver -Wall
mainclient:
	gcc mainclient.c -o mainclient -Wall
clean:
	rm mainserver mainclient
$ rm /tmp/fifo.$Pid							删除客户FIFO

        在我们的shell例子中,服务器读出客户的请求后,会阻塞在对客户的FIFO的open调用中,因为客户(shell)还没有打开该FIFO来读。服务器对该FIFO的open调用一直阻塞到我们在以后某个时候执行cat命令为止,该命令打开这个FIFO,服务器对该FIFO的open调用随之返回。这种时间顺序关系还会导致拒绝服务型攻击。

你可能感兴趣的:(读书笔记,《UNIX网络编程》)