WebSocket是基于TCP的应用层协议,用于在C/S架构的应用中实现双向通讯,关于WebSocket协议的详细规范和定义参见rfc6455。
须要特别注意的是:虽然WebSocket协议在创建链接时会使用HTTP协议,但这并意味着WebSocket协议是基于HTTP协议实现的。
javascript
实际上,WebSocket协议与Http协议有着本质的区别:
1.通讯方式不一样
WebSocket是双向通讯模式,客户端与服务器之间只有在握手阶段是使用HTTP协议的“请求-响应”模式交互,而一旦链接创建以后的通讯则使用双向模式交互,不管是客户端仍是服务端均可以随时将数据发送给对方;而HTTP协议则至始至终都采用“请求-响应”模式进行通讯。也正由于如此,HTTP协议的通讯效率没有WebSocket高。
html
2.协议格式不一样
WebSocket与HTTP的协议格式是彻底不一样的,具体来说:
(1)HTTP协议(参见:rfc2616)比较臃肿,而WebSocket协议比较轻量。
(2)对于HTTP协议来说,一个数据包就是一条完整的消息;而WebSocket客户端与服务端通讯的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。即:发送端将消息切割成多个帧,并发送给服务端;服务端接收消息帧,并将关联的帧从新组装成完整的消息。
WebSocket协议格式:前端
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
HTTP请求消息格式:java
Request-LineCRLF general-headerCRLF request-headerCRLF entity-headerCRLF CRLF [ message-body ]
HTTP响应消息格式:node
Status-LineCRLF general-headerCRLF response-headerCRLF entity-headerCRLF CRLF [ message-body ]
虽然WebSocket和HTTP是不一样应用协议,但rfc6455规定:“WebSocket设计为经过80和443端口工做,以及支持HTTP代理和中介”,从而使其与HTTP协议兼容。为了实现兼容性,WebSocket握手时使用HTTP Upgrade头从HTTP协议更改成WebSocket协议,参考:WebSocket维基百科 。nginx
随着Web应用的发展,特别是动态网页的普及,愈来愈多的场景须要实现数据动态刷新。
在早期的时候,实现数据刷新的方式一般有以下3种:
1.客户端定时查询
客户端定时查询(如:每隔10秒钟查询一次)是最原始也是最简单的实现数据刷新的方法,服务端不用作任何改动,只须要在客户端添加一个定时器便可。可是这种方式的缺点也很明显:大量的定时请求都是无效的,由于服务端的数据并无更新,相应地也致使了大量的带宽浪费。git
2.长轮训机制
长轮训机制是对客户端定时查询的一种改进,即:客户端依旧保持定时发送请求给服务端,可是服务端并不当即响应,而是等到真正有数据更新的时候才发送给客户端。实际上,并非当没有数据更新时服务端就永远都不响应客户端,而是须要在等待一个超时时间以后结束该次长轮训请求。相对于客户端定时查询方式而言,当数据更新频率不肯定时长轮训机制可以很明显地减小请求数。可是,在数据更新比较频繁的场景下,长轮训方式的优点就没那么明显了。
在Web开发中使用得最为广泛的长轮训实现方案为Comet(Comet (web技术)),Tomcat和Jetty都有对应的实现支持,详见:WhatIsComet,Why Asynchronous Servlets。github
3.HTTP Streaming
不管是长轮训机制仍是传统的客户端定时查询方式,都须要客户端不断地发送请求以获取数据更新,而HTTP Streaming则试图改变这种方式,其实现机制为:客户端发送获取数据更新请求到服务端时,服务端将保持该请求的响应数据流一直打开,只要有数据更新就实时地发送给客户端。
虽然这个设想是很是美好的,但这带来了新的问题:
(1)HTTP Streaming的实现机制违背了HTTP协议自己的语义,使得客户端与服务端再也不是“请求-响应”的交互方式,而是直接在两者创建起了一个单向的“通讯管道”。
(2)在HTTP Streaming模式下,服务端只要获得数据更新就发送给客户端,那么就须要客户端与服务端协商如何区分每个更新数据包的开始和结尾,不然就可能出现解析数据错误的状况。
(3)另外,处于客户端与服务端的网络中介(如:代理)可能会缓存响应数据流,这可能会致使客户端没法真正获取到服务端的更新数据,这实际上与HTTP Streaming的本意是相违背的。
鉴于上述缘由,在实际应用中HTTP Streaming并无真正流行起来,反之使用得最多的是长轮训机制。web
显然,上述几种实现数据动态刷新的方式都是基于HTTP协议实现的,或多或少地存在这样那样的问题和缺陷;而WebSocket是一个全新的应用层协议,专门用于Web应用中须要实现动态刷新的场景。
相比起HTTP协议,WebSocket具有以下特色:spring
在Web应用的网页中使用WebSocket,WebSocket对象提供了用于建立和管理WebSocket链接,以及能够经过该链接发送和接收数据的API。
1.构造函数
可使用WebSocket类的构造函数(WebSocket(url[, protocols]))实例化一个对象,如:
var url = "ws://host:port/endpoint"; var ws = new WebSocket(url);
执行上述语句以后,浏览器将与服务端创建一个WebSocket链接,同时返回一个WebSocket实例对象ws。
2.对象属性
WebSocket实例对象具有以下属性:
3.对象方法
WebSocket定义了2个方法:
(1)WebSocket.send(data):向服务器发送数据,将须要经过WebSocket链接传输至服务器的数据排入队列,并根据所须要传输的数据字节的大小来增长属性bufferedAmount的值 。若数据没法传输(例如数据须要缓存而缓冲区已满)时,套接字会自行关闭。
参数data为传输至服务器的数据,它必须是如下类型之一:
(2)WebSocket.close([code[, reason]]):关闭当前链接,若是链接已经关闭,则此方法不执行任何操做。
参数:
异常:
更多WebSockete API的详细内容参见W3C的定义:The WebSocket API。
以下为在网页中使用原生WebSocket的实现方式。
var url = "ws://localhost:8080/websocket/text"; var ws = new WebSocket(url); ws.onopen = function(event) { console.log("websocket connection open."); console.log(event); }; ws.onmessage = function(event) { console.log("websocket message received.") console.log(event.data); }; ws.onclose = function (event) { console.log("websocket connection close."); console.log(event.code); }; ws.onerror = function(event) { console.log("websocket connection error."); console.log(event); };
在Web网页中使用WebSocket须要浏览器支持,不一样浏览器软件版本对WebSocket的支持状况详见浏览器兼容性。
另外,WebSocket客户端除了能够在网页中使用,目前还存在一些独立的客户端组件,如:
1.Jetty WebSocket Client API
2.websockets-api-java-spring-client
3.Java-WebSocket
在服务端使用WebSocket须要服务器组件支持,以下以在Tomcat 8.5.41(Tomcat 7以后才支持WebSocket)中使用原生WebSocket为例。
因为在服务端使用WebSocket须要使用到WebSocket的API,所以须要添加API依赖管理:
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-websocket-api</artifactId> <version>8.5.41</version> </dependency>
使用注解方式编写WebSocket服务端:
@ServerEndpoint(value="/websocket/text") public class WebSocketTest { private static final Logger logger = LoggerFactory.getLogger(WsChatAnnotation.class); private static final AtomicInteger counter = new AtomicInteger(0); // 客户端计数器 private static final Set<WsChatAnnotation> connections = new CopyOnWriteArraySet<WsChatAnnotation>(); // 客户端websocket链接集合 private Session session = null; // WebSocket会话对象 private Integer number = 0; // 客户端编号 public WsChatAnnotation() { number = counter.incrementAndGet(); } /** * 客户端创建websocket链接 * @param session */ @OnOpen public void start(Session session) { logger.info("on open"); this.session = session; connections.add(this); try { session.getBasicRemote().sendText(new StringBuffer().append("Hello: ").append(number).toString()); } catch (IOException e) { e.printStackTrace(); } } /** * 客户端断开websocket链接 */ @OnClose public void close() { logger.info("session close"); try { this.session.close(); } catch (IOException e) { e.printStackTrace(); } finally { connections.remove(this); } } /** * 接收客户端发送的消息 * @param message */ @OnMessage public void message(String message) { logger.info("message: {}", message); for(WsChatAnnotation client : connections) { synchronized (client) { try { client.session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } } } @OnError public void error(Throwable t) { logger.error("client: {} error", number, t.getMessage()); } }
当下的Web应用架构一般都是集群化部署,前端使用反向代理或者直接部署负载均衡器,这就要求反向代理或者负载均衡器必须支持WebSocket协议。
目前Nginx,Haporxy都已经支持WebSocket协议。
以下为在使用nginx做为反向代理的场景下,配置nginx代理websocket协议。
# add websocket proxy location ~ /ws { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://8080; }
【参考】
https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/ Spring MVC 3.2 Preview: Techniques for Real-time Updates
https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0 WebSocket
https://www.cnblogs.com/chyingp/p/websocket-deep-in.html WebSocket协议:5分钟从入门到精通
http://www.ruanyifeng.com/blog/2017/05/websocket.html WebSocket 教程
https://blog.csdn.net/chszs/article/details/26369257 Nginx担当WebSockets代理
http://blog.fens.me/nodejs-websocket-nginx/ Nginx反向代理Websocket