第六章 高级I/O函数


6.1 pipe函数

即管道函数,用于进程间的通信.

#include<unistd.h>
int pipe(int fd[2]); // fd:filedes // 主要用于socket描述符.
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sv[2]);
//domain只能支持AF_UNIX.即本地协议族.
  • pipe函数只能创建单向管道,其中fd[0]用于读,fd[1]用于写.若实现双向则需要两个管道.

  • 默认情况下,pipe创建的这一对文件描述符是阻塞的.表现为:


    1)若write到一个满的管道,直到有空闲才执行.


    2)若read一个空的管道,直到有数据可读才执行.

  • 管道本身有容量限制,即最大可写大小,默认65536字节(自Linux 2.6.11).

  • socket有一个基础API,就是上面的socketpair函数,其可创建双向管道,双向既可读又可写.


6.2 dup,dup2,dup3函数

可用于把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接.

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd); #define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);
  • dup函数返回的总是取系统当前可用的最小整数值.dup2/3返回的是第一个不小于newfd的整数值.

  • 通过dup和dup2函数创建的文件描述符并不继承原文件描述符的属性.(如close-on-exec,non-blocking).

  • dup3可以选择是否开启close-on-exec,non-blocking属性.

例子:

int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
...
// 关闭标准输出
close(STDOUT_FILENO);
// 复制socket文件描述符connfd
dup(connfd);
// 将输出到connfd中
printf("abcd\n");
close(connfd);
...

最终printf调用的输出将被客户端获得,而不是显示在服务端程序的终端上,这就是CGI服务器的基本工作原理.


6.3 readv 和 writev函数

分别是将数据从文件描述符读到分散的内存块中(分散读),将多块分散内存数据写入文件描述符中(集中写).

#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

而iovec结构体为:

struct iovec
{
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};

当Web服务器解析完一个HTTP请求之后,如果目标文档存在且客户端具有读取该文档的权限,那么就需要发送一个HTTP应答来传输该文档,这个文档包含一个状态行,多个头部字段,一个空行和文档内容.其中,前三部分的内容可能被Web服务器放置在一块内存中,而文档的内容通常被读入到另一块单独的内存.我们并不需要把这两部分拼接到一起再发送,而是可以使用writev函数将他们同时写出.

例子:在Web服务器上的集中写,将服务上的一个文件传输给客户端.

...
static const char *status_line[2] = {"200 OK","500 Internal server error"};
...
// 取出连接.
int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
...
// 检测服务上文件是否存在.
// 检测是否有权限.
...
// 获取服务器上文件描述符.
int fd = open(file_name, O_RDONLY);
...
//下面部分内容将HTTP应答的状态行“Content-Length”头部字段和一个空行依次加入header_buf中
char header_buf[BUFFER_SIZE];
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: %lld\r\n", (long long)file_stat.st_size); //利用writev将header_buf和file_buf的内容一并写出.
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 = file_stat.st_size;
ret = writev(connfd, iv, 2);

以上代码关注的重点是HTTP应答的发送,省略了HTTP请求的接收及解析.


6.4 sendfile函数

在两个文件描述符之间直接传递数据,而且完全在内核之间操作,从而避免了缓冲区和用户缓冲区之间的数据拷贝,也称之为零拷贝.

#include <sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
  • out_fd : 需要支持mmap的文件描述符.

  • in_fd : 在Linux 2.6.3之前必须是socket文件描述符,而在其之后可以是一个普通文件描述符.

  • sendfile函数可以说是一个专门为网络上传输文件而设计的.

例子:将服务器上的一个文件传送给客户端.

int filefd = open(file_name, O_RDONLY);
assert( filefd > 0);
struct stat stat_buf;
fstat(filefd, &stat_buf);
...
int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
...
sendfile(connfd, filefd, NULL, stat_buf.st_size);
close(connfd);
...

可见没有为目标文件分配任何用户空间的缓存,也没有执行读取文件的操作.


6.5 mmap 和 munmap函数

mmap用于申请一段内存空间,可以将其作为进程间通信的共享内存,也可以将文件直接映射到其中.

munmpa函数则释放由mmap创建的这段内存.

