node后台使用WebScoket

  • WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
  • WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
  • 首先来看前端代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <input type="text" id="input" value="test message">
  <input type="button" value="发送消息" id="send">

  <button id="start">开始连接</button>
  <button id="check">查看状态</button>
  <h3>接收到的消息: </h3>
  <p></p>
  <input type="button" value="关闭聊天" id="close">
  <script>
    var pp = document.querySelector('p')
    var start = document.querySelector('#start')
    var check = document.querySelector('#check')
    var ws;
    function wsStart () {
      if ('WebSocket' in window) {
        console.log('浏览器支持webSocket!')
        // 创建一个连接
        ws = new WebSocket('ws://localhost:9999/')
        // 连接建立时触发
        ws.onopen = function (res) {
          console.log('链接已建立',res);
        }
        // 客户端接收到消息时触发
        ws.onmessage = function (response) {
          console.log(response);
          pp.innerHTML = response.data
        }
        // 连接关闭时触发
        ws.onclose = function () {
          console.log('连接已关闭');
          pp.innerHTML = '链接已关闭'
        }
        // 通信出错时触发
        ws.onerror = function() {
          console.log('连接出错');
        }
        console.log(ws.readyState)
      } else {
        console.log('浏览器不支持WebSocket!!!')
      }
    }
    // 开始连接
    start.onclick = function () { 
      wsStart()
    }
    // 查看链接状态
    check.onclick = function () {
      console.log(ws.readyState)
    }
    // 点击发送消息
    document.querySelector('#send').addEventListener('click', function () {
      var send = document.querySelector('#input').value
      // 发送消息
      ws.send(send)
    })
    // 关闭连接
    document.querySelector('#close').addEventListener('click', function () {
      ws.close()
    })
  </script>
</body>
</html>
  • 想要验证WebScoket是否能够成功连接,这时候就需要后台支持了, 这里提供一个简单的node后台服务

// 引入net模块
const net = require('net')

// 引入 crypto 模块
const crypto = require('crypto')

// 将处理请求头
function parseHeader (str) {
  let arr = str.split('\r\n').filter(item => item )
  // 第一行数据为GET /HTTP/1.1 可以丢弃
  arr.shift()
  // console.log(arr);
  // 结果
  /*[
    'Host: localhost:9999',
    'Connection: Upgrade',
    'Pragma: no-cache',
    'Cache-Control: no-cache',
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36',
    'Upgrade: websocket',
    'Origin: file://',
    'Sec-WebSocket-Version: 13',
    'Accept-Encoding: gzip, deflate, br',
    'Accept-Language: zh-CN,zh;q=0.9',
    'Sec-WebSocket-Key: hxaMHw5rY9scIiS7fHLhUg==',
    'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits'
  ]*/
  let head = {}
  arr.forEach((item) => {
    // 需要用 ":" 将数据切割成 key 和 value
    let [name, value] = item.split(':')
    // 去除无用的空格, 将属性名 转为小写
    name = name.replace(/^\s|\s+$/g, '').toLowerCase()
    value = value.replace(/^\s|\s+$/g, '')
    // 组合对象数据
    head[name] = value
  })
  return head
}

