管道和FIFO

1、概述

管道是最初Unix IPC形式,他们的存在没有名字,只能用于具有血缘关系的进程之间的通信(如父子进程)。这一点随着FIFO的引入得到改正,FIFO有时称为有名管道(named pipe)。

2、一个简单客户端-服务器例子

客户端从标准输入(stdin)读进一个路径名,并把它写入IPC通道,服务器从该IPC通道读出这个路径名,并尝试打开其文件内容来读。如果服务器能打开文件,它就读取其内容,并写回另外一个IPC通道,客户端随后从该IPC通道中读取内容,并写到标准输出(stdout).如果服务器无法读取路径指定的文件内容,则返回一个错误信息。

3、 管道

#include <unistd.h>

int pipe(int fd[2]);

 pipe()函数返回两个文件描述符, fd[0]用来读,fd[1]用来写。

宏S_ISPIPE可用来确定一个文件描述符是管道还是FIFO, 它的唯一参数是stat结构中的st_mode成员。对于管道来说通过fstat函数获取。

 pipe尽管是在单个进程中创建,但是很少在单个进程中使用,一般是用来父子进程之间的通信的,下面是一个常用的情景。

 父进程首先通过pipe创建管道,然后通过fork函数创建一个子进程,这样父子经常的管道就指向了相同的内容,他们可以根据自己的需要关闭其中一个描述符,如父进程只负责写fd[1](关闭读fd[0]), 子进程只负责读fd[0](关闭写fd[1])。

如图,这样就可以实现进程间的单向通信。

上面这种方式实现的都是半双工的(即单向通信的), 如果需要实现双向通信,则需要创建两个管道。

 

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>

#include <string.h>
void client(int rd, int wr);
void server(int rd, int wr);


int main(int argc, char *argv[])
{
	int fd1[2], fd2[2];
	pid_t childpid;
	int ret;

	ret = pipe(fd1);
	ret |= pipe(fd2);

	if (ret < 0) {
		fprintf(stderr, "pipe errr\n");
		return -1;
	}

	if ((childpid = fork()) == 0) {
		/* child process */
		close(fd1[1]);
		close(fd2[0]);
		server(fd1[0], fd2[1]);
		exit(0);  // 子进程结束之后,父进程不知道,没有回收资源,编程僵尸进程
	} else if (childpid < 0) {
		fprintf(stderr, "fork err\n");
		return -1;
	}

	/* parent */
	close(fd1[0]);
	close(fd2[1]);

	client(fd2[0], fd1[1]);

	waitpid(childpid, NULL, 0);  // 父进程结束需要回收子进程的资源,不然会变成孤儿进程
	exit(0);

}

#define MAXLINE 1024
void client(int rd, int wr)
{
	size_t len;
	ssize_t n = 0;
	char buf[MAXLINE] = {0};
	fgets(buf, MAXLINE, stdin);
	len = strlen(buf);

	if (buf[len -1] == '\n') {
		len--;
	}

	write(wr, buf, len);

	while ((n = read(rd, buf, MAXLINE)) > 0) {
		write(STDOUT_FILENO, buf, n);
	}

}

void server(int rd, int wr)
{
	int fd;
	ssize_t n;
	char buf[MAXLINE] = {0};

	if ((n = read(rd, buf, MAXLINE)) == 0) {
		fprintf(stderr, "read file path fail\n");

		exit(1);
	}

	buf[n] = '\0';
	if ((fd = open(buf, 0, O_RDONLY)) < 0) {
		snprintf(buf + n, sizeof(buf) - n, "cannot open %s\n", strerror(errno));
		n = strlen(buf);

		write(wr, buf, n);
	} else {
	
		while ((n = read(fd, buf, MAXLINE)) > 0) {
			write(wr, buf, n);
		}

		close(fd);
	}
	
}

3、全双工管道

socketpair函数:

//NAME
//       socketpair - create a pair of connected sockets
//
//SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socketpair(int domain, int type, int protocol, int sv[2]);
//On success, zero is returned.  On error, -1 is returned

全双工的可能实现如下图,隐含的意思是,整个管道只存在一个缓冲区,协议管道的任何数据都添加到缓冲区的末尾,从管道读取都是从缓冲区的开头读取。

