连接管理
重点
- http是如何使用tcp连接的;
- tcp连接的时延、瓶颈以及存在的障碍
- http的优化包括并行连接、keep-alive(持久连接)和管道化管理
- 管理连接是应该以及不应该做的事情
4.1tcp连接
世界上几乎所有的http通信都是由tcp承载的,tcp是全球计算机及网络设备都在是用的一种常用的分组交换网络分层协议集。客户端可以打开一条tcp连接,连接到服务器,一旦连接建立,客户如和服务器的交换报文就不会丢失,受损或失序。
4.11tcp可靠数据管道
http连接实际上就是tcp连接和一些使用连接的规则,tcp为http提供了一条可靠的比特传输管道。从tcp连接的一端填入的字节会以原有的方式传送出来
4.1.2tcp流是分段的由ip分组传送
http要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的tcp连接按序传输tcp收到数据流后,会将数据流分成更小的数据块称为段,将段封装在ip分组中
一个ip分组包括
- 一个ip分组首部(20字节)包含源和目的ip地址,长度
- 一个tcp首部(20字节)包含tcp端口,控制标记,校验和
- 一个tcp数据块(0-n个字节)
4.1.3保持tcp连接的正确运行
tcp通过端口号保持连接的正确,tcp连接通过四个值<源ip、源端口号、目的ip,目的端口号>
4.1.4用tcp套接字编程
套接字编程api允许用户创建tcp的端点数据结构,这些端点与远程服务器连接,并对数据流进行读写。
4.2对tcp性能的考虑
4.2.1http事务的时延
注意,与建立tcp连接,以及传输请求和响应报文相比,事务处理可能是很短的。除非客户端或服务器超载,或正在处理复杂的动态资源,否则http时延是由tcp网络时延构成的。
- 客户端首先根据url确定服务器的ip和端口号。如果最近没有对该主机访问,通过dns解析系统把主机名转换为ip可能需要数十秒
- 接下来,客户端会发送一条tcp连接请求,同时等待服务器的接受应答。每条tcp都会有建立时延,一般最多一两秒,但如果有数百个http事务,这个值会快速的增加
- 一旦连接建立,客户端通过建立的tcp管道发发送http请求。数据到达时,web服务器会从tcp连接中读取请求报文,并处理。
4.然后服务器会回送http响应。
tcp网络的时延取决于硬件速度、网络和服务器的负载,请求和响应的尺寸,客户端和服务器的距离。tcp协议的技术复杂想也会对时延产生影响。
4.2.2性能聚焦区域
可能对http程序员产生影响的tcp时延
- tcp连接建立握手;
- tcp慢启动拥塞控制;
- 数据聚集的nagle算法;
- 用于捎带确认的tcp延迟确认算法;
- time_wait时延和端口耗尽;
4.2.3tcp连接的握手时延
tcp连接需要以下几步
- 请求新的tcp连接,客户端向服务器发送一个小的tcp分组。这个分组设置了syn标记,说明这是一个连接请求
- 如果服务器接受了连接,就会对参数进行计算,并向客户端发送一个tcp分组,这个分组的syn、ack都被置位,表明连接被接受
- 最后客户端向服务器发送一条确认信息,通知它连接建立成功。现代的tcp栈都允许客户端在这个确认分组中发送数据。
4.2.4延迟确认
由于因特网自身无法确保可靠地分组传输,所以tcp实现了自己的确认机制来确认剧场来确保数据的成功传输。
每个tcp端都有一个序列号和数据完整性的校验和。每个接受者收到完整的段,都会发送确认分组,如果接收方没有在指定的时间内收到确认信息就会认为分组已经破坏,重发数据。
由于确认报文小,所以tcp允许在发往相同方向的输出数据进行“捎带”。tcp将返回的确认信息与输出的数据分组结合在一起,可以更有效的利用网络。。为了增加确认报文找到同向传输数据,很多tcp栈实现了一种“延迟确认”算法。
但是,http具有双峰特征的请求-应答行为降低了捎带信息的可能(什么双峰,不懂)。通常延迟确认算法会引入比较大的时延,根据操作系统不同,可以调整或禁止延迟确认算法。
4.2.5tcp慢启动
tcp数据传输的性能还取决于tcp连接的使用期(age)。tcp连接句随着时间进行自我“调谐”,起初会限制最大连接速度,如果数据成功,会随着时间提高传输的速度。这种调谐被称为tcp慢启动。
4.2.6nagle算法与tcp_nodelay
tcp只有一个数据流接口,应用程序可以将任意尺寸的数据放入其中,如果tcp发送了大量包含少量数据的分组,网络性能会下降。
nagle算法试图在发送一个分组前,将大量的tcp数据绑到一起,以提高网络效率。
nagle鼓励发送全尺寸的段,只有当其它分组都被确认后,nagle才允许发送非全尺寸的分组。
nagle可能印发集中http性能问题。首先,小的http分组可能无法填满一个分组,可能因为等待产生时延,其次nagle与延迟确认算法交互存在问题。nagle会阻止数据发送,直到确认分组到达,但确认分组自身被延迟确认你算法延迟100-200毫秒。
4.2.7time_wait积累与端口耗尽
time_wait端口耗尽是很严重的问题,会影响到性能基准,现实中出现较少。大多数遇到性能基准问题的人最终会遇到这个问题,而且性能会变得很差。
当tcp关闭时,在内存中会有一个小的控制块,记录最近关闭的tcp连接的ip地址和端口号。这个方法可以防止两分钟内创建,关闭并重新创建两个相同ip和端口号的连接。
即使没有遇到端口耗尽的问题,也要小心大量连接处于打开状态。,有些操作系统会因此性能下降。
4.3http连接的处理
4.3.1常被误解的connection首部
http允许客户端和最终的源服务器之间存在一串http中间实体(代理,高速缓存,等)可以从客户端开始,逐跳的将http报文经过这些中间设备,转发到源服务器上。
某些情况下,两个相邻的http应用会为它们共享的连接应用一组选项。http的connection首部字段中有一个有逗号分隔的连接标签列表,这些标签为此连接必定了一些不会传播到其它连接的选项。
connection可以承载三种不同类型的标签:
- http首部字段名,列出了只与次连接相关的首部;
- 任意标签值,用于描述此连接的非标准选项;
- 值close,说明操作完成之后需关闭这条持久连接;
如果连接标签中包含了一个http首部字段的名称,那么这个首部字段就包含了一些与连接有关的信息,不能将其转发出去。在将报文转发之前,必须删除connection首部列出的所有首部字段。
4.3.2串行事务处理时延
如果只是对连接 进行简单的管理,tcp的性能时延可能会叠加。还有一个缺点,有些浏览器在对象加载之前,无法获得对象的尺寸,所以屏幕会空白,导致用户对加载进度一无所知。
提高性能的几种方法
- 并行连接
通过多条tcp连接发起并发的http请求; - 持久连接
重用tcp连接,以消除连接及关闭时延; - 管道化连接
通过共享的tcp连接发起并发的http请求; - 复用的连接
交替传送请求和响应报文(实验阶段)
4.4并行连接
4.4.1并行连接可能提高页面的加载速度
包含嵌入对象的组合页面,如果能克服单条连接的空载时间和带宽限制,加载速度也会提高。
4.4.2并行连接不一定更快
即使并行连接的速度可能更快,但并不一定总是更快。例如:客户端的带宽不足。而且大量连接会消耗内存,从而引发自身性能问题。
4.4.3并行连接可能让人“感觉更快”
如果屏幕多个动作用户能够看到进度,在心里会感觉更快。
4.5持久连接
web客户端经常打开同一个网站的连接。因此,初始化了对某个服务器的http请求的应用程序可能在不就的将来发起更多的请求。这种性质被称为站点本地性(site locality)
因此http1.1允许http设备在事务处理之后保持tcp连接在打开状态。
重用服务器打开的空闲持久连接,就可以避免缓慢的建立连接阶段。而且已经打开的连接还可以避免慢启动的拥塞控制阶段。
4.5.1持久以及并行连接
并行连接的缺点:
- 每个事务都会打开/关闭一条新的连接,会耗费时间和带宽。
- 由于tcp慢启动特性的存在,每个新连接的性能都会下降。
- 可打开的并行连接数量实际上有限。
持久连接有一些比并行连接更好的地方。持久连接降低了时延和连接建立的开销,将连接保持在已协调状态,而且减少了打开连接的潜在数量。但是,管理持久连接一定要特别小心,不然就会出现积累大量的空闲连接。
4.5.2http1.0 keep-alive 连接
很多http1.0的浏览器和服务器进行扩展,支持keep-alive连接。
4.5.3keep-alive操作
keep-alive已经不在使用,而且在http1.1也不存在,但浏览器和服务器对keep-alive仍然在使用。
客户端发送包含 connection keep-alive首部请求将一条连接保持在打开状态。如果服务器允许,就在响应中包含同样的头部。
4.5.4keep-alive选项
可以用keep-alive通用首部中指定、有逗号分隔的选项来调节keep-alive的行为。
- 参数timeout是在keep-alive响应首部发送的。它估计了服务器希望保持在活跃状态的时间。这不是一个承诺值。
- 参数max是在keep-alive响应首部发送的,它估计了服务器希望为多少各事务保持连接的活跃状态。这不是一个承诺值。
- keep-alive还可以支持未经处理的属性,这些属性主要用于诊断和调试。语法为 name[=value]
4.5.5keep-alive连接的限制和规则
- 在http1.0中keep-alive不是默认启用的。
- connection:keep-alive首部必须随着所有希望保持持久连接的报文一起发送。
- 客户端探明响应中是否有connection:keep-alive就可以知道服务器发出响应后是否关闭连接。
- 只有在无需检测到连接的关闭即可确定报文实体主体长度的情况下,才可以将连接保持在打开状态。
- 代理和网关必须执行connection首部的规则(删除connection首部中命名的所有首部字段以及connection首部自身)
- 严格来说,不应该与无法确定是否支持connection的代理服务器建立keep-alive连接,以防出现哑代理。
- 从技术上讲,应该忽略所有来自http1.0的connection首部字段,因为他们可能是老的代理服务器误转发的。实际上,尽管可能导致老代理挂起的危险。
- 除非重复发送请求会导致一些副作用,否则如果在客户端接受完整的响应之前连接就关闭了,客户端就一定要做好重试请求的准备。
4.5.6keep-alive和哑代理
1.connection首部和盲中继
问题出现在代理上-尤其不理解connection首部,而且不知道沿着转发链路将其发送出去之前,应该将首部删除的代理。很多老的代理都是盲中继(blind relay),他们只是将字节从一个连接转发到另一个去,不对connection进行特殊处理。
- 客户端向代理发送一条报文,包含了connection首部。
- 哑代理收到了这个请求,但不理解connection首部,把它转发给服务器。
- 服务器收到这个请求,允许了进行keep-alive,并把含有connection的首部发送给哑代理。
- 哑代理把服务器的报文回给客户端,客户端认为,它正在进行keep-alive对话。
- 此时,代理完成了转发,等待服务器关闭连接,然而服务器认为,代理已经请求了keep-alive,所以代理就会挂在那里等待连接关闭。
- 客户端收到报文,即可转向下一条请求,然而,代理不认为这条连接会有请求,请求被忽略。
- 这种错误的通信方式会使浏览器处于挂起状态,直到客户端或服务器连接超时。
2.代理和逐跳首部
为避免此类通信问题的发生,现代的代理都不能转发connection首部和所有名字出现在connection值的首部。
另外还有几个不能作为connection首部值列出,也不能被代理转发或作为缓存响应使用的首部。其中包括 proxy-authenticate、proxy-connection、transfer-encoding和upgrade。参考4.3.1.
4.5.7插入proxy-connection
为了解决这个问题,一些浏览器配置了proxy-connection,很多代理能够理解他。
如果是哑代理,它转发了该首部,服务器会忽略该选项。如果是聪明的代理,则会用connection替代它。但如果在哑代理的一侧还有聪明的代理,这个问题又会出现。
4.5.8http1.1持久连接
http1.1逐渐停止了对keep-alive连接的支持,用一种名为持久连接(persistent connection)的改进型设计取代了它。
http1.1持久连接在默认情况下是激活的,要在处理事务之后关闭连接,http1.1必须显示的添加一个connection:close首部。但是,不发送connection:close不意味着服务器承诺永远保持连接。
4.5.9持久连接的限制和规则
发送了connection:close首部之后,客户端就无法在那条连接上发送更多的请求。
如果客户端不想发送其它请求,应该在最后一条请求中发送connection:close首部。
只有连接上的所有报文都正确,自定义报文长度时,实体主体的长的与相应的content-length一致,或者用分块传输编码方式编码的-连接才能持久。
http的代理必须能够分别管理客户端和服务器的持久连接-每个持久连接都只适用于一跳传输
http1.1的代理服务器不应与http1.0的客户端建立持久连接,除非他们了解客户端的处理能力。尽管服务器不应该试图在传输的过程中关闭连接,而且关闭之前应该响应一条请求,但不管connection首部取了什么值,http1.1可以在任意时刻关闭连接。
http1.1应用程序必须能够从异步的关闭中恢复出来。只要不存在可能会积累起来的副作用,客户端都应该重试这条请求。
除非重复请求会产生副作用,否则如果客户端收到整个响应之前连接关闭,客户端必须重新发送请求。
一个客户端对任何服务器或代理最多只能维持两条持久连接,以防止服务器过载。
4.6管道化连接
http1.1允许持久连接可选的使用请求管道。在响应到达之前,可以将多条请求放入队列。
管道化连接的限制
- 如果http客户端无法确认连接是持久的,不应使用管道。
- 必须按照请求相同的顺序会送http响应。如果响应失序,没办法与请求匹配。
- http客户端必须做好连接会在任意时刻关闭的准备,还要准备重发未完成的管道化请求。
- http客户端不应该用管道化的方式发送会产生副作用的请求(比如post)。总之,出错的时候,管道化的方式会阻止客户端了解服务器执行的是一系列管道化请求的哪些,由于无法安全的重试post这样的非幂等请求,所以出错时,就存在某些方法永远无法执行的风险。
4.7关闭连接的奥秘
连接管理-尤其知道什么时候关闭连接,是http的实用魔法之一。
4.7.1任意解除连接
所有的http客户端服务器或代理,都可以在任意时刻关闭一条tcp连接。通常在一条报文结束的时候关闭连接,但出错的时候,也可能在首部行的中间,或者其它奇怪的地方。
4.7.2content-length及截尾操作
每条http响应都应该有精确的content-length首部,用于描述响应主体的尺寸。客户端或代理收到一条随连接关闭而结束的http响应,而且实际传输的实体长度与content-length不匹配,接收端就应该质疑长度的真实性。如果是缓存代理,接收端不应缓存这条响应,代理应该把报文原封不动的转发出去,不应该试图去校正content-length,以维护语义的透明性。
4.7.3连接关闭容限、重试以及幂等性
即使在非错误的请求下,连接也可以在任意时刻关闭。http应用程序要做好正确处理非预期关闭的准备。如果客户端在执行事务的过程中,传输连接关闭了,那么除非事务处理会带来一些副作用,否则客户端应该重新打开连接,并重试一次。
如果一个事务不管是执行一次还是多次,得到的结果都相同。这个事务就是幂等的。实现者认为get head put delete trace options方法都是幂等的。客户端不应该以管道化的方式传送非幂等请求。
4.7.4正常关闭连接
tcp连接是双向的。tcp连接的每一端都有一个输入队列和一个输出队列,用于数据的读写。放入一端输出队列的数据最终会出现在另一端的输入队列中。
1.完全关闭和半关闭
应用程序可以关闭输入和输出信道的任意一个,或者两者都关闭了。
2.tcp关闭及重置错误
简单的http应用可以只使用完全关闭。但当应用程序开始与很多其他类型的http客户端、服务器和代理进行对话切开始使用管道化持久连接时,使用半关闭来防止对实体收到非预期的写入错误就变得很重要了
3.正常关闭
http规范建议,当客户端或服务器突然要关闭一条连接时,应该正常的关闭。
总之,实现正常关闭的应用程序首先应该关闭输出信道,然后等待另一端的对等实体关闭它的输出信道。
但不幸的是,无法确保对等实体会实现半关闭,或对其进行检查。因此要想正常关闭连接,应先关闭输出信道,然后周期性的检查信道状态。