Http实战之无状态协议、keep-alive分析(2)

简介: Http实战之无状态协议、keep-alive分析(2)

Http长连接


接下来我们来聊聊Http的长连接,说到Http的长连接,避免不了会跟Tcp的长连接做一个对比。


Tcp长连接


**TCP本身并没有长短连接的区别 **,长短与否,完全取决于我们怎么用它。


  • 短连接:每次通信时,创建 Socket;一次通信结束,调用 socket.close()。这就是一般意义上的短连接,短连接的好处是管理起来比较简单,存在的连接都是可用的连接,不需要额外的控制手段。
  • 长连接:每次通信完毕后,不会关闭连接,这样可以做到连接的复用。长连接的好处是省去了创建连接的耗时

短连接和长连接的优势,分别是对方的劣势。想要图简单,不追求高性能,使用短连接合适,这样我们就不需要操心连接状态的管理;想要追求性能,使用长连接,我们就需要担心各种问题:比如 **端对端连接的维护,连接的保活 **。操作系统给我们提供了一种Tcp的保活机制,即:TCP的keepalive机制。


Tcp保活机制


如果在一段时间(保活时间:tcp_keepalive_time)内此连接都不活跃,开启保活功能的一端会向对端发送一个保活探测报文。


  • 若对端正常存活,且连接有效,对端必然能收到探测报文并进行响应。此时,发送端收到响应报文则证明TCP连接正常,重置保活时间计数器即可。
  • 若由于网络原因或其他原因导致,发送端无法正常收到保活探测报文的响应。那么在一定**探测时间间隔(tcp_keepalive_intvl)后,将继续发送保活探测报文。直到收到对端的响应,或者达到配置的探测循环次数上限(tcp_keepalive_probes)**都没有收到对端响应,这时对端会被认为不可达,TCP连接虽存在但已失效,需要将连接做中断处理。

上面提到了三个参数保活时间:tcp_keepalive_time、探测时间间隔:tcp_keepalive_intvl、探测循环次数:tcp_keepalive_probes。


这三个参数,在linux上可以在/proc/sys/net/ipv4/路径下找到,或者通过sysctl -a | grep keepalive命令查看当前内核运行参数。

[root@vm01 ~]# cd /proc/sys/net/ipv4
[root@vm01 ipv4]# pwd
/proc/sys/net/ipv4
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
[root@vm01 ipv4]# sysctl -a | grep keepalive
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_intvl = 75
  • 保活时间(tcp_keepalive_time)默认:7200秒
  • 保活时间间隔(tcp_keepalive_intvl)默认:75秒
  • 探测循环次数(tcp_keepalive_probes)默认:9次

也就是默认情况下一条TCP连接在2小时(7200秒)都没有报文交换后,会开始进行保活探测,若再经过9*75秒=11分钟15秒的循环探测都未收到探测响应,即共计:2小时11分钟15秒后会自动断开TCP连接。


Http的keep-alive


HTTP是短连接,客户端向服务器发送一个请求,得到响应后,连接就关闭。之所以这样设计使用,主要是考虑到实际情况。例如,用户通过浏览器访问一个web站点上的某个网页,当网页内容加载完毕之后,用户可能需要花费几分钟甚至更多的时间来浏览网页内容,此时完全没有必要继续维持底层连接。当用户需要访问其他网页时,再创建新的连接即可。


因此,HTTP连接的寿命通常都很短。这样做的好处是,可以极大的减轻服务端的压力。一般而言,一个站点能支撑的最大并发连接数也是有限的,面对这么多客户端浏览器,不可能长期维持所有连接。每个客户端取得自己所需的内容后,即关闭连接,更加合理。


通常一个网页可能会有很多组成部分,除了文本内容,还会有诸如:js、css、图片等静态资源,有时还会异步发起AJAX请求。只有所有的资源都加载完毕后,我们看到网页完整的内容。然而,一个网页中,可能引入了几十个js、css文件,上百张图片,如果每请求一个资源,就创建一个连接,然后关闭,代价实在太大了。基于此背景,我们希望连接能够在短时间内得到复用,在加载同一个网页中的内容时,尽量的复用连接,这就是HTTP协议中keep-alive属性的作用。


  • HTTP 1.0中默认是关闭的,需要在http头加入「Connection: Keep-Alive」才能启用Keep-Alive;
  • HTTP 1.1中默认启用Keep-Alive,如果加入「Connection: close」才关闭。

Http的keep-alive建立在底层使用Tcp长连接的基础上,前文中我们已经提到过Tcp长连接本质上是在使用时不立马关闭连接。keep-alive的作用在于通知对端不要关闭底层socket连接,下次通信时可以使用同一个连接,接下来我们通过wireshark抓包以及代码分析证明这一点。


抓包分析


首先,我们需要对测试代码稍作改动以支持keep-alive,httpClient默认支持keep-alive,所以客户端代码不需要变动,但服务端需要做如下改动:

public class HttpHelloWorldServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    public void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();
        p.addLast(new HttpServerCodec());
        // 加入一个HttpServerKeepAliveHandler以支持keep-alive
        p.addLast(new HttpServerKeepAliveHandler());
        p.addLast(new HttpObjectAggregator(65535));
        p.addLast(new HttpHelloWorldServerHandler());
    }
}

