跳到主要内容

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