作者:大木匠

https://my.oschina.net/luozhou/blog/3003053

概   览

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

环境准备

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

环境:

  1. 1.响应http请求的服务demo

  2. 2.客户端ip:192.168.2.135

  3. 3.服务端:45.76.105.92

  4. 4.抓包工具:Wireshark

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


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

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


抓包分析

A) 三次握手

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

  1. 192.168.2.135:60738---->45.76.105.92:8081

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

我们先看第一次请求:

  1. 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 阶段扩展因子生效,调整了窗口大小。生效的抓包如下:

  1. 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. ------------------------请求连接--------------------------

  2. 1) 60738 -> 8081 [SYN] Seq=0 Win=64240 Len=0 Mss=1460 Ws=256 SACK_PERM=1

  3. 2) 8081 -> 60738 [SYN,ACK] Seq=0 ACK =1 Win=29200 Len=0 MSS=1420 SACK_PERM=1 WS=128

  4. 3) 60738 -> 8081 [ACK] Seq=1 ACK=1 Win=66560 Len=0

  5. 4) Get /test HTTP/1.1

  6. 5) 8081 -> 60738 [ACK] Seq=1 ACK=396 Win=30336 Len=0

  7. 6) HTTP/1.1 200 (text/html)

  8. 7) 60738 -> 8081 [ACK] Seq=396 ACK=120 Win=66560 Len=0

  9. ------------------断开连接-----------------------------

  10. 8) 60738 -> 8081 [FIN ACK] Seq=396 Ack=120 Win=66560 Len=0

  11. 9) 8081 -> 60738 [FIN ACK] Seq=120 Ack=397 Win=30336 Len=0

  12. 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. 1. 客户端发起一个断开请求,进入 FIN-WAIT 状态

  2. 2. 服务端确认断开请求

  3. 3. 服务端立即发送一个断开请求,进入 CLOSE-WAIT 状态

  4. 4. 客户端确认服务端断开请求,进入 TIME-WAIT 状态

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

  1. 9) 8081 -> 60738 [FIN ACK] Seq=120 Ack=397 Win=30336 Len=0

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

  1. 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. 1.检查死节点

  2. 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 信号,进而转化为三次挥手。

长按订阅更多精彩▼

如有收获,点个在看,诚挚感谢

爬虫核心原理:一次 HTTP 请求到底是如何完成的?相关推荐

  1. 冰河最新出版的《深入理解高并发编程:核心原理与案例实战》到底讲了些啥?(视频为证)

    大家好,我是冰河~~ 最近有很多小伙伴问我:<深入理解高并发编程:核心原理与案例实战>这本书有没有目录.我:安排!这不,我连夜录制了这本书的整体内容,希望能够为小伙伴们带来实质性的帮助,直 ...

  2. 干货:一文看懂网络爬虫实现原理与技术(值得收藏)

    01  网络爬虫实现原理详解 不同类型的网络爬虫,其实现原理也是不同的,但这些实现原理中,会存在很多共性.在此,我们将以两种典型的网络爬虫为例(即通用网络爬虫和聚焦网络爬虫),分别为大家讲解网络爬虫的 ...

  3. 揭秘Java网络爬虫程序原理

    随着互联网+时代的来临,越来越多的互联网企业层出不穷,涉及游戏.视频.新闻.社交.电商.房产.旅游等众多行业.如今互联网成为大量信息的载体,如何有效地从中提取有价值的信息并利用这些信息成为一个巨大的挑 ...

  4. 网络爬虫(一):爬虫基础原理

    一.学习前言 学习完Python语言后,总觉得有难以用武之地,纸上学来终觉浅,绝知此事要躬行,如果不加以使用就很容易忘记,为了加深python语言的记忆和理解,我选择学习网络爬虫的技术来进一步提升自己 ...

  5. RPC 实战与核心原理分析

    RPC 实战与核心原理分析 RPCX是一个分布式的Go语言的 RPC 框架,支持Zookepper.etcd.consul多种服务发现方式,多种服务路由方式, 例子 服务端 package maini ...

  6. 附录 区块链技术名词与核心原理

    一.区块链的技术要素 (一)区块与链 从技术角度看,区块链是一种利用去中心化和去信任的方式集体维护一本数据簿的可靠性的技术方案.该方案要让 参与系统中的任意多个节点,通过一串使用密码学方法相关联产生的 ...

  7. 分布式计算,大型网站技术架构:核心原理与案例分析

    这个回答,非常详细. 但是,大部分内容,都来自"大型网站技术架构:核心原理与案例分析". 最近,初步看了这本书,觉得写得太好了,比较系统和全面. 不过,我还是不喜欢吹B" ...

  8. redis核心原理与设计思想

    redis核心原理与设计思想 一.redis的5种基本数据结构 1.String(字符串) redis字符串扩容策略 2.list(列表) list常用命令 右边进左边出:队列 右边进右边出:栈 快速 ...

  9. Redis 面霸篇:从高频问题透视核心原理

    今天咱们从高频面试问题跟大家一起横扫 Redis 核心知识点,从根本上理解 Redis ,不做八股文的工具人,做扭转乾坤的大神. Redis 为什么这么快? 很多人只知道是 K/V NoSQl 内存数 ...

最新文章

  1. 两成开发者月薪超 1.7 万、算法工程师最紧缺! | 中国开发者年度报告
  2. Nature综述:微生物的衰老与寿命
  3. leetcode算法题--包含min函数的栈
  4. Latex博士论文格式版本(在CASthesis基础上作修改)
  5. 人脸识别撞脸名画_与名画“撞脸”火爆数博会 观众直呼“太好玩”【高清组图】...
  6. 访问者(Visitor)模式
  7. 李飞飞:新技术变革时代的数据库产业
  8. docker容器内无法下载到alpine的资源,报错network error (check Internet connection and firewall)
  9. input ios问题
  10. 虚拟机服务器实验三十一 Windows Server 2012 RDS桌面虚拟化之二VDI标准部署之托管共享桌面虚拟化...
  11. 斯坦福大学CS143编译原理课程笔记:2.编译器结构
  12. 从校园情侣到教授夫妇,最好的科研爱情是一起进步
  13. python 笔记 冒泡排序
  14. 希望能多看几遍找到自己的影子
  15. Android基础之批量发送短信
  16. linux php验证码无法显示,PHPCMS在Linux下后台验证码无法显示的解决方法
  17. 电路定理——替代定理
  18. chr python用法_使用Python内建chr, ord实现的简单的加/解密
  19. 3397. 【GDOI2014模拟】雨天的尾巴
  20. java统计在线人数_java实现在线人数统计

热门文章

  1. PTA基础编程题目集-6-7 统计某类完全平方数
  2. 计算机及网络应用基础思维导图_思维导图在生物教学中的应用
  3. HDU 4635 Strongly connected(缩点、最多可加边数使得仍然非强连通)
  4. 解题报告:POJ - 1062 昂贵的聘礼(最短路、超级源点)
  5. 计算机图形学直线扫描转论文,计算机图形学实验报告-实验1直线段扫描转换.doc...
  6. 软件测试在哪个城市好找工作,职业测试:你适合在哪个城市工作?
  7. linux 网卡流量脚本,每5分钟统计Linux 网卡流量的脚本
  8. 调用链系列四:调用链上下文传递
  9. GitHub Checks API帮助应用实现进一步的持续集成
  10. 赠 看穿一切的var_dump