管道(pipe)
管道在Unix及Linux进程间通信是最基础的,很容易理解。管道就像一个自来水管,一端注入水,一端放出水,水只能在一个方向上流动,而不能双向流动。管道是典型的单向通信,即计算机网络中所说的“半双工”。管道又名匿名管道,所以只能用在具有公共祖先的进程之间使用,通常使用在父子进程之间通信。通常是父进程创建一个管道,然后fork一个子进程,此后父子进程共享这个管道进行通信。
管道由pipe函数创建,函数原型如下:
#include<unistd.h>
int pipe(int fd[2]); 成功返回0,否则返回-1;参数fd返回两个文件描述符,fd[0]为读,fd[1]为写,fd[1]的输入是fd[0]的输出。即fd[0]对应读端,fd[1]对应写端。
举例说明一下管道的用法:模拟client-server通信过程,父进程模拟client,子进程模拟server。server向client发送一个字符串,client接收到输出到屏幕。
pipe1.c
/* 单向通信 = “半双工” */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> // #include <errno.h> //errno == EINTR #include <unistd.h> // int pipe(int fd[2]),wait int main(int argc , char** argv) { int fd[2]; pid_t child; char buf[100]; memset(buf,0,100); //创建管道 if(pipe(fd) == -1) { perror("pipe error.\n"); exit(1); } child = fork(); if(child == -1) { perror("fork error.\n"); exit(1); } //进入子进程 if(child == 0) { printf("server input a msg: "); scanf("%s",buf); close(fd[0]); write(fd[1],buf,strlen(buf)); exit(0); //退出子进程 } memset(buf, 0, 100); close(fd[1]); read(fd[0],buf, 100); printf("client recv a msg: %s",buf); waitpid(child,NULL,0); //等待子进程运行结束 return 0; }
上面程序的细节问题在于子进程需要关闭读端,父进程需要关闭写端。因为管道最早提出时候是单向,虽然现在有些系统提供全双工的管道。那么如何采用管道实现双向通信呢?很显然我们需要两个管道,控制两个不同的数据流向。现在有模拟一个Client和Server双向通信的过程,Client与Server之间可以相互发送和接收信息。此时需要两个管道进行模拟,管道1模拟Server写Client读数据流向,管道2模拟Client写Server读数据流向。代码如下所示:
pipe2.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <unistd.h> int main(int argc, char** argv) { int fd1[2],fd2[2]; pid_t child; char buf[100]; if(pipe(fd1) == -1) { perror("pipe error.\n"); exit(1); } if(pipe(fd2) == -1) { perror("pipe error.\n"); exit(1); } child = fork(); if(child == -1) { perror("fork error"); exit(1); } if(child == 0) { memset(buf,0,100); printf("server input msg: "); gets(buf); close(fd1[0]); write(fd1[1],buf,strlen(buf)); close(fd2[1]); memset(buf, 0, 100); read(fd2[0],buf,100); printf("server recv: %s\n",buf); exit(0); } memset(buf,0,100); close(fd1[1]); read(fd1[0],buf,100); printf("client recv : %s\n",buf); memset(buf,0,100); printf("client input msg:"); gets(buf); close(fd2[0]); write(fd2[1],buf,strlen(buf)); waitpid(child, NULL,0); return 0; }
2 FIFO(first in first out)
FIFO又名有名管道,相对于上述管道而言。管道没有名字,因此只能在具有共同祖先进程的各个进程之间通信,无法在无亲缘关系的两个进程之间创建一个管道进行通信。为此有了FIFO,类似管道,也是一个单向(半双工)数据流,每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。FIFO有mkfifo函数创建。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode); 成功返回0,出错返回-1。pathname是一个普通的路径名,是FIFO的名字,mode指定文件的权位。
在创建FIFO后,必须打开来读或者打开来写,不能打开来既读既写(因为FIFO是半双工)。现在采用FIFO实现上面的第二个例子,代码如下:
fifo.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <errno.h> #include <unistd.h> //memset #include <sys/stat.h> //int mkfifo(const char* pathname, mode_t mode) #include <fcntl.h> //open, O_RDONLY #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) #define FIFO1 "/tmp/fifo.1" #define FIFO2 "/tmp/fifo.2" int main(int argc, char** argv) { int readfd,writefd; pid_t child; char buf[100]; if((mkfifo(FIFO1,FILE_MODE) < 0) && (errno != EEXIST )) { perror("mkfifo error.\n"); exit(1); } if((mkfifo(FIFO2,FILE_MODE) < 0) && (errno != EEXIST)) { unlink(FIFO1); perror("mkfifo error.\n"); exit(1); } child = fork(); if(child == -1) { perror("fork error.\n"); exit(1); } if(child == 0) { printf("in server:\n"); readfd = open(FIFO1,O_RDONLY,0); writefd = open(FIFO2,O_WRONLY,0); memset(buf,0,100); printf("server input msg:"); gets(buf); write(writefd,buf,strlen(buf)); memset(buf,0,100); read(readfd,buf,100); printf("server recv: %s\n",buf); exit(0); } printf("in client:\n"); //没出现死锁 writefd = open(FIFO1,O_WRONLY,0); readfd = open(FIFO2,O_RDONLY,0); //出现死锁 //readfd = open(FIFO2,O_RDONLY,0); //writefd = open(FIFO1,O_WRONLY,0); memset(buf,0,100); read(readfd,buf,100); printf("client recv : %s\n",buf); memset(buf,0,100); printf("client input msg:"); gets(buf); write(writefd,buf,strlen(buf)); waitpid(child,NULL,0); close(readfd); close(writefd); unlink(FIFO1); unlink(FIFO2); return 0; }
上面的程序当中父进程打开FIFO的顺序不能颠倒,否则会造成死锁。因为在当前没有任何进程打开某个FIFO来写的时候,打开该FIFO来读的进程将会阻塞。交换父进程中两个open的调用顺序后,父子进程都将打开同一个FIFO进行读,而当前没有任何进程来打开该文件进行写,于是父子进程都阻塞,造成死锁。
下面采用FIFO实现无亲缘关系的两个进程之间的通信。Client与Server是两个独立的进程。
fifo.h
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) #define FIFO1 "/tmp/fifo.1" #define FIFO2 "/tmp/fifo.2"
server.c
#include "fifo.h" int main(int argc, char** argv) { int readfd, writefd; char buf[100]; if((mkfifo(FIFO1,FILE_MODE) < 0) &&(errno != EEXIST) ) { perror("mkfifo error.\n"); exit(1); } if((mkfifo(FIFO2,FILE_MODE) <0 ) &&(errno != EEXIST)) { perror("mkfifo error.\n"); exit(1); } readfd = open(FIFO1,O_RDONLY,0); writefd = open(FIFO2,O_WRONLY,0); printf("server input msg:"); gets(buf); write(writefd, buf, strlen(buf)); read(readfd, buf, 100); printf("server recv: %s",buf); return 0; }
client.c
#incldue "fifo.h" int main(int argc,char** argv) { int readfd,writefd; char buf[100]; if((mkfifo(FIFO1,FILE_MODE) < 0) && (errno != EEXIST)) { perror("mkfifo error.\n"); exit(1); } if((mkfifo(FIFO2,FILE_MODE) < 0) && (errno != EEXIST)) { perror("mkfifo error.\n"); exit(1); } writefd = open(FIFO1,O_WRONLY,0); readfd = open(FIFO2,O_RDONLY,0); memset(buf,0,100); read(readfd,buf,100); printf("client recv: %s\n"); memset(buf,0,100); printf("client input msg:"); gets(buf); writefd(writefd,buf,stelen(buf)); close(readfd); close(writefd); unlink(readfd); unlink(writefd); return 0; }