这个词看着有点熟,不少地方好像都见过。html
TCP的KeepAlive,Http的KeepAlive,如今就连一些前端框架都有相似KeepAlive的东西了(好比VUE.js,保持路由)。前端
本文介绍HTTP和TCP中的KeepAlive机制,其余方面不在本文讨论范围。java
HTTP 持久链接(HTTP persistent connection,也称做HTTP keep-alive或HTTP connection reuse,翻译过来能够是保持链接或者链接复用)是使用同一个TCP链接来发送和接收多个HTTP请求/应答,而不是为每个新的请求/应答打开新的链接的方式。nginx
HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每一个请求/应答客户和服务器都要新建一个链接,完成 以后当即断开链接(HTTP协议为无链接的协议),每次请求都会通过三次握手四次挥手过程,效率较低;当使用Keep-Alive
模式时,客户端到服务器端的链接不会断开,当出现对服务器的后继请求时,客户端就会复用已创建的链接。spring
下图是每次新建链接和链接复用在通讯模型上的区别:apache
在Http 1.0中,Keep-Alive
是没有官方支持的,可是也有一些Server端支持,这个年代比较久远就不用考虑了。bootstrap
Http1.1之后,Keep-Alive
已经默认支持并开启。客户端(包括但不限于浏览器)发送请求时会在Header中增长一个请求头Connection: Keep-Alive
,当服务器收到附带有Connection: Keep-Alive
的请求时,也会在响应头中添加Keep-Alive。这样一来,客户端和服务器之间的HTTP链接就会被保持,不会断开(断开方式下面介绍),当客户端发送另一个请求时,就能够复用已创建的链接。segmentfault
如今的Http协议基本都是Http 1.1版本了,不太须要考虑1.0的兼容问题后端
固然不是,Keep-Alive也有本身的优缺点,并非全部场景下都适用浏览器
对于某些低频访问的资源/服务,好比一个冷门的图片服务器,一年下不了几回,每下一次链接还保持就比较浪费了(这个场景举的不是很恰当)。Keep-Alive可能会很是影响性能,由于它在文件被请求以后还保持了没必要要的链接很长时间,额外占用了服务端的链接数。
在没有链接复用时,Http 接收端(注意这里是接收端,并无特指Client/Server,由于Client/Server都同是发送端和接收端)只须要读取Socket中全部的数据就能够了,解决“拆包”问题便可;可是链接复用后,没法区分单次Http报文的边界,因此还须要额外处理报文边界问题。固然这个经过Http中Header的长度字段,按需读取便可解决。
粘包拆包的介绍能够参考另外一篇文章细说 Netty 中的粘包和拆包
因为Http中Header的存在,经过定义一些报文长度的首部字段,能够很方便的处理包边界问题。
在Http中,有两种方式处理包边界问题:
这个是最一般的处理方式,接收端处理报文时首先读取完整首部(Header),而后经过Header中的Content-Length
来确认报文大小,读取报文时按此长度读取便可,超出长度的报文(“粘包”)不读取,不够长度的报文缓存等待继续读取(“拆包”)。
对于没法确认总报文大小的状况,可使用Chunked的方式来对报文进行分块传输,每一块内标示报文大小。好比Nginx,开启Gzip压缩后,就会开启Chunked的传输方式。
经过Wireshark抓包,能够很直观的看初Chunked的原理:
注意,这里的chunk包,和tcp segment不是一回事,chunk只是应用层的一个分包,而tcp的segment 是对应用层报文再次进行分组
每一个chunk报文前,会携带当前chunk的大小。
经过Keep-Alive已经作到链接复用了,但复用以后何时断开链接呢,否则一直保持链接,形成资源的浪费。
Http协议规定了两种关闭复用链接的方式:
若是服务端Response Header设置了Keep-Alive:timeout={timeout}
,客户端会就会保持此链接timeout(单位秒)时间,超时以后关闭链接。
如今在服务端设置响应Header:
Keep-Alive:timeout=15
经过Wireshark来看下配置了timeout的效果:
从上图能够看出,客户端发送请求后,在15S内(图上没有体现时间,就当15S吧)保持了链接不销毁,超时后通过了4次挥手,断开链接
可是若是在15S内再次请求,链接是能够复用的,不会从新3次握手。
下图是15S内再次请求的效果:
还有一种方式是接收端通在Response Header中增长Connection close
标识,来主动告诉发送端,链接已经断开了,不能再复用了;客户端接收到此标示后,会销毁链接,再次请求时会从新创建链接。
注意:配置close配置后,并非说每次都新建链接,而是约定此链接能够用几回,达到这个最大次数时,接收端就会返回close标识(服务端配置方法下面会介绍)
下面来测试下效果,客户端发送两次请求:
经过wireshark截图能够发现,配置了Connection:close以后(服务端设置了请求只能够用1次,所因此请求完成就销毁链接),两次请求都从新创建了链接。
Syntax: keepalive_timeout timeout [header_timeout]; Default: keepalive_timeout 75s; Context: http, server, location 第一个参数设置一个超时,在此期间保持活动的客户机链接将在服务器端保持打开状态。若是为0则禁用保Keep-Alive。第二个可选参数在“Keep-Alive: timeout=time”响应头字段中设置一个值。 “Keep-Alive: timeout=time”报头字段被Mozilla和Konqueror识别。MSIE在大约60秒内自动关闭保持链接。
Syntax: keepalive_requests number; Default: keepalive_requests 100; Context: http, server, location 设置经过一个保持活动链接能够服务的请求的最大数量。在发出最大数量的请求以后,链接关闭。
在<Connector>
标签中配置属性:
keepAliveTimeout="超时时间"
,默认值是使用为connectionTimeout属性设置的值 。值为-1表示没有(即无限)超时。
maxKeepAliveRequests="链接可用次数"
,-1为永不失效。若是未指定,默认为100。
例如:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" keepAliveTimeout="超时时间(单位秒)" maxKeepAliveRequests="链接可用次数" />
此版本是基于springboot 2.0.2.release,其余版本请自行测试
@Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory(){ TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory(); tomcatServletWebServerFactory.addConnectorCustomizers((connector)->{ ProtocolHandler protocolHandler = connector.getProtocolHandler(); if(protocolHandler instanceof Http11NioProtocol){ Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler; http11NioProtocol.setKeepAliveTimeout(60000);//millisecond } }); return tomcatServletWebServerFactory; }
此版本是基于springboot 1.5.6.release,其余版本请自行测试
@Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(){ TomcatEmbeddedServletContainerFactory tomcatServletWebServerFactory = new TomcatEmbeddedServletContainerFactory(); tomcatServletWebServerFactory.addConnectorCustomizers((connector)->{ ProtocolHandler protocolHandler = connector.getProtocolHandler(); if(protocolHandler instanceof Http11NioProtocol){ Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler; http11NioProtocol.setKeepAliveTimeout(60000);//millisecond } }); return tomcatServletWebServerFactory; }
上面列出了两个版本的配置方式,理论上只要能找到对应的类,就可使用。只是由于版本更新致使的部分类移除
其实Nginx的反向代理,也无非是增长了一个节点而已client<->nginx<->tomcat
对于client,Nginx是Server,对于Tomcat,Nginx是client。
Nginx和client创建链接,和Tomcat端也创建了链接。
那么若是在Nginx和Tomcat上同时配置Keep-Alive会是什么结果呢?
此处较为复杂,待补充……
Apache HttpClient算是Java中最强的HttpClient了,也是最主流的(后端方向),功能强大。
Apache HttpClient在处理KeepAlive的地方设计的比较灵活,提供了可配置的接口,使用者可使用Http标准的策略,也自定定制策略。
HttpClients.custom() //链接是否复用策略,经过此策略返回是否复用 //DefaultClientConnectionReuseStrategy是默认的Http策略,不设置也能够 .setConnectionReuseStrategy(new DefaultClientConnectionReuseStrategy()) //链接复用后有效期(持久时间)策略,复用后经过此策略判断复用超时时间 //DefaultConnectionKeepAliveStrategy是默认的判断超时时间策略,读取的是Keep-Alive:timeout=超时时间 .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) .build();
这里顺带说一下Apache HttpClient的使用,但愿能帮助到有须要的人。(版本Apache HttpClient 4.x)
//建立客户端,此客户端最好保持单例,这是个线程安全的类,并发下也没有问题。 //HttpClient中的链接池等组件都包含在内,若是每次都新建的话, //效率低,占用资源大,链接复用固然也不会生效了。 HttpClients.custom() //禁用自动重试,默认有3次的重试策略 .disableAutomaticRetries() //不用默认的重试策略,自定义 .setRetryHandler() //设置默认请求配置,这里能够配置一些超时时间等参数 .setDefaultRequestConfig(requestConfig()) //全局Header,每次请求都会携带 .setDefaultHeaders() //当Https证书不受信任的时候,记得自定义此项 .setSSLHostnameVerifier() //设置UA .setUserAgent() //设置代理 .setProxy() //...还有不少配置,能够自行查阅文档 .build();
TCP中的KeepAlive和Http的Keep-Alive可不是一回事,HTTP中是作链接复用的,而TCP中的KeepAlive是“心跳监测”,定时发送一个空的TCP Segment,来监测链接是否存活。下面介绍下Java中设置TCP KeepAive的一些方式。
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
channel.setOption(StandardSocketOptions.SO_KEEPALIVE,true);
Socket socket = serverSocket.accept(); socket.setKeepAlive(true);