【UNIX网络编程】进程间通信之管道

管道是最早的Unix进程间通信形式,它存在于全部的Unix实现中。关于管道,有例如以下几点须要知道:

1、它是半双工的,即数据仅仅能在一个方向上流动。虽然在某些Unix实现中管道能够是全双工的。但须要对系统进行某些设置。在Linux系统中,它是半双工的。

2、它没有名字。因此仅仅能在具有公共祖先的进程之间使用。

通经常使用在父子进程间。虽然这一点随着“有名管道FIFO”的增加得到改正了。但应该把它们看作是两种不同的进程间通信方式。

3、它由pipe函数创建,read和write函数訪问,提供单向数据流。除了pipe外。在C函数库里,还有另外一个函数popen完毕一个新管道的创建、一个新进程的启动、关闭管道的不使用端、运行shell命令、等待命令终止等一系列操作。

管道使用演示样例:

在shell命令中,我们经经常使用到"cmd1 | cmd2"这一类的命令,cmd1和cmd2之间就是通过管道来进行连接的。

shell负责两个命令的标准的输入好标准输出:

cmd1的标准输入来自终端键盘。

cmd1的标准输出传递给cmd2,作为它的标准输入。

cmd2的标准输出连接到终端屏幕。

知识点1:pipe函数

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

进程调用pipe函数创建一个管道。pipe函数的參数是一个由两个整数类型的文件描写叙述符组成的数组的指针。该函数在数组中成功填入两个新的文件描写叙述符后返回0,假设失败则返回-1并设置errno来表明失败的原因。

errno的值有下面三种可能:

EMFILE:进程使用的文件描写叙述符过多。

ENFILE:系统的文件表已满。

EFAULT:文件描写叙述符无效。

两个新填入的文件描写叙述符:fd[0]为读而打开。fd[1]为写而打开。fd[1]的输出是fd[0]的输入。也就是说利用write函数写到fd[1]的全部数据都能够从fd[0]读出来。示比例如以下:

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

#define MAXLINE 2048

int
main(int argc, char **argv)
{
	int fd[2];
	int data;
	char buff[MAXLINE];
	const char some_data[] = "123";

	memset(buff, '\0', sizeof(buff));

	if(pipe(fd) == -1){
		exit(EXIT_FAILURE);
	}else{
		data = write(fd[1], some_data, strlen(some_data));
		data = read(fd[0], buff, data);
		printf("read %d bytes : %s\n", data, buff);
		exit(EXIT_SUCCESS);
	}
}

程序执行结果:

[email protected]:/work/tmp/unp$ ./a.out
read 3 bytes : 123

上面的样例是在同一进程中使用管道的样例,但实际使用过程中非常少这样使用。一般都是在两个不同进程(一般是父子进程)间进程通信的。两个进程间使用管道的演示样例:

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

#define MAXLINE 2048

int
main(int argc, char **argv)
{
	int fd[2];
	int data;
	pid_t child_pid;
	char buff[MAXLINE];
	const char some_data[] = "123";

	memset(buff, '\0', sizeof(buff));

	if(pipe(fd) == 0){
		child_pid = fork();
		if(child_pid == -1){
			fprintf(stderr, "fork error.");
			exit(EXIT_FAILURE);
		}else if(child_pid == 0){
			close(fd[1]);  //关闭子进程的写入端
			data = read(fd[0], buff, MAXLINE);//从子进程的读取段读取数据
			printf("read %d bytes: %s\n", data, buff);
			exit(EXIT_SUCCESS);
		}else{
			close(fd[0]);//关闭父进程的读取段
			data = write(fd[1], some_data, strlen(some_data));//从父进程的写入端写入数据
			printf("wrote %d bytes\n", data);
		}
	}
	exit(EXIT_SUCCESS);
}

程序执行结果是:

[email protected]:/work/tmp/unp$ ./a.out
wrote 3 bytes
read 3 bytes: 123

[email protected]:/work/tmp/unp$ ./a.out
wrote 3 bytes
[email protected]:/work/tmp/unp$ read 3 bytes: 123

这是由于。假设父进程先于子进程结束。就会看到shell提示符了。由上面的演示样例能够看出,要通过管道完毕父子进程间的通信,先由父进程创建一个管道后调用fork派生一个自身的副本,接着,父进程关闭这个管道的读出端。子进程关闭同一管道的写入端。这就在父子进程间提供了一个单向数据流。

当管道的一端被关闭后,下列两条规则起作用:

