带着问题学习(通常是面试考点)
- HTTP是如何使用TCP连接的
- TCP连接的时延、瓶颈及存在的障碍
- HTTP的优化,包括并行连接、keep-alive(持久连接)和管道化连接
- 管理连接时应该和不应该做的事
TCP连接
TCP的数据通过IP分组(或IP数据报)的小数据块来发送。
协议顺序:HTTP
(>> SSL
or TLS
) >> TCP
>> IP
数据传输过程:
- HTTP以
流
的形式将数据报文通过TCP连接
进行按序传输 - TCP收到数据流后,将数据流砍成小数据块(称作
段
),将段
封装在IP分组
中,通过因特网传输。
IP分组包括:一个IP分组首部(通常20字节) + 一个TCP段首部(通常20字节) + 一个TCP数据块(0或多个字节)
- IP分组首部:源和目的IP地址、长度、其他标记
- TCP段首部:TCP端口号、TCP控制标记、其他数据排序和完整性检查的数值
TCP连接识别:<源IP地址、源端口号、目的IP地址、目的端口号>
TCP通过端口号保持多个连接持续运行,这4个值唯一的定义了一条连接,两条不同TCP连接不能拥有4个完全相同的地址组件值。
套接字API允许用户创建TCP的端点数据结构,将这些端点与远程服务器的TCP端点进行连接,并对数据流进行读写。
对TCP接口进行编程所需的常见套接字接口函数伪代码:
TCP客户端与服务器是如何通过TCP套接字接口进行通信的:
HTTP事务的时延
回顾一下:HTTP事务 = 请求命令(HTTP方法)+ 响应结果
串行HTTP事务的时间线 = DNS查询 + TCP连接&接受应答 + 发送请求 + 处理 + 响应 + 关闭连接
HTTP事务的时延原因:
- DNS解析URI中的主机名为IP地址
- TCP请求连接需要等待接受应答
- 发送报文及处理请求报文
- 服务器回送HTTP响应
TCP网络时延的大小取决于:硬件速度
、网络
、服务器负载
、报文尺寸
、客户端与服务器之间的距离
。TCP协议的技术复杂性
也会对时延产生影响。
常见的TCP时延:
- TCP建立握手
- TCP慢启动拥塞控制
- 数据聚集的Nagle算法
- 用于捎带确认的TCP延迟确认算法
- TIME_WAIT时延和端口耗尽
TCP连接的握手时延
在发送数据前,TCP要传送两个分组来建立连接。
TCP三次握手:a SYN请求建立连接
>> b SYN+ACK接受连接
>> c ACK确认成功建立连接
延迟确认
如果是小的HTTP事务,那么TCP连接就占了大量时间,由于确认报文很小,所以TCP允许在发往相同方向的输出数据分组中对其进行“捎带”(有点像夹带私货
…),将返回的确认信息与输出的数据分组结合,剩下一次请求报文的时间,此行为称为“延迟确认算法
”。
延迟确认算法会在特定窗口时间(200~300ms)内将输出确认存放在缓存区中,等待能够捎带的输出数据分组,没有就单独发送确认。
TCP慢启动
TCP数据传输的性能还取决于TCP连接的使用期
。
TCP连接会随着时间自我“调谐”,起初会限制连接的最大速度,如果数据传输成功,会随着时间推移提高传输的速度,这种调谐称为“TCP慢启动
”,用于防止因特网突然过载和拥塞。
TCP慢启动限制了一个TCP端点在任意时间可以传输的分组数。每成功接收一个分组,发送端就有了发送2个分组的权限。有大量数据要发送也无法一次发送出去。必须发一个等待确认,然后发2个,都确认再发4个,以此类推。这种方式称为“打开拥塞窗口
“,所以新连接比已交换过数据的连接慢一些。
Nagle算法和TCP NODELAY
Nagle算法鼓励发送全尺寸的段,将已确认的分组数据缓存起来,等待传输中的分组被确认,当缓存积累了足够发送一个全尺寸的段再将缓存数据发出去。
但小的报文可能无法填满一个分组会因为等待永不会到来的额外数据而产生时延。且确认分组自身因“延迟确认算法”而延迟100~200毫秒。
所以HTTP应用程序通常会设置参数TCP NODELAY
来禁用Nagle算法。
TIME_WAIT累计和端口耗尽
当TCP端点关闭连接时,会在内存中维护一个小的控制快,用来记录最近关闭的IP地址和端口号,保证在一段时间内不会创建相同地址和端口的新连接。这段时间通常是最大分段使用期的两倍(2MSL
,通常2分钟)。
TCP连接的4个值中:源IP、源端口、目标IP、目标端口,只有源端口是可以随意改变的。
由于源端口的数量有限(比如6万),2MSL(比如120秒)内连接不能重用,那么连接率上限就为6万➗120秒=500次/秒。不超过500就不会遇到端口耗尽的问题。
要修正这个问题,可以增加客户端会负载生成机器的数量,或者确保循环使用几个虚拟IP来增加更多的连接组合。
HTTP连接的处理
串型事务处理时延
连接时延和慢启动时延叠加
HTTP优化1:并行连接
HTTP允许客户端打开多条连接,并行地执行多个HTTP事务。
⚠️注意:如果客户端网络带宽不足,那么大部分时间可能都在传送数据,多个事务的连接很快会耗尽所有可用的带宽。同时请求也会造成服务器性能严重下降,为了均衡,浏览器会限制并行连接的总数为一个较小的值(通常是4个)。
HTTP优化2:持久连接
初始化了对某服务器HTTP请求的应用程序很可能在不久后对该服务器发起更多的请求,这种性质被称为“站点局部性
”。
因此HTTP/1.1允许HTTP设备在事务处理结束后将TCP连接保持打开状态,以便后续请求重用连接。在事务处理结束后仍保持打开状态的TCP连接被称为“持久连接
”。
持久连接降低了时延和连接建立的开销(已调谐),但是容易产生大量空闲连接,需要特别注意。
持久连接类型1:HTTP/1.0的keep-alive连接
通过Connection: Keep-Alive
保持连接
支持max
设置希望保持连接的上限数,timeout
设置希望保持连接的时间,如:Keep-Alive: max=5, timeout=120
,但对方不一定会同意。
如果服务器支持keep-alive参数,需要回送一个Connection: Keep-Alive
,否则不回送。
很多老的代理都是“盲中继
”,只转发不对Connection首部作处理。当通过作为盲中继使用的哑代理
连接时,会出现图示情形:
服务器返回响应后,哑代理返回数据后由于等待连接关闭,会忽略连接上的新请求,导致了下一条请求被挂起。这种错误的通信会使浏览器一直处于挂起状态,直到连接超时关闭。
为避免出现上述通信问题,现代的代理都绝不能转发Connection
首部和出现在该首部值中的首部。
一个变通做法是,发送非标准的Proxy-Connection
扩展首部,哑代理转发后服务器会忽略此首部,聪明的代理会转换为Connection
首部发送,以收到预期效果。但是如果代理中有哑代理又有聪明的代理就会再次出现问题…😂
持久连接类型2:HTTP/1.1的persistent连接
HTTP/1.1默认所有连接都是持久连接,如果关闭连接就显式添加一个Connection: Close
。
HTTP优化3:管道化连接
HTTP/1.1允许在持久连接上可选地使用请求管道,在响应到达之前,将多条请求放入队列。
管道化连接的限制:
- 若HTTP客户端无法确认连接是持久的,就不应该使用管道
- 必须按照与请求相同的顺序回送HTTP响应
- HTTP客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发未完成的请求
- HTTP客户端不应该用管道化的方式发送可能会产生副作用的请求(比如POST),因为出错时无法安全的重试POST这样的非幂等请求
幂等(Idempotent): 一个操作如果执行一次和多次对系统状态产生的改变完全相同(或者说效果等同于只执行一次),那么这个操作就是幂等的。
非幂等(Non-idempotent): 一个操作如果执行多次与执行一次对系统状态产生的改变不同(通常会产生额外的副作用),那么这个操作就是非幂等的。
要发送一条非幂等的请求,需要等待来自前一条请求的响应状态。
一定不能自动重试非幂等的方法。比如,大多数浏览器会在重载一个缓存的POST响应时提供一个对话框,询问用户是否再次发起事务处理。
正常关闭连接
完全关闭
:TCP输入和输出都关闭
半关闭
:单独关闭TCP输入或输出
关闭连接的输出信道总是安全的,关闭连接的输入信道比较危险。
如果另一端向你已关闭的输入信道发送数据,操作系统就会向另一端回送一条TCP“连接被对端重置”的报文,将删除对端还未读取的所有缓存数据。
正常关闭的应用程序应该首先关闭输出信道,两端都告诉对方不再发送任何数据之后,再完全关闭连接,就不会有重置的危险。