Websocket库Ws原理分析

开发 前端
我们看到ws监听了upgrade事件,当有websocket请求到来时就会执行handleUpgrade处理升级请求,升级成功后触发connection事件。我们先看handleUpgrade。handleUpgrade逻辑不多,主要是处理和校验升级请求的一些http头。

 [[394780]]

前言:本文几基于nodejs的ws模块分析websocket的原理。

ws服务器逻辑由websocket-server.js的WebSocketServer类实现。该类初始化了一些参数后就执行以下代码

  1. if (this._server) { 
  2.       // 给server注册下面事件,返回一个注销函数(用于注销下面注册的事件) 
  3.       this._removeListeners = addListeners(this._server, { 
  4.         // listen成功的回调 
  5.         listening: this.emit.bind(this, 'listening'), 
  6.         error: this.emit.bind(this, 'error'), 
  7.         // 收到协议升级请求的回调 
  8.         upgrade: (req, socket, head) => { 
  9.           this.handleUpgrade(req, socket, head, (ws) => { 
  10.             // 处理成功,触发链接成功事件 
  11.             this.emit('connection', ws, req); 
  12.           }); 
  13.         } 
  14.       }); 

我们看到ws监听了upgrade事件,当有websocket请求到来时就会执行handleUpgrade处理升级请求,升级成功后触发connection事件。我们先看handleUpgrade。handleUpgrade逻辑不多,主要是处理和校验升级请求的一些http头。ws提供了一个校验的钩子。处理完http头后,会调verifyClient校验是否允许升级请求。如果成功则执行completeUpgrade。顾名思义,completeUpgrade是完成升级请求的函数,该函数返回同意协议升级并且设置一些http响应头。另外还有一些重要的逻辑处理。

  1. const ws = new WebSocket(null); 
  2. // 设置管理socket的数据 
  3. ws.setSocket(socket, head, this.options.maxPayload); 
  4. // cb就是this.emit('connection', ws, req); 
  5. cb(ws); 

我们看到这里新建了一个WebSocket对象并且调用了他的setSocket函数。我们来看看他做了什么。setSocket的逻辑非常多,我们慢慢分析。

数据接收者

  1. class Receiver extends Writable {} 

我们看到数据接收者是一个可写流。这就意味着我们可以往里面写数据。

  1. const receiver = new Receiver(); 
  2. receiver.write('hello'); 

我们看一下这时候Receiver的逻辑。

  1. _write(chunk, encoding, cb) { 
  2.     if (this._opcode === 0x08 && this._state == GET_INFO) return cb(); 
  3.     this._bufferedBytes += chunk.length; 
  4.     this._buffers.push(chunk); 
  5.     this.startLoop(cb); 
  6.   } 

首先记录当前数据的大小,然后把数据存起来,最后执行startLoop。

  1. startLoop(cb) { 
  2.     let err; 
  3.     this._loop = true
  4.  
  5.     do { 
  6.       switch (this._state) { 
  7.         // 忽略其他case 
  8.         case GET_DATA: 
  9.           err = this.getData(cb); 
  10.           break; 
  11.         default
  12.           // `INFLATING` 
  13.           this._loop = false
  14.           return
  15.       } 
  16.     } while (this._loop); 
  17.  
  18.     cb(err); 
  19.   } 

我们知道websocket是基于tcp上层的应用层协议,所以我们收到数据时,需要解析出一个个数据包(粘包问题),所以Receiver其实就是一个状态机,每次收到数据的时候,都会根据当前的状态进行状态流转。比如当前处于GET_DATA状态,那么就会进行数据的处理。我们接着看一下数据处理的逻辑。

  1. getData(cb) { 
  2.     let data = EMPTY_BUFFER; 
  3.     // 提取数据部分 
  4.     if (this._payloadLength) { 
  5.       data = this.consume(this._payloadLength); 
  6.       if (this._masked) unmask(data, this._mask); 
  7.     } 
  8.     // 是控制报文则执行controlMessage 
  9.     if (this._opcode > 0x07) return this.controlMessage(data); 
  10.     // 做了压缩,则先解压 
  11.     if (this._compressed) { 
  12.       this._state = INFLATING; 
  13.       this.decompress(data, cb); 
  14.       return
  15.     } 
  16.     // 没有压缩则直接处理(先存到_fragments,然后执行dataMessage) 
  17.     if (data.length) { 
  18.       this._messageLength = this._totalPayloadLength; 
  19.       this._fragments.push(data); 
  20.     } 
  21.  
  22.     return this.dataMessage(); 
  23.   } 

我们执行websocket协议定义了报文的类型,比如控制报文,数据报文。我们分别看一下这两个的逻辑。

  1. controlMessage(data) { 
  2.     // 连接关闭 
  3.     if (this._opcode === 0x08) { 
  4.       this._loop = false
  5.       if (data.length === 0) { 
  6.         this.emit('conclude', 1005, ''); 
  7.         this.end(); 
  8.       } 
  9.     } else if (this._opcode === 0x09) { 
  10.       this.emit('ping', data); 
  11.     } else { 
  12.       this.emit('pong', data); 
  13.     } 
  14.     this._state = GET_INFO; 
  15.   } 

我们看到控制报文包括三种(conclude、ping、pong)。而数据报文只有this.emit('message', data);一种。这个就是接收者的整体逻辑。

2 数据发送者

数据发送者是对websocket协议的封装,当用户调研数据发送者的send接口发送数据时,数据发送者会组装成一个websocket协议的包再发送出去。

  1. send(data, options, cb) { 
  2.     const buf = toBuffer(data); 
  3.     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; 
  4.     let opcode = options.binary ? 2 : 1; 
  5.     let rsv1 = options.compress; 
  6.  
  7.     if (this._firstFragment) { 
  8.       this._firstFragment = false
  9.       if (rsv1 && perMessageDeflate) { 
  10.         rsv1 = buf.length >= perMessageDeflate._threshold; 
  11.       } 
  12.       this._compress = rsv1; 
  13.     } else { 
  14.       rsv1 = false
  15.       opcode = 0; 
  16.     } 
  17.  
  18.     if (options.fin) this._firstFragment = true
  19.     // 需要压缩 
  20.     if (perMessageDeflate) { 
  21.       const opts = { 
  22.         fin: options.fin, 
  23.         rsv1, 
  24.         opcode, 
  25.         mask: options.mask, 
  26.         readOnly: toBuffer.readOnly 
  27.       }; 
  28.       // 正在压缩,则排队等待,否则执行压缩 
  29.       if (this._deflating) { 
  30.         this.enqueue([this.dispatch, buf, this._compress, opts, cb]); 
  31.       } else { 
  32.         this.dispatch(buf, this._compress, opts, cb); 
  33.       } 
  34.     } else { 
  35.       // 不需要压缩,直接发送 
  36.       this.sendFrame( 
  37.         Sender.frame(buf, { 
  38.           fin: options.fin, 
  39.           rsv1: false
  40.           opcode, 
  41.           mask: options.mask, 
  42.           readOnly: toBuffer.readOnly 
  43.         }), 
  44.         cb 
  45.       ); 
  46.     } 
  47.   } 

send函数做了一些参数的处理后发送数据,但是如果需要压缩的话,要压缩后才能发送。数据处理完成后调用真正的发送函数

  1. sendFrame(list, cb) { 
  2.     if (list.length === 2) { 
  3.       this._socket.cork(); 
  4.       this._socket.write(list[0]); 
  5.       this._socket.write(list[1], cb); 
  6.       this._socket.uncork(); 
  7.     } else { 
  8.       this._socket.write(list[0], cb); 
  9.     } 
  10.   } 

了解了数据接收者和发送者的逻辑后,我们看一下websocket对象和setSocket函数做了什么事情,websocket对象本质是对TCP socket的封装。它接收来自底层的数据,然后透传给数据接收者,数据接收者处理完后,触发websocket对应的对应的事件,比如message事件。发送数据的时候,websocket会调用数据发送者的接口,数据发送者组装成websocket协议的数据包后再发送出去,架构如下图所示。

接下来我们看看setSocket的逻辑

  1. setSocket(socket, head, maxPayload) { 
  2.     // 数据接收者,负责处理tcp上收到的数据(socket是tcp层的socket) 
  3.     const receiver = new Receiver(...); 
  4.     // 数据发送者,负责发送数据给对端 
  5.     this._sender = new Sender(socket, this._extensions); 
  6.     // 数据接收者,负责解析数据 
  7.     this._receiver = receiver; 
  8.     // net模块的tcp socket 
  9.     this._socket = socket; 
  10.     // 关联起来 
  11.     receiver[kWebSocket] = this; 
  12.     socket[kWebSocket] = this; 
  13.     // 监听接收者的事件,解析数据的时候会回调 
  14.     receiver.on('conclude', receiverOnConclude); 
  15.     // 下面两个事件由Writable触发 
  16.     receiver.on('drain', receiverOnDrain); 
  17.     receiver.on('error', receiverOnError); 
  18.     receiver.on('message', receiverOnMessage); 
  19.     receiver.on('ping', receiverOnPing); 
  20.     receiver.on('pong', receiverOnPong); 
  21.     // 清除定时器 
  22.     socket.setTimeout(0); 
  23.     // 关闭nagle算法 
  24.     socket.setNoDelay(); 
  25.     // 升级请求中,携带的http body,通常是空 
  26.     if (head.length > 0) socket.unshift(head); 
  27.     // 监听tcp底层的事件 
  28.     socket.on('close', socketOnClose); 
  29.     socket.on('data', socketOnData); 
  30.     socket.on('end', socketOnEnd); 
  31.     socket.on('error', socketOnError); 
  32.  
  33.     this.readyState = WebSocket.OPEN
  34.     this.emit('open'); 
  35.   } 

我们看到里面监听了各种事件,下面以data事件为例,看一下处理过程。当tcp socket收到数据的时候会执行socketOnData函数。

  1. function socketOnData(chunk) { 
  2.   // 会调用receiver里的_write函数,其实就是换成到receiver对象上,如果数据解析出错,会触发socket error事件 
  3.   if (!this[kWebSocket]._receiver.write(chunk)) { 
  4.     this.pause(); 
  5.   } 

socketOnData通过接收者的接口把数据传给接收者,接收者会解析数据,然后触发对应的事件,比如message。

  1. receiver.on('message', receiverOnMessage); 
  2. function receiverOnMessage(data) { 
  3.   this[kWebSocket].emit('message', data); 

然后ws的socket对象继续往上层触发message事件。this[kWebSocket]的值是ws提供的socket对象本身。架构图如下。

这就是ws实现websocket协议的基本原理,具体细节可以参考源码。

责任编辑:武晓燕 来源: 编程杂技
相关推荐

2017-07-11 13:58:10

WebSocket

2021-04-21 07:52:39

核心SignalR应用

2010-04-14 14:23:26

2024-01-11 08:53:58

2023-06-27 07:09:39

2023-01-26 01:41:27

核心全局过滤器

2009-06-14 17:19:09

ibmdwWebSphere

2012-09-18 14:23:54

2017-08-17 17:48:06

2021-04-27 18:12:22

WebSocket持久化连接HTTP

2012-09-29 13:18:23

分布式数据库Google Span

2010-04-19 15:29:31

2023-11-28 08:49:01

短轮询WebSocket长轮询

2020-10-13 07:35:22

JUC - Count

2023-04-26 08:39:41

Bitmap元素存储

2021-10-12 17:19:17

Random局限性变量

2022-04-13 08:23:31

Golang并发

2013-01-11 11:20:47

Windows SerVMware vSph

2012-12-03 16:57:37

HDFS

2022-02-22 11:39:13

WebSocketsNode.js开发
点赞
收藏

51CTO技术栈公众号