进程间通信概述
需要进程通信的原因:
数据传输
资源共享
通知事件
进程控制
Linux进程间通信(IPC)发展由来
Unix进程间通信
基于System V进程间通信(System V:UNIX系统的一个分支)
POSIX进程间通信(POSIX:可移植操作系统接口,为了提高UNIX环境下应用程序的可移植性。很多其他系统也支持POSIX标准(如:DEC OpenVMS和Windows)。)
现在Linux使用的进程间通信方式包括:
共享文件
管道(pipe)、命名管道(FIFO):只能传输无格式的字节流
信号(signal):能够传输的信号量有限
消息队列(报文队列):克服了上述的缺点
共享内存
信号量
套接字(socket)
简单的客户端-服务器或IPC模型
几种进程间通信方式的优缺
PIPE
缺点:进程必须有相同的祖先,管道不是持久化的
FIFO
缺点
信号
共享内存
消息队列
信号量
套接字
管道通讯
管道的读写规则:http://www.cnblogs.com/mickole/p/3192461.html
管道:(半双工方式)单向的、先进先出的,把一个进程的输出和另一个进程的输入连接起来。一个进程(写进程)在管道尾部写入数据,另一个进程(读进程)从管道的头部读出数据。包括无名管道和有名管道,无名管道只用于父进程和子进程间的通信,有名管道可用于运行同一系统中的任意两个进程间的通信。管道也是文件。
注意:无论何时,当不再需要读/写端,关闭它!
查看内核中常数PIPE_BUF规定的管道缓存器的大小
$ find ./ -name "*.h" -exec grep "PIPE_BUF" {} ; -print | more
管道的创建
无名管道创建
int pipe(int filedis[2]);
int pipe_fd[2];
if(pipe(pipe_fd)<0) { printf("pipe create error "); return -1; }
当一个管道建立时,他会创建两个文件描述符,filedis[0]用于读管道,filedis[1]用于写管道。
管道的关闭
int close(); //使用close分别关闭两个文件描述符
当管道的一端被关闭后,下面两条规则起作用
1.当读(read)一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束(从技术方面考虑,管道的写端还有进程时,就不会产生文件的结束。可以复制一个管道的描述符,使得有多个进程具有写打开文件描述符。但是,通常一个管道只有一个读进程,一个写进程。下一节介绍FIFO时,我们会看到对于一个单一的FIFO常常有多个写进程)。
2.如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write出错返回,errno设置为EPIPE。
在写管道时,常数PIPEBUF规定了内核中管道缓存器的大小。如果对管道进行w r i t e调用,而且要求写的字节数小于等于PIPEBUF,则此操作不会与其他进程对同一管道(或F I F O)的w r i t e操作穿插进行。但是,若有多个进程同时写一个管道(或F I F O),而且某个或某些进程要求写的字节数超过PIPEBUF字节数,则数据可能会与其他写操作的数据相穿插。
管道在不同情况下的特定表现
1.一个进程打开了一个管道来写,然而,还什么都没写,此时,有一个进程进来读。
此时读进程会等待写进程写入东西后再读。
2.管道是空的,又没有进程打开该管道来写,此时有进程进来读。
此时读进程不会等待,立即去读空的管道。读取的数据也为空。
3.若调用exec…家族,文件描述符/管道会如何?
exec函数族,介绍见:http://www.cnblogs.com/kwseeker-bolgs/p/4346047.html
(1)子进程继承文件描述符和管道的拷贝(同时,包括信号状态,调度参数等)
(2)文件描述符号可以访问,但是相应的逻辑符号名不可访问
使用无名管道通信实例
通常,进程会先调用pipe,接着调用fork(这两步为了确保只生成一个管道,且子进程生成后能够继承文件描述符),从而创建从父进程到子进程的IPC管道。此过程中父进程与子进程分别有一对文件描述符。刚开始父进程写入,则关闭父进程的读端;子进程读出,则关闭子进程的写端。注意等待写完再读。sleep(2)
1 #include <unistd.h> 2 #include <string.h> 3 #include <sys/types.h> 4 #include <errno.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 8 int main() 9 { 10 int pipe_fd[2]; 11 pid_t pid; 12 char buf_r[100]; 13 char* p_wbuf; 14 int r_num; 15 16 memset(buf_r,0,sizeof(buf_r)); 17 18 /*创建管道*/ 19 if(pipe(pipe_fd)<0) 20 { 21 printf("pipe create error "); 22 return -1; 23 } 24 25 /*创建子进程*/ 26 if((pid=fork())==0) //子进程 27 { 28 printf(" "); 29 close(pipe_fd[1]); //写关闭 30 sleep(2); /*为什么要睡眠*/ 31 if((r_num=read(pipe_fd[0],buf_r,100))>0) 32 { 33 printf( "%d numbers read from the pipe is %s ",r_num,buf_r); 34 } 35 close(pipe_fd[0]); 36 exit(0); 37 } 38 else if(pid>0) //父进程 39 { 40 close(pipe_fd[0]); //读关闭 41 if(write(pipe_fd[1],"Hello",5)!=-1) 42 printf("parent write1 Hello! "); 43 if(write(pipe_fd[1]," Pipe",5)!=-1) 44 printf("parent write2 Pipe! "); 45 close(pipe_fd[1]); 46 sleep(3); 47 waitpid(pid,NULL,0); /*等待子进程结束*/ 48 exit(0); 49 } 50 return 0; 51 }
命名管道的创建
命名管道实质上是一个文件
命名管道的特点:
1.持久化
2.有属主和访问权限
3.严格遵循先进先出,不支持 lseek等文件定位操作
~~~~~~~~~~~~~~~~~~~~~
创建FIFO
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char* pathname, mode_t mode);
int mknod(const char* pathname, mode_t mode|S_IFIFO, (dev_t)0);
pathname: FIFO文件名
mode:属性同文件操作中的mode.
S_IFIFO:表示创建一个命名管道
创建FIFO若成功则返回0,失败返回-1.错误原因存于error中。使用perror和strerror可以打印出详细信息。
#include <stdio.h> #include <errno.h> perror("mkfifo failed"); printf("error: %s ", strerror(errno));
错误代码
EACCESS 参数pathname所指定的目录路径无可执行的权限
EEXIST 参数pathname所指定的文件已存在。
ENAMETOOLONG 参数pathname的路径名称太长。
ENOENT 参数pathname包含的目录不存在
ENOSPC 文件系统的剩余空间不足
ENOTDIR 参数pathname路径中的目录存在但却非真正的目录。
EROFS 参数pathname指定的文件存在于只读文件系统内。
一旦创建了FIFO,就可以用open打开它,一般的文件访问函数(close/read/write等)都可用于FIFO。
FIFO创建读写的模板
//创建 int ret; ... ret=mkfifo("/tmp/cmd_pipe", S_IFIFO | 0666); if(ret == 0){ //创建成功 } else ... { //创建失败 } //写入 pfp=fopen("/tmp/cmd_pipe", "w+"); ... ret=fprintf(pfp, "Here is a test string! "); //读出 pfp=fopen("/tmp/cmd_pipe", "r"); ... ret=fgets(buffer, MAX_LINE, pfp);
~~~~~~~~~~~~~~~~~~~~~
打开FIFO时,非阻塞标志O_NONBLOCK对以后的读写产生的影响:
1.没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞,直到有进程对命名管道执行写入,才正常返回;同样,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。
2.使用O_NONBLOCK:访问要求无法满足时进程不阻塞,立即出错返回,errno是ENXIO。如打开FIFO读取数据但是没有其他进程打开FIFO来写入;或打开FIFO来写入但是没有其他进程对FIFO进行读取。都会立刻返回ENXIO。
打开FIFO文件和普通文件的区别有2点:
第一个是不能以O_RDWR模式打开FIFO文件进行读写操作。这样做的行为是未定义的。
因为我们通常使用FIFO只是为了单向传递数据,所以没有必要使用这个模式。
如果确实需要在程序之间双向传递数据,最好使用一对FIFO或管道,一个方向使用一个。或者采用先关闭在重新打开FIFO的方法来明确改变数据流的方向。
第二是对标志位的O_NONBLOCK选项的用法。
使用这个选项不仅改变open调用的处理方式,还会改变对这次open调用返回的文件描述符进行的读写请求的处理方式。
O_RDONLY、O_WRONLY和O_NONBLOCK标志共有四种合法的组合方式:
- flags=O_RDONLY:open将会调用阻塞,除非有另外一个进程以写的方式打开同一个FIFO,否则一直等待。
- flags=O_WRONLY:open将会调用阻塞,除非有另外一个进程以读的方式打开同一个FIFO,否则一直等待。
- flags=O_RDONLY|O_NONBLOCK:如果此时没有其他进程以写的方式打开FIFO,此时open也会成功返回,此时FIFO被读打开,而不会返回错误。
- flags=O_WRONLY|O_NONBLOCK:立即返回,如果此时没有其他进程以读的方式打开,open会失败打开,此时FIFO没有被打开,返回-1。
#include<unistd.h> #include<stdlib.h> #include<stdio.h> #include<string.h> #include<fcntl.h> #include<sys/types.h> #include<sys/stat.h> #define FIFO_NAME "/tmp/my_fifo" int main(int argc,char *argv[]){ int res,i; int open_mode=0; if(argc < 2){ fprintf(stderr,"Usage:%s<some combination of O_RDONLY,O_WRONLY,O_NONBLOCK ", *argv);
exit(EXIT_FAILURE); } argv++;
if(strncmp(*argv,"O_RDONLY",8)==0)
open_mode|=O_RDONLY;
if(strncmp(*argv,"O_WRONLY",8)==0)
open_mode|=O_WRONLY;
if(strncmp(*argv,"O_NONBLOCK",10)==0)
open_mode|=O_NONBLOCK;
for(i = 1;i < argc;++i){
argv++;
if(*argv){
if(strncmp(*argv,"O_RDONLY",8)==0)
open_mode|=O_RDONLY;
if(strncmp(*argv,"O_WRONLY",8)==0)
open_mode|=O_WRONLY;
if(strncmp(*argv,"O_NONBLOCK",10)==0)
open_mode|=O_NONBLOCK;
}
}
if(access(FIFO_NAME,F_OK)==-1){
res=mkfifo(FIFO_NAME,0777);
if(res!=0){
fprintf(stderr,"Could not create fifo %s ",FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("process %d open FIFO with %d ",getpid(),open_mode);
res=open(FIFO_NAME,open_mode);
printf("process %d result %d ",getpid(),res);
sleep(5);
if(res!=-1)
close(res);
printf("process %d finished ",getpid());
exit(EXIT_SUCCESS);
}
open函数调用中的参数标志O_NONBLOCK会影响FIFO的读写操作。
规则如下:
- 对一个空的阻塞的FIFO的read调用将等待,直到有数据可以读的时候才继续执行/
- 对一个空的非阻塞的FIFO的read调用立即返回0字节。
- 对一个完全阻塞的FIFO的write调用将等待,直到数据可以被写入时才开始执行。
系统规定:如果写入的数据长度小于等于PIPE_BUF字节,那么或者写入全部字节,要么一个字节都不写入。
注意这个限制的作用:
当只使用一个FIF并允许多个不同的程序向一个FIFO读进程发送请求的时候,为了保证来自不同程序的数据块 不相互交错,即每个操作都原子化,这个限制就很重要了。如果能够保证所有的写请求是发往一个阻塞的FIFO的,并且每个写请求的数据长度小于等于PIPE_BUF字节,系统就可以确保数据绝不会交错在一起。通常将每次通过FIFO传递的数据长度限制为PIPE_BUF是一个好办法。
在非阻塞的write调用情况下,如果FIFO 不能接收所有写入的数据,将按照下面的规则进行:
- (1)请求写入的数据的长度小于PIPE_BUF字节,调用失败,数据不能被写入。
- (2)请求写入的数据的长度大于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0。
其中。PIPE_BUF是FIFO的长度,它在头文件limits.h中被定义。在linux或其他类UNIX系统中,它的值通常是4096字节。
使用FIFO通信实例
向命名管道写入数据,然后读出之后数据就不再存在管道中了。
fifo_write
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #define FIFO_SERVER "/tmp/myfifo" 9 10 main(int argc,char** argv) 11 { 12 int fd; 13 char w_buf[100]; 14 int nwrite; 15 16 /*打开管道*/ 17 fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0); 18 19 if(argc==1) 20 { 21 printf("Please send something "); 22 exit(-1); 23 } 24 25 strcpy(w_buf,argv[1]); 26 27 /* 向管道写入数据 */ 28 if((nwrite=write(fd,w_buf,100))==-1) 29 { 30 if(errno==EAGAIN) //EAGIN? 31 printf("The FIFO has not been read yet.Please try later "); 32 } 33 else 34 printf("write %s to the FIFO ",w_buf); 35 }
fifo_read
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #define FIFO "/tmp/myfifo" 9 10 main(int argc,char** argv) 11 { 12 char buf_r[100]; 13 int fd; 14 int nread; 15 16 /* 创建管道 */ 17 if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST)) 18 printf("cannot create fifoserver "); 19 20 printf("Preparing for reading bytes... "); 21 22 memset(buf_r,0,sizeof(buf_r)); 23 24 /* 打开管道 */ 25 fd=open(FIFO,O_RDONLY|O_NONBLOCK,0); 26 if(fd==-1) 27 { 28 perror("open"); 29 exit(1); 30 } 31 while(1) 32 { 33 memset(buf_r,0,sizeof(buf_r)); 34 35 if((nread=read(fd,buf_r,100))==-1) 36 { 37 if(errno==EAGAIN) 38 printf("no data yet "); 39 } 40 printf("read %s from FIFO ",buf_r); 41 sleep(1); 42 } 43 pause(); /*暂停,等待信号*/ 44 unlink(FIFO); //删除文件 45 }
FIFO实现的进程间的聊天程序
1 /*程序说明: 2 观察10-7和10-8两个程序,可以看出两者的实现基本是一样的,只不过对FIFO文件的读写顺序颠倒了一下,两个程序中只要定义FIFO文件名的宏的值对换一下就可以了。分别在两个终端上运行这两个程序,并在10-7端和10-8端输入数据观察它们的运行结果: 3 10-7端输入输出如下: 4 $ ./10-7 5 Server:hello 6 Client: world 7 10-8端输入输出如下: 8 $ ./10-8 9 Server: hello 10 11 Client:world 12 从运行结果可以看出,通过两个命名管道也可以实现进程间的双向通信。*/ 13 /* 14 原理图 15 stdin -----> buf <——————————————— frd 16 | ^ 17 V | 18 wfd ---------------> buf <————— stdin 19 */ 20 #include <stdio.h> 21 #include <string.h> 22 #include <sys/types.h> 23 #include <sys/stat.h> 24 #include <fcntl.h> 25 #include <stdlib.h> 26 #include <unistd.h> 27 #include <errno.h> 28 29 #define FIFO_READ "readfifo" 30 #define FIFO_WRITE "writefifo" 31 #define BUF_SIZE 1024 32 33 int main(void) 34 { 35 int wfd,rfd; 36 char buf[BUF_SIZE]; 37 int len; 38 39 umask(0); //不屏蔽权限 40 /*写管道是否存在,不存在就创建*/ 41 if (mkfifo(FIFO_WRITE,S_IFIFO|0666)) 42 { 43 printf("Can't create FIFO %s because %s",FIFO_WRITE,strerror(errno)); 44 exit(1); 45 } 46 /*以只写方式打开写管道*/ 47 umask(0); 48 wfd = open(FIFO_WRITE,O_WRONLY); 49 if (wfd == -1) 50 { 51 printf("open FIFO %s error:%s",FIFO_WRITE,strerror(errno)); 52 exit(1); 53 } 54 /*打开读管道,直到client端创建此管道,打开成功*/ 55 while((rfd = open(FIFO_READ,O_RDONLY)) == -1) 56 { 57 sleep(1); 58 } 59 60 while(1) 61 { 62 printf("Server:"); 63 /*从标准输入读取BUF_SIZE字节数据到buf,若少于BUF_SIZE,有多少读多少*/ 64 fgets(buf,BUF_SIZE,stdin); 65 /*缓冲中开始4字节为quit,关闭命名管道退出*/ 66 if (strncmp(buf,"quit",4) == 0) 67 { 68 close(wfd); 69 unlink(FIFO_WRITE); 70 close(rfd); 71 exit(0); 72 } 73 /*向写管道写入buf的内容*/ 74 write(wfd,buf,strlen(buf)); 75 /*从读管道中读出数据到buf*/ 76 len = read(rfd,buf,BUF_SIZE); 77 if (len > 0) 78 { 79 buf[len] = '