【读】这一次,让我们再深入一点 - HTTP的链接管理

这是网络系列的第七篇文章,接下来会有更多精彩内容.敬请期待! 让我们一起乘风破浪!

前言

在上篇(这一次,让我们再深入一点 - HTTP报文)中我们了解到了HTTP的报文格式,也就是客户端服务器对话的约定.那么该篇将介绍客户端服务器如何将对话内容发送到对方的.通过本篇,可以了解到如下内容:

  • HTTP是使用TCP连接的
  • TCP连接的延迟、瓶颈以及存在的障碍
  • HTTP的优化,包括并行连接、持久连接、管道化连接

TCP连接

其实TCP连接在介绍TCP协议时已经说明了. 这里在重复说下连接建立和释放的过程, 加深下印象. HTTP连接实际是TCP连接和使用连接的规则组成。TCP的可靠服务是HTTP报文完整有序到达对方的基础。对于传输层的TCP来说,其依靠下层(网络层)的IP协议进行数据的发送,以IP数据报的形式。也就是说,在HTTP想要发送一条报文时,会将报文数据通过打开的TCP连接按序传输;TCP在接收的数据后,将其分成多个数据块,并将其封装在IP数据报中进行传输。对于HTTPS(相关介绍看这里)来说,HTTP的报文会先交付给安全层进行包装,然后交付给TCP。HTTP和HTTPS使用的协议栈看上去类似下面这个图:

HTTP和HTTPS协议栈

一台计算机可能在同一时刻有几条TCP连接处于打开状态,为了彼此不干扰,TCP使用端口号来区分;而通过IP地址可以确定网络上的一台主机,所以一条连接可以由这些因素确定: <源IP地址、源端口号、目的IP地址、目的端口号> 那么这个TCP连接是如何建立的呢,当然是被人说烂了的三次握手:

三次握手建立连接模型

  • 服务器是处于监听状态的,以便及时发现客户端建立连接的需求。
  • 客户端TCP进程主动发出Flag段SYN=1,报文序列号seq=x的报文段(A),请求建立连接。状态变为SYN-SENT(同步已发送)。
  • 服务器收到对应报文段(A)后,会发出确认报文段(B)。该报文(B)的Flag段的SYN和ACK都是1,确认号ack=x+1(意为对A的确认),同时设定自己的初始序列号seq=y。状态由LISTEN(监听)变为SYN-RCVD(同步收到)。
  • 客户端收到服务器的确认后,还需向服务器发送确认。报文段(C)的Flag的ACK=1,确认号ack=y+1(意为对B的确认),序列号seq=x+1。状态变为ESTABLISHED(已建立连接)。服务器在收到报文段后状态也变为ESTABLISHED。
  • 客户端的最后确认是必要的,可以防止以失效的请求建立连接报文突然到达服务器而产生错误。

下面再来看看连接是如何释放的:

四次握手释放连接模型

  • 在连接建立完成,数据传输完毕后,通信的双发都是可以释放连接的。但通常情况,服务器都是被动的一端,客户端才知道,自己是不是真的没有数据要发送了。
  • 在数据传输过程中,客户端和服务器都处于已建立连接的状态。
  • 客户端TCP程序先发出连接释放报文(A),并停止发送数据。该报文的Flag位的FIN=1,seq=u(等于其上一个序号+1)。客户端进入FIN-WAIT-1状态,等待服务器确认。
  • 服务器接收到释放连接报文段后发出确认报文(B)Flag位ACK=1,确认号ack=u+1,序列号seq=v(等于其上一个序号+1)。服务器进入WAIT(关闭等待)状态,这条TCP连接处于半关闭状态,也就是客户端到服务器方向的通道被关闭。
  • 客户端在收到服务器的确认报文(B)后,进入FIN-WAIT-2状态,等待服务器发出连接关闭的报文。
  • 若服务器也没有其他数据需要发送,就会向客户端发出释放连接的报文(C),Flag端FIN=1,ACK=1,确认号ack=u+1(和报文B一样),序列号seq=w(服务器可能在发出报文B之后又发送了数据,w的值可能不是v+1)。服务器进入LAST-ACK状态,等待客户端的确认。
  • 客户端在收到服务器释放连接报文(C)后,发出确认报文(D)。Flag段ACK=1,确认号ack=w+1,序列号seq=u+1(报文A的seq+1)。客户端进入TIME-WAIT状态。现在TCP连接并没有释放掉,必须等待2倍MSL(最长报文段寿命Maximum Segment Lifetime,该时长是由时间等待计时器设置)后才能关闭。
  • 服务器收到报文D后,就可以关闭连接。
  • 为何要等待2MSL,客户端才能关闭连接
    • 保证客户端发出的报文D能够到达服务器。在报文D丢失的情况下,服务器未收到客户端的响应,所以会触发TCP的超时重传,而客户端可以在2MSL时间内收到重传的报文,并对之响应且重新启动2MSL计时器。最终,该连接可以正常关闭。
    • 防止失效的连接请求报文出现。可以保证在创建该TCP连接中发出的报文都在网络中消失。
  • 关于TCP的保活计时器:在客户端服务器之间的TCP连接建立后,客户端突然故障,服务器也就无法再收到来自该客户端的任何报文,为了使服务器不会白白等待客户端而使用的措施是保活计时器。服务器每次收到客户端的数据就会重置保活计时器,时间2小时。若2小时未收到客户端的任何数据,服务器发送探测报文,每隔75秒一次,若连续10个探测报文都没有客户端的响应,该连接就会被服务器关闭。

