从Ajax到websocket

一开始的时候,web页面和服务器之间就是简单的“请求”和“响应”的关系。

浏览器通过http协议发送请求到服务器,服务器把数据返回给浏览器。一般情况浏览器并不知道服务器数据是否更新,服务器也不会主动向浏览器推送数据。一般通过再次请求(比如刷新页面),重新发送http请求。
这样就存在很多弊端。比如实现两个web之间的实时通讯就很难实现,A和B聊天,A发送消息到服务器,B并不会直接收到该消息,只有当B发送http请求服务器查看是否有新消息的时候才会得到A的消息。
javaScript是单线程的,只能刷新整个web页面实现刷新数据,Ajax(异步 js xml)的出现,使得可以异步发送http请求。但是问题的本质并没有改变,仍然做不到服务器“该什么时候给浏览器发送数据”这类问题。
针对这个问题,在websocket问世之前,有一些程序员提出一些解决办法。

1.频繁轮询
使用Ajax每隔一段时间(比如1s)发送http请求,查看服务器数据是否更新,while无限循环该请求。
如果服务器数据未更新,则返回空(空的json/xml或者Content-Length为0)
如果数据更新了,则直接返回最新的数据。
该方案的缺点很明显,即使在服务器数据没有更新的时候,Ajax依然在做很多无用的http请求,浪费了大量网络资源,同时增加了服务器的压力。
2.长轮询
HTTP1.1和HTTP1.0相比较而言,最大的区别就是增加了持久连接支持(貌似最新的HTTP1.1 可以显示的指定 keep-alive),但还是无状态的。
长轮询每次发送http请求的时候,假如服务器数据并没有更新,则不会直接响应,直到数据更新了,才回反馈到浏览器。这样一来,看似修复了频繁轮询的短板,降低了资源的开销。而实际上带来了其他的问题,http和TCP协议都规定了连接超时的规定(这样的规定大部分情况都是为了提高传输效率,快速释放无用的连接),如果长时间得不到服务器的响应,http会自动断开,这时候又得重连。另一个问题是,HTTP/1.1规范中存在强制的连接限制,浏览器最多只允许同时创建2个到相同主机的连接,假如在长轮询的过程中,还需要使用Ajax获取服务器其他资源,由于长轮询长时间霸占着一条连接,这样就影响到了其他数据的操作效率
HTTP 协议中所谓的 keep-alive connection 是指在一次 TCP 连接中完成多个 HTTP请求,但是对每个请求仍然要单独发 header;
所谓的 polling 是指从客户端(一般就是浏览器)不断主动的向服务器发 HTTP 请求查询是否有新数据。
这两种模式有一个共同的缺点,就是除了真正的数据部分外,服务器和客户端还要大量交换 HTTP header,信息交换效率很低。
它们建立的“长连接”都是伪.长连接,只不过好处是不需要对现有的 HTTP server 和浏览器架构做修改就能实现。

3.分块编码
分块编码类似长轮询,
通过http协议连接成功之后,服务器响应的数据将“分块发送”,有点类似http下载大文件,而服务端可以控制“块”的大小,也可以控制“块”之间的时间间隔,收到的块长度为0的时候,响应结束。理论上,它解决的问题是长链接自动断开的问题,但是浏览器只能创建2个同主机连接的限制依然存在。

4.Applet/Flash
通过Java Applet或者Flash,在页面中内嵌一个1像素的透明的Applet或者Flash插件,然后使用插件直接创建socket连接,跳过了Http的限制,直接使用TCP协议进行通讯,以此来实现真正的双工通讯,也解决了上述3个方案的大部分缺点。(卧槽这个方法突然让我想起以前Android app保活的方法,套路有点惊人的相似,都是留有一个1像素的透明组件)。
不过这个方案也并非完美,第三方插件存在安全和内存方面的问题,同时,由于移动端的迅速崛起,绝大部分移动设备上的浏览器不支持java/flash插件。

Websocket的出现

WebSocket是HTML5出的东西(协议)
WebSocket 解决的第一个问题是,通过第一个 HTTP request 建立了 TCP 连接之后,之后的交换数据都不需要再发 HTTP request了,使得这个长连接变成了一个真.长连接。但是不需要发送 HTTP header就能交换数据显然和原有的 HTTP 协议是有区别的,所以它需要对服务器和客户端都进行升级才能实现。在此基础上 WebSocket 还是一个双通道的连接,在同一个 TCP 连接上既可以发也可以收信息。此外还有 multiplexing 功能,几个不同的 URI 可以复用同一个 WebSocket 连接。这些都是原来的 HTTP 不能做到的。
虽说WebSocket是真正的实现了长连接,但是WebSocket依然可能进入某种半死不活的状态。这实际上也是原有网络世界的一些缺陷性设计。上面所说的WebSocket 真.长连接虽然解决了服务器和客户端两边的问题,但坑爹的是网络应用除了服务器和客户端之外,另一个巨大的存在是中间的网络链路。一个 HTTP/WebSocket 连接往往要经过无数的路由,防火墙。
数据包在网络上传递的过程中,会经过无数次转发,过滤,才能最终抵达终点。在这过程中,中间节点的处理方法很可能会让人意想不到。比如,这些坑爹的中间节点可能会认为一份连接在一段时间内没有数据发送就等于失效,它们会自作主张的切断这些连接。在这种情况下,不论服务器还是客户端都不会收到任何提示,互相认为socket还正常连接。而计算机网络协议栈的实现中又会有一层套一层的缓存,除非填满这些缓存,系统根本不会发现任何错误。这样WebSocket就可能在毫不知情的情况下进入了半死不活状态。
解决方案就是让服务器和客户端能够发送 Ping/Pong Frame,就是传说中的心跳。这种 Frame 是一种特殊的数据包,它只包含一些元数据而不需要真正的 Data Payload,以此维持住中间网络的连接状态。

参考:《Java Web高级编程》