filezilla client连接后无法读取目录_连接重试or连接复用?细说KeepAlive

什么是Keep-Alive

这个词是不是看着有点熟,很多地方好像都见过。

Keep-Alive:

使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。Web服务器,基本上都支持HTTP Keep-Alive。TCP的KeepAlive,Http的KeepAlive,现在就连一些前端框架都有类似KeepAlive的东西了(比如VUE.js,保持路由)。

本文介绍HTTP和TCP中的KeepAlive机制,其他方面不在本文讨论范围。

Http中的Keep-Alive

HTTP 持久连接(HTTP persistent connection,也称作HTTP keep-alive或HTTP connection reuse,翻译过来可以是保持连接或者连接复用)是使用同一个TCP连接来发送和接收多个HTTP请求/应答,而不是为每一个新的请求/应答打开新的连接的方式。

HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP协议为无连接的协议),每次请求都会经过三次握手四次挥手过程,效率较低;当使用Keep-Alive模式时,客户端到服务器端的连接不会断开,当出现对服务器的后继请求时,客户端就会复用已建立的连接。

下图是每次新建连接和连接复用在通信模型上的区别:

8bf3edb314f0a7027aba2e1757a30b19.png

在Http 1.0中,Keep-Alive是没有官方支持的,但是也有一些Server端支持,这个年代比较久远就不用考虑了。

Http1.1以后,Keep-Alive已经默认支持并开启。客户端(包括但不限于浏览器)发送请求时会在Header中增加一个请求头Connection: Keep-Alive,当服务器收到附带有Connection: Keep-Alive的请求时,也会在响应头中添加Keep-Alive。这样一来,客户端和服务器之间的HTTP连接就会被保持,不会断开(断开方式下面介绍),当客户端发送另外一个请求时,就可以复用已建立的连接。

现在的Http协议基本都是Http 1.1版本了,不太需要考虑1.0的兼容问题

Keep-Alive真的就这么完美吗

当然不是,Keep-Alive也有自己的优缺点,并不是所有场景下都适用

优点
  • 节省了服务端CPU和内存使用量

  • 降低拥塞控制 (TCP连接减少)

  • 减少了后续请求的延迟(无需再进行握手)

缺点

对于某些低频访问的资源/服务,比如一个冷门的图片服务器,一年下不了几次,每下一次连接还保持就比较浪费了(这个场景举的不是很恰当)。Keep-Alive可能会非常影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间,额外占用了服务端的连接数。

连接复用后会有什么问题

在没有连接复用时,Http 接收端(注意这里是接收端,并没有特指Client/Server,因为Client/Server都同是发送端和接收端)只需要读取Socket中所有的数据就可以了,解决“拆包”问题即可;但是连接复用后,无法区分单次Http报文的边界,所以还需要额外处理报文边界问题。当然这个通过Http中Header的长度字段,按需读取即可解决。

粘包拆包的介绍可以参考另一篇文章细说 Netty 中的粘包和拆包

Http 连接复用后包边界问题处理

由于Http中Header的存在,通过定义一些报文长度的首部字段,可以很方便的处理包边界问题。

在Http中,有两种方式处理包边界问题:

Content-Length处理包边界

这个是最通常的处理方式,接收端处理报文时首先读取完整首部(Header),然后通过Header中的Content-Length来确认报文大小,读取报文时按此长度读取即可,超出长度的报文(“粘包”)不读取,不够长度的报文缓存等待继续读取(“拆包”)。

Chunked处理包边界

对于无法确认总报文大小的情况,可以使用Chunked的方式来对报文进行分块传输,每一块内标示报文大小。比如Nginx,开启Gzip压缩后,就会开启Chunked的传输方式。

通过Wireshark抓包,可以很直观的看出Chunked的原理:0e921732f6034db2e0f45d0c4e170760.png

注意,这里的chunk包,和tcp segment不是一回事,chunk只是应用层的一个分包,而tcp的segment 是对应用层报文再次进行分组

每个chunk报文前,会携带当前chunk的大小。

5bd365ec98bb656214f9d9ee30aabbae.png