#include <sys/mman.h>

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
int munmap(void *start,size_t length);
  • start:若设置为NULL,则由系统自动分配一个地址.

  • prot:设置内存的访问权限.

  • flags:控制内存段内容被修改后的程序行为.

  • fd:是被映射文件对应的文件描述符,一般通过open获得.


6.6 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_out 至少有一个是管道文件描述符.

  • 若fd_in是管道文件描述符,则off_in必须为NULL.

  • off_in 和 off_out 用于设置具体的偏移位置.

  • flags控制数据如何移动.

例子:将客户端的数据原样发送给客户端(回射服务).

...
int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
...
int pipefd[2];
assert(ret != -1);
ret = pipe(pipefd);
/*将connfd上 流入的客户数据定向到管道中*/
ret = splice(connfd, NULL, pipefd[1], NULL, 32768,SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1); /*将管道的输出定向到connfd客户连接文件描述 */
ret = splice(pipefd[0], NULL, connfd, NULL, 326768, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1);
close(connfd);

可见整个过程未执行send操作,因此未涉及用户空间和内核空间之间的数据拷贝.


6.7 tee函数

用于两个管道文件描述符之间复制数据,也是零拷贝.

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <fcntl.h> ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
  • fd_in 和 fd_out必须都是管道文件描述符.

  • 参数意义和splice相同.

例子:同时输出数据到终端和文件描述符.

int pipefd_stdout[2];
int ret = pipe(pipefd_stdout);
assert(ret != -1); int pipefd_file[2];
ret = pipe(pipefd_file);
assert(ret != -1); /*将标准输入内容输入管道 pipefd_stdout */
ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL,32678, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1); /*将管道pipefd_stdout的输出复制到管道pipefd_file的输入端 */
ret = tee(pipefd_stdout[0], pipefd_file[1], 32678, SPLICE_F_NONBLOCK);
assert(ret != -1); /*将pipefd_file的输出定向到文件描述符file_fd上,从而将标准输入的内容写入文件*/
ret = splice(pipefd_file[0], NULL, filefd, NULL,32678, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1); /*将管道pipefd_stdout的输出定向到标准输出, 其内容和写入文件的内容完全一致*/
ret = splice(pipefd_stdout[0], NULL, STDOUT_FILENO, NULL,32678, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1); close(filefd);
close(pipefd_stdout[0]);
close(pipefd_stdout[1]);
close(pipefd_file[0]);
close(pipefd_file[1]);

6.8 fcntl函数

用于对文件描述符的各种控制操作,另一个是ioctl,它比fcntl能够执行更多控制.

#include<fcntl.h>

int fcntl(int fd , int cmd,...);

例子:fcntl通常用来将一个文件描述符设置为非阻塞的.

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;
}

关于第六章的总结

  • 学习了几个高级I/O函数,可能并不像Linux基础的I\O函数那样常用,当通过例子可以看出来,在特定的情况下能表现出优异的性能.

  • 用于创建文件描述符的函数:pipe/dup.

  • 用于读写数据的函数,包括readv/writev,sendfile,mmap/munmap,splice,tee.

  • 用于控制I/O行为和属性的函数:fcntl.


From

Linux 高性能服务器编程 游双著 机械工业出版社

MarkdownPad2

Aaron-z/linux-server-high-performance

2017/2/4 11:43:54

