HTTP如何使用TCP连接; TCP连接时延、瓶颈和存在的障碍; HTTP的优化,包括并行连接、持久连接(Keep-Alive)和管道化连接
几乎所有HTTP通信都由TCP/IP承载,TCP/IP是全球计算机和网络设备都在使用的一种常用分组交换网络分层协议集。
客户端应用程序可以打开一条TCP/IP连接,连接到可能运行在世界任何地方的服务器应用程序。连接一旦建立,客户端和服务器之间交换的报文不会丢失、受损或失序。
TCP/IP协议簇包含UDP,此处说的应该理解为仅TCP的情况,TCP才能保证上述效果,UDP不行。
1.可靠数据传输通道
HTTP连接实际上就是TCP连接和一些使用连接的规则。TCP为HTTP提供了可靠的比特传输管道。从TCP一端填入的字节会从另一端 以原有的顺序、正确的传输出来。
TCP流分段、由IP分组传送 TCP数据通过IP数据报的小数据块发送。
HTTP是HTTP-TCP-IP协议栈的顶层,其安全版本HTTPS就是在HTTP和TCP之间插入了一个称为TLS或SSL的密码加密层
HTTP要传送一条报文(message)时,会以流的形式将报文数据内容通过一条打开的TCP连接按序传输。到TCP处,将数据流堪称成为段(segment)的小数据块,封装到IP分组(packet)中,通过因特网传输,由TCP/IP软件处理。
每个TCP段都由IP分组承载,从一个IP地址发往另一个IP地址。 每个IP分组中都包含:一个IP分组首部(通常20字节),一个TCP分段首部(通常20字节),一个TCP数据块(0或多个字节) IP首部包含源和目的IP地址、长度和其他标记。TCP段首部包含TCP端口号,TCP控制标记和用于数据排序和完整性检查的一些数字值。
保持TCP连接的正确运行 在任意时刻,计算机都可以有多条TCP连接处于打开状态。TCP通过端口号保持连接正确运行。 TCP连接通过四个值唯一标识:源、目的IP地址,源、目的端口号。
2.TCP套接字编程 几乎所有操作系统中都有操作TCP连接的工具。 套接字API允许用户创建TCP的端点数据结构,将端点与远程TCP端点连接,对数据流进行读写。隐藏了所有底层网络协议的握手细节,以及TCP数据流与IP分组之间的分段和重装细节。 Web服务器从S4开始等待连接。客户端根据URL判定IP和端口号,建立TCP连接(C3),建立连接时间长短取决于服务器距离远近、服务器负载情况和因特网拥挤程度。 连接建立后,客户端就会发送HTTP请求,服务器读取请求并处理,将数据写回。客户端读取响应数据并处理。
HTTP事务性能很大程度取决于底层TCP通道的性能。理解TCP性能特点,可以更好设计高性能的HTTP性能优化的应用程序。
1.HTTP事务时延 与建立TCP连接,传输请求和响应报文的时间相比,事务处理时间可能很短。除非客户端或服务器超载,或请求在服务端处理时间确实很长(算法、数据库IO等)。否则HTTP时延就是由TCP网络时延造成的。
2.HTTP时延的主要原因
1.客户端根据URL确定IP和端口号,通过DNS解析系统将URI主机名转换IP可能花费数十秒。(大多数HTTP客户端有DNS缓存,保存最近访问站点的IP地址,常用站点可以通过缓存快速获取)
2.客户端和服务器建立连接需要三次握手,需要花费时间
3.连接建立后,客户端通过TCP管道发送请求、服务端处理请求、服务端返回响应,都需要花费时间。
TCP时延主要取决于硬件速度、网络和服务器负载,请求响应报文大小,客户端服务器距离等。TCP协议复杂性也会很大影响时延。
常见时延 1.TCP连接握手时延
建立连接,发送任意数据前,TCP软件会交换一系列IP分组,沟通连接的相关参数。如果该连接只传输少量数据,则交换过程产生时延会在整个过程占据50%或更大分量。
2.延迟确认
因特网本身无法保证可靠的分组传输,如果路由器超负荷,会随意丢弃分组,TCP实现了确认机制保证数据的成功传输。
每个TCP段有一个序列号和数据完整性校验和。每个段接收者收到完好的段会给发送者小的确认分组,如果发送者没有在指定窗口时间内收到确认信息,就认为分组损毁或丢弃,并重发数据
TCP为更有效利用网络,会将小的确认分组和输出的数据分组结合在一起,如果该时间段无输出数据分组,则放在单独分组传送。
HTTP请求应答行为降低了捎带信息的可能。延迟确认算法常引入相当大的时延,操作系统可以调整或禁用延迟确认算法。但是修改时必须确保知道自己在做什么,TCP引入算法的目的是防止设计欠佳的应用程序对因特网造成破坏,配置的修改要确保程序不会引发算法避免的问题。
3.TCP慢启动
TCP数据传输的性能还取决于TCP连接的使用期,TCP连接会随着时间自我调谐:刚开始会限制连接的最大速度,如果成功传输,会随着时间推移提高传输速度。称为TCP慢启动,用于防止因特网突然过载或阻塞。 慢启动限制了TCP端点在任意时刻可以传输的分组数。即:
如果成功接收一个分组,发送端就有了发送另外两个分组的权限。如果HTTP事务有大量数据要发送,不会一次性发送所有分组,必须先发送一个分组,收到确认,然后可以发送两个,每个分组都必须被确认,然后发送四个,以此类推。称为“打开拥塞窗口”。
由于已存在拥塞控制特性,新连接的传输速度会比已经交换过一定量数据的连接更慢。由于已调谐的连接更快,HTTP中有一些可以重用现存连接的工具。
3.Nagle算法和TCP_NODELAY
TCP有一个数据流窗口,应用程序通过它可以将任意尺寸数据放入TCP栈,即使只有一个字节。但是每个TCP段都至少装载了40字节的标记和首部,如果TCP发送大量包含少量数据的分组,会严重影响网络性能。
Nagle算法试图在发送分组前将大量TCP数据绑定在一起,提高网络效率。该算法鼓励发送全尺寸的段(几百字节),只有当所有其他分组都被确认,算法才允许发送非全尺寸的分组。如果仍在传输过程中,则将数据缓存起来,只有当挂起分组被确认,或者缓存积累了足够发送全尺寸分组数据,才会发送缓存数据。
即先积累全尺寸段发送,缓存待发送数据,如果已发送被确认则发送缓存数据、如果缓存数据达到全尺寸则发送缓存数据。
该算法将引发几种HTTP性能问题。小的HTTP报文可能无法填满一个分组,可能会因为等待不会到来的其他数据产生时延。算法和延迟确认存在交互问题,该算法会阻止数据发送知道有确认分组到达,但是确认分组本身会被延迟确认算法延迟一定100-200ms。
HTTP程序经常设置TCP_NODELAY参数禁用Nagle算法提升性能,前提是确保TCP写入大块数据,不产生过多小分组。
4.TIME_WAIT累积与端口耗尽
相对较少出现,大多与遇到性能基准问题最终都会碰到这个问题,且性能会变得出乎意料的差。
当某个TCP端点关闭连接,会在内存维护一个小控制块,记录最近关闭连接的IP和端口,只会维持一小段时间,即2MSL(最大分段使用期的两倍)。确保在该时间不会创建具有相同地址和端口的新连接。防止两分钟内创建、关闭并重新创建相同的连接。
在2MSL内,连接无法被重用,端口也无法被重用,连接率被限制在60000(可用源端口数量)/120=500次/秒,如果服务器连接率在500以下,则不会产生TIME_WAIT端口耗尽问题,可以通过增加负载机器数量或者确保客户端和服务器循环使用几个虚拟IP来增加更多的连接组合。
1.可能提升加载速度
HTTP匀速客户端打开多条连接,并行执行多个HTT。P事务。组合页面的对象并行请求,时延将会重叠起来。相比串行加载速度将会加快。
2.不一定更快 客户端网络带宽不足时,大部分时间都是用来传输数据。如果并行加载多个对象,多个HTTP事务竞争有限带宽,将会导致每个对象都以较慢的速度加载。
相比串行,就算并行实际上由于带宽限制结果上没有提升加载速度,但是用户眼中的页面会出现多个组件对象同时加载,相比串行,用户看得见”更快的进度“。
Web客户端经常使用到同一站点的连接,如一个Web组合页面内容可能都来自同一个站点,打开一个请求,后续多个请求可能目标相同,连接其实不必关闭。
HTTP /1.1允许HTTP设备在事务处理结束后将TCP连接保持打开状态,便于和后来的HTTP事务重用。直到客户端或服务器决定关闭。可以避开缓慢的连接建立阶段和避免慢启动的拥塞适应。
持久连接的两种类型:HTTP/1.0+的 keep-alive 连接,HTTP/1.1的persistent连接。
并行连接缺点: 每个事务打开和关闭连接都会耗费时间和带宽 TCP慢启动,每条连接性能有所降低 可以打开的并行连接数量有限 持久连接缺点: 可能积累大量空闲连接 一般会配合使用,Web应用可能会打开少量并行连接,每一个都是持久连接。1.keep-alive 早期实验型持久连接。已经不再使用。但是keep-alive握手的使用相当广泛。
通过首部Connection:Keep-Alive 请求将一个连接保持在打开状态。 如果服务器愿意,则在响应中包含相同首部,如果不愿意则客户端认为服务器不支持,在响应报文收到后关闭连接。
2.persistent connection 工作机制更优。 默认情况下是激活的,除非特别指明,否则HTTP/1.1默认所有连接都是持久的。如果需要在事务处理后关闭,需要在首部加上Connection:close
HTTP客户端收到响应后,除非包含该首部,则连接处于打开状态。客户端和服务器可以随时关闭空闲连接。
限制和规则 发送了该首部后,客户端无法再该连接上发送更多请求 客户端不想再连接上发请求,就应该在最有一条请求带上该首部 当连接所有报文实体长度都和Content-Length一致,或者用分块编码方式编码,连接才能持久保持。 1.1的代理必须能够分别管理客户端和服务器的持久连接,每个持久连接都只适用一条传输 1.1不应该与1.0客户端建立持久连接,除非了解客户端处理能力,事实上很多厂商都没做 一个用户客户端对任何服务器或代理最多只能维护两条持久连接。防止服务器过载。如果有N个用户试图访问服务器,代理最多要维持2N条到任意服务器或父代理的连接1.1允许在持久连接上可选地使用请求管道 在响应到达之前,可以将多条请求放入队列。当第一条请求正在因特网流通时,第二第三条请求也可以开始发送了。
限制 HTTP客户端无法确认连接是持久的,就不应使用管道 必须按照相同顺序回送HTTP响应,避免HTTP响应失序
客户端应该做好连接可能在任意时候关闭的准备,还应准备重发所有未完成的管道请求。如客户端打开管道持久连接,发送十条请求,服务器只处理了5条关闭连接,则客户端对剩下的请求应该重新发出 客户端不应使用管道方式发送非幂等请求,因为无法安全重试。
何时、如何关闭连接
1.“任意”解除连接
HTTP客户端、服务器或代理都可以在任意时刻关闭一条TCP传输连接。通常会在一条报文结束时关闭,出错的时候,也可能在首部行的中间或其他地方。
对管道化持久连接而言很常见。持久连接持续一段时间后服务器可能关闭连接,服务器无法知道关闭空闲连接时,客户端是否在发送,于是可能出现客户端写入半截请求时连接错误。
2.Content-Length和截尾操作
HTTP响应应该具有精确的Content-Length首部,老的HTTP服务器可能有错误的长度,或干脆省略该首部,需要依赖服务器发出的连接关闭说明数据的真实末尾
客户端或代理收到随连接关闭而结束的HTTP响应时,校验长度是否匹配。如果接收端是缓存,则不应缓存。代理应该将有问题的报文原样转发而不是矫正。
3.关闭连接容限、重试和幂等性
HTTP应处理非预期关闭连接的情景,即使在非错误情况下也可能发生该情况。
如果在客户端执行事务时,传输连接关闭,除非事务处理有副作用,否则应该重新打开连接并重试。管道化连接中,客户端将大量请求放入队列排队,如果源端关闭连接,将留下大量未处理请求需要重新调度。
客户端在连接滚壁厚无法百分百判断服务器端实际激活了多少事务。如果自动不断重试了多次非幂等请求将会导致严重问题。
一个事务,不管执行一次还是多次,结果都相同,该事务是幂等的
可以认为GET、HEAD、PUT、DELETE、TRACE、OPTIONS都是幂等的(实际上需要在程序实际设计和编码中保证这一规范)。 客户端不应该以管道化方式发送非幂等请求,如POST。否则传输的过早种植将会造成不确定后果。 不应该自动重试非幂等方法或序列,失败后是否重试应该询问用户决定。 一个请求是否幂等,不取决于使用的HTTP方法名,而取决于服务端对在该接口执行的实际动作是否真的满足幂等的要求,使得请求如果错误则自动回退(事务管理),或对相同参数请求多次不会产生多种结果(如PUT请求需要检查数据库是否已经存在相同内容,如数据库设计为某个字段不可重复,一般不是自增的主键,因为主键的自增不会在客户端进行,对DELETE请求则一般携带特定信息,如主键,对不存在的内容的删除不会生效)
要发送一条非幂等请求,必须等待来自前一条请求的响应状态。
4.正常关闭连接 TCP连接的两端都有一个输入队列和一个输出队列,用于数据的读写。一端输出队列中的数据会出现在另一端的输入队列中。
1.完全关闭和半关闭
程序可以关闭TCP输入和输出信道的任意一个或都关闭。 如果套接字使用close()则会都关闭,为完全关闭 如果套接字使用shutdown()则单独关闭输入或输出,为半关闭
2.TCP关闭和重置错误
简单的HTTP程序可以只用完全关闭。 如果与其他类型的很多HTTP客户端、服务器和代理进行对话且使用管道化持久连接,半关闭可以防止对等实体收到非预期的写入错误。
关闭输出信道总是安全的。另一端会在缓冲区读取所有数据后收到通知,说明流结束,即连接关闭。 关闭输入信道较危险,除非已知对方不再发送其他数据。如果对方在向本地已经关闭的输入信道发数据,操作系统会向对方发送“连接被对方重置”的TCP报文。删除对端未读取的缓存数据是非常糟糕的事情。
如已经在一条持久连接的输出队列上发送了十条管道请求,响应已经收到。 十条请求在你的操作系统缓冲区存储,还未被程序读取。 假设发送第十一条请求时,服务器认为该连接是时候关闭,第十一条请求会发送到一条已经关闭的连接上,并回送重置信息。 该重置信息会导致你的输入缓冲区被清空。等到要读取数据的时候会得到连接被对端重置的错误,已经缓存但是为读取的响应数据尽管已经获取,但是全部丢失了。
3.正常关闭 实现正常的关闭连接需要首先关闭输出信道,等到另一端关闭输出信道。两端都告知对方 不再接收数据,则连接关闭,没有重置危险。
