- 前言
- HTTP协议
- 请求准备:
- 请求构建:
- 请求的发送:
- HTTP返回的构建:
- 如何实现一个靠谱的协议?
- QUIC协议
- 自定义连接机制:
- 自定义重传机制:
- 无阻塞的多路复用:
- 自定义流量控制:
- 往期推荐:
前言
有三个月没更笔记文了,似乎忘了这是一个技术类公众号。
在这段时间内花了两个月重学了一遍数据结构,然后在leetcode上刷了一百多道题。
还看了一本《现代操作系统》,看的有点懵,又花钱买了一个大牛专栏看
后续将这些笔记都整理一下,发到这个公众号。
还花了一周空闲时间用python写了一个小程序,每天定时获取热点信息、自动编排、上传、发布文章。热点在文章中支持点击跳转。
之前写的网络协议其实只写了一小部分,后面还包括了HTTP协议、HTTPS协议、DNS、CDN、网络模式以及容器网络等。
那么今天就来记录一下HTTP协议相关内容。
HTTP协议
浏览器上输入url,比如http://www.xxx.com,这个叫做统一资源定位符
其中www.xxx.com
是一个域名,表示互联网的一个位置。
请求准备:
浏览器会将域名发送给DNS服务器,解析成IP地址。
HTTP是基于TCP协议的,先要建立TCP连接。
目前使用的HTTP协议大部分都是1.1,在1.1的协议里面,默认开启了Keep-Alive的,这样建立的TCP连接,可以在多次请求中复用。
请求构建:
HTTP报文分为三大部分,分别是请求行,首部和请求的正文实体
请求行
URL就是http://www.xxx.com
版本就是HTTP1.1
方法有GET、POST、PUT、DELETE
首部字段
首部是Key-Value格式,通过冒号分隔,保存一些header信息
例如:Accept-Charset,表示客户端可接受的字符集。
Content-Type指的是正文的格式,例如是JSON
Cache-control 用来控制缓存
请求的发送:
HTTP 协议是基于 TCP 协议的,所以它使用面向连接的方式发送请求。
到了 TCP 层,它会转换成一个个报文段发送给服务器。
在发送给每个报文段的时候,都需要对方有一个回应 ACK,来保证报文可靠地到达了对方。
如果没有回应,那么 TCP 这一层会进行重传,直到可以到达。
TCP 层发送每一个报文的时候,都需要加上自己的地址(即源地址)和它想要去的地方(即目标地址),将这两个信息放到 IP 头里面,交给 IP 层进行传输。
IP 层需要查看目标地址和自己是否是在同一个局域网。如果是,就发送 ARP 协议来请求这个目标地址对应的 MAC 地址,然后将源 MAC 和目标 MAC 放入 MAC 头,发送出去即可;
如果不在同一个局域网,就需要发送到网关,还要需要发送 ARP 协议,来获取网关的 MAC 地址,然后将源 MAC 和网关 MAC 放入 MAC 头,发送出去。
网关收到包发现 MAC 符合,取出目标 IP 地址,根据路由协议找到下一跳的路由器,获取下一跳路由器的 MAC 地址,将包发给下一跳路由器。
这样路由器一跳一跳终于到达目标的局域网。
这个时候,最后一跳的路由器能够发现,目标地址就在自己的某一个出口的局域网上。于是,在这个局域网上发送 ARP,获得这个目标地址的 MAC 地址,将包发出去。
目标的机器发现 MAC 地址符合,就将包收起来;发现 IP 地址符合,根据 IP 头中协议项,知道上一层是 TCP 协议,于是解析 TCP 的头,里面有序列号,需要看一看这个序列包是不是我要的,如果是就放入缓存中然后返回一个 ACK,如果不是就丢弃。
TCP 头里面还有端口号,HTTP 的服务器正在监听这个端口号。于是,目标机器自然知道是 HTTP 服务器这个进程想要这个包,于是将包发给 HTTP 服务器。HTTP 服务器的进程看到,原来这个请求是要访问一个网页,于是就把这个网页发给客户端。
HTTP返回的构建:
状态码有几种,1xx、2xx、3xx、4xx、5xx
首部中,Retry-After 表示,告诉客户端应该在多长时间以后再次尝试一下。
Content-Type,表示返回的是 HTML,还是 JSON。
构造好了返回的 HTTP 报文,接下来就是把这个报文发送出去。还是交给 Socket 去发送,还是交给 TCP 层,让 TCP 层将返回的 HTML,也分成一个个小的段,并且保证每个段都可靠到达。
这些段加上 TCP 头后会交给 IP 层,然后把刚才的发送过程反向走一遍。虽然两次不一定走相同的路径,但是逻辑过程是一样的,一直到达客户端。
客户端发现 MAC 地址符合、IP 地址符合,于是就会交给 TCP 层。根据序列号看是不是自己要的报文段,如果是,则会根据 TCP 头中的端口号,发给相应的进程。这个进程就是浏览器,浏览器作为客户端也在监听某个端口。
当浏览器拿到了 HTTP 的报文。发现返回“200”,一切正常,于是就从正文中将 HTML 拿出来。HTML 是一个标准的网页格式。浏览器只要根据这个格式,渲染网页。
这就是一个正常的 HTTP 请求和返回的完整过程。
如何实现一个靠谱的协议?
为了保证顺序性,每一个包都有一个ID,在建立连接的时候,会商定起始的ID是什么,然后按照ID一个个发送,为了保证不丢包,对于发送的包都要进行应答,但这个应答也不是一个一个来,而是会应答某个之前的ID,表示都收到了,这种模式称为累计确认或者累计应答
QUIC协议
QUIC协议,是Google内部的一个基于UDP的可靠传输协议。
自定义连接机制:
TCP由一个四元组确认一个连接,发生变化就得重连。移动互联网下,网络不稳定会再次重连,导致时延。
基于UDP,不再以四元组为标识,而是以一个64位的随机数作为ID来标识,而且UDP是无连接的,所以当IP或者端口变化的时候,只要ID不变,就不需要重新建立连接。
自定义重传机制:
TCP超时重传是通过自适应重传算法,通过采用往返时间RTT不断调整。
QUIC 也有个序列号,是递增的。任何一个序列号的包只发送一次,下次就要加一了。
例如,发送一个包,序号是 100,发现没有返回;再次发送的时候,序号就是 101 了;如果返回的 ACK 100,就是对第一个包的响应。如果返回 ACK 101 就是对第二个包的响应,RTT 计算相对准确。
QUIC 定义了一个 offset 概念。QUIC 既然是面向连接的,也就像 TCP 一样,是一个数据流,发送的数据在这个数据流里面有个偏移量 offset,可以通过 offset 查看数据发送到了哪里,这样只要这个 offset 的包没有来,就要重发;如果来了,按照 offset 拼接,还是能够拼成一个流。
无阻塞的多路复用:
同一条 QUIC 连接上可以创建多个 stream,来发送多个 HTTP 请求。但是,QUIC 是基于 UDP 的,一个连接上的多个 stream 之间没有依赖。这样,假如 stream2 丢了一个 UDP 包,后面跟着 stream3 的一个 UDP 包,虽然 stream2 的那个包需要重传,但是 stream3 的包无需等待,就可以发给用户。
自定义流量控制:
TCP 的流量控制是通过滑动窗口协议。QUIC 的流量控制也是通过 window_update,来告诉对端它可以接受的字节数。但是 QUIC 的窗口是适应自己的多路复用机制的,不但在一个连接上控制窗口,还在一个连接中的每个 stream 控制窗口。
在 TCP 协议中,接收端的窗口的起始点是下一个要接收并且 ACK 的包,即便后来的包都到了,放在缓存里面,窗口也不能右移,因为 TCP 的 ACK 机制是基于序列号的累计应答,一旦 ACK 了一个序列号,就说明前面的都到了,所以只要前面的没到,后面的到了也不能 ACK,就会导致后面的到了,也有可能超时重传,浪费带宽。
QUIC 的 ACK 是基于 offset 的,每个 offset 的包来了,进了缓存,就可以应答,应答后就不会重发,中间的空档会等待到来或者重发即可,而窗口的起始位置为当前收到的最大 offset,从这个 offset 到当前的 stream 所能容纳的最大缓存,是真正的窗口大小。显然,这样更加准确。