管道 命名管道

     管道(pipe)又称无名管道
     无名管道是一种特殊类型的文件, 在应用层体现为两个打开的文件描述符

    管道是最古老的 UNIX IPC 方式, 其特点是:

         半双工, 数据在同一时刻只能在一个方向上流动
         数据只能从 管道的一端写入, 另一端读出
         写入管道中的数据遵循 先入先出 的规则
         管道所传送的数据是无格式的,这要求管道的读出方与写入方 必须事先约定好数据的格式, 如多少字节算一个消息等
         管道不是普通的文件, 不属于某个文件系统, 其只存在于内存中
         管道在内存中对应一个缓冲区。 不同的系统其大小不一定相同
         从管道读数据是一次性操作, 数据一旦被读走, 它就从管道中被抛弃, 释放空间以便写更多的数据
         管道没有名字, 只能在具有公共祖先的进程之间使用

无名管道的创建 pipe 函数 

#include <unistd.h>

/*
 * 功能:
 *  经由参数 filedes 返回两个文件描述符
 * 参数:
 *   filedes:int 型数组的首地址,其存放了管道的文件描述符 fd[0]、 fd[1]
 *   filedes[0]为读而打开,filedes[1]为写而打开管道
 *   filedes[0]的输出是 filedes[1]的输入
 * return:
 *   成功:0
 *   失败:-1
 */
int pipe(int filedes[2]);

   创建管道, 父子进程通过无名管道通信

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

int main(int argc, char *argv[])
{
    int fd_pipe[2];
    char buf[] = "hello world";
    pid_t pid;

    if(pipe(fd_pipe) < 0)
    {
        perror("pipe");
    }

    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        exit(-1);
    }
    
    if(0 == pid)
    {
        write(fd_pipe[1], buf, strlen(buf));
        _exit(0);
    }
    else
    {
        wait(NULL);
        memset(buf, 0, sizeof(buf));
        read(fd_pipe[0], buf, sizeof(buf));
        printf("buf = [%s]\n",buf);
    }
    return 0;
}

打印:

   父子进程通过管道实现数据的传输
 

 注意:
       利用无名管道实现进程间的通信, 都是父进程创建无名管道, 然后再创建子进程, 子进程继承父进程的无名管道的文件描述符, 然后父子进程通过读写无名管道实现通信

从管道中读数据的特点
      默认用 read 函数从管道中读数据是阻塞的
      调用 write 函数向管道里写数据, 当缓冲区已满时 write 也会阻塞
      通信过程中, 读端口全部关闭后, 写进程向管道内写数据时, 写进程会(收到 SIGPIPE 信号) 退出

编程时可通过 fcntl 函数设置文件的阻塞特性
     设置为阻塞:
          fcntl(fd, F_SETFL, 0);
    设置为非阻塞:
         fcntl(fd, F_SETFL, O_NONBLOCK)

   验证无名管道文件读写的阻塞和非阻塞
 

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd_pipe[2];
    char buf[] = "hello world";
    pid_t pid;

    if(pipe(fd_pipe) < 0)
    {
        perror("pipe");
    }
    
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
        exit(-1);
    }

    if(0 == fid)
    {
        sleep(3);
        write(fd_pipe[1], buf, strlen(buf));
        _exit(0);
    }
    else
    {
        //fcntl(fd_pipe[0], F_SETFL, 0);
        fucntl(fd_pipe[0], F_SETFL, O_NONBLOCK);
        while(1)
        {
            memset(buf, 0, sizeof(buf));
            read(fd_pipe[0], buf, sizeof(buf));
            printf("buf = [%s]\n",buf);
            sleep(1);
        }
    }
    
    return 0;
}

打印:

 

文件描述符概述 

     文件描述符是非负整数, 是文件的标识
          用户使用文件描述符( file descriptor) 来访问文件

       利用 open 打开一个文件时, 内核会返回一个文件描述符
       每个进程都有一张文件描述符的表, 进程刚被创建时, 标准输入、 标准输出、 标准错误输出设备文件被打开,对应的文件描述符 0、 1、 2 记录在表中
      在进程中打开其他文件时, 系统会返回文件描述符表中最小可用的文件描述符, 并将此文件描述符记录在表中

    注意:
       Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT( 即 1024)个文件,故当文件不再使用时应及时调用 close 函数关闭文件
 

文件描述符的复制

    dup 和 dup2 是两个非常有用的系统调用, 都是用来复制一个文件的描述符, 使新的文件描述符也标识旧的文件描述符所标识的文件

       int dup(int oldfd);
       int dup2(int oldfd, int newfd);
        dup 和 dup2 经常用来重定向进程的 stdin、 stdout 和 stderr

   dup 函数

#include <unistd.h>

/*
 * 功能:
 *  复制 oldfd 文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符
 * 参数:
 *   要复制的文件描述符 oldfd
 * 返回值:
 *   成功: 新文件描述符
 *   失败:-1
 */
int dup(int oldfd);

        重定向

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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

    fd1 = open("test", O_CREAT | O_WRONLY, S_IRWXU);
    if(fd1 < 0)
    {
        perror("open");
        exit(-1);
    }

    close(1);

    fd2 = dup(fd1);
    printf("fd2 = %d\n", fd2);

    return 0;
}

打印:

 dup2 函数 重定向

             

#include <unistd.h>

/*
 * 功能:
 *   复制一份打开的文件描述符 oldfd,并分配新的文件描述符 newfd,newfd 也标识 oldfd 所标识的文件
 * 注意:
 *   newfd 是小于文件描述符最大允许值的非负整数,如果 newfd 是一个已经打开的文件描述符, 则首先关闭该文件,然后再复制
 * 参数:
 *    oldfd: 要复制的文件描述符 
 *    newfd: 分配的新的文件描述符 
 * 返回值:
 *   成功:newfd
 *   失败: -1
 */
int dup2(int oldfd, int newfd);
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd1;
    int fd2;
    
    //save stdout
    fd2 = dup(1);
    printf("new:fd2 = %d\n",fd2);

    fd1 = open("test", O_CREAT | O_RDWR, S_IRWXU);

    close(1);
    dup(fd1);
    printf("hello world\n");

    close(1);
    dup("fd2");
    printf("you is sb\n");
    return 0;
}

 打印:

 复制文件描述符后新旧文件描述符的特点

       使用 dup 或 dup2 复制文件描述符后,新文件描述符和旧文件描述符指向同一个文件,共享文件锁定、 读写位置和各项权限
       当关闭新的文件描述符时, 通过旧文件描述符仍可操作文件
      当关闭旧的文件描述时, 通过新的文件描述符仍可可操作文件

  exec 前后文件描述符的特点
         close_on_exec 标志决定了文件描述符在执行 exec 后文件描述符是否可用
        文件描述符的 close_on_exec 标志默认是关闭的, 即文件描述符在执行 exec 后文件描述符是可用的
        若没有设置 close_on_exec 标志位, 进程中打开的文件描述符, 及其相关的设置在 exec 后不变, 可供新启动的程序使用

  设置 close_on_exec 标志位的方法
 

int flags;
flags = fcnt1(fd, F_GETFD);    //获取标志
flags |= FD_CLOEXEC;    //打开标志
flags &= ~FD_CLOEXEC;    //关闭标志
fcntl(fd, F_SETFD, flags);     //设置标志