本文实例讲述了node.js 使用 net 模块模拟 websocket 握手进行数据传递操作。分享给大家供大家参考,具体如下:

websocket 是一种让浏览器与服务器之间建立持久的连接,并能进行双向数据传输的一种协议。

websocket 属性应用层协议,基于tcp传输协议,并复用http的握手通道。

一、如何进行websocket连接。

websocket复用了http的握手通道,客户端通过http请求与服务端进行协商,升级协议。协议升级完后,后面的数据交换则遵照websocket协议。

1、客户端申请协议升级

Request URL: ws://localhost:8888/
Request Method: GET
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: uR5YP/BMO6M24tAFcmHeXw==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

  • Connection: Upgrade 表示要升级协议
  • Upgrade: websocket 表示升级到websocket协议
  • Sec-WebSocket-Version: 13 表示websocket的版本
  • Sec-WebSocket-Key 表示websocket的验证,防止恶意的连接,与服务端响应的Sec-WebSocket-Accept是配套。

2、服务端响应协议升级

Status Code: 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: eS92kXpBNI6fWsCkj6WxH6QeoHs=
Upgrade: websocket

Status Code:101 表示状态码,协议切换。

Sec-WebSocket-Accept 表示服务端响应的校验,与客户端的Sec-WebSocket-Key是配套的。

3、Sec-WebSocket-Accept是如何计算的

将 Sec-WebSocket-Key 的值与 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。

然后通过sha1计算,再转成base64。