Http 连接复用后怎样断开连接

通过Keep-Alive已经做到连接复用了,但复用之后什么时候断开连接呢,不然一直保持连接,造成资源的浪费。

Http协议规定了两种关闭复用连接的方式:

通过Keep-Alive Timeout标识

如果服务端Response Header设置了Keep-Alive:timeout={timeout},客户端会就会保持此连接timeout(单位秒)时间,超时之后关闭连接。

现在在服务端设置响应Header:

Keep-Alive:timeout=15

通过Wireshark来看下配置了timeout的效果:

1548e762e89a6777b6752cd417f70a26.png

从上图可以看出,客户端发送请求后,在15S内(图上没有体现时间,就当15S吧)保持了连接不销毁,超时后经过了4次挥手,断开连接

但是如果在15S内再次请求,连接是可以复用的,不会重新3次握手。

下图是15S内再次请求的效果:

3154be052f78445eb8243a89c7d3820c.png

通过Connection close标识

还有一种方式是接收端通在Response Header中增加Connection close标识,来主动告诉发送端,连接已经断开了,不能再复用了;客户端接收到此标识后,会销毁连接,再次请求时会重新建立连接。

注意:配置close配置后,并不是说每次都新建连接,而是约定此连接可以用几次,达到这个最大次数时,接收端就会返回close标识(服务端配置方法下面会介绍)

6a7d6e1a447a94ccee94e60601a54b7c.png

下面来测试下效果,客户端发送两次请求:b3bd42dd339b0d75cb1577255e9f453a.png

通过wireshark截图可以发现,配置了Connection:close之后(服务端设置了请求只可以用1次,所所以请求完成就销毁连接),两次请求都重新建立了连接。

Nginx中设置Keep-Alive(服务端)

Keep-Alive timeout配置:
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秒内自动关闭保持连接。
Keep-Alive requests(连接可用次数)配置:
Syntax:     keepalive_requests number;
Default: keepalive_requests 100;
Context: http, server, location

设置通过一个保持活动连接可以服务的请求的最大数量。在发出最大数量的请求之后,连接关闭。

Tomcat中设置Keep-Alive(服务端)

标签中配置属性:

Keep-Alive timeout配置:

keepAliveTimeout="超时时间",默认值是使用为connectionTimeout属性设置的值 。值为-1表示没有(即无限)超时。

Keep-Alive requests(连接可用次数)配置:

maxKeepAliveRequests="连接可用次数",-1为永不失效。如果未指定,默认为100。

例如:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" keepAliveTimeout="超时时间(单位秒)"maxKeepAliveRequests="连接可用次数" />

Spring Boot Tomcat embed中设置Keep-Alive

此版本是基于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反向代理到tomcat的猜想

其实Nginx的反向代理,也无非是增加了一个节点而已clientnginxtomcat

对于client,Nginx是Server,对于Tomcat,Nginx是client。

Nginx和client建立连接,和Tomcat端也建立了连接。

那么如果在Nginx和Tomcat上同时配置Keep-Alive会是什么结果呢?

此处较为复杂,待补充……

Apache HttpClient 设置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中的Keep-Alive

TCP中的KeepAlive和Http的Keep-Alive可不是一回事,HTTP中是做连接复用的,而TCP中的KeepAlive是“心跳监测”,定时发送一个空的TCP Segment,来监测连接是否存活。下面介绍下Java中设置TCP KeepAive的一些方式。

Netty中设置Keep-Alive

bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);

NIO(New NetWorking IO Lib)中设置Keep-Alive

channel.setOption(StandardSocketOptions.SO_KEEPALIVE,true);

BIO中设置Keep-Alive

Socket socket = serverSocket.accept();
socket.setKeepAlive(true);

参考

  • http://tomcat.apache.org/tomcat-7.0-doc/config/http.html

  • http://nginx.org/

  • http://hc.apache.org/

  • https://www.w3.org/Protocols/rfc2616/rfc2616.html

  • https://zh.wikipedia.org/wiki/HTTP%E6%8C%81%E4%B9%85%E8%BF%9E%E6%8E%A5

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值