来源丨木木匠

my.oschina.net/luozhou/blog/3003053

文中很多细节就是面试时关于“网络”这一块所常问的,还是得多积累一点


概   览

上一篇文章《对于Ping的过程,你真的了解吗?》通过实际抓包来分析了一次 Ping 的过程(面试常问),我们知道了 ping 是依托于 ICMP 协议,然后再局域网中还会涉及到 ARP 请求,今天这篇文章我们同样用抓包分析工具来分析我们熟悉的 HTTP 请求是怎么样的?


环境准备

本来是想找个网站进行抓包分析的,但是正式环境的网站 HTTP 请求太多,干扰太多,对分析不太友好,所以简单些了一个 demo,对 HTTP 请求返回字符串。

环境:

1.响应http请求的服务demo

2.客户端ip:192.168.2.135

3.服务端:45.76.105.92

4.抓包工具:Wireshark

把 demo部署到服务器,启动成功访问如下:


打开抓包工具 Wireshark 进行抓包,抓包结果如下:

从上图我们已经看到成功抓包到一次 HTTP 请求和响应了,但是我们看到却有很多 TCP请求,接下来我们来分析下这些 TCP 请求是做什么的?


抓包分析

A) 三次握手

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

192.168.2.135:60738---->45.76.105.92:8081

看上面的截图我们知道这是 TCP 协议的第一次握手,熟悉 TCP 协议的同学肯定知道 TCP 建立连接有三次握手,断开连接有四次挥手。

我们先看第一次请求:

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。

对于上面的概念,这里简单解释下,再介绍之前我们先看 TCPHeader 的数据结构图,对 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 确认,但是,默认只支持顺序的确认,也就是说,发送 ABC 个包,如果我收到了 AC的包, 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) 四次挥手

上述序号 8910 是我关闭浏览器后抓到的包,既然是关闭浏览器,我们肯定知道就是 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号包是对 Frame500 的 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 请求,那么是不是每次请求都会有三次握手吗?

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

在 HTTP0.9 版本和 HTTP1.0 版本中,每次请求响应都是要三次握手的, 但是 HTTP1.0 开始尝试持续连接,也就是 Keep-Alive 参数,但是官方还没有正式支持,在 HTTP1.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 包,然后服务端进行 ACK 确认,我们看到 keep-alive 包,实际上是会发带有一个字节的包,这就是 keep-alive 的实现。

说完 Keep-Alive,我们回到最开始的问题,为啥一次 HTTP 请求会有进行两个端口的握手呢?其实,这个和协议本身没有任何关系,第一个抓包的截图是用谷歌浏览器访问的,最后一个抓包图是用火狐浏览器访问的,仔细对比我们发现,火狐浏览器只有一个端口三次握手。所以这种情况的发生就是浏览器自身的实现,谷歌浏览器为什么会这么实现,猜测是:尽可能的保证HTTP访问的可用性,当某个端口不可用,可以立即切换到另外一个端口,完成HTTP的请求和响应。(个人猜测,如果有权威解答,可以评论区交流)


总   结

  • HTTP 请求是依托于 TCP 连接的,第一次连接的时候会进行 TCP 的三次握手。

  • HTTP 通过 Keep-Alive 来进行持久连接,通过定时发送一个心跳包,来告诉服务端自己还活跃。

  • HTTP 连接的断开也会导致 TCP的四次挥手,但是如果服务器判断满足条件,会合并 ACK 和 FIN 信号,进而转化为三次挥手。


后   记

若有错误或者不当之处,可在本公众号内反馈,一起学习交流!

更多热文在此:

●  Spring Boot 系列实战文章合集(源码已开源)

●  程序员写简历时必须注意的技术词汇拼写

●  基于Spring Security OAuth2的SSO单点登录+JWT权限控制实战

●  从一份配置清单详解Nginx服务器配置

●  如何在Windows下像Mac一样优雅的开发

●  Docker容器可视化监控中心搭建

●  利用ELK搭建Docker容器化应用日志中心

●  RPC框架实践之:Google gRPC

●  一文详解 Linux系统常用监控工具


更多 务实、能看懂、可复现的 技术文章尽在公众号 CodeSheep,欢迎扫码订阅,第一时间获取更新 ⬇️⬇️⬇️

