跳到主要内容

WebSocket 学习

什么是 WebSocket​

WebSocket 也是一个应用层的通信协议,它解决了 HTTP 协议通信只能由客户端发起的这个缺点。像聊天室功能,如果使用 HTTP 协议,需要获取最新的消息就只能使用轮询的方式,即每隔一段时间就发出一个询问,了解服务器有没有最新的消息。种方式效率特别的低效,因此 WebSocket 就是为了解决这个问题(关于轮询看 HTTP 那篇笔记,关于 HTTP 的长连接与这个的关系也是)

上面的问题关键就是:推和拉

  • 推送(Push)技术是一种建立在服务器上的机制,就是由服务器主动将信息发往客户端的技术,WebSocket 能支持的技术
  • 拉取(Pull)技术,主动向服务器发起请求,等待服务器响应数据给客户端,传统的 HTTP 就是这种形式

至于为什么 HTTP 不一开始就使用这种长连接看 这里,该回答的解释是,因为历史原因采用了 HTTP 这种无状态协议用来节省资源,加上浏览器不给开后门(整个浏览器都不支持直接调用系统底层的 Socket),基于浏览器的 Web 自然无法调用,只能使用封装的高级协议方案 —— WebSocket,专门处理这种长连接的问题(所以 WebSocket 相当于一个简化版的 TCP 传输子层)

怎么建立连接的?​

上面说了这么多,但是 WebSocket 到底是怎么工作的呢?WebSocket 是基于 HTTP 协议的,或者说借用了 HTTP 的协议来完成一部分握手。流程如下

首先这里是一个典型的 WebSocket 握手

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

这段类似 HTTP 协议的握手请求中,多了几个东西。

Upgrade: websocket
Connection: Upgrade

这个就是 WebSocket 的核心了,告诉 Apache、Nginx 等服务器这里发起的是 WebSocket 协议,然后再来看下面的这这几个请求字段有什么作用

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
  • Sec-WebSocket-Key 是一个 Base64 encode 的值,这个是浏览器随机生成的,由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。(服务器接收客户端 HTTP 协议升级的证明,要求服务端响应一个对应加密的 Sec-WebSocket-Accept 头信息作为应答)
  • Sec-WebSocket-Protocol 是一个用户定义的字符串,用来区分同 URL 下,不同的服务所需要的协议。
  • Sec-WebSocket-Version 表示 WebSocket 的版本,最初 WebSocket 协议太多,不同厂商都有自己的协议版本,不过现在已经定下来了。如果服务端不支持该版本,需要返回一个 Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。

然后服务器会返回下列东西,表示已经接受到请求, 成功建立 Websocket 啦!

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

这里开始就是 HTTP 最后负责的区域了,告诉客户,我已经成功切换协议啦~

Upgrade: websocket
Connection: Upgrade
  • Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key。
  • Sec-WebSocket-Protocol 则是表示最终使用的协议。

注意:Sec-WebSocket-Key / Sec-WebSocket-Accept 的换算,只能带来基本的保障,但连接是否安全、数据是否安全、客户端 / 服务端是否合法的 ws 客户端、ws 服务端,其实并没有实际性的保证。

之后就是 WebSocket 的工作内容了

WebSocket 跨域问题​

因为 websocket 协议没有同源策略的限制,所以是可以被外部访问的,因此必须在服务器层面判断这个请求的身份。但是也没什么关系,因为它也不可以携带 Cookie,所以就算被第三方页面劫持访问了这个 WebSocket API,也没有关系,因为无法使用 Cookie 就意味着程序员必须采用 localStorage 或者 SessionStorage 来存储信息,从而物理隔绝 CSRF 攻击

WebSocket 是否能通过 HTTP 代理?​

问题:WebSocket 能通过一般的 HTTP 代理吗?

因为 HTTP 这种基于 Request - Response 模型的连接方式多少能理解,代理只需转发一个请求就行了,但是像 WebSocket 这种全双工的工作模式下,不是需要直接让客户端和服务端进行连接了吗?这不就变成了 SOCKS5 代理的范畴了吗?

TODO: 待更新...

在浏览器上使用​

参考文档 MDN WebSocket

创建一个 Websocket 对象

// url 的格式说明:ws://ip_address:port/temp
const ws = new WebSocket(url);

Websocket 事件​

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

  • close:当一个 WebSocket 连接被关闭时触发。
  • error:当一个 WebSocket 连接因错误而关闭时触发,例如无法发送数据时。
  • message:当通过 WebSocket 收到数据时触发。
  • open当一个 WebSocket 连接成功时触发。
// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080');

// Connection opened
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});

除了使用上面的监听的事件的方式,还可以使用自带的属性执行回调函数

  • WebSocket.onclose 用于指定连接关闭后的回调函数。
  • WebSocket.onerror 用于指定连接失败后的回调函数。
  • WebSocket.onmessage 用于指定当从服务器接受到信息时的回调函数。
  • WebSocket.onopen 用于指定连接成功后的回调函数。

Websocket 方法​

  • 关闭当前链接:WebSocket.close([code[, reason]])
  • 对要传输的数据进行排队:WebSocket.send(data)

WebSocket 属性​

以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:

WebSocket 控制帧​

WebSocket 控制帧有 3 种: Close(关闭帧)、Ping 以及 Pong。

控制帧的操作码定义了 0x08(关闭帧)、0x09(Ping 帧)、0x0A(Pong 帧)。

Close 关闭帧很容易理解,客户端如果接受到了就关闭连接,客户端也可以发送关闭帧给服务端。Ping 和 Pong 是 websocket 里的心跳,用来保证客户端是在线的,一般来说只有服务端给客户端发送 Ping,然后客户端发送 Pong 来回应,表明自己仍然在线。

OPCODE:4位 , 解释 PayloadData,如果接收到未知的 opcode,接收端必须关闭连接。 0x0 表示附加数据帧 0x1 表示文本数据帧 0x2 表示二进制数据帧 0x3-7 暂时无定义,为以后的非控制帧保留 0x8 表示连接关闭 0x9 表示ping 0xA 表示pong 0xB-F 暂时无定义,为以后的控制帧保留

子协议的使用​

TODO: ...

References​