进程间的通信有三种形式:管道/FIFO、Posix(Portable Operating System Interface,可移植操作系统接口)消息队列、System V 消息队列,先来介绍第一种通信方式,也是最开始的一种通信方式:管道/FIFO
1.对于管道而言,它的局限在于没有名字,从而只能有秦缘关系的进程使用,但是后面有发展出FIFO(这种又被称为有名管道),一般使用的是read和write函数访问。
2.管道的创建:pipe函数:#include<unistd.h> int pipe(int fd[2]);
该函数返回两个文件描述符:前面是读,后面是写
看一个例子:
/**********************************************************
*实验要求: 使用pipe创建无名管道并实现父子进程之间的通讯。
*功能描述: 在父进程中通过无名管道的写端写入数据,通过子进程从管道读端读出相应的数据。
**********************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<sys/wait.h>
int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
int r_num;
memset(buf_r,0,sizeof(buf_r));
/*创建管道*/
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
else printf("pipe create success\n");
/*创建子进程*/
if((pid=fork())==0) //子进程执行序列
{
printf("\n");
close(pipe_fd[1]);//子进程先关闭了管道的写端
sleep(2); /*让父进程先运行,这样父进程先写子进程才有内容读*/
if((r_num=read(pipe_fd[0],buf_r,100))>0)
{
printf("%d numbers read from the pipe is %s\n",r_num,buf_r);
}
close(pipe_fd[0]);
exit(0);
}
else if(pid>0) //父进程执行序列
{
close(pipe_fd[0]); //父进程先关闭了管道的读端
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write1 Hello!\n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent write2 Pipe!\n");
close(pipe_fd[1]);
waitpid(pid,NULL,0); /*等待子进程结束*/
exit(0);
}
return 0;
}
上面是一个利用无名管道进行通信的,创建一个管道,在两个父子进程间的通信。
/**********************************************************
*实验要求: 使用pipe创建无名管道并实现父子进程之间的通讯。
*功能描述: 在父进程中通过无名管道的写端写入数据,通过子进程从管道读端读出相,子进程写入,父进程读出* 应的数据。
**********************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<sys/wait.h>
int main()
{
int pipe_fd1[2],pipe_fd2[2];
pid_t pid;
char buf_r[100];
int r_num;
memset(buf_r,0,sizeof(buf_r));
/*创建两个管道*/
if(pipe(pipe_fd1)<0)
{
printf("pipe create error\n");
return -1;
}
if(pipe(pipe_fd2)<0)
{
printf("pipe create error\n");
return -1;
}
/*创建子进程*/
if((pid=fork())==0) //子进程执行序列
{
printf("\n");
close(pipe_fd1[1]);//子进程先关闭了管道的写端
sleep(2); /*让父进程先运行,这样父进程先写子进程才有内容读*/
if((r_num=read(pipe_fd1[0],buf_r,100))>0)
{
printf("%d numbers read from the pipe is %s\n",r_num,buf_r);
}
close(pipe_fd1[0]);
close(pipe_fd2[0]);
if(write(pipe_fd2[1],"hello",6)!=-1)
printf("child write hello!\n");
close(pipe_fd2[1]);
sleep(2);
exit(0);
}
else if(pid>0) //父进程执行序列
{
close(pipe_fd1[0]); //父进程先关闭了管道的读端
if(write(pipe_fd1[1],"Hello",5)!=-1)
printf("parent write1 Hello!\n");
if(write(pipe_fd1[1]," Pipe",5)!=-1)
printf("parent write2 Pipe!\n");
close(pipe_fd1[1]);
close(pipe_fd2[1]);
sleep(3);
if((r_num=read(pipe_fd2[0],buf_r,100))>0)
{
printf("%d numbers read from the pipe is %s\n",r_num,buf_r);
}
close(pipe_fd2[0]);
waitpid(pid,NULL,0); /*等待子进程结束*/
exit(0);
}
return 0;
}
执行结果:parent write1 Hello!
parent write2 Pipe!
10 numbers read from the pipe is Hello Pipe
child write hello!
6 numbers read from the pipe is hello
当然了这个是两个半双工的管道,下面是一个全双工的例子:
/**********************************************************
*实验要求: 使用pipe创建无名管道并实现父子进程之间的通讯。
*功能描述: 在父进程中通过无名管道的写端写入数据,通过子进程从管道读端读出相,子进程写入,父进程读出* 应的数据。
**********************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<sys/wait.h>
int main()
{
int pipe_fd1[2];
pid_t pid;
char buf_r[100];
int r_num;
memset(buf_r,0,sizeof(buf_r));
/*创建两个管道*/
if(pipe(pipe_fd1)<0)
{
printf("pipe create error\n");
return -1;
}
/*创建子进程*/
if((pid=fork())==0) //子进程执行序列
{
printf("\n");
//close(pipe_fd1[1]);//子进程先关闭了管道的写端
sleep(3); /*让父进程先运行,这样父进程先写子进程才有内容读*/
if((r_num=read(pipe_fd1[0],buf_r,100))>0)
{
printf("%d numbers read from the pipe is %s\n",r_num,buf_r);
}
//close(pipe_fd1[0]);
if(write(pipe_fd1[1],"Hello",5)!=-1)
printf("child write1 Hello!\n");
sleep(3);
exit(0);
}
else if(pid>0) //父进程执行序列
{
//close(pipe_fd1[0]); //父进程先关闭了管道的读端
if(write(pipe_fd1[1],"Hello",5)!=-1)
printf("parent write1 Hello!\n");
if(write(pipe_fd1[1]," Pipe",5)!=-1)
printf("parent write2 Pipe!\n");
//close(pipe_fd1[1]);
sleep(3);
if((r_num=read(pipe_fd1[0],buf_r,100))>0)
{
printf("%d numbers read from the pipe is %s\n",r_num,buf_r);
}
waitpid(pid,NULL,0); /*等待子进程结束*/
exit(0);
}
return 0;
}
这个主要是要控制好读写的时间,以防止一直阻塞。
总结一下管道的主要局限性正体现在它的特点上:
- 只支持单向数据流;
- 只能用于具有亲缘关系的进程之间;
- 没有名字;
- 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);
- 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等;
命名管道的创建
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode)
pathname:FIFO文件名
mode:属性(与文件操作相同)
一旦创建了一个FIFO,就可以用open打开它,一般的文件访问函数(close,read,write)都可以用于FIFO
当打开FIFO时,非阻塞标志(O_NONBLOCK)
将对以后的读写产生如下影响:
1 没有使用 O_NONBLOCK:访问要求无法满足时进程阻塞。如读取空的FIFO时,或者FIFO已满时。
2 使用O_NONBLOCK:访问要求无法满足时不阻塞,立即出错返回,error是ENXIO。
与通过pipe调用创建管道不同,FIFO是以命名文件的形式存在,而不是打开的文件描述符,所以在对它进行读写操作之前必须先打开它。
下面看看FIFO的通信过程:
/**********************************************************
*实验要求: 使用mkfifo创建有名管道并实现两个进程之间的通讯。
*功能描述: 创建一个进程,并从已经建立好的有名管道中,读出事先写入的
* 数据。
**********************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#define FIFO "./myfifo"
int main(int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;
printf("Preparing for reading bytes...\n");
memset(buf_r,0,sizeof(buf_r));
/* 打开管道 */
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1)
{
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
sleep(1);
}
/*后面三句话是不会被运行到的,但不会影响程序运行的效果当程序在上面的死循环中执行时收到信号后会马上结束运行而没有执行后面的三句话。这些会在后面的信号处理中讲到,现在不理解没有关系,这个问题留给大家学习了信号处理之后来解决。*/
close(fd); //关闭管道
pause(); /*暂停,等待信号*/
unlink(FIFO); //删除文件
}
另外一个进程的程序:
/**********************************************************
*实验要求: 使用mkfifo创建有名管道并实现两个进程之间的通讯。
*功能描述: 创建一个进程,并在其中创建一个有名管道,并向其写入数据。
**********************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<unsitd.h>
#define FIFO_SERVER "./myfifo"
int main(int argc,char** argv)
{
int fd;
char w_buf[100];
int nwrite;
/*创建有名管道*/
if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL|O_RDWR)<0)&&(errno!=EEXIST))
{
printf("cannot create fifoserver\n");
}
/*打开管道*/
fd=open(FIFO_SERVER,O_WRONLY |O_NONBLOCK,0);
if(fd==-1)
{
perror("open");
exit(1);
}
/*入参检测*/
for(int i=0;i<10;i++)
w_buf[i]='h';
/* 向管道写入数据 */
if((nwrite=write(fd,w_buf,100))==-1)
{
if(errno==EAGAIN)
printf("The FIFO has not been read yet.Please try later\n");
}
else
{
printf("write %s to the FIFO\n",w_buf);
}
close(fd); //关闭管道
return 0;
}
在两个终端下执行,可以实现通信。相对于来說是不同进程间的通信。
一个比较有名的例子:单个服务器和多个客户端的通信:(引用于:http://blog.csdn.net/yang_yulei/article/details/18236897)
//注意:此为迭代型服务器。若有多个客户端请求,服务器会先处理完一个,再处理下一个。
//
//
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#define MAXLINE 1024
#define SERV_FIFO "./fifo.serv"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) //在文件系统中创建的FIFO的访问权限位
int
main(int argc, char** argv)
{
int readfifo ; //从自己的FIFO读
int writefifo ; //向进程对应的FIFO写
int fd ; //文件描述符
char buff[MAXLINE] ;
char fifoname[MAXLINE] ;
char* ptr ;
ssize_t n ;
//创建服务器的FIFO
if ((mkfifo(SERV_FIFO, FILE_MODE) < 0) && (errno != EEXIST))
printf("can't create %s", SERV_FIFO) ;
//打开服务器FIFO的读、写端
readfifo = open(SERV_FIFO, O_RDONLY, 0) ;
//读取来自客户端的请求
while ((n = read(readfifo, buff, MAXLINE)) > 0)
{
if (buff[n-1] == '\n')
buff[--n] = '\0' ;
//获取客户端的进程ID号
if ((ptr = strchr(buff, ' ')) == NULL)
{
printf("bogus request: %s", buff) ; //伪造的客户请求
continue ;
}
*ptr++ = 0 ;
//根据客户端进程ID打开客户端FIFO
snprintf(fifoname, sizeof(fifoname), "./fifo.%s", buff) ;
if ((writefifo = open(fifoname, O_WRONLY, 0)) < 0)
{
printf("cannot open : %s", fifoname) ;
continue ;
}
//向客户端FIFO中写入客户所请求的文件内容
if ((fd = open(ptr, O_RDONLY)) < 0)
{ //打开文件出错,向客户端返回错误信息
snprintf(buff+n, sizeof(buff) - n, ":can't open, %s\n", strerror(errno)) ;
n = strlen(ptr) ;
write(writefifo, ptr, n) ;
close(writefifo) ;
}
while ((n = read(fd, buff, MAXLINE)) > 0)
write(writefifo, buff, n) ;
close(writefifo) ;
close(fd) ;
}//while
exit(0) ;
}
//客户端知道服务器端FIFO的名字,且服务器知道它维护的FIFO的名字是 fifo.进程ID
//
//---------------客户端-------------
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#define MAXLINE 1024
#define SERV_FIFO "./fifo.serv"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int
main(int argc, char** argv)
{
int readfifo ;
int writefifo ;
size_t len ;
ssize_t n ;
char* ptr ;
char fifoname[MAXLINE] ;
char buff[MAXLINE] ;
pid_t pid ;
//创建自己的FIFO 用于接收服务器端的数据
pid = getpid() ;
snprintf(fifoname, sizeof(fifoname), "./fifo.%ld", (long)pid) ;
if (mkfifo(fifoname, FILE_MODE) < 0 && (errno != EEXIST))
printf("can't create %s", fifoname) ;
//构造请求消息
snprintf(buff, sizeof(buff), "%ld ", (long)pid) ;
len = strlen(buff) ;
ptr = buff + len ;
fgets(ptr, MAXLINE - len, stdin) ; //从键盘获取请求文件路径
len = strlen(buff) ;
//打开服务器的FIFO 向其中写入消息
writefifo = open(SERV_FIFO, O_WRONLY, 0) ;
write(writefifo, buff, len) ;
//打开自己的FIFO 读取来自服务器的应答数据
readfifo = open(fifoname, O_RDONLY, 0) ;
while ((n = read(readfifo, buff, MAXLINE)) > 0)
write(STDOUT_FILENO, buff, n) ;
close(readfifo) ;
unlink(fifoname) ;
exit(0) ;
}
此为迭代式服务器,另一种设计是并发服务器。即每个客户一个子进程服务器。每当有一个客户请求到达时,这种服务器就让主进程调用fork派生出一个新的子进程。该新子进程处理相应的客户请求,直到完成为止。而迭代式的处理就是先来先处理,处理完了这一个在处理下一个。