http抓包实战 pdf_抓包实战 | 浏览器里的HTTP请求到底是如何完成的?相关推荐

  1. 全网最详细charles抓包工具详细教程,实战教程(细致)

    目录:导读 一.前言 二.在PC端抓https包 三.在PC端抓https包 四.在移动端抓http包 五.在移动端抓https包 一.前言 charles相当于一个插在服务器和客户端之间的" ...

  2. Python爬虫新手教程:实战APP抓包,抖音的小姐姐等着我!

    APP抓包 前面我们了解了一些关于 Python 爬虫的知识,不过都是基于 PC 端浏览器网页中的内容进行爬取.现在手机 App 用的越来越多,而且很多也没有网页端,比如抖音就没有网页版,那么上面的视 ...

  3. Python爬虫包 BeautifulSoup 递归抓取实例详解

    Python爬虫包 BeautifulSoup 递归抓取实例详解 概要: 爬虫的主要目的就是为了沿着网络抓取需要的内容.它们的本质是一种递归的过程.它们首先需要获得网页的内容,然后分析页面内容并找到另 ...

  4. 【Wireshark系列十】wireshark怎么抓包、wireshark抓包详细图文教程

    wireshark怎么抓包.wireshark抓包详细图文教程 wireshark是非常流行的网络封包分析软件,功能十分强大.可以截取各种网络封包,显示网络封包的详细信息.使用wireshark的人必 ...

  5. Go Modules依赖包管理与Go Modules实战

    目录 Go Modules依赖包管理 Go Modules 简介 Go 包管理的历史 Go1.5 版本前:GOPATH Go1.5 版本:Vendoring "百花齐放":多种 G ...

  6. R语言将dataframe数据从宽表(wide)变为长表(long)实战:tidyr包的gather函数、cdata包的unpivot_to_blocks函数、data.table使用melt函数

    R语言将dataframe数据从宽表(wide)变为长表(long)实战:tidyr包的gather函数.cdata包的unpivot_to_blocks函数.data.table使用melt函数 目 ...

  7. R语言KMeans聚类分析确定最优聚类簇数实战:NbClust包(确定最优聚类簇数)

    R语言KMeans聚类分析确定最优聚类簇数实战:NbClust包(确定最优聚类簇数) 目录

  8. R可视化包ggplot2设置透明背景实战

    R可视化包ggplot2设置透明背景实战 目录 R可视化包ggplot2设置透明背景实战 #ggplot2设置透明背景语法 #ggplot2绘图

  9. R语言dplyr包arrage函数排序dataframe实战:单列排序、多列排序、自定义排序

    R语言dplyr包arrage函数排序dataframe实战:单列排序.多列排序.自定义排序 目录 R语言dplyr包arrage函数排序dataframe实战:单列排序.多列排序

最新文章

  1. 成为MySQL DBA 博客系列-数据库升级
  2. Spring Batch在大型企业中的最佳实践
  3. 数据同步关于去除乱码插入mysql数据库
  4. MSSQL 2005数据库与SP4补丁安装
  5. python常用的装饰器有哪些_python基本装饰器
  6. 校招软件测试面经篇二(国企、银行、运营商等)
  7. 寒假怎么过?经典计算机教材助你弯道超车
  8. 多态的概念简单明了,易掌握,易理解!
  9. 餐饮服务设备行业的互联网趋势
  10. js实现简易HTML动画-----手翻书版
  11. BaseMultiItemQuickAdapter 条目position获取
  12. 西门子TIA PORTAL 安装过程中反复要求重新启动计算机问题
  13. java 设计模式实战,适配器模式之万物拟人化
  14. 电力系统分析(Matlab代码实现)
  15. [maven报错]mvn clean install报错BUILD FAILURE
  16. 基于JAVA老鹳窝旅游网计算机毕业设计源码+数据库+lw文档+系统+部署
  17. AlphaFold2源码解析(1)--安装使用
  18. pjsip for android,编译pjsip for Android
  19. k8s的service和endpoint
  20. 分享一款网页端CF活动助手 网页打开一键领取所有CF活动

热门文章

  1. vue双向绑定时添加.sync不起作用的原因
  2. java关于Timer schedule执行定时任务 1、在应用开发中,经常需要一些周期性的操作,比如每5分钟执行某一操作等...
  3. GCD之barrier
  4. 通过layout进行适配遇到的一点小问题及解决方案
  5. PHP输出控制(Output Control)函数
  6. 关于卸载vmwave虚拟机后键盘不能输入的问题
  7. 这下终于可以方便地发表测试型网页代码了(附源码下载)
  8. asp.net关于页面不回发,不生成__doPostBack方法问题的完美解决方案--ZT
  9. 甭给《程序员》把脉——你不是主编
  10. 纪念BLives 1.0版本发布