Linux学习笔记之PIPE(管道)和FIFO知识小结

1、概述

管道是最初的UNIX IPC形式,由于管道没有名字,所以只能在用于有亲缘关系的进程(所谓的亲缘关系是指进程间有共同的祖先)。FIFO则被称为命名管道。
先说明fork,exec,_exit函数对管道及fifo函数的影响:
fork:子进程取得父进程的管道以及fifo描述字的拷贝。
exec:所有代开的描述字依旧打开,除非已经设置描述字的FD_CLOEXEC位。
_exit:关闭所有打开的描述字,最后一个描述字关闭时删除管道与FIFO中的数据。

管道和FIFO涉及的函数有:pipe, mkfifo, fcntl, open, read, write, close, unlink.

2、管道函数及说明:int pipe(int fd[2]); 

(1)、这个函数提供一个单向管道。fd是输出参数,fd[0]用来读出,fd[1]用来写入(这里的读出和写入是的宾语是管道)。函数成功返回0,错误返回-1.

注意:a、linux的system call(相当于系统API)函数的返回值是成功返回0,返回其他的就表示出错,而windows的api函数则刚好相反,错误返回0,成功返回其他。b、这里不写函数的头文件,要知道它用到那些头文件,在类unix下用man来查看即可,如:man 2 pipe,2表示pipe是一个系统调用,如果是c函数库里面的函数则按3。
(2)、管道的典型用途如下(用来作为父子进程消息的共享):
a、创建管道1(int fd1[2]) 和管道2(int fd2[2]);
b、fork创建子进程
c、父进程关闭管道1的读出端和2的写入端(fd1[0],fd2[1])
d、子进程关闭管道2的读出端和1的写入端(fd1[1],fd2[0])
代码如下:

int pipe1[2];
int pipe2[2];

pid_t childPid;
pipe(pipe1);
pipe(pipe2);

if( ( childPid = fork()) == 0 ) /* child process */
{
    close(pipe1[1]);
    close(pipe2[0]);
    ...   /* your code */
}
/* parent process */
close(pipe1[0]);
close(pipe2[1]);
...
waitpid(childPid, NULL, 0);
...

写的代码如下:实现传输一个数字,字符串可以改为 char


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // for pipe
#include <signal.h> // for signals
#include <wait.h> // for wait
#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1
void sigpipe_handler(int signo) {
  puts(" -->o Got a SIGPIPE");
}

int compute_sum(int n) {
  int i,sum=0;
  for(i=0;i<n;i++,sum+=i);
  return sum;
}

int main(void) {
  int fd[2];// file descriptors for the pipe:
                 // fd[1] is the write-end of the pipe
                 // fd[0] is the read-end of the pipe
  int WRITE_buf=4,READ_buf; // 1-int-long buffer for parent/child communication
  int child_pid; // for the fork system call
  signal(SIGPIPE,sigpipe_handler);
  /*create the pipe*/

  if (pipe(fd)==-1)
          {
               printf(stderr,"pipe failed");
               return 1;
          }
  /* fork a child process */

  child_pid=fork();

  if(child_pid<0) {
      perror("Could not fork child");
      exit(1);
  }
  else if(child_pid==0) //child
  {
      close(fd[READ_END]);
      WRITE_buf=compute_sum(10);
      write(fd[WRITE_END],&WRITE_buf,BUFFER_SIZE);
      close(fd[WRITE_END]);
              //wait(0);
              //printf("the sum number is %d",compute_sum(10));
  }
  else //parent
  {
      /* put your code here */
              //sleep(2);
              close(fd[WRITE_END]);
              read(fd[READ_END],&READ_buf,BUFFER_SIZE);
              printf("read the sum= %d",READ_buf);
              close(fd[READ_END]);

      /* put your code here */
  }

  return EXIT_SUCCESS;
}


子进程在这里用来专门做一件事情,像服务器。而父进程就相当于客户端。如是,子进程在完成任务之后就exit(0),那么但子进程终止时候变成僵尸进程(zombie),内核给父进程发送一个信号SIGCHILD,但是父进程没有扑抓,缺省行为是忽略;父进程后面调用到waitpid的时候就可以等待childPid进程的终止,取得终止状态。如果没有waitpid而直接返回,则父进程返回后那个僵尸子进程将成为托孤给init进程的孤儿进程。内核将为init进程发送SIGCHILD信号。

这样子,就变成: 客户-->管道fd1-->服务器;服务器-->fd2-->客户

匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
$ mkfifo filename
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);

--------------------- 
作者:s1mba 
来源:CSDN 
原文:https://blog.csdn.net/jnu_simba/article/details/8953960 
版权声明:本文为博主原创文章,转载请附上博文链接!

扫描二维码关注公众号,回复: 4019272 查看本文章

3、fifo 有名管道(fifo的意思是:先进先出)
也是一个半双工的单向数据流,但是有一个路径名与之相连。函数如下:
int mkfifo(const char *pathname, mode_t mode);

说明:
a、mkfifo函数已经隐含制订了O_CREAT | O_EXCL也就是说如果pathname那个管道已经存在,则返回一个错误值EEXIST,如果想打开一个已经存在的管道,用open()即可。
b、由于fifo是先进先出的,所以write函数总是往管道末尾写入数据,而read函数总是从管道的开头返回数据,如果用lseek定位文件指针,会返回ESPIPE错误。

应用:

/* server.c */
...
if( ( mkfifo(FIFO1, FILE_MODE ) < 0 ) && (errno != EEXIST ) )
    /* error handle and exit(1) */
if( ( mkfifo(FIFO2, FILE_MODE ) < 0 ) && (errno != EEXIST ) )
{
    unlink(FIFO1);
    /* error handle and exit(1) */
}
readfd = open(FIFO1, O_RDONLY, 0);
writefd = open(FIFO2, O_WRONLY, 0);
/* your code */
exit(0);
这是服务器进程的FIFO,其中FIFO1,FIFO2可以定义一个宏来指定,比如,#define FIFO1 "/temp/fifo.1"(路径名随意,不过由于是临时的文件,一般放在/temp目录下,权限比较低嘛)

打开文件之后就可以用read,write函数往管道读写信息了,FIFO1,FIFO2就是像文件描述符。

再看client的代码:

/* client.h */
writefd = open(FIFO1, O_WRONLY, 0);
readfd = open(FIFO2, O_RDONLY, 0);
/* your code */

close(writefd);
close(readfd);

unlink(FIFO1);
unlink(FIFO2);

exit(0);
说明:a、通常管道是由服务器建立,由客户端销毁(unlink函数),close只是关闭管道而已。b、注意clinet.h上的那两个open和server.h上的那两个open的顺序,否则会引起死锁(见后面)

4、阻塞态下的规则(阻塞态是默认的状态):

(1)、管道和FIFOopen函数的返回结果:
writefd = open(FIFO1, O_WRONLY, 0); 这个是用只写方式打开管道,如果FIFO1此时已经有别的进程以只读方式打开(就是说在这条代码运行之前,已经有代码open(FIFO1, O_RDONLY, 0)运行),则此函数返回成功,否则,将会阻塞到有别的进程以只读方式打开FIFO1为止。反过来也一样。

(2)、read函数作用于管道和FIFO 的返回结果:
如果FIFO1为空(就是说里面没有数据):该管道以只读方式打开,则返回0;FIFO1以只写方式打开,则阻塞到FIFO1有数据或者是FIFO1不再以写方式打开为止。

(3)、write函数作用于管道和FIFO的返回结果:
如果FIFO1没有以只读方式打开,则给进程产生SIGPIPE信号(缺省行为是终止该进程);如果已经以只读方式打开,则见下。

(4)、write操作的原子性:
如果写入数据的字节数小于等于PIPE_BUF,则该函数保证其原子性;否则不能保证。

现在来看一下上面那个程序,服务器代码先运行,因此:当起运行到readfd = open(FIFO1, O_RDONLY, 0);的时候,还没有任何进程以O_WRONLY方式打开FIFO1,进程阻塞在这里;

然后客户端代码开始运行,但运行到这个地方的时候:writefd = open(FIFO1, O_WRONLY, 0);服务器阻塞的地方开始释放,而在客户端,因为FIFO1在服务器已经是以O_RDONLY打开了,所以继续运行。

