前端学堂
学有所用

HTTP的keep-alive


HTTP协议之Keep-Alive

HTTP规范对HTTP报文解释的很多,但是很少介绍HTTP连接。HTTP连接是HTTP报文传输的关键通道,所以学习HTTP协议需要理解这些:1.HTTP如何使用TCP连接 2.TCP连接存在的时延、瓶颈,问题 3.HTTP的优化,包括并行连接,keep-alive和管道化连接 4.管理连接该怎么做

HTTP的keep-alive

转载请注明出处://fed123.oss-ap-southeast-2.aliyuncs.com/2014/09/28/2014_http1/

TCP连接

几乎所有的HTTP通道都是有TCP/IP来承载的,TCP/IP是全球网络设备通用的分组交换网络分层协议集。客户端程序可以打开一个连到网络上的服务器的TCP/IP连接,一旦连接建立起来,在通道上交换的报文就永远不会丢失、受损或者失序。
HTTP的keep-alive
HTTP连接实际上是TCP连接+使用连接的规则。TCP连接是通过IP分组(IP数据报)来发送数据块的。所以,HTTP其实是HTTP over TCP over IP,HTTP是协议栈的最顶层了。HTTPS 实际上是在HTTP和TCP之间插入了TLS或者SSL密码加密层。
HTTP的keep-alive
TCP链接通过4个值来定义一条连接。如下:

1
<源IP地址 源端口号 目的IP地址 目的端口号>

两个不同的连接,不能拥有相同的这4个值。ip数据包如下:
HTTP的keep-alive
最直接的操作TCP连接的是socket编程。

TCP连接性能问题

HTTP连接是建立在TCP连接之上的,下图是HTTP事务的处理过程。
HTTP的keep-alive
由此可见,HTTP事务处理时间很短,大部分时间用在发送请求和接受响应报文。所以HTTP的时延,主要有TCP的网络时延构成。
HTTP时延主要出现在以下过程:
1) 根据URL确定ip地址和端口号
2) 发送TCP连接请求,等待应答
3) 连接建立起来后,通过建立的TCP管道传输请求,服务端则处理报文。
4) web服务器送回HTTP响应

下面主要讨论几个关键的性能点:
1) TCP连接建立握手时延
2) TCP慢启动拥塞控制时延
3) 数据聚集的Nagle算法
4) 用于捎带确认的TCP延时确认算法
5) TIME_WAIT时延和端口耗尽

TCP连接建立时延

传送少量数据时,每次握手建立连接的时间很可观。
HTTP的keep-alive
TCP连接握手需要经过下面几个步骤:
1) 请求新的TCP连接时,客户端先向服务端发送一个小的TCP分组(通常是40-60字节)。这个分组中设置了一个特殊的SYN标记,说明这是一个连接请求。
2) 如果服务器接受了这个请求,就会对连接参数进行计算,并向客户端送回一个分组,这个分组中SYN和ACK都会被置位,说明连接已经被接受。
3) 最后,客户端向服务端发送一条确认信息,通知该链接已经成功建立。现在的TCP协议栈允许在这个确认分组中发送数据。

延迟确认

由于因特网无法确认可靠的数据传输,因为路由器超负荷的时候可以随意丢弃分组。所以TCP实现了自己的确认机制来确保数据成功传输。
每个TCP段都有一个序列号和数据完整性校验和。每个段的接受者收到完好的数据时,都会向发送者发送小的确认分组。如果发送者在指定的时间窗口内没有收到确认信息,发送者就认为改分组已被破坏或损毁,重发数据。
由于确认报文很小,所以没比较通过建立连接来发送,而是通过发往同方向的数据分组中进行“捎带”的方式,这样可以更有效的利用网络。

TCP慢启动策略

TCP的传输性能取决于TCP的使用期,TCP会随着时间自我控制传输速度,以避免网络发生堵塞。称为慢启动策略。
TCP慢启动策略限制了一个TCP断点在任意时刻可以传输的分组数目。简单来说,没成功接受一个分组,发送端就有权限尝试发送两个分组。如果这两个分组成功接受,发送端就有权限尝试发送4个分组。这个阶段称为打开阻塞端口。如果HTTP事务有大量的数据要发送的时候,需要有这样一个慢启动的过程。