// 使用net模块创建服务器, 返回的是一个原始的socket 对象, 与socket对象不同
const server = net.createServer((socket) => {
  // 当有客户端链接到 服务器时, 代码线运行到此处, 但此时WebSocket连接还未建立, 客户端的ws.onopen 不会触发
  console.log('--socket--');
  console.log(socket);

  // 第一次接收到WebSocket 的 http 连接是, 进行处理
  socket.once('data', (buffer) => {
    // 接收到 http 请求头数据
    const str = buffer.toString()
    // console.log('--str--1');
    // console.log(str);
    
    // 将请求头数据转为对象
    const headers = parseHeader(str)
    console.log(headers);
    
    // 判断请求是否为WebSocket 连接
    if(headers['upgrade'] != 'websocket') {
      // 当前请求不是WebSocket 关闭连接
      console.log('--------非WebSocket连接----------');
      socket.end()
    } else if (headers['sec-websocket-version'] != '13') {
      // 判断WebSocket版本是否为13, 防止 其他版本, 造成兼容错误
      console.log('---------WebSocket版本错误------------------');
      socket.end()
    } else {
      // 校验 sec-websocket-version 完成连接
      const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
      const key = headers['sec-websocket-key']
      const hash = crypto.createHash('sha1') // 创建一个签名为 sha1 的哈希对象

      hash.update(`${key}${GUID}`) // 将key和GUID连接后, 更新到 hash
      const result = hash.digest('base64') // 生成 base64 字符串
      const header = `HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: ${result}\r\n\r\n` // 生成供前端校验用的请求头
      socket.write(header) // 返回HTTP头, 告知客户端校验结果, HTTP状态码 101 表示切换协议 			http://httpstatuses.com/101

      // 若客户端校验结果正确, 在控制台 的NetWork 模块可以看到 http 请求状态码变成101 Switching Protocols, 同时客户端的ws.onopen 触发
      console.log(header);
      socket.on('data', (buffer) => {
        const data = decodeWsFrame(buffer)
        console.log(data);
        console.log(data.payloadData && data.payloadData.toString());

        if (data.opcode == 8) {
          socket.end() 
        } else {
          // 接收到客户端数据是的处理, 此处默认为返回接收到的数据
          socket.write(encodeWsFrame({payloadData: `服务端接收到的消息为: ${data.payloadData?data.payloadData.toString(): '' }`}))
        }
      })
    }
  })
  socket.on('end', () => {
    console.log('连接断开!');
  })
})
// 处理收到的数据
function decodeWsFrame(data) {
  let start = 0;
  let frame = {
    isFinal: (data[start] & 0x80) === 0x80,
    opcode: data[start++] & 0xF,
    masked: (data[start] & 0x80) === 0x80,
    payloadLen: data[start++] & 0x7F,
    maskingKey: '',
    payloadData: null
  };
  if (frame.payloadLen === 126) {
    frame.payloadLen = (data[start++] << 8) + data[start++];
  } else if (frame.payloadLen === 127) {
    frame.payloadLen = 0;
    for (let i = 7; i >= 0; --i) {
      frame.payloadLen += (data[start++] << (i * 8));
    }
  }
  if (frame.payloadLen) {
    if (frame.masked) {
      const maskingKey = [
        data[start++],
        data[start++],
        data[start++],
        data[start++]
      ];
      frame.maskingKey = maskingKey;
      frame.payloadData = data
        .slice(start, start + frame.payloadLen)
        .map((byte, idx) => byte ^ maskingKey[idx % 4]);
    } else {
      frame.payloadData = data.slice(start, start + frame.payloadLen);
    }
  }
  console.dir(frame)
  return frame;
}

// 处理发出的数据
function encodeWsFrame(data) {
  const isFinal = data.isFinal !== undefined ? data.isFinal : true,
    opcode = data.opcode !== undefined ? data.opcode : 1,
    payloadData = data.payloadData ? Buffer.from(data.payloadData) : null,
    payloadLen = payloadData ? payloadData.length : 0;
  let frame = [];
  if (isFinal) frame.push((1 << 7) + opcode);
  else frame.push(opcode);

  if (payloadLen < 126) {
    frame.push(payloadLen);
  } else if (payloadLen < 65536) {
    frame.push(126, payloadLen >> 8, payloadLen & 0xFF);
  } else {
    frame.push(127);
    for (let i = 7; i >= 0; --i) {
      frame.push((payloadLen & (0xFF << (i * 8))) >> (i * 8));
    }
  }
  frame = payloadData ? Buffer.concat([Buffer.from(frame), payloadData]) : Buffer.from(frame);

  console.dir(decodeWsFrame(frame));
  return frame;
}
// 监听服务器端口
server.listen(9999, () => {
  console.log('--服务器启动了,访问 http://localhost:9999');
})

使用node运行这个代码, 然后就可以正确连接了, 觉得有用就点个赞吧

猜你喜欢

转载自blog.csdn.net/weixin_45289067/article/details/103237257