进程间通信方式----管道通信

来源:互联网 发布:淘宝回收相机定金1000 编辑:程序博客网 时间:2024/05/21 12:45

管道通信

介绍

一、管道的定义

管道(Pipe)及有名管道(Named Pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。


二、管道的通信方式

管道的通信方式分为无名管道有名管道,无名管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制。


三、管道的特点

管道是Linux支持的最初Unix  IPC 形式之一,具有以下特点:

1、管道是半双工的,数据只能向一个方向流动;需要双方通信,需要建立两个管道。

2、只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。

3、单独构成一种独立的文件系统。

4、数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。



操作

一、建立无名管道

表头文件:#include<unistd.h>

定义函数:int pipe(int   filedes[2])

函数说明:pipe()会建立管道,并将文件描述符由参数filedes数组返回。filedes[0]为管道里的读取端,  filedes[1]为管道的输入端。

返回值:成功返回0,否则-1;



      例子:#include <unistd.h>

#include<stdio.h>

#include<stdlib.h>

#include <sys/types.h>

int main()

{

pid_t pid;

int filedes[2];

char buffer[80];

pipe(filedes);

pid = fork();



if(-1 == pid)

{

perror("fork");

exit(1);

}

else if(pid > 0)

{

/*父进程*/

char s[]="hello!\n";

write(filedes[1],s,sizeof(s));

}

else

{

/*子进程*/

read(filedes[0],buffer,80);

printf("%s",buffer);

}

return 0;

}



执行:hello!

管道用于不同进程间通信。通常先创建一个管道,再通过 fork 函数创建一个子进程,该子进程会继承父进程所创建的管道。

注意:必须在系统调用 fork() 前调用 pipe () , 否则子进程将不会继承文件描述符。


二、读写无名管道


     示例:#include <unistd.h>

#include<stdio.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>


int main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[100];
char w_buf[4];
char *p_wbuf;
int r_num;
int cmd;


memset(r_buf,0,sizeof(r_buf));
memset(w_buf,0,sizeof(w_buf));
p_wbuf = w_buf;
if(pipe(pipe_fd)<0)
{
printf("pipe create error!\n");
return -1;
}


if((pid = fork())==0)
{
printf("\n");
close(pipe_fd[1]);
sleep(3);          //确保父进程关闭写端


r_num = read(pipe_fd[0],r_buf,100);
printf("read num is %d the data read from the pipe is %d\n",r_num,atoi(r_buf));
close(pipe_fd[0]);
exit(0);
}


else if(pid>0)
{
close(pipe_fd[0]);   //read
strcpy(w_buf,"6666");
if(write(pipe_fd[1],w_buf,4)!=-1)
{
printf("parent write over!\n");
}


close(pipe_fd[1]);
printf("parent close fd[1] over!\n");
sleep(10);


执行:parent write over!
   parent close fd[1] over!
   read num is 4 the data read from the pipe is 6666


附加结论:管道写段关闭后,写入的数据将一直存在,知道读出为止
向管道中写入数据:向管道中写入数据时,Linux 将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走缓冲区中的数据,那么写操作将一直阻塞。
注意:只有在管道的读端存在时,向管道写入数据才有意义。


对管道的写规则的验证1:写端对读端存在的依赖性。
     

     例子:#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[4];
char* w_buf;
int writenum;
int cmd;


memset(r_buf,0,sizeof(r_buf));


if(pipe(pipe_fd)<0)
{
printf("pipe create error!\n");
return -1;
}


if((pid = fork())==0)
{
close(pipe_fd[0]);
close(pipe_fd[1]);
sleep(10);
exit(0);
}


else if(pid>0)
{
sleep(1);
close(pipe_fd[0]);
w_buf="111";
if((writenum = write(pipe_fd[1],w_buf,4))==-1)
printf("write to pipe error!\n");
else
printf("the bytes write to pipe is %d \n",writenum);


close(pipe_fd[1]);
}


return 0;
}
执行: broken pipe
原因:该管道及其所有 fork() 产物的读端已经被关闭。




对管道的写规则的验证2:Linux不保证写管道的原子性验证
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<errno.h>
#include<string.h>


int main(int argc,char **argv)
{
int pipe_fd[2];
pid_t pid;
char r_buf[4096];
char w_buf[4096*2];
int writenum;
int rnum;


memset(r_buf,0,sizeof(r_buf));


if(pipe(pipe_fd)<0)
{
printf("pipe create error!\n");
return -1;
}


if((pid = fork())==0)
{
close(pipe_fd[1]);
while(1)
{
sleep(1);
rnum = read(pipe_fd[0],r_buf,1000);
printf("child:readnum is %d \n",rnum);
}
close(pipe_fd[0]);
exit(0);
}


else if(pid > 0)
{
close(pipe_fd[0]);
memset(r_buf,0,sizeof(r_buf));
if((writenum=write(pipe_fd[1],w_buf,1024))==-1)
printf("write to pipe error!\n");
else
printf("the bytes write to pipe is %d \n",writenum);
writenum=write(pipe_fd[1],w_buf,4096);
close(pipe_fd[1]);
}


return 0;
}


了解即可,运行后会陷入死循环(或在 while(1)处添加限制条件)


三、无名管道应用实例

   1.实例一:用于shell
管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。
2.实例二:用于具有亲缘关系的进程间通信
 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>


int main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[4];
char* w_buf[256];
int childexit=0;
int i;
int cmd;


memset(r_buf,0,sizeof(r_buf));


if(pipe(pipe_fd)<0)
{
printf("create pipe error!\n");
return -1;
}


if((pid = fork())==0)
{
printf("\n");
close(pipe_fd[1]);
sleep(2);


while(!childexit)
{
read(pipe_fd[0],r_buf,4);
cmd = atoi(r_buf);
if(cmd == 0)
{
printf("child:receive command from parent over\nnow childprocess exit\n");
childexit=1;
}
else if(handle_cmd(cmd)!=0)
return ;
sleep(1);
}
close(pipe_fd[0]);
exit(0);
}


else if(pid > 0)
{
close(pipe_fd[0]);
w_buf[0]="003";
w_buf[1]="005";
w_buf[2]="777";
w_buf[3]="000";


for(i=0;i<4;i++)
write(pipe_fd[1],w_buf[i],4);
close(pipe_fd[1]);
}


return 0;
}


