前言

最近在一个项目中,需要使用到websocket,之前对websocket不是很了解,于是就花了一点时间来熟悉websocket。

在浏览器与服务器通信间,传统的 HTTP 请求在某些场景下并不理想,比如实时聊天、实时性的小游戏等等,

其面临主要两个缺点:

  1. 无法做到消息的「实时性」;
  2. 服务端无法主动推送信息;

其基于 HTTP 的主要解决方案有:

  1. 基于 ajax 的轮询:客户端定时或者动态相隔短时间内不断向服务端请求接口,询问服务端是否有新信息;其缺点也很明显:多余的空请求(浪费资源)、数据获取有延时;

  2. Long Poll:其采用的是阻塞性的方案,客户端向服务端发起 ajax 请求,服务端挂起该请求不返回数据直到有新的数据,客户端接收到数据之后再次执行 Long Poll;该方案中每个请求都挂起了服务器资源,在大量连接的场景下是不可接受的;

可以看到,基于 HTTP 协议的方案都包含一个本质缺陷 —— 「被动性」,服务端无法下推消息,仅能由客户端发起请求不断询问是否有新的消息,同时对于客户端与服务端都存在性能消耗。

WebSocket 是 HTML5 开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebSocket 通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI 被 W3C 定为标准。 在 WebSocket API 中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

WebSocket 是 HTML5 中提出的新的网络协议标准,其包含几个特点:

  1. 建立于 TCP 协议之上的应用层;
  2. 一旦建立连接(直到断开或者出错),服务端与客户端握手后则一直保持连接状态,是持久化连接;
  3. 服务端可通过实时通道主动下发消息;
  4. 数据接收的「实时性(相对)」与「时序性」;

实践

在浏览器中使用 Websocket 非常简单,在支持 Websocket 的浏览器中提供了原生的 WebSocekt 对象,其中对于消息的接收与数据帧处理在浏览器中已经封装好了。

以下将用一个简单的例子解释如何使用 WebSocekt;

浏览器中提供了原生类 WebSocket ,使用 new 关键字实例化它:

WebSocket WebSocket(String url,optional String | [] protocols);

接收两个参数:

  1. url 表示需要连接的地址,比如:ws://localhost:8080;
  2. protocols 可选参数,可以是一个字符串或者一个数组,用来表示子协议,这样做可以让一个服务器实现多种 WebSocket 子协议;

实例化对象提供两个方法:

  1. send 接收一个 String|ArrayBuffer|Blob 数据,作为数据发送到服务端;
  2. close 接收一个(可选)的 code(关闭状态号,默认为 1000) 与一个(可选)的字符串(表示断开原因),客户端主动断开连接;

连接状态:

  1. WebSocket 类提供了一些常量表示连接状态:

  2. WebSocket.CONNECTING 0 连接还没开启;

  3. WebSocket.OPEN 1 连接已开启并准备好进行通信;

  4. WebSocket.CLOSING 3 连接正在关闭的过程中;

  5. WebSocket.CLOSED 4 连接已经关闭,或者连接无法建立;

  6. WebSocket 的实例对象中提供了 readyState 属性来判断当前状态;

实例化对象中可以监听到以下事件:

  1. open 连接打开的回调事件,这时 readyState 变为 OPEN;
  2. message 收到消息的回调事件,同时回调函数接收到一个 MessageEvent 数据;
  3. close 连接关闭的回调事件,这时 readyState 变为 CLOSED;
  4. error 建立与连接过程发生错误的回调事件;

代码实现

const ws = new WebSocket('ws://localhost:8080');

let sendTimmer = null;
let sendCount = 0; ws.onopen = function () {
console.log('@open'); sendCount++;
ws.send('Hello Server!' + sendCount); sendTimmer = setInterval(function () {
sendCount++;
ws.send('Hi Server!' + sendCount); if (sendCount === 10) {
ws.close();
}
}, 2000);
}; ws.onmessage = function (e) {
console.log('@message');
console.log(e.data);
}; ws.onclose = function () {
console.log('@close');
sendTimmer && clearInterval(sendTimmer);
}; ws.onerror = function () {
console.log('@error');
};

控制台可以看到

@open
@message
Hello Client
@message
received: Hello Server!1(From Server)
@message
received: Hi Server!2(From Server)
@message
received: Hi Server!3(From Server)
@message
received: Hi Server!4(From Server)
@message
received: Hi Server!5(From Server)
@message
received: Hi Server!6(From Server)
@message
received: Hi Server!7(From Server)
@message
received: Hi Server!8(From Server)
@message
received: Hi Server!9(From Server)
@close

