WebSocket介绍

1、什么是WebSocket

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于[服务器推送技术]的一种。
WebScoket是一种让客户端和服务器之间能进行双向实时通信的技术。它是HTML最新标准HTML5的一个协议规范,本质上是个基于TCP的协议,它通过HTTP/HTTPS协议发送一条特殊的请求进行握手后创建了一个TCP连接,此后浏览器/客户端和服务器之间便可以通过此连接来进行双向实时通信。

2、为什么要用WebSocket?

1)一直以来,HTTP协议是无状态、单向通信的,即客户端请求一次,服务器回复一次。如果想让服务器消息及时下发到客户端,需要采用类似于轮询的机制,即客户端定时频繁的向服务器发出请求,这样效率很低,而且HTTP数据包头本身的字节量较大,浪费了大量带宽和服务器资源;最典型的场景就是聊天室。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)

2)为提高效率,出现了AJAX/Comet技术,它实现了双向通信且节省了一定带宽,但仍然需要发出请求,本质上仍然是轮询;

3)新一代HTML标准HTML5推出了WebSocket技术,它使客户端和服务器之间能通过HTTP协议建立TCP连接,之后便可以随时随地进行双向通信,且交换的数据包头信息量很小;

3、如何使用WebSocket?
在支持WebSocket的浏览器中,创建Socket之后,通过onopen、onmessage、onclose、onerror四个事件的实现来处理Socket的响应;

4、WebSocket与HTTP、TCP的关系
WebSocket和HTTP都属于应用层协议,且都是基于TCP的,它们的send函数最终也是通过TCP系统接口来做数据传输。那么WebSocket和HTTP的关系呢?WebSocket在建立握手连接时,数据是通过HTTP协议传输的,但是在连接建立后,真正的数据传输阶段则不需要HTTP协议的参与。它们之间的关系如下图:

WebSocket - 图1

5、什么情况下使用WebSocket?
如果游戏需要同时支持手机端、Web端,那毫无疑问应该使用WebSocket,现在各个平台都提供了相应的WebSocket实现。如果游戏不需要支持Web端,且对实时性要求比较高,如多人射击、MMORPG之类,那么使用TCP/UDP结合的原生Socket会比较好。

6、SocketIO
WebSocket是HTML5最新提出的规范,虽然主流浏览器都已经支持,但仍然可能有不兼容的情况,为了兼容所有浏览器,给程序员提供一致的编程体验,SocketIO将WebSocket、AJAX和其它的通信方式全部封装成了统一的通信接口,也就是说,我们在使用SocketIO时,不用担心兼容问题,底层会自动选用最佳的通信方式。因此说,WebSocket是SocketIO的一个子集。

WebSocket 是浏览器提供的对象,同时也是一个构造函数。用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的 API。

构造函数

**WebSocket()**构造函数会返回一个 WebSocket 对象。

语法

  1. var aWebSocket = new WebSocket(url [, protocols]);

参数

url要连接的URL;这应该是WebSocket服务器将响应的URL。

protocols 可选 一个协议字符串或者一个包含协议字符串的数组。这些字符串用于指定子协议,这样单个服务器可以实现多个WebSocket子协议(例如,您可能希望一台服务器能够根据指定的协议(protocol)处理不同类型的交互)。如果不指定协议字符串,则假定为空字符串。

Demo 创建实例

  1. <script type="text/javascript">
  2. // 浏览器提供 WebSocket 对象
  3. var ws = new WebSocket('ws://localhost:3000')
  4. // 发送
  5. ws.onopen = function () {
  6. ws.send('hello world')
  7. }
  8. // 接收
  9. ws.onmessage = function (mes) {
  10. alert(mes.data)
  11. if (mes.data === 'hello world') {
  12. ws.close()
  13. }
  14. }
  15. </script>

实例属性

实例的属性
image.png

onopen 连接成功时

用于指定连接成功后的回调函数。

  1. ws.onopen = function () {
  2. ws.send('hello world')
  3. }

onerror 连接失败时

用于指定连接失败后的回调函数。

onmessage 接收信息时

指定从服务器接收信息时的回调函数。

  1. ws.onmessage = function (mes) {
  2. alert(mes.data)
  3. if (mes.data === 'hello world') {
  4. ws.close()
  5. }
  6. }

onclose 连接关闭时

用于指定连接关闭后的回调函数。

实例方法

close() 关闭连接

send() 发送数据

事件

使用 addEventListener() 或将一个事件监听器赋值给本接口的 on_eventname_ 属性,来监听下面的事件。

[close](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close_event) (en-US)

当一个 WebSocket 连接被关闭时触发。也可以通过 [onclose](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/onclose) 属性来设置。

[error](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/error_event)

当一个 WebSocket 连接因错误而关闭时触发,例如无法发送数据时。也可以通过 [onerror](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/onerror) 属性来设置.

[message](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/message_event)

当通过 WebSocket 收到数据时触发。也可以通过 [onmessage](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/onmessage) 属性来设置。

[open](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/open_event) (en-US)

当一个 WebSocket 连接成功时触发。也可以通过 [onopen](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/onopen) 属性来设置。

  1. // Create WebSocket connection.
  2. const socket = new WebSocket('ws://localhost:8080');
  3. // Connection opened
  4. socket.addEventListener('open', function (event) {
  5. socket.send('Hello Server!');
  6. });
  7. // Listen for messages
  8. socket.addEventListener('message', function (event) {
  9. console.log('Message from server ', event.data);
  10. });

WebSocket协议本身不要求同源策略(Same-origin Policy),也就是某个地址为[http://a.com](http://a.com)的网页可以通过WebSocket连接到ws://b.com。但是,浏览器会发送Origin的HTTP头给服务器,服务器可以根据Origin拒绝这个WebSocket请求。所以,是否要求同源要看服务器端如何检查。

参考

https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

使用示例、问题

WebSocket重连

心跳就是客户端定时的给服务端发送消息,证明客户端是在线的, 如果超过一定的时间没有发送则就是离线了。

它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。

在 TCP 的机制里面,本身是存在有心跳包的机制的,也就是 TCP 的选项:SO_KEEPALIVE 。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。

心跳包一般来说都是在逻辑层发送空的 echo 包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。

在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。

心跳检测步骤:

  • 客户端每隔一个时间间隔发生一个探测包给服务器
  • 客户端发包时启动一个超时定时器
  • 服务器端接收到检测包,应该回应一个包
  • 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
  • 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
  1. // 前端解决方案:心跳检测
  2. var heartCheck = {
  3. timeout: 30000, //30秒发一次心跳
  4. timeoutObj: null,
  5. serverTimeoutObj: null,
  6. reset: function(){
  7. clearTimeout(this.timeoutObj);
  8. clearTimeout(this.serverTimeoutObj);
  9. return this;
  10. },
  11. start: function(){
  12. var self = this;
  13. this.timeoutObj = setTimeout(function(){
  14. //这里发送一个心跳,后端收到后,返回一个心跳消息,
  15. //onmessage拿到返回的心跳就说明连接正常
  16. ws.send("ping");
  17. console.log("ping!")
  18. self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了
  19. ws.close(); //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
  20. }, self.timeout);
  21. }, this.timeout);
  22. }
  23. }

使用 reconnecting-websocket.min.js 库进行重连