const crypto = require('crypto');
function getSecWebSocketAccept(key) {
  return crypto.createHash('sha1')
    .update(key   '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
    .digest('base64');
}
console.log(getSecWebSocketAccept('uR5YP/BMO6M24tAFcmHeXw=='));

4、协议升级完后,后续的数据传输就需要按websocket协议来走。

websocket客户端与服务端通信的最小单位是 帧,由1个或多个帧组成完整的消息。

客户端:将消息切割成多个帧,发送给服务端。

服务端:接收到消息帧,将帧重新组装成完整的消息。

5、数据帧的格式

单位是1个比特位,FIN,PSV1,PSV2,PSV3 占1个比特位,opcode占4个比特位。

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
 - - - - ------- - ------------- ------------------------------- 
|F|R|R|R| opcode|M| Payload len |  Extended payload length  |
|I|S|S|S| (4) |A|   (7)   |       (16/64)      |
|N|V|V|V|    |S|       |  (if payload len==126/127)  |
| |1|2|3|    |K|       |                |
 - - - - ------- - ------------- ------------------------------- 
|   Extended payload length continued, if payload len == 127 |
 ------------------------------- ------------------------------- 
|                |Masking-key, if MASK set to 1 |
 ------------------------------- ------------------------------- 
| Masking-key (continued)    |     Payload Data     |
 ------------------------------- ------------------------------- 
|           Payload Data continued ...        |
 --------------------------------------------------------------- 
|           Payload Data continued ...        |
 --------------------------------------------------------------- 

FIN  占1位,用来表示该帧是否是最后一帧,1表示是,0表示不是。

RSV1,RSV2,RSV3  分别占1位,一般情况下全为0,扩展使用,值的含义由扩展进行定义。

opcode 占4位,表示如何解析后面的数据载荷(Payload Data)。

  %x0 表示一个延续帧,opcode为0时,表示数据传输采用了数据分片,当前的数据帧只是其中一个数据分片。

  %x1 表示这是一个文本帧

  %x2 表示这是一个二进制帧

  %x3-7 保留的操作代码,用于定义后续的非控制帧。

  %x8 表示连接断开

  %x9 表示这是一个ping操作

  %xA 表示这是一个pong操作

  %xB-F 保留的操作代码,用于定义后续的控制帧。

MASK 占1位,表示是否要对数据载荷进行掩码操作。

  客户端向服务端发数据,需要对数据进行掩码操作,服务端向客户端发数据,不需要对数据进行掩码操作。

  如果Mask为1,则Masking-key中会定义一个掩码键,通过该掩码键对数据载荷进行反掩码。客户端发送给服务端的数据帧,MASK都是1。

Payload len 为7位,或7 16位,或7 64位,表示数据载荷的长度,单位字节。

  如果Payload len=0~125,表示,数据的长度为0~125字节。

  如果Payload len=126,表示,后续的2个字节代表一个16位的无符号整数,该整数表示数据的长度。

  如果Payload len=127,表示,后续的8个字节代表一个64位的无符号整数,该整数表示数据的长度。

  如果Payload len占用多个字节,Payload len的二进制表达采用Big-endian。

Masking-key 占0或32位,客户端向服务端发送数据帧,数据载荷都进行了掩码操作,Mask为1,且带了4字节的Masking-key。如果Mask为0,则没有Masking-key。

注意数据载荷的长度,不包括Masking-key的长度。

6、掩码的算法

Masking-key掩码键是由客户端生成的32位随机数,掩码操作不会影响数据载荷的长度。

function unmask(buffer, mask) {
  const length = buffer.length;
  for (var i = 0; i < length; i  ) {
    buffer[i] ^= mask[i & 3];
  }
}

7、实现websocket的握手

const crypto = require('crypto');
const net = require('net');
//计算websocket校验
function getSecWebSocketAccept(key) {
  return crypto.createHash('sha1')
    .update(key   '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
    .digest('base64');
}
//掩码操作
function unmask(buffer, mask) {
  const length = buffer.length;
  for (var i = 0; i < length; i  ) {
    buffer[i] ^= mask[i & 3];
  }
}
//创建一个tcp服务器
let server = net.createServer(function (socket) {
  socket.once('data', function (data) {
    data = data.toString();
    //查看请求头中是否有升级websocket协议的头信息
    if (data.match(/Upgrade: websocket/)) {
      let rows = data.split('\r\n');
      //去掉第一行的请求行
      //去掉请求头的尾部两个空行
      rows = rows.slice(1, -2);
      let headers = {};
      rows.forEach(function (value) {
        let [k, v] = value.split(': ');
        headers[k] = v;
      });
      //判断websocket的版本
      if (headers['Sec-WebSocket-Version'] == 13) {
        let secWebSocketKey = headers['Sec-WebSocket-Key'];
        //计算websocket校验
        let secWebSocketAccept = getSecWebSocketAccept(secWebSocketKey);
        //服务端响应的内容
        let res = [
          'HTTP/1.1 101 Switching Protocols',
          'Upgrade: websocket',
          `Sec-WebSocket-Accept: ${secWebSocketAccept}`,
          'Connection: Upgrade',
          '\r\n'
        ].join('\r\n');
        //给客户端发送响应内容
        socket.write(res);
        //注意这里不要断开连接,继续监听'data'事件
        socket.on('data', function (buffer) {
          //注意buffer的最小单位是一个字节
          //取第一个字节的第一位,判断是否是结束位
          let fin = (buffer[0] & 0b10000000) === 0b10000000;
          //取第一个字节的后四位,得到的一个是十进制数
          let opcode = buffer[0] & 0b00001111;
          //取第二个字节的第一位是否是1,判断是否掩码操作
          let mask = buffer[1] & 0b100000000 === 0b100000000;
          //载荷数据的长度
          let payloadLength = buffer[1] & 0b01111111;
          //掩码键,占4个字节
          let maskingKey = buffer.slice(2, 6);
          //载荷数据,就是客户端发送的实际数据
          let payloadData = buffer.slice(6);
          //对数据进行解码处理
          unmask(payloadData, maskingKey);
          //向客户端响应数据
          let send = Buffer.alloc(2   payloadData.length);
          //0b10000000表示发送结束
          send[0] = opcode | 0b10000000;
          //载荷数据的长度
          send[1] = payloadData.length;
          payloadData.copy(send, 2);
          socket.write(send);
        });
      }
    }
  });
  socket.on('error', function (err) {
    console.log(err);
  });
  socket.on('end', function () {
    console.log('连接结束');
  });
  socket.on('close', function () {
    console.log('连接关闭');
  });
});
//监听8888端口
server.listen(8888);

index.html的代码:

<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
<script>
  var ws = new WebSocket('ws://localhost:8888');
  ws.onopen = function () {
    console.log('连接成功');
    ws.send('你好服务端');
  };
  ws.onmessage = function (ev) {
    console.log('接收数据', ev.data);
  };
  ws.onclose = function () {
    console.log('连接断开');
  };
</script>
</body>
</html>

希望本文所述对大家node.js程序设计有所帮助。

node.js 使用 net 模块模拟 websocket 握手进行数据传递操作示例的更多相关文章

  1. 五分钟学会HTML5的WebSocket协议

    这篇文章主要介绍了五分钟学会HTML5的WebSocket协议,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. 前端监听websocket消息并实时弹出(实例代码)

    这篇文章主要介绍了前端监听websocket消息并实时弹出,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. HTML5 WebSocket实现点对点聊天的示例代码

    这篇文章主要介绍了HTML5 WebSocket实现点对点聊天的示例代码的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. ios – Websockets可以在移动电话上工作吗?

    相关地,我怀疑长轮询客户端可能是实现类似功能的好方法,但我想知道我可能遇到的移动特定问题.到目前为止,我已经读过长时间的轮询请求可能会对电池寿命产生相当大的影响.我还听说iOS以某种方式限制了对单个服务器的连接数量,这可能是个问题.有没有人在使用实时组件的移动应用程序上工作?

  5. ios-swift,objective-c协议实现

    作为隐式解开的可选项.

  6. ios – 红蜘蛛代表没有被召集

    变量不是nil,我有一个很好的连接,url是正确的,但没有调用委托方法.我也正在实现WebSocketDelegate解决方法套接字应该是您的类的属性或变量,以确保它附近.如果仅在函数堆栈上分配它,它将超出范围,并且永远不会调用委托以下是我在项目中使用的代码,以防万一这是link到故事板,以防万一你想要

  7. swift 实现websocket与后台通信(swift 如何构建简单的json字符串)

    一个应用不可避免要与服务器进行通信,主要有,http与socket。http暂时不论,我们先看看socket下面衍生的websocket,今天我就把自己怎么利用websocket与服务器进行交互记录下来:首先你需要集成websocket到自己的项目,如果不明白如何集成,请看上一篇《swift集成websocket库》集成websocket到自己项目后还需要添加SwiftyJSON到自己项目,具体步骤和集成websocket一样。首先打开你项目,记得通过cocoapods生成的.xcworkspace文件打

  8. 如何在Android上托管REST webservices?

    有没有人知道一个用Java编写的能够在Android上托管REST服务的开源Web服务器?

  9. android – WebSocket没有关闭重装应用程序(React Native)

    附:哦,我用Android进行测试.解决方法你的代码看起来不错.通过刷新你的意思是在调试模式下运行时刷新javascript?

  10. android – 移动设备上的WebSocket支持

    对于Android多人游戏的玩家之间的通信,我正在使用WebSocket服务器和客户端的TooTallNate’sJavalibrary,以在Android应用程序中启用WebSocket支持.所以只是要明确指出,移动浏览器中的WebSocket支持对我来说并不重要.不幸的是,用户报告说他们遇到了连接失败或未接收消息等问题.这是移动设备上WebSockets的一般问题,还是客户端代码中的一个缺陷?您是否具有WebSocket客户端库的经验,例如上面的那个?WebSocket技术不是完全正确的解决方案,因此

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部