NagLe算法

TCP有一个数据流接口,应用程序可以将任意大小的数据放入TCP协议栈中,即使1个字节也可以,但是每个TCP段都至少装载40个字节的标记和首部数据,如果发送了许多的包含少量数据的分组,就会大大降低网络性能。
Nagle算法试图在发送一个分组之前,将多个TCP数据绑定在一起,以提高网络效率。

TIME_WAIT累积和端口耗尽

这个在系统防御上 经常出现,比如常见的DOSS FLOOD攻击,很快会导致Socket段的端口耗尽,导致客户端无法连接的问题。

当某个TCP端点关闭TCP连接时,会在内存中维护一个小的控制块,用来记录最近所关闭的连接的ip和端口号,这类信息会维持一段时间,通常是所估计的最大分段使用期的两倍,称为(2MSL),这段时间内不会创建相同地址和端口号的新链接。

举个例子,客户端每次连接到服务器上时都会获得一个新的端口,以实现连接的唯一性。但是可以使用的端口是有限的,比如60000个,在2MSL时间内,比如120s,连接是无法重用的,这样连接率就被现在在了60000/120=500次每秒。如果经过优化,服务端的连接率小于500次每秒,就不会遇到端口耗尽的问题。要想避免这个问题可以在客户端和服务端之间采用虚拟ip地址的方法增加连接组合。

HTTP连接的处理

HTTP首部字段中有个connection字段,该字段用于为连接添加一些不会传播到其他链接中的选项,比如指定:connection:close 来说明发送完下一条报文后关闭连接。
Connection首部可以承载3种不同类型的标签:
1) HTTP首部字段名,列出与此连接相关的首部
2) 任意标签值,用于描述此链接
3) close,说明操作完成之后关闭这个连接

如果是第一种情况,也即是connection值为此链接相关首部字段的名称,那么这个首部字段就包含了连接有关的信息,不能将其转发出去,再将报文发出去之前,必须删除connection中列出的所有首部字段,由于connection首部可以防止无意中转发本地的首部,可以将逐跳首部名放入connection中,这种做法称为“对首部的保护”。如下例:
HTTP的keep-alive

HTTP串行处理事务的延时
如果一个页面包含了许多的图片,浏览器这样一个接着一个的方式建立连接,下载图片无疑是很耗时的。
有几种方法可以提高HTTP的连接性能
1) 并行连接
2) 持久连接
3) 管道化连接

并行连接

并行连接就是同时建立多个HTTP连接,这样可以提高页面的加载速度。但是并行连接并不一定会更快,应为如果客户端网络带宽有限,或服务端网络带宽有限,这些连接竞争这样有限的带宽,带来的性能的提升是不足道的。
同时还存在的问题是,客户端打开的连接多,服务端就要处理这些连接,增加了服务器的负担。浏览器通常会限制并行连接的数目,一般在4个。

持久连接

在HTTP/1.1以及HTTP/1.0的增强版中,允许HTTP设备在事务处理完成之后将TCP连接保持在打开状态,以便未来的HTTP请求复用该链接。在事务处理结束后,依然保持打开的连接称为持久连接。非持久连接会在每个事务结束之后关闭。而持久连接会一直保持,直到客户端或者服务器将其关闭为止。

并行连接可以提高嵌有iframe的复合页面的加载速度,但是也有许多缺点。
1) 每个事务都会打开或关闭一条新的连接,耗费时间及带宽。
2) 由于TCP的慢启动特性,每条新链接的性能都会有所降低
3) 可以打开的并行连接数是有限制的

持久连接有比并行连接更好的地方,持久连接降低了时延和建立连接的开销,将连接保持在“自我调整”的状态,减少了打开链接的数目,但是管理持久连接要特别小心,否则会积累出大量的空闲连接,耗费客户端和服务端的资源。

持久连接和并行连接的配合使用是目前比较高效的方法,目前很多的web浏览器也是这么做的。持久连接有两种类型:
1) HTTP/1.1 persistent连接
2) HTTP/1.0 keep alive连接

keep alive已经不再使用了,HTTP1.1中也没有对其进行说明,不过keep alive的握手使用仍然广泛。
1) http客户端如果想实现http/1.0 keep-alive需要在请求首部包含connection:keep-alive
2) 如果服务端愿意将连接保持,需要在响应中包含相同的首部,如果没有包含connection:keep-alive,则说明服务器不支持keep-alive,会在发回响应之后关闭连接。

