WebSocket 协议

最近有个项目要做一个Slack Robot, 就是根据用户发到Slack 某个Channel的内容, 给出智能回复.

与Slack的交互方式有2种: HTTP连接的方式和WebSocket的方式. HTTP 连接的方式又有2种实现方式:

  1. 我们提供一个公开的endpoint, 当Slack的Channel 有消息的时候, Slack 以webhook的方式及时通知我们, 我们的app 给出回复.
  2. 我们不提供endpoint, 我们连接Slack的endpoint, 以固定时间间隔的方式去poll 消息, 如果有新消息, 我们的app 给出回复.

以上这2种方式都不是很好的方式, 对于第一种, 我们要提供一个公开的endpoint, 在我们的生产环境基本不可能, 对于第二种方式, 不是基于事件的, 不管有没有消息, 都要固定的去poll, 对于一个app 里面有多个实例的情况, 还要控制那个实例去poll.

所以 WebSocket 的方式是最好的, 不过当时就有开发人员提出了意见: 我们的生产环境时不能连外网的, 要连只能通过http 代理, 而我们的生产环境的代理只支持 http 代理, 不支持sock 方式, 所以这种 WebSocket 行不通. 是真的吗?

在我们看看 WebSocket 到底是什么之前, 先做一个 WebSocket 的例子: 使用Node.js 开一个WebSocket 服务, 然后在浏览器开一个 WebSocket 客户端.

WebSocket 服务端

  1. 新建一个文件夹

    mkdir wsServer
    cd wsServer
  2. 新建一个 package.json

    vim package.json

    输入如下代码

    {
      "name": "wsServer",
      "version": "0.0.1",
      "type": "module",
      "dependencies": {
          "ws":"8.8.1"
      }
    }
  3. 新建一个服务端代码文件

    vim server.js

    输入如下代码:

    var WebSocketServer = require('ws').Server
    var fs = require('fs')
    
    const wss = new WebSocketServer({ port: 8080 });
    
    wss.on('connection', function connection(ws) {
      ws.on('message', function message(data) {
        console.log('received: %s', data);
        ws.send(data + " -> server ack");
      });
    
      fs.watch('/tmp/', (eventType, fileName) => {
        console.log('Get event on file ' + fileName + ', type: ' + eventType);
        ws.send('file -> ' + fileName + ' -> ' + eventType);
      })
    
      ws.send('welcome to WebSocket world!');
    });

上面这段代码做3件事情:

  1. 启动 WebSocket 服务器在8080 端口上, 当有人来连接的时候, 发送欢迎消息;
  2. 当收到客户端消息的时候, 打印收到的消息, 并且发送给客户端 ack 消息;
  3. 监听本地 /tmp 文件夹的文件变动事件, 打印日志, 并推送给客户端;

启动服务端代码:

node server.js

WebSocket 客户端

在 Chrome 浏览器打开任意页面的控制台, 输入如下JavaScript 代码

const ws = new WebSocket('ws://10.249.64.103:8080');

// Listen for messages
ws.addEventListener('message', (event) => {
    console.log('Message from server: ', event.data);
});

document.addEventListener('click', (event) => {
    console.log("just clicked");
    ws.send('click on (' + event.x + ', ' + event.y + ')');
});

上面的代码做下面的事情:

  1. 连接服务端 WebSocket;
  2. 然后当收到服务器端消息的时候, 打印收到的消息;
  3. 当页面上收到点击事件的时候, 推送给服务端点击事件的坐标;

运行效果

客户端效果:
client.png

服务器端效果:
server.png

使用代理

因为客户端使用的是浏览器, 可以设置在chrome 设置对于这个IP 启用代理, 使用http 代理, 依然能联通server 端, 正常运转.

使用curl

使用curl 只能被动的接受服务器传来的消息, 不能发送任何消息. (这个 Sec-WebSocket-Key 是从浏览器刚才发送的历史中复制过来的)

curl \
    --include \
    --no-buffer \
    --header "Connection: Upgrade" \
    --header "Upgrade: websocket" \
    --header "Host: 10.249.64.103:8080" \
    --header "Origin: http://10.249.64.103:8080" \
    --header "Sec-WebSocket-Key: 1TFTcjPQ7iG2XvsZ83WgZg==" \
    --header "Sec-WebSocket-Version: 13" \
    http://10.249.64.103:8080

效果(curl 对回应消息只能拼接, 不换行):
server.png

JavaScript WebSocket API 文档

官方文档在这里: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket
这个 WebSocket 类很简单:

  1. 构造函数给出服务器的地址, 包括ws协议, host 加 port;
  2. 有几个字段: 比如 url, readyState, protocol, bufferedAcmount, binaryType等, 有些是只读的;
  3. 客户端只有发送 send() 和 close() 方法;
  4. event handler:

    1. message: 当收到消息时;
    2. open: 当连接建立时;
    3. error: 当发送错误时;
    4. close: 当关闭连接时;

Node.js 的 WebSocket 实现库 ws 的文档

官方文档: https://www.npmjs.com/package/ws#sending-and-receiving-text-data
它不仅仅包含一个server端的API实现, 还包含一个做为 Node.js 客户端端代码实现

WebSocket 协议

官方文档: https://www.rfc-editor.org/rfc/rfc6455
当我们看过上面的 WebSocket 的例子之后, 再来看这个RFC 文档, 就不是那么难了.

为什么需要 WebSocket 协议

在 web 通信等某些场景下, 如果服务端发生了某些事件, 需要实时推送给客户端. 在传统的基于web的技术下, 需要客户端不断的去poll消息, 不管服务端到底有没有事件更新, 每次poll 都需要客户端发送一个http request, 并且如果发送的频率过低, 可能不能及时收到服务端事件更新, 如果频率过高, 又会对网络和服务端造成一些压力.

所以, 如果建立一个连接的情况下, 服务端事件变更主动推送客户端, 客户端只要等待就好了, 就完美解决了这个问题, 于是就有了 WebSocket 协议.

WebSocket 基本介绍

  1. 它可以使客户端和服务端在一个连接里面双向不间断通信;
  2. 通信过程分2阶段, 先是使用http协议握手连接, 然后以数据帧的方式双向发送数据;
  3. 广泛使用在游戏, 股票等需要实时消息通讯等软件中;
  4. WebSocket 可以使用现有web的 proxy和认证等成熟的机制;
  5. 通常开在80或443, 可以通过防火墙;

通常的 连接握手协议:

The handshake from the client looks as follows:

        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

   The handshake from the server looks as follows:

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

标签: none

添加新评论