sendfile实质是linux系统中一项优化技术,用以发送文件和网络通信时,减少用户态空间与磁盘倒换数据,而直接在内核级做数据拷贝,这项技术是linux2.4之后就有的,现在已经很普遍的用在了C的网络端服务器上了,而对于java而言,因为java是高级语言中的高级语言,至少在C语言的层面上可以提供sendfile级别的接口,举个例子,java中可以通过jni的方式调用c的库,而这种在tomcat中其实就是APR通道,通过tomcat-native去调用类似于APR库,这种调用思路虽然增大了java调用链条,但可以在java层级中获得如sendfile的这种linux系统级优化的支持,可谓是一举多得。
上述的内容,实际就是本文的背景,本文就从系统调用的层级,逐步讲解tomcat中的sendfile是怎么实现的。
1. 介绍linux的sendfile机制
sendfile是一个系统调用,可以man一下,看到其函数细节:
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
sendfile()是作用于数据拷贝在两个文件描述符之间的操作函数.这个拷贝操作是内核中操作的,所以称为"零拷贝".sendfile函数比起read和write函数高效得多,因为read和write是要把数据拷贝到用户应用层操作.
参数说明:
out_fd 是已经打开了,用于写操作(write)的文件描述符;
in_fd 是已经打开了,用于读操作(read)的文件描述符;
offset 偏移量;表示sendfile函数从in_fd中的哪一偏移量开始读取数据.如果是零表示从文件的开始读,否则从相应的便宜量读取.如果是循环读取的时候,下一次offset值应为sendfile函数返回值加上本次的offset的值.
count是在两个描述符之间拷贝的字节数(bytes)
返回值:
如果成功的拷贝,返回写操作到out_fd的字节数,错误返回-1,并相应的设置error信息.
EAGAIN 无阻塞I/O设置O_NONBLOCK时,写操作(write)阻塞了.
EBADF 输出或者输入的文件描述符没有打开.
EFAULT 错误的地址.
EINVAL 描述符不可用或者锁定了,或者用mmap()函数操作的in_fd不可用.
EIO 当读取(read)in_fd时发生未知错误.
ENOMEM 读(read)in_fd时内存不足.
总结一下,实际sendfile就是一个高效的函数,用于替换write,我们看看程序和普通的网络send系统调用程序的区别:
发送端传统方式代码段如下:
fd = open(FILENAME, O_RDONLY);
while((len =read(fd, buff, sizeof(buff))) >0)
{
send(sockfd, buff, len ,0);
}
close(fd);
再看看使用sendfile:
使用sendfile()传输代码段.
off_t offset = 0;
stat(FILENAME, &filestat);
fd = open(FILENAME, O_RDONLY);
sendfile(sockfd, fd, &offset, filestat.st_size) );
close(fd);
sendfile调用和send调用非常类似,很容易就可以在API级别进行替换,程序几乎不用修改什么东西,而换成了高级的优化调用,再高并发的场景下,会明显从数据看出来,性能提升不少。
2.sendfile优化效果
介绍完系统调用之后,来看看sendfile究竟优化省略了哪几步的操作,
传统的网络发送请求,肯定会走内存,因为内存中可能有一些数据需要处理,例如数据的一些加工啊,抽取之类的,
但对于纯文本字符,例如文件这种的,不需要进行修改,直接发送,那么其实就没有必要再走内存了,也就是上面的方框部分虚线引入的部分直接就不走了,直接可以从磁盘到内核缓冲区,然后在内核级别直接将这块数据流转到网卡缓冲区,然后直接由网络介质发送了,可见,这就是sendfile的功效所在。
因此,sendfile的使用场景,我们也应该非常清楚了,对于例如web服务器中的静态资源,静态文件这种的http请求,不需要再内存之中进行加工的,sendfile是最优的选择。
3.Defaultservlet的sendfile逻辑
对于Tomcat中的静态资源处理,直接对应的就是DefaultServlet了,这个类是嵌入在Tomcat源码中,专门处理静态资源的类,我们来看其比较关键的doget(之后调用的serveReource方法)的源码:
对于上述的代码逻辑是,当checkSendfile方法不为true,说明该请求就是普通的请求,那么按照此逻辑,需要将请求的文件输入到ostream流中,最后将这个流通过从copy方法,转接到Outputstream中,通过网络传输出去。
但是,如果请求request中设置了 org.apache.tomcat.sendfile.support 设为Boolean.TRUE,则表示支持 sendfile,那么这个属性就代表着该reqeuest请求发送的文件吗,是通过sendfile系统调用来进行发送,而不是通过send系统调用(默认的java网络socket发送流,实际jvm底层调用的就是send系统调用)。
对于上述的这个 org.apache.tomcat.sendfile.support 属性来说,相当于每一个request请求都可以通过设置该属性,告诉服务器,我这个请求要使用sendfile,而不是send,这相当于是非常的灵活了。
除了 org.apache.tomcat.sendfile.support 属性,通过代码的分析,还有几个属性也可以在request中进行设置,分别为:
org.apache.tomcat.sendfile.filename
作为字符串发送的标准文件名。
org.apache.tomcat.sendfile.start
开始位置偏移值,长整型值。
org.apache.tomcat.sendfile.end
结束位置偏移值,长整型值。
当然,如果不进行设置,那么默认的文件名就是该request请求的文件,start是0,end是length,从上述的代码中也可以看得出来。
我们回头看一下,这几个参数为什么需要设置,对应sendfile的几个参数就可以明白了。
如果是checkSendfile方法为true,那么在DefaultServlet中不进行流的转接,该处理是在Tomcat前端中的不同XXXEndpoint类中,请继续往下看。
值得注意的一点是,一般http响应的数据包都会进行压缩,这样的好处是能极大的减小带宽占用,而响应头中发现了compression压缩属性,浏览器会自动首先进行解压缩,从而正确的将response响应主体刷到页面中。
但是,当sendfile属性开启后,这个compression压缩属性就不生效了,因此,当需要传输的文件非常大的时候,而网络带宽又是瓶颈的时候,sendfile显然并不是合适之举。
4.sendfile在BIO通道中的实现
以Tomcat8为例,不同的Tomcat前端通道中的sendfile的java包装是不同的,但实际上都是在调用系统调用sendfile。
对于,BIO来说,JIOEndpoint是不支持sendfile的,这个可以通过代码中看出来:
5.sendfile在NIO通道中的实现
在NIO通道中,有一个useSendfile属性,这个useSendfile属性是做什么的呢?
这个是可以设置在Connector中的,以NIO通道为例,配置为:
这个useSendfile属性是允许request进行sendfile的总体开关(前面讲的org.apache.tomcat.sendfile.support 属性是针对于每一个request的),这个useSendfile属性在NIO通道中默认就是打开的,当reqeust设置org.apache.tomcat.sendfile.support 属性为true的时候,response就会准备一个SendFileData的数据结构,这个数据结构就是NIO通道下的sendfile的媒介:
这个数据结构是用于传递给sendfile系统调用,用于发送。
因此,NIO的sendfile实现可以分为三个阶段:
第一阶段,实际上就是前面的XXXDefaultServlet中(不仅仅是DefaultServlet,其它的Servlet只要设置这个属性也可以调用sendfile)对Request的sendfile属性的设置,当该请求设置上述的属性后,证明该请求为sendfile请求。
第二阶段,servlet处理完之后,业务逻辑完成,对应的Response该commit了,而在Response的准备阶段,会初始化这个SendFileData的数据结构,这块的代码逻辑都在Http11NioProcessor类中:
从上述的代码逻辑来看,prepareSendfile方法是从前面DefaultServlet中设置的reqeust属性中,拿到file名称,字符位置的start,end,然后将这些属性作为传入的参数,初始化SendFileData实例;
第三阶段,我们记得NIO前端通道的Acceptor,Poller线程,Worker线程的三个线程,当Worker线程干完活之后,返回给客户端,依然要通过Poller线程,也就是会重新注册KeyEvent,读取KeyAttachment,这个时候当为sendfile的时候,前面初始化的SendFileData实例是会注册在KeyAttachment上的:
上述的processSendfile就是Poller线程的run中的一个判断分支,当为sendfile的时候,Poller线程就对SendFileData数据结构中的file名字取出,通过FileChannel的transferTo方法。
对于这个transferTo方法,我们可以看到其中的一个重要的解释:
上述的解释中就是sendfile系统调用。
6.sendfile在APR通道中的实现
在NIO通道中sendfile实现算是比较复杂的了,在APR通道中更加的复杂,我们可以回过头先看看NIO通道中的sendfile,实际是通过每一个Poller线程中的FileChannel的transferTo方法来实现的,对于transferTo方法是阻塞的,这也就意味着,当文件进行sendfile的时候,Poller线程是阻塞的,而我们前面研究过Tomcat前端,Poller线程是很珍贵的,不仅仅是为某几个sendfile服务的,这样会导致Poller线程产生瓶颈,从而拖慢了整个Tomcat前端的效率。
对于APR来讲,基于上述更进一步,通过下面的配置就可以看出端倪:
useSendfile属性没什么可说的,就是全局的sendfile开关;
sendfileThreadCount对应的就是APR通道中,将sendfile的功能从Poller线程中剥离开来,
这相当于sendfileData的数据结构,直接加入到Sendfile线程中了:
好处不言自明,Poller就干Poller的事,而遇到Sendfile的需求的时候,sendfile线程就挺身而出,把活给接了;
最后,对于APR通道是通过JNI调用的APR库,sendfile自然就不是java的API了:
总结:
SendFile实际上是操作系统的优化,Tomcat中基于在不同的通道中有不同的实现,配置也不尽相同,但实际上都是调用操作系统的SendFile的系统调用!
- 在 Tomcat 中配置 SSL/TLS 以支持 HTTPS
本件详细介绍了如何通过几个简单步骤在 Tomcat 中配置 SSL/TLS .使用 JDK 生成自签名的证书,最终实现在应用中支持 HTTPS 协议. 生产密钥和证书 Tomcat 目前只能操作 JK ...
- Tomcat中的Session小结
什么是Session 对Tomcat而言,Session是一块在服务器开辟的内存空间,其存储结构为ConcurrentHashMap: Session的目的 Http协议是一种无状态协议,即每次服务端 ...
- 【Mail】Tomcat提供JNDI方式支持JavaMail(三)
流程介绍 Tomcat提供了JavaMail的支持,是通过JNDI的方式实现的,具体流程是: Tomcat启动的时候,自身产生一个Session对象,放在JNDI容器中给其他项目调用,其他项目只要通过 ...
- CAS 在 Tomcat 中实现单点登录
单点登录(Single Sign On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统 中,用户只需要登录一次就可以访问所有相互信任的应用系统.CA ...
- 【IBM】使用 CAS 在 Tomcat 中实现单点登录
来源: IBM Developer http://www.ibm.com/developerworks/cn/opensource/os-cn-cas/ 张 涛 (zzhangt@cn.ibm.com ...
- Tomcat中JSP引擎工作原理
http://blog.csdn.net/linjiaxingqqqq/article/details/7164449 JSP运行环境: 执行JSP代码需要在服务器上安装JSP引擎,比较常见的引擎有W ...
- 使用 CAS 在 Tomcat 中实现单点登录
单点登录(Single Sign On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.CAS ...
- 用JAX-WS在Tomcat中公布WebService
JDK中已经内置了Webservice公布,只是要用Tomcat等Webserver公布WebService,还须要用第三方Webservice框架. Axis2和CXF是眼下最流行的Webservi ...
- SpringBoot应用部署到Tomcat中无法启动问题
SpringBoot应用部署到Tomcat中无法启动问题 背景 最近公司在做一些内部的小型Web应用时, 为了提高开发效率决定使用SpringBoot, 这货自带Servlet容器, 你在开发We ...
随机推荐
- sql经常会遇到“将截断二进制或字符串”的错误——处理办法
sql经常会遇到“将截断二进制或字符串”的错误——处理办法 1.修改列长度——无法定位具体字段 2.程序逻辑中增加判断,以定位具体字段 由于我是在报表数据库中直接写SQL,没有校验逻辑,所以想把全部字 ...
- XMLPuLL解析
1 package com.bawei.day14_xmlpull; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 i ...
- HTTP标头
HTTP头信息 头信息由“键:值”组成.它们描述客户端或者服务器的属性.被传输的资源以及应该实现连接. 四种不同类型的头标 通用头标:即可用于请求,也可用于响应,是作为一个整体而不是特定资源与事务相关 ...
- java常用英文解释
java常用名词解释: OO: object-oriented ,面向对象 OOP:object-oriented programming,面向对象编程 Author:JCC Object:对象JDK ...
- python获取绝对路径
import osimport sysprint 'os.getcwd()=',os.getcwd()print 'sys.argv=',sys.argvprint 'sys.argv[0]=',sy ...
- 深度系统deepin使用初体验
最近使用linux系统比较多,因为很多项目要用到,而且厌烦了windows流氓软件各种广告的繁琐,因此决定在自己的本子上安装linux系统.然后了解到了deepin操作系统,竟然是武汉一个公司发行的版 ...
- js判断变量是否等于undefined
js中判断变量是否等于undefined,不是使用==,而是使用typeof. typeof(featureId)!="undefined"
- 卓越精Forsk.Atoll.v3.3.2.10366无线网络
卓越精Forsk.Atoll.v3.3.2.10366无线网络 Atoll是法国 FORSK 公司开发的,是一个全面的.基于Windows的.支持2G.3G.4G多种技术,用户界面 友好的无线网络规划 ...
- ssh 配置config 别名
打开shell 当前用户 cd ~ cd .ssh vim config Host (别名) User root(git) 登陆远程shell的用户 HostName 10.0.0.1 ip地址 ...
- gulp编译sass
前言:前段时间学习了sass语法,但是一直用的是"考拉"这个软件工具将我写的sass代码编译成css,然后再引用到项目里面去的,随着对sass的更加深入的了解,我开始尝试着将sas ...