在使用keep-alive模式中有个问题,既然连接是不关闭的,客户端如何知道服务端已经发送完了数据?

比如客户端请求一张图片,客户端如何判断这个图片已经接受完了?

1) 服务器在发送确定性的文件时,会在消息头部通过content-length字段告诉客户端要接受多少数据。如果没有这个content-length,那么只有通过服务端关闭连接来确认结尾。这样是无法保持持久连接的
2) 如果是动态页面,服务端无法预知内容大小,可以使用transfer-encoding:chunk的模式来传输数据,这就是一边产生数据,一边发送数据。

chunk编码将数据分成一块一块的发生。Chunked编码将使用若干个Chunk串连而成,由一个标明长度为0的chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF)隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Chunk编码的格式如下:
Chunked-Body = *chunk
"0" CRLF
footer
CRLF
chunk = chunk-size [ chunk-ext ] CRLF
chunk-data CRLF
hex-no-zero = <HEX excluding "0">
chunk-size = hex-no-zero *HEX
chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-value ] )
chunk-ext-name = token
chunk-ext-val = token | quoted-string
chunk-data = chunk-size(OCTET)
footer = *entity-header
即Chunk编码由四部分组成:1、0至多个chunk块,2、"0" CRLF,3、footer,4、CRLF.而每个chunk块由:chunk-size、chunk-ext(可选)、CRLF、chunk-data、CRLF组成。

keep-alive消息长度总结

,上面2中方法都可以归纳为是如何判断http消息的大小、消息的数量。RFC 2616对消息的长度总结如下:一个消息的transfer-length(传输长度)是指消息中的message-body(消息体)的长度。当应用了transfer-coding(传输编码),每个消息中的message-body(消息体)的长度(transfer-length)由以下几种情况决定(优先级由高到低):

任何不含有消息体的消息(如1XXX、204、304等响应消息和任何头(HEAD,首部)请求的响应消息),总是由一个空行(CLRF)结束。
如果出现了Transfer-Encoding头字段 并且值为非“identity”,那么transfer-length由“chunked” 传输编码定义,除非消息由于关闭连接而终止。
如果出现了Content-Length头字段,它的值表示entity-length(实体长度)和transfer-length(传输长度)。如果这两个长度的大小不一样(i.e.设置了Transfer-Encoding头字段),那么将不能发送Content-Length头字段。并且如果同时收到了Transfer-Encoding字段和Content-Length头字段,那么必须忽略Content-Length字段。
如果消息使用媒体类型“multipart/byteranges”,并且transfer-length 没有另外指定,那么这种自定界(self-delimiting)媒体类型定义transfer-length 。除非发送者知道接收者能够解析该类型,否则不能使用该类型。
由服务器关闭连接确定消息长度。(注意:关闭连接不能用于确定请求消息的结束,因为服务器不能再发响应消息给客户端了。)
为了兼容HTTP/1.0应用程序,HTTP/1.1的请求消息体中必须包含一个合法的Content-Length头字段,除非知道服务器兼容HTTP/1.1。一个请求包含消息体,并且Content-Length字段没有给定,如果不能判断消息的长度,服务器应该用用400 (bad request) 来响应;或者服务器坚持希望收到一个合法的Content-Length字段,用 411 (length required)来响应。

所有HTTP/1.1的接收者应用程序必须接受“chunked” transfer-coding (传输编码),因此当不能事先知道消息的长度,允许使用这种机制来传输消息。消息不应该够同时包含 Content-Length头字段和non-identity transfer-coding。如果一个消息同时包含non-identity transfer-coding和Content-Length ,必须忽略Content-Length 。

谢谢!

转载请注明出处://fed123.oss-ap-southeast-2.aliyuncs.com/2014/09/28/2014_http1/

欢迎关注皓眸学问公众号(扫描左侧二维码),每天好文、新技术!任何学习疑问或者工作问题都可以给我留言、互动。T_T 皓眸大前端开发学习 T_T

赞(0) 打赏
未经允许不得转载:前端学堂 » HTTP的keep-alive

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