1、当读一个写端已被关闭的管道时,在全部数据都被读取后,read返回0,以指示达到了文件结束处。

2、假设写一个读端已被关闭的管道,则产生信号SIGPIPE。

假设忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1,errno设置为EPIPE。

上面的两个样例都是半双工的即单向的,仅仅提供一个方向的数据流。

当须要一个双向数据流时,必须创建两个管道。每一个方向一个。实际过程例如以下:

1、创建管道1和管道2

2、fork

3、父进程关闭管道1的读出端、关闭管道2的写入端

3、子进程关闭管道1的写入端、关闭管道2的读出端。

创建两个管道后就能够完毕一个简单的client-server样例。相关演示样例请点此链接

知识点2:popen和pclose函数

popen和pclose函数不是Unix实现的。它们时标准I/O库提供的,它们实现的操作时:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端。执行一个shell以执行命令,然后等待命令终止。

#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);//返回值:若成功则返回文件指针,若出错则返回NULL
int     pclose(FILE *fp);//返回值:cmdstring的终止状态,若出错则返回-1

函数popen先运行fork,然后调用exec以运行cmdstring,而且返回一个标准I/O文件指针。

假设type是“r”,则文件指针连接到cmdstring的标准输出。返回的文件指针时可读的。

假设type是“w”,则文件指针连接到cmdstring的标准输入,返回的文件指针时可写的。

图示popen例如以下:

假设type是"r",被调用程序的输出就能够由调用程序使用,调用程序利用popen函数返回的文件指针,就能够通过经常使用的stdio库函数(如fread)来读取被调用程序的输出;假设type是"w",调用程序就能够用fwrite调用向被调用程序发送数据,而被调用程序能够在自己的标准输入上读取这些数据。

type是r的演示样例程序例如以下:

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

int
main(int argc, char **argv)
{
	FILE *read_fp;
	char buff[BUFSIZ + 1];
	int  chars_read;
	memset(buff, '\0', sizeof(buff));
	read_fp = popen("uname -a", "r");//打开连接到uname命令的管道。把管道设置为可读方式并让read_fp指向该命令输出
	if(read_fp != NULL){
		chars_read = fread(buff, sizeof(char), BUFSIZ, read_fp);
		if(chars_read > 0)
				printf("Output was : -\n%s\n", buff);
		pclose(read_fp);
		exit(EXIT_SUCCESS);
	}
	exit(EXIT_SUCCESS);
}

执行结果:

Output was : -
Linux book-desktop 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux

type是w的演示样例程序例如以下:

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

int
main(int argc, char **argv)
{
	FILE *fp;
	char buffer[BUFSIZ + 1];

	sprintf(buffer, "Once upon a time there was ...\n");
	fp = popen("od -c", "w");
	if(fp != NULL){
		fwrite(buffer, sizeof(char), strlen(buffer), fp);
		pclose(fp);
		exit(EXIT_SUCCESS);
	}
	exit(EXIT_SUCCESS);
}

程序结果例如以下:

0000000   O   n   c   e       u   p   o   n       a       t   i   m   e
0000020       t   h   e   r   e       w   a   s       .   .   .  \n
0000037

也能够上上面的pipe函数一样实现client-server程序。演示样例代码请点此链接

參考:

1、上面解说的图示部分:http://blog.csdn.net/to_be_it_1/article/details/28138063

2、《Linux程序设计》 Neil Matthew&&Richard Stones

3、《UNIX环境高级编程》Richard Stevenson

4、《UNIX网络编程 卷2》 Richard Stevenson

时间: 2024-10-04 05:08:05

【UNIX网络编程】进程间通信之管道的相关文章

UNIX网络编程 卷2:进程间通信

这篇是计算机类的优质预售推荐>>>><UNIX网络编程 卷2:进程间通信(第2版)> UNIX和网络专家W. Richard Stevens的传世之作 编辑推荐 两卷本的<UNIX网络编程>是已故著名技术作家W. Richard Stevens的传世之作.卷2着重讨论怎样让应用程序与在其它机器上的应用程序进行对话. 良好的进程间通信(IPC)机制是提高UNIX程序性能的关键. 本书全面深入地解说了各种进程间通信形式,包括消息传递.同步.共享内存及远程过程调用

【UNIX网络编程】FIFO