通过客户端发送请求后抓包如下:

第一次发送请求:

68ead27e0a3bb318213a72d64a4805a9.png


wirkshark抓包的表达式为:tcp.port==8080,代表我们要抓取8080端口上的所有数据包。关于wirkshark抓包的细节操作请参考上篇文章:《Http实战之wireshark抓包实战》


通过抓包我们可以发现,第一次发送请求时进行了tcp握手,但并没有关闭连接。接着我们再次通过客户端发送一个请求,注意,两次请求请不要关闭客户端!


第二次发送请求:

35dbac2e0cdd6e2369a31c7f3d4ba0af.png



可以看到第二次请求并没有重新进行tcp握手,就直接完成了http通信,这就表示底层的tcp得到了复用。


代码分析


httpClient相关实现代码位于org.apache.http.impl.execchain.MainClientExec#execute中,如下:

451f41dcb3901b534ead1feac278ef18.png

1.reuseStrategy.keepAlive(response, context),发起请求时,会先判断客户端是否开启了keep-alive,代码如下:

6161dad05f6fb011d58a684b1e866179.png

2.逻辑很简单,只要请求头中没有Connection: close便是开启keep-alive。

处理服务端返回的信息,确定连接保持时间。实际就是处理响应头中的Keep-Alive字段

de4db2670e7191138316756cb91e71a8.png

netty对于keep-alive的处理都位于HttpServerKeepAliveHandler中,核心代码如下:

69b5464e7176856da89c84c9b1bc903c.png

通过代码分析我们能分析出这么几个细节


  1. keep-alive是由客户端发起的。这好像是句废话,毕竟http请求就是客户端发起的
  2. 即使客户端发起了keep-alive,服务器也可以拒绝
  3. 服务器可以通过响应头中的Keep-Alive字段决定连接保持的时间


总结


限于篇幅原因,本文只分析了http协议无状态的含义以及http长连接,本系列文章是实战篇,主要的实战方式是抓包+代码分析。Http系列还会有一篇文章,包括「Http缓存」、「分块传输」、「数据压缩」等。


希望大家能理解一句话,协议就只是协议,只有协议的双方同时遵守协议并按约定实现协议,协议才有意义!!!


参考:


https://www.zhihu.com/question/23202402

https://datatracker.ietf.org/doc/html/rfc6265#page-3

https://hc.apache.org/httpcomponents-client-4.5.x/current/tutorial/html/statemgmt.html#d5e515

https://www.cnkirito.moe/tcp-talk/


相关文章
|
2月前
HTTP协议中请求方式GET 与 POST 什么区别 ?
GET和POST的主要区别在于参数传递方式、安全性和应用场景。GET通过URL传递参数,长度受限且安全性较低,适合获取数据;而POST通过请求体传递参数,安全性更高,适合提交数据。
360 2
|
2月前
|
运维 网络协议 安全
为什么经过IPSec隧道后HTTPS会访问不通?一次隧道环境下的实战分析
本文介绍了一个典型的 HTTPS 无法访问问题的排查过程。问题表现为 HTTP 正常而 HTTPS 无法打开,最终发现是由于 MTU 设置不当导致报文被丢弃。HTTPS 因禁止分片,对 MTU 更敏感。解决方案包括调整 MSS 或中间设备干预。
|
2月前
|
缓存 网络协议 UED
深度解析HTTP协议从版本0.9至3.0的演进和特性。
总的来说,HTTP的演进是互联网技术不断发展和需求日益增长的结果。每一次重要更新都旨在优化性能,增进用户体验,适应新的应用场景,而且保证了向后兼容,让互联网的基础架构得以稳定发展。随着网络技术继续进步,我们可以预期HTTP协议在未来还会继续演化。
344 0
|
2月前
|
缓存 网络协议 API
HTTP/1.1相较于HTTP/1.0所实现的性能提升点分析。
通过以上的技术改进,HTTP/1.1显著提升了Web的性能和可靠性,同时减少了带宽的使用和服务器的负载。这些特性直到今天仍然是现代Web通信的基础。尽管如今HTTP/2和HTTP/3逐渐取代了旧的协议,以上所述的HTTP/1.1性能提升对所有后续版本仍然有着深远影响。
79 0
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
3月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
3月前
|
XML 安全 网络架构
深度对比SOAP与HTTP协议:详细理解它们的工作原理和差异
在设计服务和系统交云策略时,考虑到上述差异是至关重要的。SOAP适合需要高安全性、可靠性和事务支持的企业级应用。而HTTP适合Web界面浏览、RESTful服务和需要快速响应的轻量级通信。根据具体需求和上下文,开发者可以选择合适的协议以实现最优的系统性能和用户体验。
313 0
|
XML 缓存 算法
HTTP协议详解
HTTP协议详解
697 1
HTTP协议详解
|
Web App开发 网络协议 .NET
第206天:http协议终极详解---看这一篇就够了
HTTP简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
3744 0

热门文章

最新文章