第六章 高级I/O函数的更多相关文章

  1. UNP学习笔记(第十四章 高级I/O函数)

    本章讨论我们笼统地归为“高级I/O”的各个函数和技术 套接字超时 有3种方法在涉及套接字的I/O操作上设置超时 1.调用alarm,它在指定超时时期满时产生SIGALRM信号 2.在select中阻塞 ...

  2. 深入理解Magento - 第六章 - 高级Magento模型

    我们讲过Magento有两种模型,简单模型和EAV(Entity Attribute Value)模型.上一章我们讲过所有的Magento模型都是继承自Mage_Core_Model_Abstract ...

  3. R in action读书笔记(22)第十六章 高级图形进阶(下)

    16.2.4 图形参数 在lattice图形中,lattice函数默认的图形参数包含在一个很大的列表对象中,你可通过trellis.par.get()函数来获取,并用trellis.par.set() ...

  4. R in action读书笔记(21)第十六章 高级图形进阶(上)

    16.1 R 中的四种图形系统 基础图形函数可自动调用,而grid和lattice函数的调用必须要加载相应的包(如library(lattice)).要调用ggplot2函数需下载并安装该包(inst ...

  5. 【阅读笔记】《C程序员 从校园到职场》第六章 常用文件操作函数 (Part 1)

    参考链接:https://blog.csdn.net/zhouzhaoxiong1227/article/details/24926023 让你提前认识软件开发(18):C语言中常用的文件操作函数总结 ...

  6. linux高性能服务器编程 (六) --高级I/O函数

    第六章 高级I/O函数 Linux提供了很多高级的I/O函数,它不是基础的I/O函数(open/read) 1.创建文件描述符的函数比如:pipe.dup/dup2函数 2.读写数据的函数比如:rea ...

  7. 【MySQL函数】MySQL 5.5从零开始学第六章

    说明:本文总结自:<MySQL 5.5从零开始学>第六章 MySQL中的函数包括: 数学函数.字符串函数.日期和时间函数.条件判断函数.系统信息函数和加密函数等. 函数: 表示对输入参数值 ...

  8. 《Javascript高级程序设计》阅读记录(六):第六章 下

    这个系列以往文字地址: <Javascript高级程序设计>阅读记录(一):第二.三章 <Javascript高级程序设计>阅读记录(二):第四章 <Javascript ...

  9. 《Entity Framework 6 Recipes》中文翻译系列 (30) ------ 第六章 继承与建模高级应用之多对多关联

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第六章  继承与建模高级应用 现在,你应该对实体框架中基本的建模有了一定的了解,本章 ...

随机推荐

  1. 用cookie实现记住用户名和密码

    1.当第一次发送请求时,在jsp页面并不能获取cookie对象,第一次是addCookie,之后再请求时才能获得. session和sessionid在服务器端生成的时候,同时把sessionID放在 ...

  2. 能成为一名合格的Java架构师

    原文地址:http://www.dalbll.com/Group/Topic/ArchitecturedDesign/4943 俗话说“没有见过好程序,怎么可能写出好程序”,同样,也可以说“不了解架构 ...

  3. 使用py2exe将python程序打包成exe程序

    近日帮朋友写了个python小程序,从互联网上抓取一些需要的文章到本地.为了运行方便,希望能转换成exe程序在windows下定期执行.从百度上找了些文章,发现py2exe的应用比较多,遂使用之. 1 ...

  4. oracle查询相关注意点

    单表查询: .or 和 and 混合使用 需求:查询业主名称包含'刘'或门牌号包含'5'的,并且地址编号为3的记录 and 的权限优先于 or 所以需要在or的两边添加() 2. 范围查询 除了传统的 ...

  5. iOS视频倒放

    iOS视频倒放 视频的倒放就是视频从后往前播放,这个只适应于视频图像,对声音来说倒放只是噪音,没什么意义,所以倒放的时候声音都是去除的. 倒放实现 一般对H264编码的视频进行解码,都是从头至尾进行的 ...

  6. React最佳实践(1)

    React最佳实践不敢妄谈,但最差实践非知乎莫属. 旧版知乎看起来土了点,但体验流畅,起码用起来舒服. 新版知乎看起来UI现代化,技术实现上采用了React,但是可能因为知乎缺钱,请不起高水平的前端工 ...

  7. 删除文件的第一列 -Linux

    删除文件 text中第一列 方式一 awk '{$1="";print $0}' text 方式二 sed -e 's/[^ ]* //' text

  8. div盒子水平垂直居中的方法推荐

    父盒子是position:relative 方法一:(宽高确定) div绝对定位水平垂直居中[margin 负间距], 方法二: (宽高确定) div绝对定位水平垂直居中[margin:auto实现绝 ...

  9. Pagination

    using System.Collections.Generic; namespace Oyang.Tool { public interface IPagination { int PageInde ...

  10. 史上更全的 MySQL 高性能优化实战总结!

    1 前言 2 优化的哲学 3 优化思路 3.1 优化什么 3.2 优化的范围有哪些 3.3 优化维度 4 优化工具有啥? 4.1 数据库层面 4.2 数据库层面问题解决思路 4.3 系统层面 4.4 ...