管道作为进程间通信的最古老方式,它的缺点是没有名字,因此只能用在有亲缘关系的父子进程之间.对于无亲缘关系的进程间,无法用管道进行通信.FIFO可以完成无亲缘关系的进程间的通信,FIFO也被称为命名管道.它是一种特殊类型的文件,在文件系统中以文件名的形式存在,但它的行为却和上面提到的管道类似. 创建命名管道有两种方法: 1.在命令行上执行命令:mkfifo filename 来创建FIFO. 2.使用mkfifo函数创建FIFO. #include <sys/stat.h> #include &

Unix网络编程 之 socket简介

概述 Socket的英文原意是"孔"或"插座",现在,作为Unix的进程通信机制,常常取"插座"这一意义.日常生活中常见的插座,有的是信号插座,有的是电源插座,有的可以接收信号或能量,有的可以发送信号或能量.举例来说,电话线与电话机之间需要一个插座(相当于两者之间的接口,这一部分装置物理上是存在的).对于网络编程,socket就相当于电话线与电话机之间的插座. 将电话系统与面向连接的Socket机制相比,两者之间有着惊人的相似的地方.以一个国家的

【UNIX网络编程(四)】TCP套接字编程详细分析

引言: 套接字编程其实跟进程间通信有一定的相似性,可能也正因为此,stevens这位大神才会将套接字编程与进程间的通信都归为"网络编程",并分别写成了两本书<UNP1><UNP2>.TCP套接字编程是套接字编程中非常重要的一种,仔细分析,其实它的原理并不复杂.现在就以一个例子来详细分析TCP套接字编程. 一.示例要求: 本节中试着编写一个完成的TCP客户/服务器程序示例,并对它进行深入的探讨.该示例会用到绝大多数的基本函数,未用到但比较重要的函数会在后面的补充上

将UNIX网络编程卷2的库函数合并到卷1的库函数中

源起 前面讲述了unix网路编程卷1库函数的配置.但是卷2还有一个配置,而且其中的关于进程间通信的函数在卷1中也没有. 我们使用两个库函数不免有些不方便,现在将卷2中的在卷1中没有的函数都合并到卷1的库函数中. 1.创建unix网络编程卷2——进程间通信configure.h配置文件    cd 目录    ./configure    之后创建了configure.h文件. 2.合并unix网路编程卷1和卷2的configure.h文件    将上面生成的configure.h的头文件的宏定义

Unix网络编程_卷1卷2

1. UNIX 网络编程(第2版)第1卷:套接口API和X/Open 传输接口API PDFhttp://www.linuxidc.com/Linux/2014-04/100155.htm UNIX网络编程卷1:套接字联网API(第3版) 中文高清带完整书签 PDFhttp://www.linuxidc.com/Linux/2014-04/100222.htm UNIX网络编程.卷2:进程间通信(第2版)http://www.linuxidc.com/Linux/2013-01/77936.ht

UNIX网络编程-基本API介绍(二)

参考链接:http://www.cnblogs.com/riky/archive/2006/11/24/570713.aspx 1.getsockname和getpeername getsockname函数获取与套接口关联的本地协议地址. getpeername函数获取与套接口关联的远程协议地址. ---------------------------------------------------------------------- #include <sys/socket.h> int 

Unix网络编程-同步

1.互斥锁(量)和条件变量 默认情况下互斥锁和条件变量用于线程间同步,若将它们放在共享内存区,也能用于进程间同步. 1.1 互斥锁 1.概述: 互斥锁(Mutex,也称互斥量),防止多个线程对一个公共资源做读写操作的机制,以保证共享数据的完整性. 用以保护临界区,以保证任何时候只有一个线程(或进程)在访问共享资源(如代码段).保护临界区的代码形式: lock_the_mutex(...); 临界区 unlock_the_mutex(...); 任何时刻只有一个线程能够锁住一个给定的互斥锁. 下面

UNIX网络编程入门——TCP客户/服务器程序详解

前言 最近刚开始看APUE和UNP来学习socket套接字编程,因为网络这方面我还没接触过,要等到下学期才上计算机网络这门课,所以我就找了本教材啃了一两天,也算是入了个门. 至于APUE和UNP这两本书,书是好书,网上也说这书是给进入unix网络编程领域初学者的圣经,这个不可置否,但这个初学者,我认为指的是接受过完整计算机本科教育的研究生初学者,需要具有完整计算机系统,体系结构,网络基础知识.基础没打好就上来啃书反而会适得其反,不过对于我来说也没什么关系,因为基础课也都上得差不多了,而且如果书读