websocket 原理和实践

websocket

基于现象探索原理

似懂非懂,还是不懂。项目里用到了 ws 的东西,之前写业务代码没觉得多难,突然想自己搭建同样的环境了发现一头雾水不知道从哪里开始。故作此文记录过程和增加理解。

我们来看一个 WebSocket 链接,是一个基于 layabox 的小游戏的,可以点这里

//General
Request URL: ws://139.224.68.169:8080/
Request Method: GET
Status Code: 101 Switching Protocols

//Response Header
Connection: Upgrade
Sec-WebSocket-Accept: lsJN4kVMKgmL9vuwfjelSi2I2oU=
Upgrade: websocket

//Request Header
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cache-Control: no-cache
Connection: Upgrade
Host: 139.224.68.169:8080
Origin: http://laya.h5gamebar.com
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: 8559tF5jtqqXWQnt3tA08g==
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

报文内容如上,WS 链接是以ws:或者wss开头的。

什么是 WS/WSS?

WebSocket (WS)是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好地节省服务器资源和带宽并达到实时通讯

WebSocket 建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是: WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像> Socket 一样;WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。

WSS(Web Socket Secure)是 WebSocket 的加密版本。

一般的云服务器对 Websocket 都支持,可以看下阿里云的介绍。

  • 建立在 TCP 协议之上,服务器端的实现比较容易
  • WS 没有同源策略,可以跟任意服务器通信。
  • 支持发送文本和二进制数据。
  • 与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器

网络模型 这张图片很棒,很好的说明了协议间的关系。而且要记住,ws 链接握手阶段是通过 HTTP 协议的

端口

TCP/IP 网络协议分五层,从上往下分别为

  • 应用层
  • 传输层(端口)
  • 网络层(IP)
  • 数据链路层
  • 物理层

端口是属于传输层部分的东西。众所周知,默认 http 端口是 80,https 是 443。而 WebSocket 设计之初就是为了更好的兼容 Http,所以 WebSocket 默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

跨域看上面的 WS 报文信息。Status Code: 101 Switching Protocols

WS 框架

个人推荐的是: socketcluster 嗯,我也只接触过这个框架,还有其他的,感兴趣的自己搜。

操作步骤

安装也很简单:

  1. 全局安装 socketcluster
npm i -g socketcluster
1
  1. 创建简单应用
socketcluster create myApp
1
  1. 运行应用
cd myApp
node server.js 或者 npm run start
1
2
  1. 访问 打开localhost:8000,你会发现一个简单的页面,对应源码的public/index.html的例子。

sc demo 控制台

其实 demo 已经是前端页面跟 ws 服务器建立了链接了。socketcluster 的 demo 是结合的 express 的。

我们可以在myApp/worker.js下看到一句代码

app.use(serveStatic(path.resolve(__dirname, 'public')))
1

其中对public做了静态代理的意思,就是 public 下的所有静态内容都可以链接访问。属于静态资源。那么我们看下index.html的内容。

前端跟 ws 服务建立链接的过程,前提是要引入一个socketcluster.js的文件,封装好了方法了。引入后全局就有一个window.socketCluster的变量。

// Initiate the connection to the server
var socket = socketCluster.connect()

socket.on('error', function(err) {
  console.error(err)
})

socket.on('connect', function() {
  console.log('Socket is connected')
})

socket.on('random', function(data) {
  console.log('Received "random" event with data: ' + data.number)
})

var sampleChannel = socket.subscribe('sample')
sampleChannel.on('subscribeFail', function(err) {
  console.error(
    'Failed to subscribe to the sample channel due to error: ' + err
  )
})

sampleChannel.watch(function(num) {
  console.log('Sample channel message:', num)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

其实前端建立链接后就是监听方法就好了,这里是监听了random方法。而例子里面后端 worker 是每 1S 更改一次 count 的值,并且推送数据。因为这里前端监听了random方法,所以会收到推送。可以理解建立链接后就是发布/订阅模式的应用,互相监听消息,然后消息触发。用起来是不是很简单呢?

var interval = setInterval(function() {
  socket.emit('random', {
    number: Math.floor(Math.random() * 5)
  })
}, 1000)
1
2
3
4
5

SC 基本原理

  1. 主进程(Server.js)一切开始的地方,可以设置参数,并会调用 Workers 和 Brokers
  2. Workers:在 workerController 可以设置 HTTP 服务器逻辑,还有管理 SocketCluster 实时连接及事件(发送的广播等)
  3. Brokers:主要应用在 SocketCluster 内部,允许高效的在不同的 Workers 间分享通道数据,也可使用它会话数据及在多服务器间水平扩展节点

广播和单发

sc 有 publish 广播和 emit 单发两种模式,publish 即广播所有 ws 用户。而 emit 只会发送给一个用户。 emit方式对不同的用户都是不同的

我们开了另外一个 tab,可以看到不同的 client 收到的 websocket 服务推送的数据 number 是不同的,这就是 emit 的效果。

我们来给 demo 加点料,我们想要一个全局的数据(现在的服务器时间),同步给所有的 client。

<button id="btn">send data to server</button>
1
// bind event for button
document.getElementById('btn').addEventListener('click', btnClick, false)
function btnClick() {
  socket.emit('sampleClientEvent', {
    msg: 'message from clinet'
  })
}
1
2
3
4
5
6
7

点击按钮,服务器日志如图。message from clinet 同时所有客户端会收到服务器广播的消息。如下图。

publish message

这里感觉是个彩蛋,前端代码我找不到sampleClientEvent方法的调用,但是服务器端是有的,所以自己补充了。 源代码

增加了一个按钮,注册了点击事件,是发送给服务端一个信息.

项目地址,传送门

参考文献