高级IO函数

高级IO函数

用于创建文件描述符的函数

pipe

pipe函数可用于创建一个管道,以实现进程间通信。

pipe函数的参数是一个包含两个int型整数的数组指针。

成功时返回0,并将一对打开的文件描述符值填入其参数指向的数组。 如果失败,则返回-1并设置errno。

往$fd[1]$写入的数据可以从$fd[0]$读出。要实现双向数据的传输,就应该使用两个管道。

socket的基础API中有一个socketpair函数。它能足够方便地创建双向管道

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

前三个参数与socket系统调用的参数完全相同。socketpair创建的这对文件描述符都是既可读又可写的。

dup函数和dup2函数

把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接(比如CGI编程)。

可以通过用于复制文件描述符的dup或dup2函数实现:

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

dup函数创建一个新的文件描述符,该新文件描述符和原有文件描 述符file_descriptor指向相同的文件、管道或者网络连接。并且dup返回 的文件描述符总是取系统当前可用的最小整数值dup2和dup类似,不 过它将返回第一个不小于file_descriptor_two的整数值。dup和dup2系统 调用失败时返回-1并设置errno。

 close(STDOUT_FILENO);
 dup(connfd);
 printf("ABCD\n");
 close(connfd);

关闭标准输出文件描述符 STDOUT_FILENO(其值是1),然后复制socket文件描述符connfd。 因为dup总是返回系统中最小的可用文件描述符,所以它的返回值实际上是1。服务器输出到标准输出的内容(这里是“abcd”)就会直接发送到与客户连接对应的socket上,因此printf调用的输出将被客户端获得(而不是显示在 服务器程序的终端上)。这就是CGI服务器的基本工作原理。

用于读写数据的函数

readv函数和writev函数

readv函数将数据从文件描述符读到分散的内存块中,即分散读;

writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。

#include<sys/uio.h> 
ssize_t readv(int fd,const struct iovec*vector,int count)
ssize_t writev(int fd,const struct iovec*vector,int count);

fd参数是被操作的目标文件描述符。

vector参数的类型是iovec结构 数组。iovec结构体描述一块内存区。 count参数是vector数组的长度,即有多少块内存数据需要从fd读出或写到fd。

readv和writev在成功时返回读出/写入fd的字节数,失败则返 回-1并设置errno。相当于简化版的recvmsg和sendmsg函数

if(connfd<0) {
        printf("error is %d\n",errno);
    }
    else {
        /*用于保存HTTP应答的状态行、头部字段和一个空行的缓存区*/
        char header_buf[BUFFER_SIZE];
        memset(header_buf,'\0',BUFFER_SIZE);
        /*用于存放目标文件内容的应用程序缓存*/
        char *file_buf;
        /*用于获取目标文件的属性,比如是否为目录,文件大小等*/
        struct stat flie_stat;
        /*记录目标文件是否是有效文件*/
        bool valid = true;
        /*缓存区header_buf目前已经使用了多少字节的空间*/
        int len = 0;
        if(stat(filename,&flie_stat)<0) {//目标文件不存在
            valid = false;
            // printf("valid:false\n");
        }
        else{
            if(S_ISDIR(flie_stat.st_mode)) {//目标文件是一个目录
                valid = false;
                // printf("valid:false\n");
            }
            else if(flie_stat.st_mode&S_IROTH) {//当前用户有读取目标文件的权限
            /*动态分配缓存区file_buf,并指定其大小为目标文件的大小file_stat.st_size 加1,然后将目标文件读入缓存区file_buf中*/
                 int fd = open(filename,O_RDONLY);
                 file_buf = new char[flie_stat.st_size+1];
                 memset(file_buf,'\0',flie_stat.st_size+1);
                 if(read(fd,file_buf,flie_stat.st_size+1)<0) {
                    valid = false;
                 }
            }
            else {
                valid = false;
            }
        }
        if(valid) {
            /*下面这部分内容将HTTP应答的状态行、“Content-Length”头部字段和一个空行依 次加入header_buf中*/
            ret = snprintf(header_buf,BUFFER_SIZE-1,"%s%s\r\n","HTTP/1.1 ",status_line[0]);
            len+=ret;
            ret = snprintf(header_buf+len,BUFFER_SIZE-1-len,"Content-Length:%d\r\n",flie_stat.st_size);
            len+=ret;
            ret = snprintf(header_buf+len,BUFFER_SIZE-1-len,"%s","\r\n");
            // len+=ret;
            struct iovec iv[2];
            iv[0].iov_base = header_buf;
            iv[0].iov_len = strlen(header_buf);
            iv[1].iov_base = file_buf;
            iv[1].iov_len = flie_stat.st_size;
            ret = writev(connfd,iv,2);
        }
        else {
            ret = snprintf(header_buf,BUFFER_SIZE-1,"%s%s\r\n","HTTP/1.1",status_line[1]);
            len+=ret;
            ret = snprintf(header_buf+len,BUFFER_SIZE-len-1,"%s","\r\n");
            send(connfd,header_buf,strlen(header_buf),0);
        }
        close(connfd);
        delete[] file_buf;
    }

