本文分享自华为云社区《输入网址,小手一点,后面到底发生了什么?》,作者:龙哥手记。
输入网址并点回车,后台到底发生了什么。透析 HTTP 协议与 TCP 连接之间的千丝万缕的关系。掌握为何是三次握手四次挥手?time_wait 存在的意义是什么?全面图解重点问题,再也不用担心面试问这个问题。
大致流程
- URL 解析,解析 http 协议、端口、资源地址。
- DNS 查询:首先查询本地 host,再访问 DNS 服务器将 域名解析成 ip 地址。
- 建立 TCP 连接。
- 服务器收到请求后处理,并且构造响应返回给客户端。
- 客户端接收 HTTP 报文响应。
- 渲染页面,最后有可能会四次挥手断开连接,也可能不会而是复用连接。
重点来了:
- 如何理解 TCP 的三次握手与四次挥手?每次握手客户端与服务端是怎样的状态?
- 为何挥手会出现 2MSL,遇到大量 Socket 处在 TIME_WAIT 或者 CLOSE_WAIT 状态是什么问题?
- 三次握手与四次挥手的过程是怎样的?
- HTTP 的报文格式又是怎样的?
URL 解析
url 遵守的规则是这个样子
scheme://host.domain:port/path/filename
每个名词的含义如下解释:
- scheme 定义应用层协议类型,比如 http、https、 ftp 等;
- host 定义域主机(http 的默认主机是 www);
- domain 定义因特网域名,比如 segmentfault.com;
- port 主机的端口,http 默认是 80, https 默认是 443;
- path 服务器上的资源路径;
- filename - 定义文档 / 资源的名称;
DNS 查询
浏览器不能直接通过域名找到服务器,只能通过 IP 地址。
那浏览器是如何通过域名查询到我们输入的 url 对应的 IP 呢?
- 浏览器缓存:按照一定频率缓存 DNS 数据。
- 操作系统缓存:如果浏览器缓存找不到记录则去操作系统中找。
- 路由缓存:路由器 DNS 缓存。
- ISP 的 DNS 服务器:ISP 是互联网服务提供商 (Internet Service Provider) 的简称,ISP 有专门的 DNS 服务器应对 DNS 查询请求。
- 根服务器:ISP 的 DNS 服务器还找不到的话,它就会向根服务器发出请求,进行递归查询(DNS 服务器先问根域名服务器.com 域名服务器的 IP 地址,然后再问 .baidu 域名服务器,依次类推)
TCP 连接建立与断开
通过域名解析出 IP 地址以后就要建立 TCP/IP 连接了。
TCP/IP 分为四层,每一层都会加上一个头部再发送给下一层。到了接收方后,对应的每一层则把对应层的头部解析拆除,丢上上一层,跟发送端的过程反过来。
TCP/IP 四层模型
应用层:发送 HTTP 请求
浏览器从地址栏得到服务器 IP,接着构造一个 HTTP 报文,其中包括:
- 请求行包含请求方法、URL、协议版本
- 请求报头 (Request Header):由 “关键字:值” 对组成,每行一对,关键字与值使用英文 “:” 分割
- 请求体:请求参数,并不是所有的请求都有请求参数。一般 get 参数 的格式 name=tom&password=1234&realName=tomson, 也可以将参数放在 body 里面。
传输层:TCP 传输报文
在传输报文之前会先建立 TCP/IP 连接,也就是后面我们要说的三次握手。
在这一层解决了数据可靠传输、及流量控制、拥塞控制。
可靠传输
对于发送方发送的数据,接收方在接受到数据之后必须要给予确认,确认它收到了数据。如果在规定时间内,没有给予确认则意味着接收方没有接受到数据,然后发送方对数据进行重发。
TCP 的可靠传输是通过确认和超时重传的机制来实现的,而确认和超时重传的具体的实现是通过以字节为单位的滑动窗口机制来完成。
TCP 拥塞控制
TCP 协议通过慢启动机制、拥塞避免机制、加速递减机制、快重传和快恢复机制来共同实现拥塞控制。
流量控制
采用通知窗口实现对发送端的流量控制,通知窗口大小的单位是字节。TCP 通过在 TCP 数据段首部的窗口字段中填入当前设定的接收窗口 (即通知窗口) 的大小,用来告知对方 ' 我方当前的接收窗口大小 ',以实现流量控制。
通信双方的发送窗口大小由双方在连接建立的时候商定,在通信过程,双方可以动态地根据自己的情况调整对方的发送窗口大小。
网络层:IP 协议查询 MAC 地址
将数据段打包,并加入源及目标的 IP 地址,并且负责寻找传输路线。判断目标地址是否与当前地址处于同一网络中,是的话直接根据 Mac 地址发送,否则使用路由表查找下一跳地址,以及使用 ARP 协议查询它的 Mac 地址。
链路层:以太网协议
根据以太网协议将数据分为以 “帧” 为单位的数据包,每一帧分为两个部分:
- 标头:数据包的发送者、接受者、数据类型
- 数据:数据包具体内容
Mac 地址
以太网规定了连入网络的所有设备都必须具备 “网卡” 接口,数据包都是从一块网卡传递到另一块网卡,网卡的地址就是 Mac 地址。每一个 Mac 地址都是独一无二的,具备了一对一的能力。
三次握手
在传输层传输数据之前需要建立连接,也就是三次握手创建可靠连接。
三次握手
首先建立链接前需要 Server 端先监听端口,因此 Server 端建立链接前的初始状态就是 LISTEN 状态,这时 Client 端准备建立链接,先发送一个 SYN 同步包,发送完同步包后,Client 端的链接状态变成了 SYN_SENT 状态。Server 端收到 SYN 后,同意建立链接,会向 Client 端回复一个 ACK。
由于 TCP 是双工传输,Server 端也会同时向 Client 端发送一个 SYN,申请 Server 向 Client 方向建立链接。发送完 ACK 和 SYN 后,Server 端的链接状态就变成了 SYN_RCVD。
Client 收到 Server 的 ACK 后,Client 端的链接状态就变成了 ESTABLISHED 状态,同时,Client 向 Server 端发送 ACK,回复 Server 端的 SYN 请求。
Server 端收到 Client 端的 ACK 后,Server 端的链接状态也就变成了的 ESTABLISHED 状态,此时建连完成,双方随时可以进行数据传输。
在面试时需要明白三次握手是为了建立双向的链接,需要记住 Client 端和 Server 端的链接状态变化。另外回答建连的问题时,可以提到 SYN 洪水攻击发生的原因,就是 Server 端收到 Client 端的 SYN 请求后,发送了 ACK 和 SYN,但是 Client 端不进行回复,导致 Server 端大量的链接处在 SYN_RCVD 状态,进而影响其他正常请求的建连。可以设置 tcp_synack_retries = 0 加快半链接的回收速度,或者调大 tcp_max_syn_backlog 来应对少量的 SYN 洪水攻击
四次挥手
我们只要关注 80 端口与 13743 端口建立的连接断开过程,浏览器通过 13747 端口发送 [FIN, ACK] 这里是不是跟很多网上看到的不一样?
- 其实是客户端在发送 [FIN] 报文的时候顺带发了一个 [ACK] 确认上次传输确认。
- 接着服务端通过 80 端口响应了 [ACK] ,然后立马响应 [FIN, ACK] 表示数据传输完了,可以关闭连接。
- 最后浏览器通过 13743 端口 发送 [ACK] 包给服务端,客服端与服务端连接就关闭了。
具体流程如下图抓包所示:
三次握手与四次挥手
TCP 连接与断开
客户端:
- SYN_SENT - 客户端发起第 1 次握手后,连接状态为 SYN_SENT ,等待服务端内核进行应答,如果服务端来不及处理(例如服务端的 backlog 队列已满)就可以看到这种状态的连接。
- ESTABLISHED - 表示连接处于正常状态,可以进行数据传送。客户端收到服务器回复的 SYN+ACK 后,对服务端的 SYN 单独回复(第 3 次握手),连接建立完成,进入 ESTABLISHED 状态。服务端程序收到第 3 次握手包后,也进入 ESTABLISHED 状态。
- FIN_WAIT_1 - 客户端发送了关闭连接的 FIN 报文后,等待服务端回复 ACK 确认。
- FIN_WAIT_2 - 表示我方已关闭连接,正在等待服务端关闭。客户端发了关闭连接的 FIN 报文后,服务器发回 ACK 应答,但是没进行关闭,就会处于这种状态。
- TIME_WAIT - 双方都正常关闭连接后,客户端会维持 TIME_WAIT 一段时间,以确保最后一个 ACK 能成功发送到服务器端。停留时长为 2 倍的 MSL (报文最大生存时间),Linux 下大约是 60 秒。所以在一个频繁建立短连接的服务器上通常可以看到成千上万的 TIME_WAIT 连接。
服务端:
- LISTEN - 表示当前程序正在监听某个端口时。
- SYN_RCVD - 服务端收到第 1 次握手后,进入 SYN_RCVD 状态,并回复一个 SYN+ACK(第 2 次握手),再等待对方确认。
- ESTABLISHED - 表示连接处于正常状态,可以进行数据传送。完成 TCP3 次握手后,连接建立完成,进入 ESTABLISHED 状态。
- CLOSE_WAIT - 表示客户端已经关闭连接,但是本地还没关闭,正在等待本地关闭。有时客户端程序已经退出了,但服务端程序由于异常或 BUG 没有调用 close () 函数对连接进行关闭,那在服务器这个连接就会一直处于 CLOSE_WAIT 状态,而在客户机已经不存在这个连接了。
- LAST_ACK - 表示正在等待客户端对服务端的关闭请求进行最终确认。
TIME_WAIT 状态存在的理由:
划重点了
- 可靠地实现 TCP 全双工连接的终止 在进行关闭连接四路握手协议时,最后的 ACK 是由主动关闭端发出的,如果这个最终的 ACK 丢失,服务器将重发最终的 FIN,因此客户端必须维护状态信息允 许它重发最终的 ACK。如 果不维持这个状态信息,那么客户端将响应 RST 分节,服务器将此分节解释成一个错误( 在 java 中会抛出 connection reset 的 SocketException)。因而,要实现 TCP 全双工连接的正常终 止,必须处理终止序列四个分节中任何一个分节的丢失情况,主动关闭 的客户端必须维持状 态信息进入 TIME_WAIT 状态。
- 允许老的重复分节在网络中消逝 TCP 分节可能由于路由器异常而 “迷途”,在迷途期间,TCP 发送端可能因确认超时而重发这个 分节,迷途的分节在路由器修复后也会被送到最终目的地,这个 原来的迷途分节就称为 lost duplicate。在关闭一个 TCP 连接后,马上又重新建立起一个相同的 IP 地址和端口之间的 TCP 连接,后一个连接被称为前一个连接的化身 ( incarnation),那么有可能出现这种情况,前一 个连接的迷途重复分组在前一个连接终止后出现,从而被误解成从属于新的化身。为了避免 这个情 况,TCP 不允许处于 TIME_WAIT 状态的连接启动一个新的化身,因为 TIME_WAIT 状 态持续 2MSL,就可以保证当成功建立一个 TCP 连接的时 候,来自连接先前化身的重复分组已 经在网络中消逝。
另外回答断链的问题时,可以提到实际应用中有可能遇到大量 Socket 处在 TIME_WAIT 或者 CLOSE_WAIT 状态的问题。一般开启 tcp_tw_reuse 和 tcp_tw_recycle 能够加快 TIME-WAIT 的 Sockets 回收;而大量 CLOSE_WAIT 可能是被动关闭的一方存在代码 bug,没有正确关闭链接导致的。
简单地说就是
- 保证 TCP 协议的全双工连接能够可靠关闭;
- 保证这次连接的重复数据段从网络中消失,防止端口被重用时可能产生数据混淆;