引言
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的信息,但是标准输出已经重定向到目标文件中,故向标准输出写的数据将会写到目标文件中。