❕输入 URL 按下回车后
数据传输的全过程
Last updated
数据传输的全过程
Last updated
作为 HTTP 协议中的 user agent(请求方),浏览器要收发数据,就要建立 TCP 连接,因为 HTTP 是基于 TCP/IP 实现的数据的可靠传输。而要建立 TCP 连接,就得知道 [transport protocol, IP address, port number]
。
浏览器根据 URL 的语法结构解析 URL,提取里面的 scheme(协议)、host(主机)、port(端口)、path(路径)、query(参数)等。
其中,scheme 和 path 是通用 URI 必需的,host 是 HTTP URL 必需的。
若没有提供 port,则默认取 scheme 对应的 port
若没有提供 path,则默认是 /
如果 URL 里的 host 不是 IP address 而是 domain name,那就要用 DNS 协议来解析域名了,即把 domain name 转成 IP address。下图分别是 IPv4 和 IPv6 的地址格式:
域名解析的过程,大致如下:
本地域名 server(递归查询,所以查询顺序≠生效顺序)
浏览器 cache
OS cache
hosts file
远程查询
非权威 DNS server
DNS 核心系统:根域名 server -> 顶级域名 server -> 权威域名 server
拿到 IP address 和 port 之后,浏览器会依据 TCP 协议的规范,使用三次握手与 web 服务器建立连接。
TCP 协议用 scheme 和 port 来区分不同的服务。
浏览器在开始建立 TCP 连接之前,会得到一个动态 port,比如 52085 端口
web 服务器会 bind 到某个 port,并一直 listen 着,比如 80 端口、443 端口
可以用 Wireshark 抓到最开始的三个包:[SYN]
、[ACK, SYN]
、[ACK]
。
三次握手并不是 TCP 本身的要求,而是为了解决“在不可靠的信道上进行可靠传输”的问题。三次通信是理论上的最小值,因为客户端和服务端要确认彼此都可以正常地“收发”。
三次握手的目的:(比较书面化,知道即可)
为了防止“已失效的连接请求报文段”突然又传送到了服务端,进而产生错误
或为了解决“网络中存在延迟的重复分组”的问题
如果信道是可靠的(即无论什么时候发送消息,对方都一定能收到)或是我们就不关心对方能否收到消息,那么就可以像 UDP 那样发送消息了。
TCP 连接是全双工通信,也就是说它能进行双向通信,且有两个通道,从 A→B 是一个通道,而从 B→A 又是另一个通道。
双工通信(duplex communication)可以在两个方向上相互通信,有两种类型:
全双工(full-duplex)可以双向通信,且允许同时发生,比如老式电话。严格来说它们之间有两个 communication channels(沟通信道)
半双工(half-duplex 或 semiduplex)可以双向通信,但一次只能提供一个方向,比如对讲机
单工通信(simplex communication)只能向一个方向发送信息,比如广播电台、电视、监控摄像头
A 给 B 发送一个建立连接的请求 SYN
B 回一个同意并确认 ACK
B 给 A 发送一个建立连接的请求 SYN
A 回一个同意并确认 ACK
其中,第 2 个和第 3 个可以合并成一个请求,所以有三次握手。如下:
大写的 ACK 是报文类型,小写的 ack 是报文里的确认号(值是对方的 seq+1)
一次握手 SYN
:客户端向服务端发起连接请求,服务端(不一定)收到请求
若收到了(便达成了)服务器知道了“客户端能发”
二次握手 ACK + SYN
:服务端确认收到了请求,同时告知客户端自己也要发送数据
若收到了(便达成了)客户端知道了“服务器能收”,且“服务器能发”
三次握手 ACK
:客户端确认
若收到了(便达成了)服务端知道了“客户端能收”
在三次握手的过程中,客户端和服务端也会交换彼此的 ISN(Init Sequense Number,初始化序列号),以便让对方知道后续如何按序列号组装收到的数据。还会交换 TCP 的窗口大小信息。
经过了三次握手,浏览器和服务器之间就建立了全双工通信。有了可靠的 TCP 连接通道之后,HTTP 协议就可以开始工作了。
请求方和应答方,都必须依据 HTTP 的规范来构建和解析报文,且在收到 HTTP 报文之后,会给对方回一个 TCP 的确认,表示已经收到了。
比如,浏览器按照 HTTP 协议规定的格式,通过 TCP 发送了一个请求报文 GET / HTTP/1.1
,即下图中的第 4 个包。随后,web 服务器回复了第 5 个包,是在 TCP 协议的层面对第 4 个包进行了确认“刚才的报文我已经收到了”。
web 服务器在收到 HTTP 的请求报文之后,也是依据 HTTP 协议的规定,解析报文,然后把处理结果拼接成符合 HTTP 格式的报文,再发回去,即第 6 个包 HTTP/1.1 200 OK
。同样,在浏览器收到之后,会给服务器一个 TCP 的 ACK
确认,即第 7 个包。
一对 HTTP 请求 + 响应,一来一回外加两个确认,共四个包。
至此,图中出现的 11 个包(3次握手+2个HTTP请求),交互逻辑大致如下:
在抓包的截图里,没有出现关闭 TCP 连接的四次挥手,这是因为 HTTP/1.1 的长连接特性,默认不会立即关闭连接。
浏览器收到响应之后,就开始解析并渲染页面。
解析 HTML,构建 DOM 树,同时根据样式表构建 CSSOM 树
在此过程中浏览器可能还会发起其它 HTTP 请求,比如下载外链 CSS 和 JS 以及图片等资源,它会并行下载页面中的各种资源,其中 HTTP 1.1 每个域的并行数量有限制
若遇 GET 请求还可能会命中浏览器的缓存
将 DOM 树和 CSSOM 树,合并成 Render 树
会忽略非视觉节点,比如元数据、display:none
根据 Render 树来进行布局(确定各个元素的位置和尺寸)和渲染
TCP 连接是全双工通信,所以两个方向需要单独关闭。
A 给 B 发送一个关闭连接的请求 FIN
B 回一个同意并确认 ACK
B 给 A 发送一个关闭连接的请求 FIN
A 回一个同意并确认 ACK
之所以不能像握手时将第 2 个和第 3 个合并成一个 [ACK, FIN]
,是因为:虽然一方不发数据了,但它要接收的数据可能还没完;而且结束数据传输的指令是由应用层给出的,TCP 也是没法立马回对方一个 FIN
。
三次握手时的发起者只能是客户端,而四次挥手的发起者可以是任何一方。假设是由客户端发起的关闭 TCP 连接的请求 FIN
,如下:
客户端给服务器发送 FIN
,表示从客户端→服务器的通道要关闭了。此时,客户端就不能再向服务器发正常的数据请求了,而服务器还是可以向客户端发送正常的数据请求的
服务器给客户端发送 ACK
,表示收到了
服务器给客户端发送 FIN
,表示从服务器→客户端的通道要关闭了
客户端给服务器发送 ACK
,之后客户端进入 TIME_WAIT
状态,然后等 2*MSL 再进入 CLOSED
状态。而当服务端收到该确认之后,就成功变成 CLOSED
状态
MSL,Maximum Segment Lifetime。客户端之所以要等 2*MSL,是因为:要确保 ACK
能到达服务器,如果 ACK
丢了,服务器会再次发送 FIN
,这 2*MSL 就是留给客户端收重传报文的;要等还在路上的报文,要么收到、要么被重传、要么过期,结果就是让本次 connection 中的重复数据都能从网络中消失。如果不等这 2*MSL 就直接进入 CLOSED
状态,假如客户端又再次向服务端发起新的连接且 port 和上次一样,那么新连接就会收到脏数据。
TCP,Transmission Control Protocol,传输控制协议。
TCP 提供 reliable、ordered、error-checked 的八位字节流传输
TCP 是 connection-oriented,在发送 data 前 client 和 server 间的 connection 要建立好
确保 reliability(可靠性)的手段:(有状态,但增加了 latency 延迟)
three-way handshake:三次握手
ACK + retransmission:确认 + 重传
error detection:错误检测
flow control(流量控制)和 congestion control(拥塞控制)
结合 TCP 的三次握手和四次挥手,浅浅地感受下 TCP 的状态和报文段首部格式。
TCP 协议的操作,可以分为三个阶段:connection establishment、data transfer 以及 connection termination(close connection、release(释放)所有分配的资源)。
TCP connection 由 operating system 通过 Internet socket(套接字)来管理。Internet socket 是 computer network 中 network node 里的软件结构。
随着 TCP/IP 协议的标准化,术语 network socket 通常用在 Internet 协议族的上下文中,此时 socket 会通过三元组的 socket address [transport protocol, IP address, port number]
将自己标识给其它 host。
术语 socket 也用于 node(节点)内部 IPC(Inter-Process Communication,进程间通信)的 software endpoint(软件端点),它通常使用和 network socket 相同的 API。
TCP socket states 有:
CLOSED
✔
✔
LISTEN
✔
等待来自任意远程 TCP end-point 的 connection request
SYN-SENT
✔
等待匹配的 connection request,在发送了一个 connection request 之后
SYN-RECEIVED
✔
等待确认,在接收和发送了连接请求之后
ESTABLISHED
✔
✔
一个 open connection data transfer 阶段的正常状态
FIN-WAIT-1
✔
✔
等待来自远程 TCP 的 connection termination request,或等待对先前发送的连接终止请求的确认
FIN-WAIT-2
✔
✔
等待来自远程 TCP 的 connection termination request
CLOSE-WAIT
✔
✔
等待来自 local user 的 connection termination request
CLOSING
✔
✔
等待来自远程 TCP 的连接终止请求确认
LAST-ACK
✔
✔
等待对先前发送到远程 TCP 的连接终止请求的确认
TIME-WAIT
✔
✔
等待足够的时间以确保 connection 上的所有剩余 packets 都已过期
CLOSED
✔
✔
TCP segment(报文段)由 header 和 data 组成:segment header 包含 10 个必填字段和一个可选的扩展字段(options,即表格中的粉色背景),data 跟在 header 之后,是为 application 携带的 payload data。
segment header 中没有指定 data section 的长度,但它可以算出来,用 IP header 中指定的 IP datagram 总长度减去 IP header 和 segment header 的组合长度。
source port 和 destination port:源端口和目的端口,各占两个字节
标明了 send port 和 receive port
IP address + port number 就可以确定一个进程地址
seq,sequence number,序列号
若 SYN=1,则是 initial sequence number(初始序列号)
若 SYN=0,则是当前 session 的该段的第一个 data byte 的累积序列号
ack,acknowledgment number,确认号
若 ACK=1,则值是 ACK 发送方期望的下一个序列号
data offset,数据偏移。从 TCP segment(段)开始到实际数据的偏移量
reserved,保留
九个 1-bit flags,又称 control bits(控制位)
NS,
CWR,congestion window reduced,拥塞窗口减少标志
ECE,ECN-Echo,Explicit Congestion Notification 显式拥塞通知,Echo 回音
若 SYN=1,则表示 TCP peer(对等端)支持 ECN
若 SYN=0,则表示 IP header 中设置了拥塞经历标志的数据包
URG,紧急位。表示 urgent pointer 字段有用
ACK,确认位。表示 acknowledgment 字段有用
PSH,推送位。推送功能,要求将 buffered data(缓冲数据)推送给 application
RST,复位。重新建立连接
SYN,synchronize sequence numbers,同步序列编号。用来建立连接请求
SYN = 1 时,不能携带数据
FIN,Finish,终止位。用来释放一个连接
来自 sender 的最后一个 packet
window size,窗口大小
checksum,校验和
urgent pointer,紧急指针
options,选项。该字段的长度由 data offset 字段决定。为了确保 TCP header 在 32-bit 边界上结束,如需要会用 0 来填充
真实的网络环境会更复杂,如下:
首先,通过网关接入网络,获得静态或动态的 IP 地址。
其次,DNS 域名解析。
第三,CDN,内容分发网络。DNS 解析可能会给出 CDN 服务器的 IP 地址,这样拿到的就是 CDN 服务器而不是目标网站的实际地址。CDN 会缓存网站的大部分资源,比如图片、CSS 样式表,所以有的 HTTP 请求就不需要再发到最终的服务器,CDN 就可以直接响应我们的请求。对于不能缓存的资源(比如 POST 请求),CDN 无法缓存,就只能从目标网站上获取。于是 HTTP 请求就开始了在互联网上的漫长跋涉,经过无数的路由器、网关、代理,最后到达目的地。
第四,服务器。目标网站的服务器对外表现的是一个 IP 地址,但为了能扛住高并发,其内部也是一套复杂的架构。其中有负载均衡设备(比如四层的 LVS 或七层的 Nginx),通常它们也会有自身的缓存策略,比如处理一些静态资源。到达应用服务器(比如 Apache、Nginx),其中缓存服务器通常有 memory 级缓存(如 Redis)和 disk 级缓存(如 Varnish),它会把最频繁访问的数据缓存几秒或几分钟,其作用和 CDN 类似,只不过是工作在内部网络里。
第五,数据库服务,比如 MySQL, PostgreSQL, MongoDB,它也会有自己的缓存策略。
最后,HTTP 的响应数据会按原路返回,最终到达我们的设备。它可能是 HTML、JSON、图片或者其它格式的数据,需要浏览器的解析和处理才能显示出来。如果数据里还有超链接指向别的资源,就会重复类似的流程,直到所有的资源都下载完毕。
为了减少响应时间,整个过程中的每一个环节都有缓存实现短路操作。虽然现实中的 HTTP 传输过程非常复杂,但理论上仍然可以简化成“请求方-应答方”的两点模型。