世界上几乎所有的http连接都是由tcp/ip协议承载的,所以要了解http连接的管理机制,就一定要从了解tcp连接的原理与机制开始。
- tcp连接
tcp为http提供了一条可靠的比特传输管道。从tcp连接一端填入额字节会从另一端以原有的顺序,正确的传输出来,tcp为http的传输解决了可靠性的问题,使http进行传输时不用顾及底层的传输方式。
tcp流是分段的,由ip分组传送。tcp的数据通过名为ip分组的小数据块来发送。当http要传送一个报文时,会以流的形式将报文数据的内容通过一条打开的tcp连接按顺序传输,tcp收到之后,会将数据流分割成被称作段的小数据块,并将段封装在ip分组里,通过网络传输,这些步骤对http来说是透明的。
每个tcp段都是由ip分组承载的,从一个ip地址发送到另一个地址,每个ip分组都包括:
- 一个ip分组首部(通常为20字节)
- 一个tcp段首部(通常为20字节)
- 一个tcp数据块(0个或者多个字节)
ip的首部包括源和目的ip地址,长度和其他一些标记,tcp段首部包括了tcp端口号,tcp控制标记,以及用于数据排序和完整性检查的一些数字值。
tcp是通过端口号来保持所有这些连接正确运行的。ip地址可以将你连接到正确的计算机上,而端口号能将你连接到正确的应用程序上。
tcp是通过四个值来识别的:
<源ip地址、源端口号、目的ip地址、目的端口号>
这四个值唯一的定义了一条连接,两条不同的tcp连接不能有四个完全相同的值,但可以有部分相同。
操作系统都会提供一些操纵其tcp连接的工具。程序猿可以使用api对其进行使用。
进行通过套接字实现http事务连接的步骤:
- 服务器创建新的套接字(socket)
- 服务器将套接字绑定到端口80上(http默认为80,bind方法)
- 服务器对端口进行监听(listen)
- 服务器等待接收连接(accept)
- 客户端获取ip和端口号
- 客户端创建新的套接字(socket)
- 客户端连接到目标服务器目标端口上(connect)
- 服务器通知应用程序有连接
- 服务器开始读取请求(read)
- 客户端连接成功
- 客户端发送http请求(write)
- 客户端等待http响应(read)
- 服务器处理http请求报文
- 服务器回送http响应(write)
- 客户端处理http响应
- 服务器关闭连接
- 客户端关闭连接
- 关于tcp性能的种种
http紧挨着tcp,所以http的性能很大程度上取决于tcp通道的性能。
与建立tcp连接,以及传输相比,事务处理的时间可能是非常少的,所以除了服务器出现问题之外,很大程度上http延迟是由tcp网络延时造成的。
可能存在以下几种原因:
- 客户端需要根据uri确定服务器的ip和端口号,如果dns服务器最近没有对该uri进行解析(木有缓存),那么从新解析一个地址可能要花数十秒时间
- 下一步客户端会向服务器发送一条tcp请求,等待服务器回送一个请求接受消息。每一次新建一个tcp连接时,都会有这个步骤,所以会有延时,这个延时一般在两秒钟以下,如果多个http事务进行请求的话,时间有可能会增加。
- 连接建立起来后,就可以通过新建立的tcp管道发送http请求。数据到达时,服务器从中读取请求报文,并对报文进行处理,这些也都需要时间
- 最后服务器回送响应时也需要时间
tcp网络的延时大小取决于硬件的速度、网络和服务器的负载,请求报文与响应报文的尺寸,客户端与服务器之间的距离,tcp协议的复杂性也会产生影响。
最常见的tcp相关延时:
- tcp连接建立握手
建立一个新的tcp连接时,在发送任何数据之前,都会交换一些列ip分组,对连接的有关参数进行沟通。如果每次连接只用来传输少量的数据,那么就会降低http的性能。
tcp连接三次握手步骤:
- 客户端向服务器发送一个小的tcp分组(通常40~60字节)。这个分组设置一个特殊的syn标记,说明这是一个连接请求
- 如果服务器接受了请求,则会对参数进行计算,并向客户端回送一个tcp分组,这个分组syn和ack都会被置位,表示请求被接受。
- 最后客户端向服务器回送一条确认信息,通知他连接已经建立,现在的tcp栈都允许客户端在这个确认分组中发送数据。
通常http事务都不会交换太多数据,所以三次握手所占的延时比例是主要的部分,tcp连接的ack分组通常足够大,在客户端向服务器回送确认信息时
可以将http请求报文同时放在其中,一起发送。
所以,小的http事务可能会在tcp连接建立上浪费百分之五十的时间,甚至更多,可以使用持久连接减少这种浪费。
- tcp慢启动
tcp数据传输性能还取决于tcp连接的使用期。tcp连接会随着时间自我调整。起初会限制最大速度,如果数据传输成功,那么就会随着时间提高传输速度,这就被称为tcp慢启动,目的是为了防止互联网的突然过载。
慢启动限制了tcp端点在任意时刻的可以传输数据的分组数,例如,每成功接收一个分组,发送端就有了发送另两个分组的权限,如果要发送大量数据,是不能一次性将所有分组都发出去的,必须发送一个分组,等待确认,然后可以发送两个分组,每个都确认,然后可以发送四个,以此类推,这个方式叫做打开拥塞窗口。所以,新连接会比已经使用过一段时间的连接慢一些,为了保存这些使用过一段时间的连接,可以使用持久连接的方式。
- 延迟确认
由于在网络超负荷的情况下,路由器可以随意丢弃分组,所以tcp需要确保传输的正确性。每个tcp段都有个序列号和数据完整性校验和。每个段的接受者接收到完整的段时,都会向发送者回送一个确认分组,告知发送端已经发送成功,如果发送端没有在一定时间内接收到确认分组,那么就会认为发送失败,会从新发送该段数据。
由于确认报文都很小,所以tcp允许在发往同一方向的输出数据分组中捎带它,这可以更有效利用网络带宽。为了提高这种捎带的几率,很多tcp栈都实现了一种“延迟确认”方法,他会在一个特定的时间内(通常100~200毫秒)内将输出确认存放到缓存中,等待可以捎带他的数据,如果在时间诶没有数据,那么就独立传输。所以http具有双峰特征的请求应答行为降低了捎带信息的可能性,所以捎带的确认信息往往会等待一段时间后,独立发送,这无疑浪费了很多时间,当http请求是这种情况时候,可以考虑关闭延迟确认。
- 数据聚集的nagle算法
应用程序可以通过数据流接口将任意尺寸的数据放入tcp栈中,即使一次只有一字节也可以,但是每个tcp段都至少装载了40个字节的标记和首部,为了发送1字节浪费了40字节,这显然会造成网络性能的下降。
nagle算法为了提高效率,试图将大量tcp数据捆绑在一起。该算法鼓励发送全尺寸(lan最大尺寸是1500字节,因特网上市几百字节)的段,只有所有其他分组被确认之后,判断等不到继续打包的数据,就发送非全尺寸的段,如果仍有其他分组在传送过程中,就将那部分数据缓存起来,等待有其他数据可以捆绑起来一起发出。
这种算法虽然会提高http效率,但是也会引发性能问题,首先有的小http报文可能永远无法填满一个分组,可能会因为等待那些永远都不会到来的捆绑数据而产生延迟。而且该算法与延迟确认之间的交互有问题,会阻止数据的发送,直到有确认分组到达为止,这也会造成延时。
- time_wait时延和端口耗尽
当某个tcp端点关闭连接时,会在内存中保存一个控制块,记录最近关闭连接的ip和端口号,这个信息只会存在一小段时间,通常是所估计的最大分段使用期的两倍(称为2msl,通常为2分钟),以确保这段时间内不会创建具有相同地址的和端口号的新连接。
由于现在高速路由的使用,使得重复分组几乎不肯能在几分钟内出现。从中可以出,这可能造成端口的不足,如果进行性能基准测试时,只有一台或几台用来产生流量的计算机,由于以上所说的机制,
- http连接处理
http允许客户端和最终的服务器之间存在一些实体(例如代理,高速缓存等),从客户端开始经过这些设备,最终传送到服务器。
在某些情况下,两个相邻的http应用程序会为它们共享的连接应用一组选项。connection首部中有一个又逗号分隔的连接标签列表,这些标签为连接指定了一些不会发送传播到其他连接中去的选项。
connection首部可以承载3种不同类型的标签:
- http首部字段名,列出了只与此连接有关的首部
- 任意标签值,用于描述此连接的非标准选项
- close,说明操作完成后需要关闭这持久连接
应用程序收到带有connection首部的报文之后,应该对列表解析,并删除报文中所有的connection首部中出现过的首部,它主要用于有代理的网络环境。
- 并行连接
有一个包含了三个图片的页面,那么浏览器就需要四个http事务来显示这个页面,每个事务都需要(串行的)建立一条新连接,那么每次建立连接的时间与慢启动的时间都会叠加起来,显示的速度就会很慢。串行的两一个缺点是有些浏览器在对象加载完毕之前无法确定对象的尺寸,所以在加载足够多的对象之前没法在屏幕上显示任何内容,而这时用户面对的是一片空白,当然会十分烦躁。为了解决这些问题,并行连接就出现了。
并行连接——客户端可以同时打开多条连接,并行的执行多个http事务,也就是说上面的例子中,html文件和三个图片文件可以同时进行传输,每个事务都有自己的tcp连接。这样延时可以重叠起来,而且如果哪条连接没有完全利用带宽的话,其他的连接也可以利用。
并行连接并不一定更快,如果带宽不足,多个tcp连接对资源进行争抢,这样带来的性能提升就很小甚至没有了。而且打开大量连接会消耗很多内存资源,从而引发自身性能问题,实际上,浏览器确实使用了并行连接,但是它们会将总数限制为一个较小的值(通常为4个),服务器可以随便关闭来自特定客户端的超量连接。
并行连接主要的目的是让用户感觉快一些,不能让用户长时间在空白的页面等待。