首先触发 open 事件,之后每次发送数据服务端都会回复数据,因此触发了 message 事件,当发送 10 次之后浏览器主动断开连接,因此触发 close 事件;这里最后一次发送之后未收到服务端回复也是因为客户端立即断开了连接;

当然,更具体的数据交互可以从 network 看到;

事件与数据

对 WebSocket 实例监听事件有两种方式,这里以 message 事件为例:

  1. 对 onmessage 属性直接赋值,正如以上: ws.onmessage = function () {};
  2. 使用 addEventListener 监听事件,如: ws.addEventListener('message', function () {});

在 message 回调函数中得到 MessageEvent 类型参数 e ,我们需要的数据可以通过 e.data 获取;

需要注意的一点是:不论服务端与客户端,其接受到的数据都是序列化后的字符串(当然也有 ArrayBuffer|Blob 类型数据),很多时候我们需要解析处理数据,比如 JSON.parse(e.data)

连接稳定性

由于网络环境复杂,某些情况会出现断开连接或者连接出错,需要我们在 close 或者 error 事件中监听非正常断开并重连;

由于一些原因在 error 时浏览器并不会响应回调事件,因此稳妥的做法还需要在 open 之后开启一个定时任务去判断当前的连接状态 readyState ,在出现异常情况下尝试重连;

心跳

websocket规范定义了心跳机制,一方可以通过发送ping(opcode 0x9)消息给另一方,另一方收到ping后应该尽可能快的返回pong(0xA)。

心跳机制是用于检测连接的对方在线状态,因此如果没有心跳,那么无法判断一方还在连接状态中,一些网络层比如 nginx 或者浏览器层会主动断开连接,

在 JavaScript 中,WebSocket 并没有开放 ping/pong 的 API ,虽然浏览器自带了心跳处理,然而不同厂商的实现也不尽相同,因此需要在我们开发时候与服务端约定好一个自实现的心跳机制;

比如浏览器中,检测到 open 事件后,启动一个定时任务,每次发送数据 0x9 给服务端,而服务端返回 0xA 作为响应;

实践下来,心跳的定时任务一般是相隔 15-20 秒发送一次。

网络协议

前文说到,Websocket 是建立与 TCP 之上,那么其与 HTTP 协议有和关系呢?

Websocket 连接分为建连阶段与连接阶段,在建立连接阶段借助于 HTTP ,而在连接阶段则与 HTTP 无关。

建连阶段

从浏览器的 Network 中,找到 ws 连接,可以看到:

General
Request URL:ws://localhost:8080/
Request Method:GET
Status Code:101 Switching Protocols Response Headers
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: py9bt3HbjicUUmFWJfI0nhGombo= Request Headers
GET ws://localhost:8080/ HTTP/1.1
Host: localhost:8080
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36
DNT: 1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,la;q=0.6,ja;q=0.5
Sec-WebSocket-Key: 2idFk3+96Hs5hh+c9GOQCg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

这是一个标准的 HTTP 请求,相比于我们常见的 HTTP 请求协议,请求头中多了几个字段:


Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 2idFk3+96Hs5hh+c9GOQCg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Connection 为 Upgrade ,Upgrade 为 websocket ,表示告知 Nginx 与 Apache 等服务器该次连接并非为 HTTP 连接,实质上是一个 websocket ,因此服务器会转发到相应的 websocket 任务处理;

Sec-WebSocket-Key 是一个 Base64 encode 的值,由浏览器随机生成的,用于验证服务器连接的正确性;

Sec-WebSocket-Versio 表示为使用的 websocket 服务版本;

响应头中:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: py9bt3HbjicUUmFWJfI0nhGombo=

可以看到其返回状态码为 101 ,表示切换协议;

Upgrade 与 Connection 用于回复客户端表示已经切换协议成功;

Sec-WebSocket-Accept 字段与 Sec-WebSocket-Key 相对应,用于验证服务的正确性;

连接阶段

当通过 HTTP 建立连接握手后,接下来则是真正的 Websocket 连接了,其基于 TCP 收发数据,Websocket 封装并开放接口。

WSS

在 HTTP 协议中,很多时候为了加密与安全需要使用 HTTPS 请求(HTTP + TCL);

相应的,在 Websocket 协议中,也是可以使用加密传输的 —— wss ,比如 wss://localhost:8080。

使用的也是与 HTTPS 一样的证书,在这里一般是交由 Nginx 等服务层去做证书处理。

本文参考文章: https://qiutc.me/post/websocket-guide.html

