TCP 连接

世界上几乎所有的 HTTP 通信都是由 TCP/IP 承载的,TCP/IP 是全球计算机及网络设备都在使用的一种常用的分组交换网络分层协议集。

TCP 的可靠数据通道

TCP 为 HTTP 提供了一条可靠的比特传送管道。从 TCP 连接一端填入的字节会从另一端以原有的顺序、正确地传送出来。

http://www.google.com.hk

  • (1) 浏览器解析出域名
  • (2) 浏览器查询这个主机名的 IP 地址 (DNS) (220.255.2.153)
  • (3) 浏览器获得端口号 (80)
  • (4) 浏览器发起到 220.255.2.153 端口 80 的连接
  • (5) 浏览器向服务器发送条 HTTP GET 报文
  • (6) 浏览器从服务器读取 HTTP 响应报文
  • (7) 浏览器关闭连接

TCP 流是分段的、由 IP 分组传送

TCP 的数据是通过名为 IP 分组(或 IP 数据报)的小数据块发送的。

HTTP 就是 “HTTP over TCP over IP”,其安全版本 HTTPS 就是在 HTTP 和 TCP 之间插入一个(称为 TLS 或 SSL)密码加密层。

每个 IP 分组中都包括:

  • 一个 IP 分组首部(通常为 20 字节)
  • 一个 TCP 段首部(通常为 20 字节)
  • 一个 TCP 数据块(0 个或多个字节)

IP 首部包含了源和目的 IP 地址、长度和其他的一些标记。TCP 段的首部包含了 TCP 端口号、TCP 控制标记,以及用于数据排序和完整性检查的一些数字值。

用 TCP 套接字编程

套接字 API 最初是为 Unix 操作系统开发的,但现在几乎所有的操作系统和语言中都有其变体存在。

表. 对 TCP 连接进行编程所需的常见套接字接口函数

套接字 API 调用 描 述
s = socket (<parameters>) 创建一个新的、未命名、未关联的套接字
bind (s, <local IP:port >) 向套接字赋一个本地端口号和接口
connect (s, <remote IP:port >) 创建一条连接本地套接字与远程主机及端口的连接
listen(s, …) 标识一个本地套接字,使其可以合法接受连接
s2 = accept(s) 等待某人建立一条道本地端口的连接
n = read(s, buffer, n) 尝试从套接字向缓冲区读取 n 个字节
n = write(s, buffer, n) 尝试从缓冲区中向套接字写入 n 个字节
close(s) 完全关闭 TCP 连接
shutdown(s, <side>) 只关闭 TCP 连接的输入或输出端
getsockopt(s, …) 读取某个内部套接字配置选项的值
setsockopt(s, …) 修改某个内部套接字配置选项的值

对 TCP 性能的考虑

HTTP 紧挨着 TCP,位于其上层,所以 HTTP 事务的性能很大程度取决于底层 TCP 通道的性能。

HTTP 事务的时延

  • (1) 客户端首先需要根据 URI 确定 Web 服务器的 IP 地址和端口号。如果最近没有对 URI 中的主机进行访问,通过 DNS 解析系统将 URI 中的主机名转换成一个 IP 地址可能要花费数十秒时间。

  • (2) 接下来,客户端会向服务器发送一条 TCP 连接请求,并等待服务器回送一个请求接受应答。每条新的 TCP 连接都会有连接建立时延。这个值通常最多只有一两秒钟,但如果有数百个 HTTP 事务的话,这个值会快速地叠加上去。

  • (3) 一旦连接建立起来,客户端就会通过新建立的 TCP 管道来发送 HTTP 请求。数据到达是,Web 服务器会从 TCP 连接中读取请求报文,并对报文进行处理。

    因特网传输请求报文,以及服务器处理请求都需要时间。

  • (4) 然后,Web 服务器会回送 HTTP 响应,这也需要时间。

这些 TCP 网络时延的大小取决于硬件速度、网络和服务器的负载,请求和响应报文的尺寸,以及客户端和服务器之间的距离。TCP 协议的技术复杂性也会对时延产生巨大的影响。

