浅析一次HTTP请求

1、概览

上一篇文章《对于Ping的过程,你真的了解吗?》咱们经过抓包工具来分析了一次 Ping 的过程,咱们知道了 ping 是依托于 ICMP 协议,而后再局域网中还会涉及到 ARP 请求,今天这篇文章咱们一样用抓包分析工具来分析咱们熟悉的 HTTP 请求是怎么样的?html

2、环境准备

原本我是想找个网站进行抓包分析的,可是正式环境的网站 HTTP 请求太多,干扰太多,对分析不太友好,因此我简单些了一个demo,对 HTTP 请求返回字符串。浏览器

环境:服务器

1.响应http请求的服务demo
2.客户端ip:192.168.2.135
3.服务端:45.76.105.92
4.抓包工具:Wireshark
复制代码

我把demo部署到服务器,启动成功访问以下:网络

打开抓包工具 Wireshark 进行抓包,抓包结果以下:数据结构

图 Http-Request
从上图咱们已经看到成功抓包到一次 HTTP 请求和响应了,可是咱们看到却有不少TCP请求,接下来咱们来分析下这些 TCP 请求是作什么的?

3、抓包分析

A) 三次握手

1.最开始是本地发送了2次请求到服务器,这里为何会有两次请求,稍后再说,咱们先主要看 HTTP 对应的端口请求,以下:socket

192.168.2.135:60738---->45.76.105.92:8081
复制代码

看上面的截图咱们知道这是 TCP 协议的第一次握手,熟悉 TCP 协议的同窗确定知道 TCP 创建链接有三次握手,断开链接有四次挥手。(对 TCP 协议不太了解的同窗能够查看这篇文章《跟着动画来学习 TCP 三次握手和四次挥手》)工具

咱们先看第一次请求:post

60738 -> 8081 [SYN] Seq=0 Win=64240 Len=0 Mss=1460 Ws=256 SACK_PERM=1
复制代码

咱们来解析下这段包请求信息:学习

  • 60783 -> 8081 端口号:源端口--->目标端口
  • [SYN] :同步握手信号
  • Seq : 消息编号
  • Win: TCP 窗口大小
  • Len: 消息长度
  • Mss: 最大报文段长度
  • Ws: 窗口缩放调整因子
  • SACK_PERM : SACK选项,这里等于1表示开启 SACK。

对于上面的概念,这里简单解释下,再介绍以前咱们先看 TCP Header 的数据结构图,对 TCP 头部数据结构有个直观的了解动画

1. Win: TCP 窗口大小,是指TCP传输能接受的最大字节数,这个能够进行动态调节,也就是TCP的滑动窗口,经过动态调整窗口大小,来控制发送数据的速率。上图中占用2个字节,也就是16位,那么能够支持的最大数就是2^16=65536,因此默认状况下TCP头部标记能支持的最大窗口数是65536字节,也就是64KB。

2. Len: 消息长度 就是指数据报文段,由于整个TCP报文=Header+packSize,因此这个消息长度就是指要传送的数据包总共长度,在本次分析中也就是HTTP报文的大小。

3. Mss: 最大报文段长度:这个就是规定最大的能传输报文的长度,为了达到最佳的传输效能,TCP 协议在创建链接的时候一般要协商双方的 MSS 值,这个值 TCP 协议在实现的时候每每用 MTU 值代替(须要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)因此通常 MSS 值1460,这也和咱们抓包图中的值一致。

4. Ws: 窗口缩放调整因子:在前面说 TCP 窗口大小中咱们说到,默认状况下,TCP 窗口大小最大只能支持64KB的缓冲数据,在今天这个高速上网时代,这个大小确定不知足条件了,因此,为了可以支持更多的缓冲数据 RFC 1323 中就规定了 TCP 的扩展选项,其中窗口缩放调整因子就是其中之一,这个是如何起做用的呢?首先说明,这个参数是在 [SYN] 同步阶段进行协商的,咱们结合上面抓包数据分析下。咱们看到第一次请求协商的结果是WS=256,而后再 ACK 阶段扩展因子生效,调整了窗口大小。生效的抓包以下:

60738 ->8081   [ACK] Seq=1 ACK=1 Win=66560 Len=0
复制代码

咱们发现这个窗口变成了66560,比默认的窗口要大,咱们查看报文详情:

咱们发现,实际请求声明的窗口是260,WS扩展因子是256,最终计算的窗口大小是66560,因此咱们知道了,这个扩展因子的做用就是,用原窗口大小乘以扩展因子,获得最终的窗口大小,也就是260*256=66560.

5. SACK_PERM:SACK选项 ,咱们知道 TCP 传输有包的确认机制,默认状况下,接受端接受到一个包后,发送 ACK 确认,可是,默认只支持顺序的确认,也就是说,发送 A,B,C 个包,若是我收到了A,C的包,B没有收到,那么对于C,这个包我是不会确认的,须要等B这个包收到后再确认,那么TCP有超时重传机制,若是一个包好久没有确认,就会当它丢失了,进行重传,这样会形成不少多余的包重传,浪费传输空间。为了解决这个问题,SACK就提出了选择性确认机制,启用 SACK 后,接受端会确认全部收到的包,这样发送端就只用重传真正丢失的包了。

简单介绍了上面的基础概念后,咱们来根据抓包梳理下 HTTP 请求的过程,根据 HTTP 请求本地端口是 60378,我梳理的流程以下:

------------------------请求链接--------------------------
1) 60738 -> 8081 [SYN] Seq=0 Win=64240 Len=0 Mss=1460 Ws=256 SACK_PERM=1
2) 8081 -> 60738 [SYN,ACK] Seq=0 ACK =1 Win=29200 Len=0 MSS=1420 SACK_PERM=1 WS=128
3) 60738 -> 8081  [ACK] Seq=1 ACK=1 Win=66560 Len=0
4) Get /test HTTP/1.1
5) 8081 -> 60738  [ACK] Seq=1 ACK=396 Win=30336 Len=0
6) HTTP/1.1 200 (text/html)
7) 60738 -> 8081  [ACK] Seq=396 ACK=120 Win=66560 Len=0

------------------断开链接-----------------------------
8) 60738 -> 8081 [FIN ACK] Seq=396 Ack=120 Win=66560 Len=0
9) 8081 -> 60738  [FIN ACK] Seq=120 Ack=397 Win=30336 Len=0
10) 60738 -> 8081 [ACK] Seq=397 Ack=121 Win=66560 Len=0
复制代码

咱们根据上面的流程梳理,能够知道,序号1-序号3是明显的三次握手,而后序号4进行了一次 HTTP 请求,接着序号5是对 HTTP 请求的一次接收确认,序号6是响应 HTTP 请求,序号7是对响应请求的确认。

B) 四次挥手

上述序号 8,9,10 是我关闭浏览器后抓到的包,既然是关闭浏览器,咱们确定知道就是 TCP 链接的断开了。这里有同窗应该已经发现了问题了,咱们的断开是4次挥手,你这抓的包只有三条记录,是你写错了吧?我要告诉你的是,我没有写错,这是真实的抓包抓的,至于为何是三次,咱们来分析一下:

正常状况下,链接断开是4次挥手的,4次挥手过程以下图:

咱们分析这图,挥手流程是这样的:

1.客户端发起一个断开请求,进入 FIN-WAIT 状态
2.服务端确认断开请求
3.服务端当即发送一个断开请求,进入 CLOSE-WAIT 状态
4.客户端确认服务端断开请求,进入 TIME-WAIT 状态
复制代码

咱们发现上面的流程2和流程3都是由服务端发起的,那么有没有可能合并这两个请求,一次发送给客户端?答案是 能够。在 RFC 2581中的4.2 节有提到,ack能够延迟确认,只要求保证在500ms以内保证确认包到达便可。在这样的标准下,TCP确认是有可能进行合并延迟确认的,因此,根据这一点,咱们推断下面这个包:

9) 8081 -> 60738  [FIN ACK] Seq=120 Ack=397 Win=30336 Len=0
复制代码