sendfile函数

sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝

#include<sys/sendfile.h> 
ssize_t sendfile(int out_fd,int in_fd,off_t*offset,size_t count);

in_fd参数是待读出内容的文件描述符,out_fd参数是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置。count参数指定在文件描述符in_fd和out_fd之间传输的字节数。sendfile成功时返回传输的字节 数,失败则返回-1并设置errno。

in_fd必 须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道;而out_fd则必须是一个socket。

sendfile(connfd,filefd,NULL,stat_buf.st_size);

mmap函数和munmap函数

mmap函数用于申请一段内存空间。我们可以将这段内存作为进程间通信的共享内存也可以将文件直接映射到其中munmap函数则释 放由mmap创建的这段内存空间。

#include<sys/mman.h> 
void*mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset); 
int munmap(void*start,size_t length);

start参数允许用户使用某个特定的地址作为这段内存的起始地址。 如果它被设置成NULL,则系统自动分配一个地址。

length参数指定内存段的长度prot参数用来设置内存段的访问权限

  • PROT_READ,内存段可读。
  • PROT_WRITE,内存段可写。
  • PROT_EXEC,内存段可执行。
  • PROT_NONE,内存段不能被访问。

flags参数控制内存段内容被修改后程序的行为。它可以被设置为表中的某些值(这里仅列出了常用的值)的按位或(其

MAP_SHARED和MAP_PRIVATE是互斥的,不能同时指定)。

常用值 含义
MAP_SHARED 在进程间共享这段内存。对该内存段的修改将反应到被映射的文件中,它提供了进程间共享内存的POSIX方法
MAP_PRIVATE 内存段为调用进程所私有。对该内存段的修改不会被反应到被映射的文件中
MAP_ANONYMOUS 这段内存不是从文件映射而来的。其内容被初始化为全0。这种情况下,mmap的最后两个参数会被忽略。
MAP_FIXED 内存段必须位于start参数指定的地址处。start必须是内存页面大小(4096字节)的整数倍。
MAP_HUGETLB 按照“大内存页面”来分配内存空间。“大内存页面”的大小可通过/proc/meminfo文件练查看

fd参数是被映射文件对应的文件描述符。它一般通过open系统调用获得。offset参数设置从文件的何处开始映射(对于不需要读入整个文 件的情况)。

mmap函数成功时返回指向目标内存区域的指针,失败则返回 MAP_FAILED((void*)-1)并设置errno。munmap函数成功时返回0, 失败则返回-1并设置errno。

splice函数

splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。

#include <fcntl.h>
ssize_t splice(int fd_in,loff_t *off_in,int fd_out,loff_t *off_out,size_t len,unsigned int flags);

fd_in参数是待输入数据的文件描述符。如果fd_in是一个管道文件描述符,那么off_in参数必须被设置为NULL。如果fd_in不是一个管道 文件描述符(比如socket),那么off_in表示从输入数据流的何处开始 读取数据。此时,若off_in被设置为NULL,则表示从输入数据流的当 前偏移位置读入;若off_in不为NULL,则它将指出具体的偏移位置。

fd_out/off_out参数的含义与fd_in/off_in相同,不过用于输出数据流。

len 参数指定移动数据的长度;flags参数则控制数据如何移动。

使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。

splice函数调用成功时返回移动字节的数量。它可能返回0,表示没有数据需要移动,这发生在从管道中读取数据(fd_in是管道文件描述 符)而该管道没有被写入任何数据时。

tee函数

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。 它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作

#include<fcntl.h> 
ssize_t tee(int fd_in,int fd_out,size_t len,unsigned int flags);

该函数的参数的含义与splice相同(但fd_in和fd_out必须都是管道文件描述符)。

tee函数成功时返回在两个文件描述符之间复制的数据 数量(字节数)。返回0表示没有复制任何数据。tee失败时返回-1并设 置errno。

用于控制IO行为和属性的函数

fcntl函数

fcntl函数,正如其名字(file control)描述的那样,提供了对文件描述符的各种控制操作。

另外一个常见的控制文件描述符属性和行为 的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制

#include <fcntl.h>
int fcntl(int fd,int cmd,...)

fd参数是被操作的文件描述符,cmd参数指定执行何种类型的操作。根据操作类型的不同,该函数可能还需要第三个可选参数arg

int setnonblocking(int fd) 
{ 
    int old_option=fcntl(fd,F_GETFL);/*获取文件描述符旧的状态标志*/ 
    int new_option=old_option|O_NONBLOCK;/*设置非阻塞标志*/ 
    fcntl(fd,F_SETFL,new_option); 
    return old_option;/*返回文件描述符旧的状态标志,以便*/ /*日后恢复该状态标志*/ 
}