通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统中的协议栈。
协议栈的内部分为几个部分,分别承担不同的工作。上下关系是有一定的规则的,上面的部分会向下面的部分委托工作,下面的部分收到委托的工作并执行。
应用程序(浏览器)通过调用 Socket 库,来委托协议栈工作。协议栈的上半部分有两块,分别是负责收发数据的 TCP 和 UDP 协议,这两个传输协议会接受应用层的委托执行收发数据的操作。
协议栈的下面一半是用 IP 协议控制网络包收发操作,在互联网上传数据时,数据会被切分成一块块的网络包,而将网络包发送给对方的操作就是由 IP 负责的。
此外 IP 中还包括 ICMP 协议和 ARP 协议。
ICMP 用于告知网络包传送过程中产生的错误以及各种控制信息。
ARP 用于根据 IP 地址查询相应的以太网 MAC 地址。
IP 下面的网卡驱动程序负责控制网卡硬件,而最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收操作。
数据包看了这份指南表示:“原来我需要那么多大佬的协助啊,那我先去找找 TCP 大佬!”
可靠传输 —— TCP
HTTP 是基于 TCP 协议传输的,所以在这我们先了解下 TCP 协议。
TCP 包头格式
我们先看看 TCP 报文头部的格式:
首先,源端口号和目标端口号是不可少的,如果没有这两个端口号,数据就不知道应该发给哪个应用。
接下来有包的序号,这个是为了解决包乱序的问题。
还有应该有的是确认序列,目的是确认发出去对方是否有收到。如果没有收到就应该重新发送,直到送达,这个是为了解决丢包的问题。
接下来还有一些状态位。例如 SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。
还有一个重要的就是窗口大小。TCP 要做流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。
除了做流量控制以外,TCP还会做拥塞控制,对于真正的通路堵车不堵车,它无能为力,唯一能做的就是控制自己,也即控制发送的速度。不能改变世界,就改变自己嘛。
三次握手建立连接
在 HTTP 传输数据之前,首先需要 TCP 建立连接,TCP 连接的建立,通常称为三次握手。
一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。
然后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。
服务端收到发起的连接,返回 SYN,并且 ACK 客户端的 SYN,之后处于 SYN-RCVD 状态。
客户端收到服务端发送的 SYN 和 ACK 之后,发送对 SYN 确认的 ACK,之后处于 ESTABLISHED 状态,因为它一发一收成功了。
服务端收到 ACK 的 ACK 之后,处于 ESTABLISHED 状态,因为它也一发一收了。
所以三次握手目的是保证双方都有发送和接收的能力。
如何查看 TCP 的连接状态?
TCP 的连接状态查看,在 Linux 可以通过netstat -napt命令查看。
TCP 分割数据
如果 HTTP 请求消息比较长,超过了 MSS 的长度,这时 TCP 就需要把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据。
MTU:一个网络包的最大长度,以太网中一般为 1500 字节。
MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度。
TCP 报文生成
TCP 协议里面会有两个端口,一个是浏览器监听的端口(通常是随机生成的),一个是 Web 服务器监听的端口(HTTP 默认端口号是 80, HTTPS 默认端口号是 443)。
在双方建立了连接后,TCP 报文中的数据部分就是存放 HTTP 头部 + 数据,组装好 TCP 报文之后,就需交给下面的网络层处理。
至此,网络包的报文如下图。
此时,遇上了 TCP 的 数据包激动表示:“太好了,碰到了可靠传输的 TCP 传输,它给我加上 TCP 头部,我不再孤单了,安全感十足啊!有大佬可以保护我的可靠送达!但我应该往哪走呢?”