从TCP的特点看HTTP性能

由于HTTP依赖TCP,其性能是否优越,很大层度上取决于TCP的性能。回顾下HTTP事务过程:

HTTP事务流程概览

从上图可以看出一下几个点会影响HTTP的表现:

  • 根据URI确定服务器的地址和端口号。也就是DNS系统的性能。
  • TCP连接的建立。
  • 请求数据,处理数据过程
  • TCP连接的关闭。

接下来重点看下和TCP有关的,毕竟这部分是个大头。

  • TCP连接的握手时延;从上一部分可以了解到TCP在发送数据之前需要通过3次握手建立连接,即使HTTP需要传送一个很小的数据也要有这个过程。这种情况下,一个HTTP事务可能在建立连接上花费50%或更多时间。
  • TCP的延迟确认;TCP需要确定报文的送达,存在自己的确认机制。确认报文一般会在另一个报文中进行“捎带”;为了找到这个能够“捎带”的报文,TCP存在一个“延迟确认”算法,该算法也会影响其上层的HTTP。
  • TCP慢启动;为了防止大量数据短时间进入网络,造成网络堵塞,TCP在开始发送数据时会限制最大速度,随着时间推移,TCP检测到之前发送的数据都被确认后,会逐渐提高速度(当然也有可能减慢速度)。这个特性使得新建的连接不如传送过数据的连接速度快。
  • Nagle算法与TCP_NODELAY;由于TCP包结构包含标记和首部字段,若TCP发送了大量的包含少量数据的分组,网络的性能会下降。Nagle算法试图在发送分组之前将大量的数据绑定在一起,提高效率。在较小的HTTP报文可能无法填满一个分组,可能会因为等待那些永远无法达到的额外数据而产生时延。同时,Nagle算法会阻止数据的发送,直到有确认的分组到达,但确认分组自身会被延迟确认算法延迟。HTTP应用程序可以设置参数TCP_NODELAY,禁用Nagle算法,这样的话,一定要确保向TCP写入大数据块。
  • TIME-WAIT累积和端口耗尽;在TCP释放连接时,客户端会在最后一个确认报文发出后等待2MSL时长在断开连接,若这种状态的连接太多,会造成端口耗尽。

HTTP的处理办法

  • Connection首部;不仅可以用来控制持久连接(Connection:Keep-Alive/Close),而且可以说明不需要进行传输的头部字段(Connection:首部名称)。
  • 串行事务,一个事务是否能开始,取决于上一个事务是否完成。比如一个包含3个图片的Web页面,浏览器需要发起4个事务来完成数据请求。串行事务如下: 串行事务示例

  • 并行连接,同时打开多个连接,执行多个事务。但是,多事务会对带宽资源进行抢夺,导致每个资源都会以较小的速度加载;另外,大量的连接会消耗自身的硬件资源,引起性能问题。所以,连接数量要有较好的控制才行。

  • 持久连接,在事务处理结束后任然保持打开状态的TCP连接称为持久连接。这样可以避开连接的重复建立以及TCP慢启动的特点。
    • HTTP/1.0 keep-alive;客户端可以通过包含Connection:Keep-Alive首部请求将一条连接保持在打开状态,若服务器愿意为下一请求将连接保持在打开状态,就在响应中包含相同的首部;若响应中没有Connection:Keep-Alive首部,客户端就认为服务器不支持keep-alive。下面是调节keep-alive行为的选项:
      • timeout,在响应首部发出。说明了服务器希望将连接保持在活跃状态的时间。参考值。
      • max,在响应首部发出。说明了服务器希望为多少个事务保持连接的活跃状态。参考值。

        1
        2
        3
         Connection: Keep-Alive
         Keep-Alive: max=5, timeout=120
         服务器希望为另外5个事务或2分钟之内保持连接的活跃状态。
        

        在HTTP/1.0,keep-alive不是默认使用的。

    • HTTP/1.1 persistent;默认打开持久连接,可以在请求首部中包含Connection:close来关闭。
  • 管道化连接,在持久化连接的前提上建立。将多个请求放入队列中,当第一个请求到达服务器后,后续的请求也就可以发送了。注意点:
    • 若HTTP客户端无法确认连接是持久的,就不应该使用管道。
    • 服务器要按序响应。
    • 客户端必须做好连接在任意时刻关闭的准备,未发出的请求,要能重新发出。
    • 客户端不应该使用管道化方式发出带有副作用的请求(如POST)。

    下面是串行连接、持久连接、管道连接在完成4个事务的区别:

结语

该篇我们主要了解了TCP是如何影响HTTP的, 以及HTTP所作出的优化.希望大家能理解相关概念.