合并了对客户端的ack确认以及服务端发送的FIN断开信号包。咱们点击该包详情以下: 这里红框中体现了,这个9号包是对 Frame 500 的 ACK 确认,咱们根据最开始的截图能够知道,这个包就是8号包

8) 60738 -> 8081 [FIN ACK] Seq=396 Ack=120 Win=66560 Len=0
复制代码

而且 9号包 自己本身是发送的 FIN 信号包,因此,咱们能够认为 9号包合并了ACK 和 FIN 的内容,因此一般的4次挥手,通过合并后变成了3次挥手。

以上就是一个 HTTP 完整的请求,整个流程用图表示以下:

C) Keep-Alive

这里确定有同窗会问,既然这是一次完整的 HTTP 请求,那么是否是每次请求都会有三次握手吗?

答案是:目前的协议是不用的

在 HTTP 0.9 版本和 HTTP 1.0 版本中,每次请求响应都是要三次握手的, 可是 HTTP 1.0 开始尝试持续链接,也就是 Keep-Alive 参数,可是官方尚未正式支持,在 HTTP 1.1协议中,官方默认就是支持 Keep-Alive 参数的,默认是持续链接。Keep-Alive 的做用主要有两点:

1.检查死节点
2.防止链接因为不活跃而断开
复制代码

检查死节点

主要是为了让链接快速失败被发现,能够进行从新链接,好比A 和 B 两端已经创建了链接,B节点由于 异常缘由挂掉了,同时 A 节点并不知道,这时候有两种状况:

1.假设 B 节点尚未恢复,那么 B 节点不会回复 ACK,A节点就会一直重试,重试到必定次数才能知道 B 节点是死节点。

2.B节点在A发送数据以前重启成功了,这个时候A节点发送数据,B节点并不会接受,而是会发送一个 RST 信号(在一个已关闭的 socket 上收到数据时,将发送RST数据包,要求对端关闭异常链接且对端不须要回复ACK),而后 A 才知道 B 节点须要重连了。

以上两种状况,都会致使只有到发送数据的时候才知道对方已经出异常了。而Keep-Alive 每隔一段时间就会发送心跳,就能够很快的知道服务端节点的状况。

防止链接因为不活跃而断开

咱们知道,网络链接的创建和维持是消耗资源的,一个服务器上能创建的链接是有限的,因此像防火墙或者操做系统中会为了节省资源会释放掉不活跃的链接,而 Keep-Alive 每隔一段时间发送一个心跳包,就是告诉防火墙或者操做系统,我这个链接是活跃的,不要杀我。

我从新抓了一次带有 Keep-Alive 的包,截图以下:

图 Keep-Alive
在上图中最后两个包就是发的 Keep-Alive 包,而后服务端进行 ACK 确认,咱们看到 keep-alive 包,其实是会发带有一个字节的包,这就是 keep-alive 的实现。

说完 Keep-Alive,咱们回到最开始的问题,为啥一次 HTTP 请求会有进行两个端口的握手呢?其实,这个和协议自己没有任何关系,第一个抓包的截图(图 Http-Request )是我用谷歌浏览器访问的,最后一个抓包图(图Keep-Alive)是我用火狐浏览器访问的,仔细对比咱们发现,火狐浏览器只有一个端口三次握手。因此这种状况的发生就是浏览器自身的实现,谷歌浏览器为何会这么实现,个人猜想是:尽量的保证HTTP访问的可用性,当某个端口不可用,能够当即切换到另一个端口,完成HTTP的请求和响应。(我的猜想,若是有权威解答,麻烦告知交流)

4、总结

  • HTTP 请求是依托于 TCP 链接的,第一次链接的时候会进行 TCP 的三次握手。
  • HTTP 经过 Keep-Alive 来进行持久链接,经过定时发送一个心跳包,来告诉服务端本身还活跃。
  • HTTP 链接的断开也会致使TCP的四次挥手,可是若是服务器判断知足条件,会合并 ACK 和 FIN 信号,进而转化为三次挥手。

5、参考

[1] RFC 1323

[2] RFC 2581

[3] TCP Keepalive HOW TO

相关文章
相关标签/搜索