- 作者: 雪山肥鱼
- 时间:20211109 06:57
- 目的: 了解http 的连接管理
4.1 TCP 连接(老生常谈了)
TCP/IP 全球计算机及网络设备都在使用的一种常用分组交换网络分层协议集。意思就是还有其他的比如 ipx/spx 之类的。
收到一个url:
(1) 到(3) 将浏览器的ip地址和端口号从url 分离出来
(4) 建立tcp连接
(5) 发送请求报文
(6) 读取响应
(7) 关闭连接
4.1.1 TCP的可靠数据管道
传输层: TCP
引用层: HTTP
注意上述数据方向,网络传输均为大端序。
4.1.2 TCP是分段的,由IP分组传输
tcp分段的原因是 IP层的作用。tcp的数据是通过名为ip分组的小数据块来发送的。
HTTP: http over tcp over ip.
HTTPS: 在http 和 tcp 之间插入了 一个 tls/ssl 密码加密层。
http 传送一条报文,会以流的形式将报文数据的内容通过一条打开的tcp连接按顺序传输。tcp收到数据流之后,会将数据流砍成小块,并将数据封装在ip分组中。通过internet传输。这些工作都是由TCP/IP软件来处理的,http程序员什么都看不到。
最简单的包
- ip首部
- tcp 段首部
- tcp数据块
4.1.3 保持tcp连接的正确运行
IP可以帮你连接到正确的计算机,而端口号则可以将你连接到正确的应用程序上去。tcp中 下面2对标定了一个tcp/ip 连接
再加上协议版本,就是五元组。
4.1.4 tcp 套接字编程
4.2 对tcp性能的考虑
对tcp 的性能有所了解
4.2.1 http 事务的时延
与建立 tcp 连接,以及传输请求和响应报文的时间相比,事务处理时间可能会很短。
除非客户端和服务器超载,或正在处理复杂的动态资源,否则http时延就是tcp网络时延构成的。
Http 事务的时延有以下几种主要原因
- 客户端首先需要根据URI 确定 Web 服务器的IP地址和端口号。如果最近没有对URI中的主机名进行访问,通过dns解析系统将URI中的主机名转换成一个IP地址可能要花费数十秒的时间。
http客户端会有一个小的dns缓存,用来保存近期所访问站点的ip地址。 - 发送tcp连接请求,等待服务器的请求应答。每条新的tcp连接都会有新的连接建立时延,时间大概在1~2s. 但如果有数百个http事务的话,这个值会快速的叠加上去。
- 网络时延。
- 硬件速度、网络、服务器负载、请求和响应报文的尺寸,以及客户端和服务器之间的举例,tcp协议的复杂性等,都会对时延造成巨大影响。
4.2.2 性能聚焦区域
tcp 相关时延:
- tcp连接建立握手
- tcp 慢启动拥塞控制
- 数据聚集的 nagle算法
- 用于捎带确认的tcp延迟确认算法
- time_wait 时延和端口耗尽
4.2.3 tcp 连接的握手时延
在发送数据之前,tcp要传送两个分组建立连接
小的http事务可能会在tcp建立上花费50%,或更多的时间。
4.2.4 延迟确认
因为IP层是不可靠的分组传输。所以tcp层实现了自己的确认机制来确保数据的成功传输。
每个tcp段都有一个序列号和数据完整校验和。并且每个段的接收者收到完好的段时,都会向发送者送回小的确认分组。如果在指定的窗口时间内收到确认信息,发送者就认为分组破坏,则重发。
因为确认分组很小,所以tcp允许在发往相同方面输出数据分组,捎带这个确认包。也就是说返回确认信息与输出的数据分组结合在一起。
延迟确认算法:
- 确认包会在一个特定的窗口时间(100ms ~ 200ms)内,将输出确认放入缓存区
- 寻找能够捎带它的输出分组(和来的时候同方向)
- 200ms后没有同方向的输出分组,则单独发送确认分组。
但http 的 请求 响应 模式,不太希望这种捎带信息的可能,会引入大量的时延,根据操作系统的不同,和业务去修,可以取消延迟确认的算法。
4.2.5 tcp 慢启动
tcp连接随着时间进行自我调节。起初会限制连接的最大速度,如果数据传出,会慢慢加快。用于防止internet 的突然过载和拥堵。
一开始慢,随后快的行为也叫做打开拥塞窗口。
所以新的连接肯定会比老的连接快,所以后续的上层http可以复用这些现存的连接工具。
4.2.6 Nagle 算法 与 TCP_NODELAY
tcp 有 一个 数据流接口,可以放任意尺寸在tcp栈中。一次放放一个字节也是可以的!但是tcp段中至少装在了40个字节的标记和首部,如果tcp 发送了大量包含少量数据的分组,网络的性能会严重下降。
即:窗口综合症。
Nagle算法走另一个极端,鼓励全尺寸发送。只有当其他全尺寸分组得到确认后,Nagle算法才允许发送非全尺寸分组。一旦发出去,数据会被挂起,只有当挂起的分组被确认,才会发送其余的缓存数据。
Nagle算法也会引发几种http性能问题。
- 小的http报文可能无法填满一个分组。引起时延
- nagle 算法和延迟确认之间存在交互问题。
nagle 算法会阻止数据发送,直到有确认分组到达为止,但确认分组自身会因为延迟确认算法延迟100 - 200 ms。
http应用和程序常常会在自己的栈中设置参数 tcp_nodelay,金庸Nagle算法。如果这样组,要确保向tcp写入大块数据。以免产生窗口综合征,产生一堆小分组。
4.2.7 TIME_WAIT累积与端口耗尽
tcp端口耗尽归类于严重的性能问题。
原理:
当某个tcp端点关闭tcp连接时,会在内存中维护一个小的控制块,用来记录最近所关闭连接的ip地址和端口号。这类信息会维持一段时间,是所估计的最大分段使用期的两倍,成为2MSL,通常2分钟。
将2msl值取为2分钟是有历史原因的。早期路由器速度慢,将一个赋值副本丢弃之前,它可以在internet队列中保留最多一分钟,现在生存中期要小的多。
如果msl时间太短,要小心来组原来socket的分组会被复制,然后插入新的tcp流,破坏tcp数据。
解决:增加客户端负载生成机器的数量,也就是增加源端口与源ip的组合。
即使没有遇到端口耗尽问题,也要特别小心有大量连接处于打开状态的情况,或为处于等待状态的连接分配了大量控制块的情况,大量控制块会导致有些操作系统速度严重减缓
4.3 http 连接的处理
4.3.1 connection 首部
两个相邻的http应用程序会为他们共享的连接应用一组选项。
- 如果连接标签中包含了一个http首部字段的名称,那么这个首部字段就包含了一些和连接有关的信息。不能将其转发出去!也就是说,不能转发到其他节点。
- 将报文转发之前,必须删除connection首部列出的所有首部字段。
- http应用程序收到 一条带有connection 首部报文时,接收端会解析发送端请求的所有选项,并将其应用。将报文转发到下一条地址之前,删除connection 首部以及connection中列出的所有首部
4.3.2 串行事务处理时延
串行时延高,且用户体验差
- 并行连接
通过多条tcp连接发起并发的http请求 - 持久连接
重用tcp连接,消除连接及关闭时延 - 管道化连接
通过共享的tcp连接发起并发的http请求 - 复用的连接
交替传送请求和响应报文
4.4 并行连接
页面上每个组件都包含一个独立的http事务
4.4.1 并行连接可能会提高页面的加载速度
多个事务一起来,肯定快的
4.4.2 并行连接并不一定更快
- 占带宽
- 打开大量连接会消耗很多内存资源。引发自身的性能问题。
4.5 持久连接
http/1.1 开始运去http设备在事务处理结束之后将tcp连接保持在打开状态,以便未来的http请求重用现存的连接。
在事务结束之后,仍保持在打开状态的tcp连接被成为持久连接。
4.5.1 持久以及并行连接
并行连接的缺点:
- 每个事务都会打开/关闭 一条新的连接,会消耗时间和贷款
- 由于tcp慢启动特性的存在,每条新连接的性能都会有所降低
- 可打开的并行连接数量,实际是有限的。
持久连接 + 并行连接 是最高效的
4.5.2 http/1.0 + keep-alive 连接
keep-alive 连接 是 早期实验型持久连接
实现http/1.0 keep-alive 连接的客户端可以通过包含 Connection:Keep-Alive 首部请求,将一条连接保持在打开状态。
如果服务器愿意为下一条请求将连接保持在打开状态,就在响应中包含相同的首部。如果响应中没有connection:keep-alive 首部,客户端就认为服务器不支持 keep-alive。就会在发挥响应报文之后关闭连接。
4.5.3 keep-alive 连接的限制和规则
- 至少在http/1.0中,keep-alive 并不是默认使用的。客户端必须发送Connection:keep-alive 请求首部来激活keep-alive连接。
- Connection:keep-alive 首部必须随所希望保持持久连接的报文一起发送,客户端没有发送Connection:keep-alive首部,服务器就会在那条请求后关闭连接
- 代理和网关必须执行Connection首部规则。代理或网关必须在将报文转发出去或高速缓存之前,删除在connection首部中命名的所有首部字段以及Connection 首部自身。
- 不应该与无法确认是否支持Connection首部的代理服务器建立keep-alive 连接,以放置出现下面要介绍的呀代理问题。
4.5.4 Keep-Alive 和 哑代理
-
connection首部 和 盲中继/哑代理
问题出现代理上,尤其是那些不理解Connection首部,而且不知道沿着转发链路将其发送之前,应该删掉首部的代理。很多老的或简单的代理都是盲中继,他们只是将字节从一个连接转发到另一个连接去,不对connection机型处理
由于中间的哑代理对keep-alive一无所知,所以会将收到的所有数据都会送给客户端,然后等待源端服务器关闭连接。但服务器认为代理已经显示的请求他将连接保持在打开状态,所以不会关闭连接。这样代理就会挂在那里等待连接的关闭。
下一条请求,则忽略,浏览器就在这里转圈,无任何进展。
- 代理和逐跳首部
避免以上问题,现代的代理都绝不能转发 Connection首部和所有名字出现在connection值中的首部。
4.5.5 插入 Proxy - Connection
针对哑代理变通做法,不要求所有的web应用程序支持高版本的http。名为Proxy-Connection的新首部。
哑代理:Proxy-Connection: 非标准的首部,哑代理转发后,服务器会忽略。则不会出现任何问题
聪明代理: 用一个Connection首部取代无意义的Proxy-Connection首部。
但也只是针对只有c/s之间一个代理的问题。而且网络中存在很多不可见的代理,如防火墙,拦截缓存等,亦或者是反向代理服务器的加速器。这种情况仍无法解决
4.5.6 HTTP/1.1 持久连接
用持久连接(persistent connection)的改进设计取代。持久连接的目的与keep-alive 相同,但工作机制更为优秀。
默认打开 持久连接 应用程序必须向报文中显示的添加一个Connection:close 首部. 服务器收到后 关闭。
但是C/S之间仍然可以随时关闭空闲连接,不发送Connection:close并不意味着服务器承诺永远连接保持在打开状态
每个持久连接只适用于一跳传输
4.6 管道化连接
在响应到达之前,可以将多条请求放入队列。当第一条请求通过网络流向另一端服务器时,第二条第三条页开始发送了。
限制:
- 非持久连接,则不应该使用管道
- 必须与请求相同的顺序回送http响应,http报文中没有序列号标签,因此收到响应失序了,就无法与请求匹配了。
- 有重发机制
4.7 关闭连接
4.7.1 任意时刻 接触连接
http客户端 服务器,代理都可以在任意时刻关闭一跳tcp传输连接。通常会在一跳报文结束时候关闭连接,但出错的时候,也可能在首部行的中间,或其他奇怪的地方关闭连接。
对管道化持久连接来说,这种情况是很常见的。http应用程序可以在经过任意时间后,关闭持久连接。
如果服务器突然关闭,客户端就会在写入半截请求报文时发现出现了错误连接。
4.7.2 Content - length 及截尾操作
每条http响应都应该有精确的Content-length首部,用以描述响应主体的尺寸。
客户端或代理收到一跳随连接关闭而结束的http响应,且实际传输的实体长度与Content-length并不匹配,则接收端应该质疑长度的正确性。
其中中间代理不应该纠正Content-length。保证透明性
4.7.3 连接关闭容限,重试以及幂等
非错误情况下,连接也可以任意时刻关闭。http应用程序要做好正确处理非预期关闭的准备。
比如客户端执行事务,传输连接关闭了。那么队列里会出现大量未处理的请求。需要重新调度。
最明显的副作用是:
在线订书,post 一张订单,突然关闭连接,客户端无法确定服务端实际激活了多少事务,get之类的还好,像这种post,可能会出现重复执行多次的情况,会有多次下单的危险。
一个事务,不管执行一次还是多次,结果是相同的,这个事务就叫做幂等的
4.7.4 正常关闭连接
完全关闭与半关闭
两者都调close 则 全关闭
用shutdown单独关闭输入或输出信道,成为半关闭-
tcp关闭及重置错误
总之关闭连接的输出信道是安全的。玩意还有数据进来呢?
如果另一端向你已经关闭的输入信道发送数据,操作系统就会向另一端机器回送一跳tcp "连接被对端重置"的报文
大部分操作系统都会将这种情况作为很严重的错误处理,删除对端还未读取的所有缓存数据(存于客户端/服务器)。对管道化连接是致命的。
比如, 你在一条持久连接发送了10条管道式请求,响应页收到了,正在操作系统中缓存(应用程序没有取走)。现在假设你发送了第11条请求,但服务器认为你使用这条连接的时间已经够长,关闭了额,那么你的第11条请求会被发送到一条已经关闭的连接,并会向你会送一条重置信息。这个重置信息会清空你的输入缓冲区
当你试图读数据的时候,会得到一个连接重置的错误,数据已经丢失,景观其中大部分已经成功抵达过你的机器了。
- 正常关闭
- 先关闭输出信道
- 两端都告诉对方他们不再发送任何数据
- 周期性检查输入信道的状态(查找数据,或流的末尾)
一定时间内对端没有关闭输入信道,则强制关闭,节省资源