性能聚焦区域

  • TCP 连接建立握手;
  • TCP 慢启动拥塞控制;
  • 数据聚集的 Nagle 算法;
  • 用于捎带确认的 TCP 延迟确认算法;
  • TIME_WAIT 时延和端口耗尽

HTTP 连接的处理

  • 并行连接
    • 并行连接可能会提高页面的加载速度

      包含嵌入对象的组合如果能(通过并行连接)克服单条连接的空载时间和带宽限制,加载速度也会有所提高。时延可以重叠起来,而如果单条连接没有充分利用客户端的因特网带宽,可以将未用带宽分配来装载其他对象。

    • 并行连接不一定更快

      1. 客户端的网络带宽不足
      2. 打开大量连接会消耗很多内存资源

      实际上,浏览器确实使用了并行连接,但它们会将并行连接的总是限制为一个较小的值(通常是 4 个)。服务器可以随意关闭来自特定客户端的超量连接。

    • 并行连接可能让人“感觉”更快一些

      如果整个屏幕上有很多动作在进行,即使实际上秒表显示整个页面的下载时间更长,人们也会认为 Web 页面加载得更快一些。

  • 持久连接

    重用已对目标服务器打开的空闲持久连接,就可以避开缓慢的连接建立阶段。而且,已打开的连接还可以避免慢启动的拥塞适应阶段,以便更快速地进行数据的传输。

    • HTTP/1.0+ keep-alive 连接

      大约从 1996 年开始,很多 HTTP/1.0 浏览器和服务器都进行了扩展,以支持一种被称为 keep-alive 连接的早期实验型持久连接。

      keep-alive 已经不再使用了,而且当前的 HTTP/1.1 规范中也没有对他的说明了。

      实现 HTTP/1.0 keep-alive 连接的客户端可以通过包含 Connection: Keep-Alive 首部请求将一条连接保持在打开状态。

      如果服务器愿意为下一条请求将连接保持在打开状态,就在响应中包含相同的首部。如果响应中没有 Connection: Keep-Alive 首部,客户端就认为服务器不支持 keep-alive,会在发回响应报文之后关闭连接。

      注意,keep-alive 首部只是请求将连接保持在活跃状态。发出 keep-alive 请求之后,客户端和服务器并一定会进行 keep-alive 会话。它们可以在任意时刻关闭空闲的 keep-alive 连接,并可以随意现在 keep-alive 连接所处理事务的数量。

        Connection: Keep-Alive
        Keep-Alive: max=5, timeout=120
      

      服务器最多还会为另外 5 个事务保持连接的打开状态,或者将打开状态保持空闲 2 分钟之后。

      • Keep-Alive 和哑代理
        1. Connection 首部和盲中继
        2. 代理和逐条首部
        3. 插入 Proxy-Connection
    • HTTP/1.1 持久连接

      HTTP/1.1 逐渐停止了对 keep-alive 连接的支持,用一种名为持久连接 (persistent connection) 的改进型设计取代了它。持久连接的目的和 keep-alive 连接的目的相同,但工作机制更有一些。

      • 与 HTTP/1.0+ 的 keep-alive 连接不同,HTTP/1.1 持久连接在默认情况下是激活的。除非特别指明,否则 HTTP/1.1 假定所有的连接都是持久的。要在事务处理结束之后将连接关闭,HTTP/1.1 应用程序必须向报文中添加一个 Connection: close 首部。

      • HTTP/1.1 客户端假定在收到响应后,除非响应中包含了 Connection: close 首部,不如 HTTP/1.1 连接就仍维持在打开状态。但是,客户端和服务器仍然可以随时关闭空闲的连接。不发送 Connection: close 并不意味着服务器承诺永远将连接保持在打开状态。

    • 管道化连接

      HTTP/1.1 允许在持久连接上可选地使用请求管道。在响应到达之前,可以将多条的请求放入队列。

关闭连接的奥秘

  • “任意”解除连接
  • Content-Length 及截尾操作
  • 连接关闭容限、重试以及幂等性
  • 正常关闭连接
    • 完全关闭和半关闭
    • TCP 关闭及重置错误
    • 正常关闭

References

  1. HTTP权威指南