如果客户端的这两个open交换一个顺序,那么readfd = open(FIFO2, O_RDONLY, 0);先运行,则由于FIFO2还没有以O_WRONLY方式打开,所以客户端也阻塞,客户和服务器都阻塞,大家都在等对方的资源,这种情况我们称之为死锁(deadlock)

5、非阻塞态下的规则:

(1)、非阻塞态的设置。
a、调用open时可以指定 readfd = open(FIFO1, O_RDONLY | O_NONBLOCK );
b、如果readfd已经打开,则可以用fcntl来设置O_NONBLOCK标志。

(2)、对open操作的影响:
如果当前操作是wrfd = open(FIFO1, O_WRONLY | O_NONBLOCK, 0); 那么如果是FIFO1在此以前没有用O_RDONLY方式打开过,返回ENOXIO错误,否则都成功返回。

(3)、对空管道或空FIFO read操作的影响:read(readfd, ...); 如果该readfd对应的FIFO用O_WRONLY打开返回0,否则返回EAGAIN。

(4)、对write操作的影响同阻塞态下。

6、技巧:单个服务器多个客户时候,服务器中有连续的两行代码:
readfifo = open(SERV_FIFO, O_RDONLY, 0); 
dummy = open(SERV_FIFO, O_WRONLY, 0);
作用:a、服务器运行到readfifo,阻塞,直到有客户用O_WRONLY打开SERV_FIFO为止。然后因为SERV_FIFO已经以O_RDONLY打开,因此这个dummy成功返回。
b、到客户完成任务,关闭SERV_FIFO时,SERV_FIFO变成空,因此,服务器在运行到read语句的时候,阻塞知道下一个客户以O_WRONLY方式打开SERV_FIFO为止(也就是直到有下一个用户请求为止)

7、Dos攻击(拒绝服务型攻击)
有见上面的阻塞,有个攻击方法就是说,发送一条请求,但是从来不打开自己的FiFO,让服务器死等。服务器一直阻塞,没办法用了。

当然,现在的服务器都是并发性服务器,最多只能阻塞他的一个子进程;但是即使在这种情况下一样可以进行Dos攻击,方法是发送大量的请求,以至于服务器开辟的进程达到极限,而且每个子进程都用上面的办法来阻塞。(此时不但服务器进程被阻塞了,整个服务器系统都被阻塞了)。

8、字节流
管道和FIFO是以字节流的方式来传递信息的,类似于TCP。那么怎样区分信息的界限呢?有下面三种常用的技巧:

a、带内特殊终止符:分隔标志(类似与SLIP的标记)
b、显示长度在信息头:像TCP协议栈实现。
c、每次连接一个记录:像HTTP1.0协议实现。
--------------------- 
作者:jiangnanyouzi 
来源:CSDN 
原文:https://blog.csdn.net/jiangnanyouzi/article/details/3193722 
版权声明:本文为博主原创文章,转载请附上博文链接!

  > File Name: process_.c
    > Author: Simba
    > Mail: [email protected]
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    mkfifo("tp", 0644);
    int infd = open("Makefile", O_RDONLY);
    if (infd == -1)
        ERR_EXIT("open error");

    int outfd;
    outfd = open("tp", O_WRONLY);
    if (outfd == -1)
        ERR_EXIT("open error");

    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0)
        write(outfd, buf, n);

    close(infd);
    close(outfd);

    return 0;
}
--------------------- 
作者:s1mba 
来源:CSDN 
原文:https://blog.csdn.net/jnu_simba/article/details/8953960 
版权声明:本文为博主原创文章,转载请附上博文链接!
/*************************************************************************
    > File Name: process_.c
    > Author: Simba
    > Mail: [email protected]
    > Created Time: Sat 23 Feb 2013 02:34:02 PM CST
 ************************************************************************/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{

    int outfd = open("Makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (outfd == -1)
        ERR_EXIT("open error");

    int infd;
    infd = open("tp", O_RDONLY);
    if (infd == -1)
        ERR_EXIT("open error");

    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0)
        write(outfd, buf, n);

    close(infd);
    close(outfd);
    unlink("tp"); // delete a name and possibly the file it refers to
    return 0;
}
--------------------- 
作者:s1mba 
来源:CSDN 
原文:https://blog.csdn.net/jnu_simba/article/details/8953960 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/weixin_42462804/article/details/83356123