大致了解下websocket

493 阅读6分钟

背景

当websocket并不存在自己的知识范围内,前端如何保证获取的数据和后端保持同步?传统做法一般有两种:http轮询,Commet推送。那么,这两种方式有何不妥呢?

HTTP轮询

使用轮询的方法,在设定的时间间隔,客户端向服务端发送http请求,获取服务端最新的数据。

Comet推送

Comet推送技术,服务器实时地将信息传递给客户端,Comet是通过长轮询或者iframe流实现的。

长轮询:在打开了一条连接后,在服务端推送了数据过来后才关闭。

iframe流:将一隐藏的iframe插入页面,利用iframe的src属性,在客户端和服务端建立一条长连接,服务端向iframe传送数据(通常HTML,内有负责插入信息的js),实时更新页面。googletalk使用的就是这种方法。

不妥

  • HTTP请求每次都要携带完整(包含协议控制的数据包)的头部,开销有点大。
  • HTTP轮询中,服务端需要接收到客户端的请求,才能发送实时数据给客户端,实时性还是不够强。
  • HTTP是无状态协议,也就意味着,每次发送请求时,客户端需要携带状态信息,可能时候请求参数的数据都不及这些状态信息的体积大。
  • Comet,依然需要反复发出请求,而且采用的是长链接(是使用同一个tcp连接来发送和接收多个HTTP请求/应答,而不是为每一个新的请求/应答打开新的连接的方法,connection: keep-alive,Apache默认保持15秒),消耗服务器资源(对于单个文件被不断请求的服务(例如图片存放网站),Keep-Alive可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间)。

websocket

备受瞩目的websocket横空出世,解决了一系列问题,使用起来也是极为方便的。 websocket是一个基于TCP的全双工通信协议,HTTP一样,都是一种处于应用层的通信协议,并不是什么特别存在的协议。websocket使用的是ws或者wss的统一资源标识符,写法和http的相似:

http://a.b.com/xxx
https://secure.b.com/xxx

ws://a.b.com/wsapi
wss://secure.b.com/

在websocket的世界,实时通信相对来说不那么难了。服务端和客户端完成一次握手后,建立了一次持久性的连接,彼此可以主动向对方发送实时信息。 websocket相比于传统方式,有哪些优势呢?

  • 实时性较强,服务端可随时主动向客户端推送信息。
  • 保持连接状态,建立连接后的每次通信,可以省略携带状态信息。
  • 支持二进制
  • 更好的压缩效果

常用api

在前端,通过ajax发送HTTP请求。而websocket则是通过Websocket构造函数,传入符合websocket规范的url,创建一个实例,建立连接,这个实例本身含有多个可供开发者调用的api。

构造函数Websocket

const socket = new Websocket('ws://examp.b.com');

属性

  • onopen 监听事件,当websocket连接建立,可以收发数据。
socket.onpen = (event) => {
    console.log('the websocket is opened now');
}
  • onmessage 监听事件,当websocket收到信息时触发。
socket.onmessage = (event) => {
    console.log('the websocket receive data');
}
  • onclose 监听事件,当websocket关闭时
socket.onclose = (event) => {
    console.log('the websocket is closed');
}
  • readyState websocket状态:0(CONNECTING正在建立连接),1(OPEN,连接打开,可以收发信息), 2(CLOSING正在关闭连接), 3(CLOSED连接关闭)
console.log(socket.readyState);
  • bufferedAmount 表示还有多少字节的二进制数据没有被发送出去,一般可以用来判断数据是否发送完成,毕竟,目前没有一个可以监听数据发送是否完成的函数存在。
console.log(socket.bufferAmount)

方法

  • send 发送信息,发送的信息可以string,blob,ArrayBuffer.
socket.send('hello')
  • close  主动关闭websocket连接.传递的参数有两个,code, reason。 第一个参数为code(number类型),解释socket关闭的原因(code),若不传code,默认code的值为1005,表明无code可以传。 第二个参数reason(string类型),人为解释关闭的原因,字符串的字节长度不能超过UTF-8文本的123个字节。
socket.close(code, reason);

Websocket连接的创建过程

都说websocket都是一个基于TCP的全双工通信协议,websocket有何关系?同样作为应用层的通信协议,除了上述的区别之外,websocket和http有什么联系?这些可从websocket连接的创建过程得知。

计算机网络的五层模型:物理层、链路层、网络层、传输层、应用层。TCP是传输层协议,websocket是应用层协议,建立websocket连接的过程中,必定要经过传输层,所以,在建立连接之前,还是需要经过TCP的三次握手。

在TCP三次成功握手之后,意味着服务端和客户端可以进行信息的传输,这时候,使用websocket协议传递信息还是不可以的,需要客户端向服务端发送HTTP请求,表示客户端希望双方使用websocket协议来进行通信。在这次握手后,双方可以通过websocket协议主动向对方发送信息。下面这两段来自于RFC。

  • 客户端发送升级为websocket协议的请求
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

在客户端发送的这个特殊的HTTP请求中,有几个特殊的字段: Upgrade:websocket,Connection: Upgrade这两组键值对表明客户端请求升级到websocket通信协议。 Sec-webSecket-version,采用的websocket协议版本。

  • 服务器返回同意升级为websocket协议的信息
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

响应信息的第一行,状态码为101,服务端根据客户端的请求切换协议,服务端也返回了Upgrade:websocket,Connection: Upgrade,服务端将切换到websocket协议。

同时,可以看到客户端请求携带了Sec-websocket-key,这是一个由客户端产生的一个base64编码的随机字符串,这个字符串和服务端返回的Sec-Websocket-Accept字段有着密切的联系。这两者用户保护websocket通信的安全性。在服务端收到来自客户端开始的握手后,将客户端携带的Sec-websocket-key和全局唯一一个标识符258EAFA5-E914-470A-95CA-C5ABOdc85b11结合,然后加密,作为Sec-websocket-Accept的值返回给客户端。那这么做的意义是什么呢?

服务端向客户端证明自己收到了来自他的websocket握手,服务端将不再接受来自客户端的其他非websocket通信(指的是基于这次tcp连接和websocket握手完成后的通信,并不针对客户端和服务端之间所有的接口通信)。这么做有什么好处呢?服务端将可避免攻击者使用XMLHttpRequest或者表单提交发送精心设计的包来进行攻击。当然,并不意味者websocket通信绝对安全,就像http对应还有一个相对安全的https,ws还有一个相对来说比较安全的wss。

抓包分析

目前,我所了解到的websocket抓包工具有两种:chrome的开发者模式中的Network,filder。有一篇文章写的很棒,推荐一下:点我点我!

参考文档

我只是个搬运工!!!