高级I/O函数一(pipe/dup/dup2)

引言

Linux提供了很多高级I/O函数,并不像基础I/O(read,open)那么常用,但是在特定情况下可以表现出更好的性能。
在此讨论和网络编程相关的几个,这些函数大致可以分为三类:

  • 用于创建文件描述符:pipe,dup/dup2等
  • 用于读写数据的函数:readv/writev,sendfile,mmap/munmap,splice,tee
  • 用于I/O行为和属性 :fcntl

一、创建文件描述符的函数

1.1、pipe函数

pipe函数可以创建一个管道,管道是一种把两个进程之间的标准输入和输出连接起来的机制。从而可以实现让多进程之间进行通信。pipe创建的管道是半双工的,需要提供两个文件描述符来操作管道。其中一个进行写操作,另外一个进行读操作。

#include<unistd.h>
int pipe(int fd[2])
//例:
int pipefd[2];
if (pipe (pipefd) == -1)
{
    perror ("pipe");
    exit (EXIT_FAILURE);
}

pipe函数的参数是含有两个int 整形的数组指针,成功时返回0 ,并将文件描述符值填入其参数指向的数组中。如果失败则返回-1并设置errno。

fd[0]表示读端,fd[1]表示写,两端不可颠倒,往fd[1]写入的数据可以从fd[0]读出。

默认情况这两个文件描述符都是阻塞的,如果当管道为空时,调用系统调用read()时,将被阻塞,直到管道有数据可读;当管道为满时,调用write()也会被阻塞,直到管道有足够多的空闲空间可用。
但是如果应用程序将fd[0] fd[1]设置为非阻塞,则read()和write()就会有不同的行为。
当fd[1]的引用计数减为0,即没有任何一个进行往管道中写入数据,则对于fd[0] 的read()操作返回值为0,表示读到末尾(EOF);
当fd[0]的引用计数减为0,即没有任何一个进程需要从管道读取数据,则fd[1]写端的write()将失败,引发SIGPEIPE信号;
管道内传输的数据都是字节流。和TCP字节流类似,但是不一样。应用程序能往TCP连接中写入多少字节流的数据。取决于对方的接收通告窗口的大小和本端的拥塞窗口大小。而管道本身拥有一个容量限制。通过 fcntl 函数可以修改管道容量。

另外 socket 的基础api 提供了创建双向管道,可通过socketpair创建双向管道。其定义如下:

#include<sys/types.h>
#include<sys/socket.h>
int socketpair(int domain,int type, int protocol,int fd[2]);

前三个参数有socket相同,domain 只能使用AF_UNIX。

1.2、dup函数和dup2函数

有时候我们希望把标准输入重定向到一个文件,或者标准输出重定向到一个网络连接。这可以通过下面用于复制文件描述符的dup和dup2函数来实现:

#include<unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);

dup函数的作用:复制一个现有的句柄,产生一个与“源句柄特性”完全一样的新句柄(也即生成一个新的句柄号,并关联到同一个设备)

dup2函数的作用:复制一个现有的句柄到另一个句柄上,目标句柄的特性与“源句柄特性”完全一样(也即首先关闭目标句柄,与设备断连,接着从源句柄完全拷贝复制到目标句柄)

dup函数说明:

函数dup 允许你复制一个file_descriptor文件描述符,返回一个与该描述符“相同”的新的文件描述符。即这两个描述符共享相同的内部结构,共享所有的锁定,读写位置和各项权限或flags等等。例如:对一个文件描述符进行了lseek操作,另一个文件描述符的读写位置也会随之改变。不过,文件描述符之间并不共享close-on-exec flags.

返回值:如成功则返回新的文件描述符,否则出错返回-1.

注意:由dup函数返回的新文件描述符一定是当前可用文件描述符中的最小值。

例子1:复制文件描述符,并向文件写数据.

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
 
void main()
{
      int fd,newfd;
      char *bufFD="Advanced Programming! write by fd\n";
      char *bufNewFD="Advanced Programming! write by NewFD\n";
      fd = open("test.txt",O_RDWR|O_CREAT,0644);
      if(fd==-1)
      {
              printf("open file error%m\n");
              exit(-1);
      }

      //开始复制了
      newfd = dup(fd);
      //使用fd写
      write(fd,bufFD,strlen(bufFD));
      close(fd);

      //使用newfd写
      write(newfd,bufNewFD,strlen(bufNewFD));

      if(close(newfd)==-1)
      {
              printf("close error\n");
      }
      exit(0);
}

输出:
查看test.txt文件,里面有两段字符串:
:Advanced Programming! write by fd
:Advanced Programming! write by newfd

可以看出,对fd或newfd进行读写操作时对同一个文件操作,而且还可以看到fd关闭后,对newfd没有影响,使用newfd还可以操作打开的文件。

dup2函数说明:

dup2也是用于复制文件描述符的,但是对于这个函数我们可以指定它的文件描述符值,而不是在进程表的进程表项里查找最小的。
int dup2(fd1, fd2) 这个函数会先判断fd1和fd2是不是同一个值,如果是的就直接返回fd2。如果不是的,它会先把fd2指向的文件关闭,然后把fd1复制给fd2然后把fd2返回。

因为dup2这个特性,我们有时候会这么用
dup2(fd, STDOUT_FILENO);
执行结果将会通过标准输出写到控制台上,如果不需要在控制台输出,而是需要将结果存入文件,因此将标准输出重定向到fd;

dup2所复制的文件描述符与原来的文件描述符共享各种文件状态。共享所有的锁定,读写位置和各项权限或flags等等.

在这里插入图片描述
例子2:将标准输出重定向到目标文件test.txt

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
 
void main()
{
     int fd;
     int refd;
     char *buf="Advanced Programming!\n";
     fd = open("test.txt",O_RDWR|O_CREAT,0644);
     if(fd==-1)
     {
             printf("open file error:%m\n");
             exit(-1);
     }
     refd = dup2(fd,fileno(stdout));
     if(refd==-1)
     {
             printf("redirect standard out error:%m\n");
             exit(-1);
     }

     //写数据,本应该写入到stdout的信息重定向而写入到目标文件中(test.txt)
     printf("dup2的返回值:%d\n",refd);
     write(fileno(stdout),buf,strlen(buf));
     close(fd);
     exit(0);
}

输出:
查看test.txt文件,可以看到以下内容
:Advanced Programming!
:dup2的返回值:1

写数据,本应该写入到stdout的信息,但是标准输出已经重定向到目标文件中,故向标准输出写的数据将会写到目标文件中。

下一节:高级I/O函数二

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值