概念
WebSocket,是一种HTML5提供的一种浏览器与服务器进行全双工通信的网络传输协议,属于应用层协议。可在单个TCP连接上进行全双工通信,能更好的节省服务器资源和带宽并达到实时通迅
它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。
WebSocket原理:客户端向 WebSocket 服务器通知(notify)一个带有所有接收者ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。
WebSocket 的出现就解决了半双工通信的弊端。它最大的特点是:服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。
从上图可见,websocket服务器与客户端通过握手连接,连接成功后,两者都能主动的向对方发送或接受数据
特点
全双工
通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合
例如指 A→B 的同时 B→A ,是瞬时同步的
二进制帧
采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,相比http/2,WebSocket更侧重于“实时通信”,而HTTP/2 更侧重于提高传输效率,所以两者的帧结构也有很大的区别
不像 HTTP/2 那样定义流,也就不存在多路复用、优先级等特性
自身就是全双工,也不需要服务器推送
协议名
引入ws和wss分别代表明文和密文的websocket协议,且默认端口使用80或443,几乎与http一致
ws://www.chrono.comws://www.chrono.com:8080/srvwss://www.chrono.com:445/im?user_id=xxx
握手
WebSocket也要有一个握手过程,然后才能正式收发数据
客户端发送数据格式如下:
GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13
- Connection:必须设置Upgrade,表示客户端希望连接升级
- Upgrade:必须设置Websocket,表示希望升级到Websocket协议
- Sec-WebSocket-Key:客户端发送的一个 base64 编码的密文,用于简单的认证秘钥。要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept应答,否则客户端会抛出错误,并关闭连接
- Sec-WebSocket-Version :表示支持的Websocket版本
服务端返回的数据格式:
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol: chat
- HTTP/1.1 101 Switching Protocols:表示服务端接受 WebSocket 协议的客户端连接
Sec-WebSocket-Accep:验证客户端请求报文,同样也是为了防止误连接。具体做法是把请求头里“Sec-WebSocket-Key”的值,加上一个专用的 UUID,再计算摘要
优点
较少的控制开销:数据包头部协议较小,不同于http每次请求需要携带完整的头部
- 更强的实时性:相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少
- 保持创连接状态:创建通信后,可省略状态信息,不同于HTTP每次请求需要携带身份验证
- 更好的二进制支持:定义了二进制帧,更好处理二进制内容
- 支持扩展:用户可以扩展websocket协议、实现部分自定义的子协议
更好的压缩效果:Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率
应用场景
基于websocket的事实通信的特点,其存在的应用场景大概有:
弹幕
- 媒体聊天
- 协同编辑
- 基于位置的应用
- 体育实况更新
- 股票基金报价实时更新
使用方法
客户端
// 在index.html中直接写WebSocket,设置服务端的端口号为 9999let ws = new WebSocket('ws://localhost:9999');// 在客户端与服务端建立连接后触发ws.onopen = function() {console.log("Connection open.");ws.send('hello');};// 在服务端给客户端发来消息的时候触发ws.onmessage = function(res) {console.log(res); // 打印的是MessageEvent对象console.log(res.data); // 打印的是收到的消息};// 在客户端与服务端建立关闭后触发ws.onclose = function(evt) {console.log("Connection closed.");};
创建
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
var Socket = new WebSocket(url, [protocol] );
以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
属性
| Socket.readyState | 只读属性 readyState 表示连接状态,可以是以下值: - 0 - 表示连接尚未建立。 - 1 - 表示连接已建立,可以进行通信。 - 2 - 表示连接正在进行关闭。 - 3 - 表示连接已经关闭或者连接不能打开。 |
|---|---|
| Socket.bufferedAmount | 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
|---|---|
事件
| 事件 | 事件处理程序 | 描述 |
|---|---|---|
| open | Socket.onopen | 连接建立时触发 |
| message | Socket.onmessage | 客户端接收服务端数据时触发 |
| error | Socket.onerror | 通信发生错误时触发 |
| close | Socket.onclose | 连接关闭时触发 |
方法
| 方法 | 描述 |
|---|---|
| Socket.send() | 使用连接发送数据 |
| Socket.close() | 关闭连接 |
轮询
短轮询的基本思路
浏览器每隔一段时间向服务器发送 http 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。
这种方式的优点是比较简单,易于理解。缺点是这种方式由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源。当用户增加时,服务器端的压力就会变大,这是很不合理的。
长轮询的基本思路
首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制才返回。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
长轮询和短轮询比起来,它的优点是明显减少了很多不必要的 http 请求次数,相比之下节约了资源。长轮询的缺点在于,连接挂起也会导致资源的浪费。
坑
在古早的时候遇到一种情况就是后端需要前端主动轮询然后返回一个时间戳,把这个时间戳再携带发送回后端,当时实现之后运行一段时间后总会出问题导致时间戳计算错误,最后排查是网络原因导致上一个请求未处理完下一个请求先处理造成携带的时间戳不是预期的数据,现在分析其原因应该是:
- 前端实现使用的是setInterval,原理来讲setInterval很适合间隔一段时间执行一次动作,但轮询又比较特殊,轮询的操作是一个异步动作(ajax请求),所以受限制网络或者服务器处理速度的原因不能保证在一定的时间间隔内前一个请求能返回,然后请求下一个请求。
- 后来知道了一个概念叫【电梯效应】(也可能理解的不对)大致的意思就是一个长期运行的电梯总有出错的时候,而与使用setInterval这种总是预期能正常运行的情况是相违背的;还有一种解释是日常我们做电梯会发现电梯通常不是先发的先到达,也类似于公交车先发的不一定先到。并且会出现同时到达的情况(对于服务来说这就是并发吧)
SSE
SSE
SSE 的基本思想:服务器使用流信息向服务器推送信息。严格地说,http 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息。也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。
SSE在本质上就与之前的长轮询、短轮询不同,虽然都是基于http协议的,但是轮询需要客户端先发送请求。而SSE最大的特点就是不需要客户端发送请求,可以实现只要服务器端数据有更新,就可以马上发送到客户端。
SSE vs WebSocket
WebSocket 是 HTML5 定义的一个新协议,与传统的 http 协议不同,该协议允许由服务器主动的向客户端推送信息。使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息,而 SSE 的方式是单向通信的,只能由服务器端向客户端推送信息,如果客户端需要发送信息就是属于下一个 http 请求了。
上面的四个通信协议,前三个都是基于HTTP协议的。
对于这四种即使通信协议,从性能的角度来看:
WebSocket > 长连接(SEE) > 长轮询 > 短轮询
但是,我们如果考虑浏览器的兼容性问题,顺序就恰恰相反了:
短轮询 > 长轮询 > 长连接(SEE) > WebSocket
所以,还是要根据具体的使用场景来判断使用哪种方式。
