附加:进程通信-实验1:管道通信

一.实验目的

·理解并掌握无名管道和命名管道的原理和使用方法

二.实验背景

·什么是管道?
本质上,一个管道是一个只存在于内存中的文件,但与一般文件的属性不同,它不能以读写方式打开。对这个文件的操作要通过两个分别以只读和只写方式打开的文件进行,它们分别代表管道的两端,即读端、写端。通过写端和读端,管道实现了两个进程间进行单向通信的机制。根据适用范围的不同,管道可以分为无名管道和命名管道。

·无名管道主要用于父、子进程或兄弟进程等相关进程之间的通信。在Linux系统中可以通过系统调用建立起一个单向的通信管道,这种关系一般都是由父进程建立。当需要双向通信时需要建立两个管道,各自实现一个方向上的通信。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写数据,而另一个从管道中读取数据。

·命名管道主要用于不相关进程间的通信,进程可以通过管道的名称来查找该管道。实现一个命名管道实际上就是实现一个FIFO(First In First Out)文件,该文件建立在实际的文件系统上,拥有自己的文件名称,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。命名管道一旦建立,之后它的读、写以及关闭操作都与无名管道完全相同。

·虽然与命名管道对应的FIFO文件inode节点是建立在文件系统中,但是仅是一个节点而已,文件的数据还是存在于内存缓冲页面中,这一点和无名管道相同。

三.关键代码及分析

·用标准函数库提供的popen和pclose函数

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

·popen函数通过创建一个管道、创建子进程、启动并调用shell等步骤实现一个子进程的执行;

·参数command表示生成的子进程启动shell后要指明的命令;type指明文件的属性(读/写)

int pclose(FILE *stream);

·pclose函数用于关闭由popen创建的关联文件流。pclose只在popen启动的进程结束后才返回,如果调用pclose时被调用进程仍在运行,pclose调用将等待该进程结束。它返回关闭的文件流所在进程的退出码

·无名管道的使用方法2

int pipe(int pipefd[2]);

·pipe是对管道的一个底层调用,功能是创建一个用于进程间通信的管道。数组pipefd返回所创建管道的两个文件描述符,其中pipefd[0]表示管道的读端,而pipefd[1]表示管道的写端。从写端写入到管道中的数据由内核进行缓存,直到有进程从读端将数据读出。

·pipe函数跟popen函数的一个重大区别是,popen函数是基于文件流(FILE)工作的,而pipe函数是基于文件描述符工作的,所以在使用pipe创建的管道要使用read和write调用来读取和发送数据。

·命名管道相关函数

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

·功能是创建一个FIFO特殊文件,也就是命名管道,该文件的名称为第一个参数pathname,第二参数mode指明该文件的权限。

·命名管道在使用之前必须打开

open(const char * pathname, O_RDONLY); 
open(const char * pathname, O_RDONLY | O_NONBLOCK);
open(const char * pathname, O_WRONLY);
open(const char * pathname, O_WRONLY | O_NONBLOCK);

·基于管道的双向通信
实现服务器与客户端的双向传输,需要两个管道。一个管道用于从客户端向服务器传送数据,客户端使用管道的写端,服务器使用管道的读端;另一个管道实现从服务器到客户端的数据传送,服务器使用管道的写端,客户端使用管道的读端。

fwrite(buf, sizeof(char), len, fpw);              
len = fread(buf, sizeof(char), BUFSIZ, fpr);

write(fd[WRITE_END], write_msg, strlen(write_msg)+1);
read(fd[READ_END], read_msg, BUFFER_SIZE);
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>

#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1

int main(void)
{
char write_msg[BUFFER_SIZE] = "Greetings";
char read_msg[BUFFER_SIZE];
pid_t pid;
int fd[2];
if (pipe(fd) == -1) { /* 创建管道 */
fprintf(stderr,"Pipe failed");
return 1;
}
pid = fork(); /*创建子进程*/
if (pid < 0) {
fprintf(stderr, "Fork failed");
return 1;
}
if (pid > 0) { /* 父进程*/
close(fd[READ_END]); /* 关闭不使用的读端*/
/*向管道中写入数据 */
write(fd[WRITE_END], write_msg, strlen(write_msg)+1);
close(fd[WRITE_END]); /* 关闭管道的写端*/
wait(NULL);
}
else { /* 子进程 */
close(fd[WRITE_END]); /* 关闭不使用的写端*/
/*从管道中读取数据 */
read(fd[READ_END], read_msg, BUFFER_SIZE);
printf("child read %s\n",read_msg);
close(fd[READ_END]); /* 关闭管道的读端*/
}
return 0;
}

修改上述的else中的代码:

//使标准输入指向fd[READ_END]  
close(0);
dup(fd[READ_END]);
//关闭fd[WRITE_END],fd[READ_END],只剩下标准输入
close(fd[WRITE_END]);
close(fd[READ_END]);
//启动新进程od
execlp("/usr/bin/od", "od", "-c", NULL);
return 0;


for(;;)
{
nread=read(p[0],buf,MSGSIZE);//根据读取返回确定后续操作
switch(nread)
{
case -1:
if(errno==EAGAIN) //非阻塞模式下才会有的返回值
{
printf("pipe empty\n");
sleep(1);
break;
}
else
error("read call");
case 0:
printf("End of conversation\n");
exit(0);
default:
printf("MSG=%s\n",buf);
if (strcmp(buf, msg2) ==0)
{
printf("the parent %d will exit\n",getpid());
}

}
}

四.实验结果与分析

· //编译连接生成可执行程序
gcc -o pipe_test01 pipe_test01.c
gcc -o pipe_test02 pipe_test02.c
gcc -o pipe_test03 pipe_test03.c

附加:进程通信-实验1:管道通信_操作系统

· //执行生产的可执行程序
./pipe_test01
./pipe_test02
./pipe_test03

附加:进程通信-实验1:管道通信_linux_02