# HTTP的传输编码

# 持续连接

持久连接通俗来讲,就是长连接,英文叫 Persistent Connection,其实按字面意思理解就好了。

在早期的 HTTP 协议中,传输数据的顺序大致分为发起请求、建立连接、传输数据、关闭连接等步骤,而持久连接,就是去掉关闭连接这个步骤,让客户端和服务端可以继续通过此次连接传输内容。

这其实也是为了提高传输效率,我们知道 HTTP 协议是建立在 TCP 协议之上的,自然有 TCP 一样的三次握手、慢启动等特性,这样每一次连接其实都是一次宝贵的资源。为了尽可能的提高 HTTP 的性能,使用持久连接就显得很重要了。为此在 HTTP 协议中,就引入了相关的机制。

在早期的 HTTP/1.0 协议中并没有持久连接,持久连接的概念是在后期才引入的,当时是通过 Connection:Keep-Alive 这个头部来标记实现,用于通知客户端或服务端相对的另一端,在发送完数据之后,不要断开 TCP 连接,之后还需要再次使用。

而在 HTTP/1.1 协议中,发现持久连接的重要性了,它规定所有的连接必须都是持久的,除非显式的在报文头里,通过 Connection:close 这个首部,指定在传输结束之后会关闭此连接。

实际上在 HTTP/1.1Connect 这个头部已经没有 Keep-Alive 这个取值了,由于历史原因,很多客户端和服务端,依然保留了这个报文头。

长连接带来了另外一个问题,如何判定当前数据发送完成。

对于非持续连接,浏览器可以通过连接是否关闭来界定请求或响应实体的边界;而对于持续连接,这种方法显然不奏效。 有时,尽管我已经发送完所有数据,但浏览器并不知道这一点,它无法得知这个打开的连接上是否还会有新数据进来,只能傻傻地等了。

这时有2种方案:

# Content-length

计算实体长度,并通过头部告诉对方。浏览器可以通过 Content-Length 的长度信息,判断出响应实体已结束。

但是Content-length引入的新问题: 由于 Content-Length 字段必须真实反映实体长度,但是对于动态生成的内容来说,在内容创建完之前,长度是不可知的。这时候要想准确获取长度,只能开一个足够大的 buffer,等内容全部生成好再计算。 但这样做一方面需要更大的内存开销,另一方面也会让客户端等更久。

我们需要一个新的机制:不依赖头部的长度信息,也能知道实体的边界——分块编码(Transfer-Encoding: chunked)就这么产生了。

# Transfer-Encoding

Transfer-Encoding 在最新的 HTTP/1.1 协议里,就只有 chunked 这个参数,标识当前为分块编码传输

分块编码传输既然只有一个可选的参数,我们就只需要指定它为 Transfer-Encoding:chunked ,后续我们就可以将内容实体包装一个个块进行传输。

分块传输的规则:

  1. 每个分块包含一个 16 进制的数据长度值和真实数据。

  2. 数据长度值独占一行,和真实数据通过 CRLF(\r\n) 分割。

  3. 数据长度值,不计算真实数据末尾的 CRLF,只计算当前传输块的数据长度。

  4. 最后通过一个数据长度值为 0 的分块,来标记当前内容实体传输结束。

用一段nodejs代码来感受下:

const net = require('net');

net.createServer(function(sock) {
  sock.on('data', function() {
    sock.write('HTTP/1.1 200 OK\r\n');
    sock.write('Transfer-Encoding: chunked\r\n');
    sock.write('\r\n');

    sock.write('b\r\n');
    sock.write('01234567890\r\n');

    sock.write('5\r\n');
    sock.write('12345\r\n');

    sock.write('0\r\n'); //代表结束
    sock.write('\r\n');
  });
}).listen(9090, '127.0.0.1');

在浏览器输入:http://localhost:9090

显示:0123456789012345

如果去掉最后的代表结束的2句,那么,在浏览器网络里可以看到,接口一直处于pending状态。如果加个延时,响应也会在延时时间后才会结束。

# http2.0

HTTP 2.0 最大的特点: 不会改动 HTTP 的语义,HTTP 方法、状态码、URI 及首部字段,等等这些核心概念上一如往常,却能致力于突破上一代标准的性能限制,改进传输性能,实现低延迟和高吞吐量。而之所以叫 2.0,是在于新增的二进制分帧层。

可以这么简单区分这几个版本:

  • 2.0 的分帧传输是在TCP和HTTP之间加了一个帧的概念,帧可以乱序传输。多个帧可以同时传输,速度快(多个buffer同时传)

  • 1.1 的chunked是分块传递,一块一块传输,顺序(块可以理解成一个buffer,一个一个传)

  • 1.0 是字节传输,一字节一字节连续传输(没有buffer)

因为1.1没有分帧的规定,所以用了Transfer-Encoding: chunked这种机制。而http2之后因为有了原生的分帧功能,所以就没有必要用Transfer-Encoding: chunked,直接发一个大的http请求出去,协议会自动分帧加快传输效率。

参考: