第六章 高级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. redis安装和简介(2)

    承接上篇未完成的配置...此次使用的的 Redis-x64-3.2.100 版本 一.打开redis服务器 方式一:打开 redis-server.exe 显示如下图: 图中: 显示运行进程号.当前运 ...

  2. Lambda收集器示例

    Collectors常用方法 工厂方法 返回类型 作用 toSet Set 把流中所有项目收集到一个 Set,删除重复项 toList List 收集到一个 List 集合中 toCollection ...

  3. C/C++判断文件/文件夹是否存在 转

    一.判断文件夹是否存在:     1.用CreateDirectory(".//FileManege",NULL);如果文件夹FileManege不存在,则创建.     2.或者 ...

  4. Tag It 一款 Jquery控件,当你在文本框中输入逗号时,自动帮你分隔开相关内容

    Demo地址:http://webspirited.com/tagit/ 使用方法: 除了JQuery脚本外,下面的脚本也是必须的,这些脚本你都可以去GitHub下载:https://github.c ...

  5. java 编写小工具 尝试 学习(一)

    1.单片机 调试经常 需要  用 串口 工具 发送 一些 特定的  协议或者 命令,每次要 翻译  写成 2进制 很麻烦 ,因此 打算自己用 java 写一个 工具 方便自己  调试,2017年3月2 ...

  6. EF Core 2.1 中的 Eager loading、Explicit loading和LazyLoading (转自MSDN)

    Entity Framework Core allows you to use the navigation properties in your model to load related enti ...

  7. PHP实现数组递归转义的方法

    本文以实例形式讲述了PHP实现数组递归转义的方法,分享给大家供大家参考之用.具体方法如下: 主要功能代码如下: $arr = array('a"aa',array("c'd&quo ...

  8. 安装MySQL8.0.13

    引用于:CrazyDemo,博客地址:http://www.cnblogs.com/CrazyDemo 下载地址: https://www.mysql.com/downloads/ 现在最下边的社区版 ...

  9. JavaScript小练习2-网页换肤

    题目 分析 三个皮肤切换按钮的选择 用li即可. 点击显示白点 li中嵌套一个li,onclick时改变子元素li的css onload 当页面加载完成后立即执行一段JavaScript代码. onl ...

  10. Colored Boots题解

    题目来自Codeforce 1141Dhttp://codeforces.com/problemset/problem/1141/D 因为是全英文题面,就先简单的阐述一下题面. 首先输入一个数n,然后 ...