HTTP的TCP链接管理

Socket是大部分应用层协议的基础,常见的Socket主要有两种类型:TCP和UDP,HTTP协议默认使用的是TCP类型的Socket,80端口。 为何呢?由于TCP链接可靠,用其它协议也是容许的[1],然而,也正是由于一开始使用的是TCP链接,才使得后面的优化方案有可能被实现。html

1. 最开始HTTP链接

一个服务器须要服务不少请求,这些请求若是链接之后不断开,就会有大量处于等待状态的链接须要维护,而进程、内存资源、带宽资源、文件描述符数量有上限,针对客户端与服务器端交换数据间歇性较大的特色,因此,最开始——HTTP1.0版本——将协议设计为请求时建链接、请求完释放链接,以尽快将资源释放出来服务其余客户端。[2]前端

形成的结果就是:早期的HTTP服务器实现方式是每个HTTP的请求/响应过程都建立一次新的TCP链接,响应完就断开,下一个请求过来再建立一次链接。 这在当时是合理的解决方案:简单粗暴、不用管理链接、性能也比较好。node

但接下来,HTTP的发展迅猛,网页中加入了愈来愈多的图片、表单,单个用户短期内须要屡次请求服务器上的资源,无链接已经不能知足日益增加的需求,哪怕牺牲点服务器资源来维护这个TCP链接,也比每次请求一个资源都创建一个新请求的开销要小,这个开销包括网络带宽、时间、建立、关闭链接……nginx

2. 一条TCP链接要完成屡次HTTP请求/响应过程

因此,你们试着在HTTP1.0的基础上,扩展了一个新的Header:Keep-Alive: timeout=5, max=100,发现效果不错。在HTTP1.1中,干脆直接把默认状态设置成开启,除非显式声明Connection: Close的时候才不保持开启状态。git

这也就是你们常常说的长链接(Persistent Connections)了,对于客户端与服务器须要频繁传输数据的场景,长链接比较合适。github

长链接开启之后有不少用法,好比HTTP Pipelining。web

3. HTTP Pipelining技术

浏览器在解析完html之后,发现有三个图片须要下载,这个时候怎么用这一条随时能够双向通讯的长链接来完成三个资源的获取呢? 方法一:发起第一个图片的请求,而后等待服务器返回结果,再发起第二个图片的请求……直到都得到。 方法二:发起第一个图片的请求,而后接着解析,发现须要第二个图片,就接着发第二个图片的请求(无论第一个请求有没有返回),而后等待服务器返回结果,而服务器保证:三个响应按照我三个请求的顺序返回就能够了。 算法

优势:完成时间明显提早,这是RFC规范的一部分 缺点:可是你们都不遵照规范 [3] 你们费了九牛二虎之力达成了一致,制订出了标准,最后你们又不用了!为啥?

  1. 使用的是FIFO,容易致使Head-of-line blocking,就是:即使第二三四个请求都已经准备好了,若是第一个请求没有完成,也要等到第一个请求准备好再发送,所以实际优化效果并不明显
  2. 重要缘由是:服务器若是开启,存在DDos风险——憋一堆拼凑起来的大请求一块儿发出,服务器就扛不住了
  3. 只能是幂等的HTTP方法——多是由于须要重试,若是POST重试屡次,形成重复提交问题
  4. 还有代理服务器不稳定等等 但依然有人在为此做出努力,并给出了一些解决方案但依然不流行

4. Pipelining以外的思路

一条TCP链接虽然能够复用屡次了,但对于须要并行加载的环境,Pipelining因为各类限制和缺陷也不堪重用,那索性,你们都不按规矩办事,你规范既然说:chrome

Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. 只是个SHOULD NOT,你没说MUST NOT呀,因此,我多建立几条试试,你们通过多个版本的调整,你们以为对于同一个域,这个值设置在4-6个并发链接还比较靠谱。express

固然,这个默认值用户也是能够修改的 再后来的事情,就是应用程序开发者利用这一特性作了多域名、CDN部署等优化,你们就都比较熟悉了。

5. 如何实时得到服务器上的最新消息?

5.1 轮询(polling)

轮询的问题:在没有数据更新的时候,请求无心义,关键点在于轮询时间的把握——须要综合考虑用户数量、实时行要求和服务器性能 毫无疑问,这是一个假的实时方案

5.2 comet

客户端发起一个链接,啥时候有更新啥时候返回结果,客户端等返回结果之后,立马创建一个新的链接等待返回新数据。好聪明的方案!

5.3 HTTP的chunked技术 [4]

服务器准备好一部份内容,就输出一部份内容,浏览器接收到一部份内容,就响应一部份内容 实现方法:

  1. 语言实现: Java、PHP: flush() Node原生: 只要没执行end,write便可输出,无需flush方法——若是真的没有输出的话,强制调用一下response.flushHeaders()应该也是能够的 Express:通常来讲,框架提供了更好的封装而不具有相对低层次操做的借口,好比express的render,一次性输出了全部内容,因此貌似没有方法能够在express中分部分输出——我还没具体看源码
  2. XHR的支持——Firefox和Safari的支持更好,直接就支持,Chrome须要设置一个header:X-Content-Type-Options: nosniff我是在这个问题中获得解决方案的,另外,在curl中,若是内容结尾不加换行符号,输出结果不会被curl立马输出。
  3. 注意:若是存在Nginx等负载均衡设备,须要在这些服务的配置中关掉缓存逻辑或者在Header中声明不备缓存X-Accel-Buffering: no。若是还有问题,那有多是中间某个环节没有开启TCP的TCP_NODELAY参数。 Wireshare监听到的分段请求:

