TCP
TCP连接
tcp/ip是全球计算机以及网络设备都在使用的一种常见的分组交换网络分层协议集,客户端可以打开一条tcp/ip连接,连接到可能运行在世界各地的服务器应用程序,一旦连接建立起来了,在客户端和服务器的计算机之间交换的报文就永远不会丢失,受损或者失序。
TCP的可靠数据管道
HTTP连接实际上就是TCP连接以及使用规则,TCP连接是互联网上的可靠连接。
TCP为HTTP提供了一条比较靠谱的比特传输管道,从TCP连接一段填入字节会从另一端以原有的顺序,正确的表达出来。
TCP会按序,无差错的承载HTTP数据。
TCP流是分段的,由IP分组传送
TCP的数据是通过名为IP分组或者IP数据报的小数据块来发送的,安全版本就是在HTTP和TCP之间插入了一个TLS或者SSL密码加密层
HTTP要传送一个报文的时候,会以流的形式讲报文数据的内容通过一条打开的tcp连接按照时序传输。tcp收到数据流之后,会将数据流砍成叫做段的小数据块,所有的这些工作都是由tcp ip软件来处理的。程序员不会参与。
每个tcp段都是由ip分组承载,从一个ip地址发送到另一个ip地址的,每个ip分组包括
- 一个ip分组首部
- 一个TCP段首部
- 一个TCP数据块
ip首部包含了源和目的ip地址,长度和其他一些标记。tcp段的首部包含了tcp端口号,tcp控制标记,用于将数据排序和完整性检查的一些数字值。
保持tcp连接持续不断的运行
在任意时间计算机都可以由几条tcp连接处于被打开状态,tcp是通过端口号来保持所有这些连接持续不断的运行
tcp连接是通过4个值来识别的<源ip地址, 源端口号, 目的ip地址, 目的端口号>
两条不同的tcp连接不能拥有4个完全相同的地址组件值。但是不同的部分组件可以有相同的值。
用tcp套接字编程
套接字api允许用户创建tcp的端口数据结构,将这些端点与远程服务器的tcp端点进行连接,对数据流进行读写。
tcp的api隐藏了所有网络协议的握手细节,以及tcp数据流和ip分组的分段和重装细节
一旦建立了连接,客户端就会发送HTTP请求,服务器就会读取请求,一旦服务器获取了整条报文请求,就会对请求进行处理,执行所请求的动作,并将数据写回客户端,客户端读取数据,并对响应的数据进行处理
对tcp性能的考虑
HTTP紧挨着TCP,位于上层,所以HTTP事务的性能很大程序取决于底层tcp通道的性能。
HTTP事务的时延
建立TCP连接,以及传输请求和响应报文的时间相比,事务时间可能是很短的。除非客户端和服务器超载,或者正在处理复杂的动态资源。狗则http就是由tcp网络时延构成的。
HTTP事务的时延由以下几种原因
- 客户端首先需要根据URI确定出web服务器的ip地址和端口号,如果没有对URI中的主机名进行访问,通过DNS解析系统会将URI的主机名转换为一个ip地址,这个可能需要花费数10s的时间。
- 接下来,客户端会向服务器发送一条tcp连接请求,并等待服务器回送一个请求接受应答,每条tcp连接都会有连接建立时延,最多只有一二秒钟,但是如果是数百个HTTP事务的话,会叠加上去。
- 一旦建立起来了连接,客户端就会通过新建立的tcp管道来发送http请求,数据到达的时候,web服务器就会从tcp连接中读取报文,对请求进行处理。
- web服务器回送回HTTP响应,这个也需要花费时间。
性能聚焦的区域
- tcp连接建立握手
- tcp慢启动拥塞控制
- 数据聚集的Nagle算法
- 用于捎带确认的tcp延迟确认算法
- TIME_WAIT时延和端口耗尽
tcp连接的握手时延
如果建立一条新的tcp连接,甚至是发送任意的数据之前,tcp软件会交换一系列的ip分组,对连接的有关参数会进行沟通,如果连接只是用来传输少量的数据,这些交换过程就会降低http的性能
建立tcp的几个步骤
- 建立tcp连接时,客户端需要向服务器发送一个小的tcp分组,这个分组中有设置syn标记,表明是一个连接请求
- 如果服务器接受了请求,就会对参数进行计算,并向客户端返回一个分组,这个分组中的syn和ack都被置位,说明连接已成功
- 客户端向服务器回送一条确认信息,通知它连接已成功建立
小的HTTP事务可能会在TCP建立上花费50%
延迟确认
每个tcp段都会有一个小的序列号和数据完整性校验和,每个段的接收者受到完整的段的时候,会像发送者会送小的确认分组,如果发送者没有再指定的窗口时间内受到确认的信息,发送者就会认为分组被损坏和损毁,重新发送数据。
延迟算法为了增强确认报文找到通向传输数据分组的可能性。延迟确认算法会在一个特定的窗口时间中将输出确认存放在缓存区中,以寻找能够捎带它的输出数据分组,如果在这个时间段内没有输出分组,就将确认信息放在单独的分组中去。如果没有输出分组,就会将确认信息放在单独的分组中传送。
HTTP具有双峰特征的请求——应答行为,降低了捎带信息的可能,延迟确认算法会引入相当大的时延。
tcp慢启动
tcp会随着时间提高传输的速度。用于防止因特网的突然过载和拥塞。
如果HTTP事务由大量的数据要发送,不能一次将所有的分组都发送出去,发送一个分组,等待确认,就可以发送两个分组,每个分组必须被确认。
Nagle算法和TCP_NODELAY
如果tcp发送了大量含有少量数据的分组,网络的性能就会严重的下降。 发送大量单字节分组的行为称为“发送端傻窗口综合症”。这种行为效率很低、违反社会道德,而且可能会影响其他的因特网流量
Nagle算法鼓励发送全尺寸的段,只有当所有其他分组都被确认之后,才会允许发送非全尺寸的分组。如果其他分组仍然在传输过程中,就将那部分数据缓存起来。只有当挂起分组被确认,或者缓存中积累了足够发送一个全尺寸分组的数据时,才会将缓存的数据发送出去
缺点: 1. 小的HTTP报文可能无法填满一个分组,可能会因为等待那些永远不会到来的额外数据而产生时延
TCP_NODELAY:http应用在自己的栈中设计参数,禁用这个算法,提高性能。但是要确保在tcp中写入大块的数据
TIME_WAIT累积和端口耗尽
当某个TCP端点关闭TCP连接时,会在内存中维护一个小的控制块,用来记录最近所关闭连接的IP地址和端口号,这类信息维持2MSL时间。
TIME_WAIT状态通常会持续2倍的最大报文段生存时间(2MSL),以确保在这段时间内不会创建具有相同地址和端口号的新连接。
当系统中存在大量的短连接时,每个连接在关闭后都会进入TIME_WAIT状态,导致TIME_WAIT状态的数量迅速增加。由于每个TCP连接都需要一个唯一的端口号,而端口资源是有限的,因此大量的TIME_WAIT状态可能会导致端口资源耗尽,从而影响新的连接的建立。
HTTP连接的处理
connection首部
http允许在客户端和最终的源服务器之间存在一串http中间实体,在某些情况下,http的connection首部字段中有一个由都好分割的连接标签列表,这些列表为连接指定了一些不会传播到其他连接中的选项。
- HTTP首部字段名
- 任意标签值,用于描述此连接的非标准的选项
- close: 表示关闭这个持久连接。
如果连接中包含了一个http首部字段的名称,那么这个首部字段中就包含了一些和连接有关的信息。
http引用程序受到一条带有connection首部的报文的时候,接收端会解析发送端请求的所有选项。然后汇集报文转发给下一条的地址之前,删除connection首部和connnection中列出的所有首部。
串行事务处理时延
并行连接
并行连接可能会提高页面的加载速度
包含嵌入对象的组合页面能够克服单挑连接的空载时间和带宽限制,加载速度也会有所提高。如果单挑链接没有充分利用客户端的带宽,可以利用带宽分配来装载其他对象。
并行连接不一定更快
如果并行加载多个对象,每个对象都会竞争有限的带宽,每个对象都会以慢的速度比例加载。
持久连接
站点局部性: Web客户端倾向于对同一站点(或服务器)进行连续的、多次的访问
HTTP/1.1允许设备在事务处理结束后将tcp连接保持在打开状态,以便为未来的http请求重用现存的连接。这种tcp连接叫做:持久连接。非持久连接会在每个事务结束之后关闭。持久连接会在不同事务之间保持打开状态。
持久以及并行连接
每个事务打开或者关闭一条新的连接,会消耗时间和带宽。由于tcp的慢启动,每个新的连接的性能会有所降低。可以打开的并行连接数量是有限的。
Keep-Alive操作
HTTP/1.0 keep-alive连接的客户端可以包含connection: Keep-Alive首部请求保持一条连接在打开状态。
Keep-Alive选项
timeout: 估计了服务器希望将连接保持此连接的活跃时间
max:估计了服务器希望多少个事务保持此连接的活跃状态。
Keep-Alive:首部还可以支持任意未经处理的属性。
Keep-Alive首部是可选的,只有在提供connection: Keep-Alive
之后才可以使用。
Keep-Alive连接的限制和规则
代理和网关必须执行Connection首部的规则,代理和网关在报文发出去或者是将其高速缓存之前,删除connection首部中命名的所有首部字段以及connection首部自身。
Keep-Alive和哑代理
客户端正在与一台web服务器对话,客户端可以发送要给connection: Keep-Alive首部来告知服务器保持连接的活跃状态,如果支持keep-alive,就返回要给Connection:Keep-Alive首部
- Connection首部和盲中继
- 代理和逐跳首部
插入Proxy-Connection
浏览器会向代理发送非标准的Proxy-Connection拓展首部,如果代理是盲中继,会将无意义的Proxy-Connection首部转发给web服务器,服务器会忽略此首部,不会带来任何问题,如果代理是比较聪明的代理,就会用一个Connection首部取代无意义的Proxy-Connection首部,然后将其发送给服务器。
但是现在网络总不可见的代理的情况变得越来越多,这些代理例如防火墙,拦截缓存。或者是反向代理服务器的加速带,这些设备对于浏览器是不可见的,所以浏览器不会将他们发送proxy-Connection首部
HTTP/1.1 持久连接
HTTP/1.1逐渐停止了对于Keep-alive连接的支持,用一种名为持久连接的改进型取代了他,持久连接的目的和keep-alive连接的目的是一样的,但是工作机制更加优秀。
HTTP/1.1持久连接在默认情况下是激活的,除非特别指明,否则假定所有的丽娜姐都是持久的,在事务处理结束之后将其关闭,http/1.1应用程序必须在报文中显示添加一个Connection: close首部,这是和之前的HTTP协议版本很重要的区别,在以前的版本中,keep-alive连接要么是可选的,要么就是不支持
http/1.1客户端假定在收到响应的时候,除非响应中包含了connection:close首部,不然http/1.1连接就会维持在打开状态,但是客户端和服务器任然可以随时关闭空闲的连接,不发送connection:close并不意味着服务器将永远保持在连接打开状态。
持久连接的限制和规则
在持久连接的使用中有如下的限制
- 发送了connection:close请求首部之后,客户端无法在那条连接上发送更多的请求
- 如果客户端不想再连接上发送其他请求,就应该再最后一条请求中发送connection:close
- 只有当连接上所有的报文都有正确的,自定义的报文长度的时候,也就是说实体主体部分的长度和响应的connection-length一直。或者是使用分块传输编码方式编码-连接才能够持久保持
- http/1.1的代理必须能够分别管理与客户端和服务器的持久连接-每个持久连接都适用于一跳传输。
- http/1.1代理服务器不应该与HTTP/1.0客户端建立持久连接,除非他们了解客户端的处理能力
- 尽管服务器不应该试图再传输保卫呢的过程中关闭连接,而且关闭连接之前至少应该响应一条请求,但是不管connection首部取了什么值,http/1.1设备都可以在任意时刻关闭连接。
- http/1.1应用程序必须能够用异步的关闭中恢复出来,只要不存在可能累计起来的副作用,客户端都应该重试这个请求。
- 除非重复发起的请求会产生副作用,否则在客户端收到整条响应之前关闭连接,客户端就必须重新发起请求
- 一个用户客户端对任何服务或者代理最多只能维护两条持久连接,防止服务器过载,代理需要更多到服务器的连接来支持并发用户的同行,所以如果有n个用户试图访问服务器的话,代理需要维持an跳到任意服务器或父代理的连接。
管道连接
http/1.1允许在持久连接上可选的使用请求管道,这个是相对于keep-alive连接的性能优化,在响应到达之前,可以将多条请求放入队列,当第一条请求通过网络发送到服务器的时候,第二条第三条可以开始发送了,在高时延的网络条件下,这样可以做到降低网络的环回时间,提高性能
对管到化连接的限制
- 如果http客户端无法确认连接是持久的,就不应该使用管道
- 必须按照与请求相同的顺序回送HTTP响应,HTTP报文中没有序列号标签,因此如果收到的响应失序了,就没有半分和请求匹配起来了
- HTTP客户端必须做好连接会在任意时刻关闭的设备,还要准备好重发所有未完成的管道化请求,如果客户端打开了一条持久连接,并立即发送了10跳请求,服务器可能在只处理了5跳请求后关闭连接,剩下的五跳请求会失败,客户端必须能够应对这些过早关闭的情况,并重新发送这些请求
- HTTP客户端不应该用管道化的方式发送回产生副作用的请求,出错的时候,管道化会阻塞客户端了解服务器执行的是一系列管道化的请求中的拿一些,由于无法重试post这样的非幂等请求,所以出错的时候,就存在某些方法永远不会执行的风险。
关闭连接的奥秘
HTTP应用程序可能在金国任意一段时间后,关闭持久连接,但是服务器永远都无法去饿顶在关闭空闲连接的那一刻,在线路的那一头有没有数据要发送,如果出现这种情况,客户端就会在写入半截请求报文的时候发现出现了连接错误
Content-Lenght以及截尾操作
客户端或者代理收到一条随链接关闭而结束的HTTP响应而且实际传输的实体长度与content-length不匹配,接收端就应该被之一长度的正确性
如果接收端是个缓存代理,接收端就不应该缓存这条响应,已降低今后潜在的错误报文混合起来的可能,代理因该将有问题的报文原封不动的转发出去,而不应该试图去矫正content-length,维护语义的透明性
连接关闭容限,重试以及幂等性
就算是在非错误的情况下,连接也可以在任意时刻关闭,http应用程序要做好正确处理非预期关闭的准备,如果在客户端执行事务的过程中,传输连接关闭了,那么除非事务处理会带来一些副作用,否则客户端就应该重新打开连接,并重试一次。
如果一个事务,不管执行一次或者多次,得到的结果都是相同的,那么这个事务就是幂等的,客户端不应该以管道化的方式发送非幂等请求,否则传输连接的过早终止就会造成不确定的后果,要发送一些费米等请求,就需要等待来自前一条请求的响应状态
正常关闭连接
完全关闭和半关闭
应用程序可以关闭tcp输入和输出信道中的任意一个,或者将两者都关闭。套接字调用close接口会将tcp连接的输入和输出信道都关闭,这称为完全关闭,调用shutdown接口单独关闭输入或者输出信道,称为半关闭
TCP关闭以及重置错误
简单的http应用程序可以只使用完全关闭,但是当应用程序开始和很多其他类型的http客户端,服务器以及代理进行对话并且使用管道话持久连接的时候,使用半关闭来方式对等实体收到非预期的写入错误就变得很重要,关闭连接的输出信道总是很安全的,连接另一端的对等实体会在起缓冲区中读出所有的数据之后收到一条通知,说明流结束了,这样对面就会知道即将关闭。
正常关闭
HTTP规范建议,当客户端或者服务器突然要关闭一条连接的时候,应该正常的关闭传输连接,但是并没有说明要如何去做。
总之,实现正常的关闭的应用程序首先关闭他们的输出信道,然后邓艾另一端的对等实体关闭它的输出信道,当两端都高速对方不会再发出任何数据之后,连接就会被完全关闭,而不会有重置的风险
想要正常关闭连接的应用程序应该先半关闭其输出信道,然后周期性的检查输入信道的状态,如果再一定的时间区间内对端没有关闭输入信道,应用程序可以强制关闭连接,以节省资源。