int handle_cmd(int cmd)
{
if((cmd<0)||(cmd)>256)
{
printf("child:invalid command\n");
return -1;
}
printf("child:th cmd from parent is %d\n",cmd);
return 0;
}



执行:
child:th cmd from parent is 3
child:th cmd from parent is 5
child:invalid command



创建有名管道

  表头文件:#include <sys/types.h>
        #include <sys/stat.h>

          定义函数:int mkfifo(const char *pathname, mode_t mode);
  函数说明:mkfifo() 会依参数 pathname 建立特殊的 FIFO 文件,该文件必须不存在,而参数  mode为该文件的权限(mode%~umask),因此 umask 值也会影响到 FIFO 文件的权限。 mkfifo() 建立的 FIFO 文件其他进程都可以用读写一般文件的方式存取。当使用 open() 来打开 FIFO文件时,O_NONBLOCK 旗标会有影响;
(1)当使用O_NONBLOCK 旗标时,打开 FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开 FIFO 文件来读取,则写入的操作会返回 ENXIO 错误代码。
(2)没有使用O_NONBLOCK 旗标时,打开 FIFO 来读取的操作会等到其他进程 FIFO 文件来写入才正常返回。同样的,打开 FIFO 文件来写入的操作会等到其他进程打开 FIFO 文件来读取后才正常返回


     例如:#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include <fcntl.h>

#define FIFO "/home/self/2"

int main()
{
char buffer[80];
int fd;
unlink(FIFO);
mkfifo(FIFO,0666);


if(fork()>0)
{
char s[]="hello!\n";
fd = open(FIFO,O_WRONLY);
write(fd,s,sizeof(s));
close(fd);
}
else
{
fd = open(FIFO,O_RDONLY);
read(fd,buffer,80);;
printf("%s",buffer);
close(fd);
}

return 0;
}

执行:hello!

读写有名管道

1、从 FIFO 中读取数据

约定:如果一个进程为了从 FIFO 中读取数据而阻塞打开 FIFO ,那么称该进程内的读操作作为设置了阻塞标志的读操作。

(1)如果有进程写打开 FIFO,且当前 FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。
(2)对于设置了阻塞标志的读操作来说,造成阻塞的原因有两种:当前 FIFO 内有数据,但其他进程在读这些数据;另外就是 FIFO 内没有数据。
(3)读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其他将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO 中没有数据也一样(此时,读操作返回0);
(4)如果没有进程打开 FIFO,则设置阻塞标志的读操作会阻塞。

2、向 FIFO 中写入数据
(1)对于设置了阻塞标志的写操作:
    1)当要写入的数据量不大于 PIPE_BUF 时,Linux将保证写入的原子性。
    2)当要写入的数据量大于PIPE_BUF 时,Linux 将不再保证写入的原子性。
  
 (2)对于没有设置阻塞标志的写操作:
1)当要写入的数据量大于 PIPE_BUF 时,Linux将不再保证写入的原子性。
     2)当要写入的数据量不大于PIPE_BUF 时,Linux将保证写入的原子性。

3、对 FIFO 读写规则的验证

   













































原创粉丝点击