【HTTP笔记】第4章 连接管理

4.1 TCP 连接

4.1.1 TCP的可靠数据管道

  1. HTTP实际上就是TCP连接及其使用规则
  2. TCP 为 HTTP 提供了一条可靠的比特传输管道

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

  1. TCP 的数据是通过名为 IP 分组(或 IP 数据报)的小数据块来发送的。
  2. HTTP 要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的 TCP 连接按序传输。TCP 收到数据流之后,会将数据流砍成被称作段的小数据块,并将段封装在 IP 分组中,通过因特网进行传输
  3. 每个 TCP 段都是由 IP 分组承载,从一个 IP 地址发送到另一个 IP 地址的。每个 IP 分组中都包括:一个 IP 分组首部(通常为 20 字节);一个 TCP 段首部(通常为 20 字节);一个 TCP 数据块(0 个或多个字节)。
  4. IP 首部包含了源和目的 IP 地址、长度和其他一些标记。TCP 段的首部包含了 TCP 端口号、TCP 控制标记,以及用于数据排序和完整性检查的一些数字值。

4.1.3 保持TCP连接的持续不间断地运行

  1. TCP 是通过端口号来保持所有这些连接的正确运行的。
  2. TCP 连接是通过 4 个值来识别的:<源IP 地址、源端口号、目的IP 地址、目的端口号>

4.1.4 用TCP套接字编程

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

4.2 对 TCP 性能的考虑

4.2.1 HTTP事务的时延

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

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

  3. 一旦连接建立起来了,客户端就会通过新建立的 TCP 管道来发送 HTTP 请求。数据到达时,Web 服务器会从 TCP 连接中读取请求报文,并对请求进行处理。因特网传输请求报文,以及服务器处理请求报文都需要时间。

  4. 然后,Web 服务器会回送 HTTP 响应,这也需要花费时间。

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

4.2.2 性能聚焦区域

本节其余部分列出了一些会对 HTTP 程序员产生影响的、最常见的 TCP 相关时延,其中包括:

  1. TCP 连接建立握手;

  2. TCP 慢启动拥塞控制;

  3. 数据聚集的 Nagle 算法;

  4. 用于捎带确认的 TCP 延迟确认算法;

  5. TIME_WAIT 时延和端口耗尽。

4.2.3 TCP连接的握手时延

  1. TCP 连接握手需要经过以下几个步骤。

    • 请求新的 TCP 连接时,客户端要向服务器发送一个小的 TCP 分组(通常是 40 ~ 60 个字节)。这个分组中设置了一个特殊的 SYN 标记,说明这是一个连接请求。(参见图 4-8a)。

    • 如果服务器接受了连接,就会对一些连接参数进行计算,并向客户端回送一个 TCP 分组,这个分组中的 SYN 和 ACK 标记都被置位,说明连接请求已被接受(参见图 4-8b)。

    • 最后,客户端向服务器回送一条确认信息,通知它连接已成功建立(参见图 4-8c)。现代的 TCP 栈都允许客户端在这个确认分组中发送数据。

  2. 这些分组都由 TCP/IP 软件管理,对其是不可见的。

4.2.4 延迟确认

  1. TCP 实现了自己的确认机制来确保数据的成功传输。
  2. 每个 TCP 段都有一个序列号和数据完整性校验和。
  3. 由于确认报文很小,所以 TCP 允许在发往相同方向的输出数据分组中对其进行“捎带”。
  4. 很多TCP 栈都实现了一种“延迟确认”算法
  5. 但是,HTTP 具有双峰特征的请求 ??应答行为降低了捎带信息的可能。

4.2.5 TCP慢启动

  1. TCP 数据传输的性能还取决于 TCP 连接的使用期(age)
  2. TCP 连接会随着时间进行自我“调谐”,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐被称为 TCP 慢启动(slow start),用于防止因特网的突然过载和拥塞。
  3. TCP 慢启动限制了一个 TCP 端点在任意时刻可以传输的分组数。
  4. 由于存在这种拥塞控制特性,所以新连接的传输速度会比已经交换过一定量数据的、“已调谐”连接慢一些

4.2.6 Nagle算法与TCP_NODELAY

  1. TCP 有一个数据流接口,应用程序可以通过它将任意尺寸的数据放入 TCP 栈中
  2. Nagle 算法(根据其发明者 John Nagle 命名)试图在发送一个分组之前,将大量 TCP 数据绑定在一起,以提高网络效率
  3. Nagle 算法鼓励发送全尺寸(LAN 上最大尺寸的分组大约是 1500 字节,在因特网上是几百字节)的段
  4. Nagle 算法会引发几种 HTTP 性能问题。首先,小的 HTTP 报文可能无法填满一个分组,可能会因为等待那些永远不会到来的额外数据而产生时延。其次,Nagle 算法与延迟确认之间的交互存在问题——Nagle 算法会阻止数据的发送,直到有确认分组抵达为止,但确认分组自身会被延迟确认算法延迟 100 ~ 200 毫秒。
  5. HTTP 应用程序常常会在自己的栈中设置参数 TCP_NODELAY,禁用 Nagle 算法,提高性能

4.2.7 TIME_WAIT累积与端口耗尽

  1. 当某个 TCP 端点关闭 TCP 连接时,会在内存中维护一个小的控制块,用来记录最近所关闭连接的 IP 地址和端口号。这类信息只会维持一小段时间,通常是所估计的最大分段使用期的两倍(称为 2MSL,通常为 2 分钟 6)左右,以确保在这段时间内不会创建具有相同地址和端口号的新连接。实际上,这个算法可以防止在两分钟内创建、关闭并重新创建两个具有相同 IP 地址和端口号的连接。
  2. 现在高速路由器的使用,使得重复分组几乎不可能在连接关闭的几分钟之后,出现在服务器上。有些操作系统会将 2MSL 设置为一个较小的值,但修改此值时要特别小心。分组确实会被复制,如果来自之前连接的复制分组插入了具有相同连接值的新 TCP 流,会破坏 TCP 数据。
  3. 进行性能基准测试时,通常只有一台或几台用来产生流量的计算机连接到某系统中去,这样就限制了连接到服务器的客户端 IP 地址数。而且,服务器通常会在 HTTP 的默认 TCP 端口 80 上进行监听。用 TIME_WAIT 防止端口号重用时,这些情况也限制了可用的连接值组合。在只有一个客户端和一台 Web 服务器的异常情况下,构建一条 TCP 连接的 4 个值:<source-IP-address, source-port, destination-IP-address, destination-port>。其中的 3 个都是固定的——只有源端口号可以随意改变:<client-IP, source-port, server-IP, 80>。客户端每次连接到服务器上去时,都会获得一个新的源端口,以实现连接的唯一性。

  4. 要修正这个问题,可以增加客户端负载生成机器的数量,或者确保客户端和服务器在循环使用几个虚拟 IP 地址以增加更多的连接组合。
  5. 即使没有遇到端口耗尽问题,也要特别小心有大量连接处于打开状态的情况,或为处于等待状态的连接分配了大量控制块的情况

4.3 HTTP 连接的处理

4.3.1 常被误解的Connection首部