分段的细节:

In the following example, three chunks of length 4, 5 and 14 are shown. The chunk size is transferred as a hexadecimal number followed by rn as a line separator, followed by a chunk of data of the given size.

4\r\n
  Wiki\r\n
  5\r\n
  pedia\r\n
  E\r\n
  in\r\n
  \r\n
  chunks.\r\n
  0\r\n
  \r\n
  解析结果:
  Wikipedia in
  chunks.
复制代码

扩展:TCP默认开启的Nagle算法

在单一TCP链路上,短期内须要传输多个短片断的时候,是按照下面这张图的模式来传输:

关闭了Nagle算法的TCP链接
能够看到,每个小的TCP段都源源不断地在网络上传输,这就带来了一个问题:接收端若是一直没有收到TCP段,怎么办? 解决方案是:在接收到第一个TCP段的确认信号以前,发送端先暂存后续须要发送的包,等收到第一个TCP段的确认信号之后,发送缓存中的暂存中的片断,这样,就大大减少了网络上传输大量无用包的风险。

开启了Nagle算法的TCP链接
更多Nagle算法的介绍能够查看 www.potaroo.net/ispcol/2004…里面的Interactive TCP一节

更有意思的是Nagle算法和延迟ACK两个优化条件相遇的时候,会形成明显的延迟

思考:Bigpipe是咋实现的?

原理仍是chunked,在此之上封装了一些pagelet等概念,详细介绍看这里 根本上说,这是一种对HTML文件进行分段发送的实际应用。 那对JSON是否是能够用相似技术来发送呢?固然有:eBay也出了一个jsonpipe方案,主要解决了一个json在发送的时候,若是是一个标准的json,不能被直接parse的问题。 { "id": 12345, "title": "Bruce Wayne", "price": "$199.99" } \n\n { "id": 67890, "title": "Bane", "price": "$299.99" }

区分:chunked与206请求不同

若是在DevTools中查看请求,会发现mp3文件的状态码多数时候是206,这个与分段传输有什么区别? 206的状况是:客户端和服务器要一部分数据,好比:就要一个mp3的前3MB 而后等用户播放这个音乐了,在哪一段时间,我再加载那一部分的数据,用户点到了75%的进度,我再从请求75%的位置处请求一小段。这样,就作到了分端下载。适用场景是文件下载、音乐或视频文件的在线播放等。 迅雷等多线程下载工具应用的主要就是这个特性——同时从一个文件的不一样位置处请求,所有完成了,再拼成一个完整的文件。 chunked,你能够简单理解为,chunked是直播流输出,而206是点播啥看啥。

5.4 SSE

Server-Sent Events,从技术上,没有啥新东西,从标准化和API的设计层面来看,算是个进步吧。

  1. 传输内容是文本类型(text/event-stream, charset=utf8)
  2. 浏览器单向接受服务器发送
  3. 能够在服务器端和前端自定义事件(默认是message) 须要注意的是,这个不属于传输层面的创新,仅仅是应用层面的新协定,因此,不在RFC规范文档里面,而是由W3C来制订规范这里有详细描述

5.5 WebSocket简介

基于HTTP,经过升级,完成双向链接——使用的仍是以前创建的TCP通道,握手完成,直接基于TCP通讯。 tools.ietf.org/html/rfc645… 可以双向传输二进制流。 可以作的事情要多不少了,在此之上能够本身开发协议,也能够移植其它协议到这个链路上面来。 最经常使用的库就是socket.io

5.6 HTTP2.0

主要特性

  1. 新的二进制传输
  2. 消息在共享的连接上多路复用,随机发送和随机接收,如何判断?用id来区分。
  3. Header压缩:已经发送过的header在以后的消息中再也不发送
  4. 能够取消某个request
  5. ServerPush
  6. 浏览器支持是HTTPS only

新变化

HTTP2.0的多路复用和ServerPush使得原来针对HTTP协议自己的一些性能优化原则,都已经不那么重要了:

  1. 请求不须要合并了
  2. 多域名部署也不用了
  3. 图片inline写法不须要了 另外,HTTP2.0也不是全部场景都适合的,好比说:大文件下载的状况下。

思考:有了HTTP2.0了是否不须要chunked 、SSE、WebSocket了?

先看定位: chunked是HTTP1.1协议自己的一部分,是一种应用层数据传输方式。 SSE是基于chunked技术之上的一种新约定。 WebSocket能够理解为一种新形态的Socket,能作的事情和传统Socket的基础功能差很少。 而HTTP2.0,是对HTTP1.1的复用方式的改善,是服务器和浏览器的事情,并无对前端提供任何新的JS接口来操做这条链路和感知ServerPush等。 另一个问题是,升级了HTTP2.0之后,原有WebSocket的操做(握手、通讯的那一套,都是基于HTTP1.1来作的)就不能再使用了。基于HTTP2.0标准的WebSocket还处于比较初期的阶段。WebSocket over HTTP/2WebSocket2 over HTTP/2

参考资料


  1. (www.w3.org/Protocols/r…) ↩︎

  2. blog.csdn.net/bingjing123… ↩︎

  3. stackoverflow.com/questions/3… ↩︎

  4. en.wikipedia.org/wiki/Chunke… ↩︎

相关文章
相关标签/搜索