所周知,Web 应用的交互过程一般是客户端经过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客户端浏览器将信息呈现,这种机制对于信息变化不是特别频繁的应用尚可,但对于实时要求高、海量并发的应用来讲显得捉襟见肘,尤为在当前业界移动互联网蓬勃发展的趋势下,高并发与用户实时响应是 Web 应用常常面临的问题,好比金融证券的实时信息,Web 导航应用中的地理位置获取,社交网络的实时消息推送等。
传统的请求-响应模式的 Web 开发在处理此类业务场景时,一般采用实时通信方案,常见的是:
轮询,原理简单易懂,就是客户端经过必定的时间间隔以频繁请求的方式向服务器发送请求,来保持客户端和服务器端的数据同步。问题很明显,当客户端以固定频率向服务器端发送请求时,服务器端的数据可能并无更新,带来不少无谓请求,浪费带宽,效率低下。
基于 Flash,AdobeFlash 经过本身的 Socket 实现完成数据交换,再利用 Flash 暴露出相应的接口为 JavaScript 调用,从而达到实时传输目的。此方式比轮询要高效,且由于 Flash 安装率高,应用场景比较普遍,但在移动互联网终端上 Flash 的支持并很差。IOS 系统中没有 Flash 的存在,在 Android 中虽然有 Flash 的支持,但实际的使用效果差强人意,且对移动设备的硬件配置要求较高。2012 年 Adobe 官方宣布再也不支持 Android4.1+系统,宣告了 Flash 在移动终端上的死亡。
从上文能够看出,传统 Web 模式在处理高并发及实时性需求的时候,会遇到难以逾越的瓶颈,咱们须要一种高效节能的双向通讯机制来保证数据的实时传输。在此背景下,基于 HTML5 规范的、有 Web TCP 之称的 WebSocket 应运而生。
早期 HTML5 并无造成业界统一的规范,各个浏览器和应用服务器厂商有着各异的相似实现,如 IBM 的 MQTT,Comet 开源框架等,直到 2014 年,HTML5 在 IBM、微软、Google 等巨头的推进和协做下终于尘埃落地,正式从草案落实为实际标准规范,各个应用服务器及浏览器厂商逐步开始统一,在 JavaEE7 中也实现了 WebSocket 协议,从而不管是客户端仍是服务端的 WebSocket 都已完备,读者能够查阅
HTML5 规范,熟悉新的 HTML 协议规范及 WebSocket 支持。
WebSocket 机制
如下简要介绍一下 WebSocket 的原理及运行机制。
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通讯,能更好的节省服务器资源和带宽并达到实时通信,它创建在 TCP 之上,同 HTTP 同样经过 TCP 来传输数据,可是它和 HTTP 最大不一样是:
WebSocket 是一种双向通讯协议,在创建链接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 同样;
WebSocket 须要相似 TCP 的客户端和服务器端经过握手链接,链接成功后才能相互通讯。
非 WebSocket 模式传统 HTTP 客户端与服务器的交互以下图所示:
图 1. 传统 HTTP 请求响应客户端服务器交互图
使用 WebSocket 模式客户端与服务器的交互以下图:
图 2.WebSocket 请求响应客户端服务器交互图
上图对比能够看出,相对于传统 HTTP 每次请求-应答都须要客户端与服务端创建链接的模式,WebSocket 是相似 Socket 的 TCP 长链接的通信模式,一旦 WebSocket 链接创建后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 链接或 Server 端断掉链接前,不须要客户端和服务端从新发起链接请求。在海量并发及客户端与服务器交互负载流量大的状况下,极大的节省了网络带宽资源的消耗,有明显的性能优点,且客户端发送和接受消息是在同一个持久链接上发起,实时性优点明显。
在客户端,new WebSocket 实例化一个新的 WebSocket 客户端对象,链接相似 ws://yourdomain:port/path 的服务端 WebSocket URL,WebSocket 客户端对象会自动解析并识别为 WebSocket 请求,从而链接服务端端口,执行双方握手过程,客户端发送数据格式相似:
清单 1.WebSocket 客户端链接报文
GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
能够看到,客户端发起的 WebSocket 链接报文相似传统 HTTP 报文,”Upgrade:websocket”参数值代表这是 WebSocket 类型请求,“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答,不然客户端会抛出“Error during WebSocket handshake”错误,并关闭链接。
服务端收到报文后返回的数据格式相似:
清单 2.WebSocket 服务端响应报文
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
“Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,“HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端链接,通过这样的请求-响应处理后,客户端服务端的 WebSocket 链接握手成功, 后续就能够进行 TCP 通信了。读者能够查阅
WebSocket 协议栈了解 WebSocket 客户端和服务端更详细的交互数据格式。
在开发方面,WebSocket API 也十分简单,咱们只须要实例化 WebSocket,建立链接,而后服务端和客户端就能够相互发送和响应消息,在下文 WebSocket 实现及案例分析部分,能够看到详细的 WebSocket API 及代码实现。
WebSocket 实现
如上文所述,WebSocket 的实现分为客户端和服务端两部分,客户端(一般为浏览器)发出 WebSocket 链接请求,服务端响应,实现相似 TCP 握手的动做,从而在浏览器客户端和 WebSocket 服务端之间造成一条 HTTP 长链接快速通道。二者之间后续进行直接的数据互相传送,再也不须要发起链接和相应。
清单 3.WebSocket 服务端 API 示例
@ServerEndpoint("/echo")
public class EchoEndpoint {
@OnOpen
public void onOpen(Session session) throws IOException {
//如下代码省略...
}
@OnMessage
public String onMessage(String message) {
//如下代码省略...
}
@Message(maxMessageSize=6)
public void receiveMessage(String s) {
//如下代码省略...
}
@OnError
public void onError(Throwable t) {
//如下代码省略...
}
@OnClose
public void onClose(Session session, CloseReason reason) {
//如下代码省略...
}
}
清单 5.WebSocket 客户端 API 示例
var ws = new WebSocket(“ws://echo.websocket.org”);
ws.onopen = function(){ws.send(“Test!”); };
ws.onmessage = function(evt){console.log(evt.data);ws.close();};
ws.onclose = function(evt){console.log(“WebSocketClosed!”);};
ws.onerror = function(evt){console.log(“WebSocketError!”);};