websocket使用指南的更多相关文章

  1. HTML5 WebSocket 权威指南 学习一 (第二章 WebSocket API)

    WebSocket 协议两种URL方案 ws 客户端和服务器之间的非加密流量 wss 客户端和服务器之间的加密流量 WebSocket Secure 表示使用传输层安全性(SSL)的WebSocket ...

  2. java实现websocket 终极指南

    大概思路:  首先用户登陆  获取用户信息存储到httpsession中,然后客户端链接服务端websocket,首先HandshakeInterceptor这个拦截器会拦截请求 调用 beforeH ...

  3. 转:WebSocket与Java

    Bozhidar Bozhanov是Ontotext AD的高级软件工程师,拥有多年的从业经验,也是stackoverflow上的活跃用户.他精通于Java与Java技术栈,如Spring.JPA.J ...

  4. 学习html5的WebSocket连接

    1.什么是WebSocket WebSocket 是一种自然的全双工.双向.单套接字连接.使用WebSocket,你的HTTP 请求变成打开WebSocket 连接(WebSocket 或者WebSo ...

  5. webSocket学习与应用

    非原创,版权归原作者所有http://www.cnblogs.com/shizhouyu/p/4975409.html 1.什么是WebSocket WebSocket 是一种自然的全双工.双向.单套 ...

  6. 微信小程序WebSocket报错:Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received

    Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was ...

  7. 【HTML5 WebSocket】WebSocket对象特性和方法

    <HTML5 WebSocket权威指南>学习笔记&3 WebSocket方法的对象特性 1. WebSocket方法 a. send方法 send方法用于在WebSocket连接 ...

  8. WebSocket协议探究(二)

    一 复习和目标 1 复习 协议概述: WebSocket内置消息定界并且全双工通信 WebSocket使用HTTP进行协议协商,协商成功使用TCP连接进行传输数据 WebScoket数据格式支持二进制 ...

  9. WebSocket协议探究(一)

    一 复习和目标 1 复习 上一节使用wireshark抓包分析了WebSocket流量 包含连接的建立:HTTP协议升级WebSocket协议 使用建立完成的WebSocket协议发送数据 2 目标 ...

随机推荐

  1. MSQL的基准测试

    Mysql基准测试 基准测试 直接.简单.易于比较,用于评估服务器的处理能力 压力测试 对真实的月数据进行测试,获得真是系统所能承受的压力 基准测试的目的 1.建立MySQL服务器的性能基准线 2.模 ...

  2. MyBatis框架概述

    MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动.创建connection.创建statement.手动设 ...

  3. 电脑开机后,就会自动运行chkdsk,我想取消chkdsk,怎么取消

     每次开机都自动检查磁盘,检测通过后下次还是一样,NTFS/FAT32分区都有可能有这样的情况,即使重装系统,仍可能出现同样情况,但是硬盘可以通过Dell 随机带的检测程序解决方法:在命令行窗口中 ...

  4. (二十四)mongodb中group的问题二

    今天的工作还是继续昨天没有完成的,由于对mongodb数据库的不熟悉,导致昨天的思路上也出了一点问题,我需要查询出同一个ruleID中不同的processingID的条数,然后根据条数来排列先后顺序, ...

  5. class-朴素贝叶斯NaiveBayes

    1 朴素贝叶斯法的学习与分类1.1 基本原理2 参数估计2.1 极大似然估计2.2 算法2.3 贝叶斯估计 1 朴素贝叶斯法的学习与分类 Naive Bayes是基于贝叶斯定理和特征条件独立的假设的分 ...

  6. PyCharm运行报编码错误

    运行报如下错误: SyntaxError: Non-ASCII character '\xe8' in file /home/ubuntu/code/201803091253-text.py on l ...

  7. history对象的使用--JavaScript基础

    history对象提供与历史清单有关的信息,包含最经访问过的10个网页的URL 1.history对象常用属性 length 返回浏览器历史列表中URL数量 <!DOCTYPE html> ...

  8. CSS布局方案

    居中布局 水平居中 1)使用inline-block+text-align 原理:先将子框由块级元素改变为行内块元素,再通过设置行内块元素居中以达到水平居中. 用法:对子框设置display:inli ...

  9. python拓扑排序

    发现自己并没有真的理解拓扑排序和多重继承,再次学习了下 拓扑排序要满足如下两个条件 每个顶点出现且只出现一次. 若A在序列中排在B的前面,则在图中不存在从B到A的路径. 拓扑排序算法 任何无回路的顶点 ...

  10. [POJ2115]C Looooops 拓展欧几里得

    原题入口 这个题要找到本身的模型就行了 a+c*x=b(mod 2k) ->  c*x+2k*y=b-a 求这个方程对于x,y有没有整数解. 这个只要学过拓展欧几里得(好像有的叫扩展欧几里德QA ...