但是这样的实现是有问题的,实际上sockerpair的实现的全双工是使用两个半双工管道实现的,写入fd[1]的只能从fd[0]读取, 从fd[0]写入的只能从fd[1]读取。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>


int main(int argc, char *argv)
{
	int fd[2];
	int n;
	char c;
	
	pid_t childpid;
	
	if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, fd) < 0) {
		fprintf(stderr, "socketpair fail\n");
		exit(-1);
	}
	
	if ((childpid = fork()) < 0) {
		fprintf(stderr, "fork fail\n");
		close(fd[0]);
		close(fd[1]);
		
		exit(-1);
	} else if (childpid == 0) {
		/* child process */
		sleep(3);
		if ((n = read(fd[0], &c, 1)) != 1) {
			fprintf(stderr, "child read return %d\n", n);
			exit(-2);
		}	
		
		fprintf(stderr, "child read  %c\n", c);
		
		write(fd[0], "c", 1);
		exit(0);
	}
	
	/* parent process */
	write(fd[1], "p", 1);
	
	if ((n = read(fd[1], &c, 1)) != 1) {   // block here wait child write back
		fprintf(stderr, "child read return %d\n", n);
		exit(-1);
	}
	
	fprintf(stderr, "parent read  %c\n", c);
	
	exit(0);	
}

 4、popen和pclose函数

标准I/O函数提供了popen函数,它创建了一个管道并启动另外一个进程,该进程要么从该管道读取标准输入,要么往该管道写入标准输出。

//NAME
//       popen, pclose - pipe stream to or from a process

//SYNOPSIS
       #include <stdio.h>

       FILE *popen(const char *command, const char *type);

       int pclose(FILE *stream);

popen函数调用进程和所指定的命令之间创建一个管道,由popen返回的值是一个FILE指针,该指针用于输入或者输出,取决于type.

- 如果type为r, 则调用进程读取command的标准输出。

- 如果type为w, 则调用进程写到command的标准输入。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define MAXLINE 1024

int main(int argc, char *argv[])
{

	size_t n;
	char buf[MAXLINE], cmd[MAXLINE + 5];
	FILE *fp = NULL;
	
	if (argc != 2) {
		fprintf(stderr, "usage: ./a.out <filename>\n");
		exit(-1);
	}

	snprintf(cmd, sizeof(cmd), "cat %s", argv[1]);
	
	if ((fp = popen(cmd, "r")) == NULL) {
		fprintf(stderr, "popen: %s fail\n", cmd);
		exit(-2);
	}
	
	while ( fgets(buf, MAXLINE, fp) != NULL) {
		fputs(buf, stdout);
	}
	
	pclose(fp);
	
	exit(0);
}

5、FIFO

管道是没有名字的,这就限制了它只能在有血缘关系的进程之间使用,这就引入了一个有名管道fifo,它可以在没有关系的进程之间通信。

FIFO由mkfifo函数创建。

//NAME
//       mkfifo, mkfifoat - make a FIFO special file (a named pipe)

//SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);

pathname: 是一个普通的unix路径名,他是该FIFO的名字。

mode: 指定该文件权限位,同open函数,定义在<sys/stat.h>头文件中。

mkfifo函数已经隐含指定了O_CREAT | O_EXCL,也就是说要么创建成功,要么返回一个EEXIST错误。如果不希望创建一个新的FIFO, 就改为调用open函数而不是mkfifo函数,要调用或者创建一个FIFO,应先调用mkfifo函数,再检查是否返回EEXIST错误,若返回该错误则调用open函数。

6、管道和FIFO的额外属性

设置为非阻塞状态:

1. 调用open时可指定O_NONBLOCK标志。

writefd = open(FIFO1, O_WRONLY | O_NONBLOCK);

 2. 如果一个描述符已经打开,可以调用fcntl函数设置为非阻塞。对于管道来说必须使用这种技术,因为管道没有open调用,在pipe调用中也无法指定O_NONBLOCK标志。

int flags;

if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
    printf("fail\n");
    exit(-1);
}

flags |= O_NONBLOCK;

if (fcntl(fd, F_SETFL, flags ) < 0) {
    printf("fail\n");
     exit(-2);
}

注意:设置属性的时候不能删除其他属性设置,所有要先获取,然后在原来的基础上做操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值