websocket原理和实践

作者:ManfredHu
链接:http://www.manfredhu.com/2019/01/07/43-websocket/index.html
声明:版权所有,转载请保留本段信息,谢谢大家

websocket

基于现象探索原理

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//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

报文内容如上,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

    1
    npm i -g socketcluster
  2. 创建简单应用

    1
    socketcluster create myApp
  3. 运行应用

    1
    2
    cd myApp
    node server.js 或者 npm run start
  4. 访问
    打开localhost:8000,你会发现一个简单的页面,对应源码的public/index.html的例子。

sc demo 控制台

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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);
});

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

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

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。

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

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

publish message

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

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

项目地址,传送门

参考文献


Copyright © 2015 - 2019 ManfredHu胡文峰的个人博客

All rights reserved. Designed and powered by ManfredHu.

粤ICP备18133029号