• WebSocket入门篇(一)


    1、什么是WebSocket?

    概念

    • WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯网络传输协议

    • WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

    • 在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

    特点

    • TCP连接,于HTTP协议兼容

    • 双向通信,主动推送(服务器端向客户端)

    • 无同源限制,协议标识符是ws(加密是wss)

    通信方式:

    • 单工通信

    • 半双工通信

    • 全双工通信


    对比分析

    • Http:

      • 无法监听连续变化

      • 效率低下

      • 浪费资源

    • Websocket

      • 长连接形式,节省服务器资源和带宽,可以更好的进行实时通信

    • 问题分析

      问:长连接是否消耗服务器资源?

      答:不会,Websocket设计便是为了解决服务器资源的问题,相对于Http只是建立连接,并未在逻辑上进行任何处理或者是查询数据库,所以不会造成系统资源的浪费,同时网络资源上,由于进行通信所以不会占用网络资源,也就是所说的带宽,保持长连接的状态同样不会造成网络资源的浪费,在没有发送消息的时候,整个信道处于空的状态,并不占用带宽。

    相关内容

    • Http:超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服

      务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的传

      输协议,它可以使浏览器更加高效,使网络传输减少

    • ajax轮询:

      • 方式1:设定一个定时器,无论有无结果返回,时间一到就会继续发起请求,这种轮询耗费资源,也不一定能得到想要的数据,这样的轮询是不推荐的

      • 方式2:

        轮询就是在第一次请求的时候,如果返回数据了那么就在成功的回调里面再次发起这个请求,就像递归一样,调用本方法。

        如果时间太久,失败了,同样的再次调用这个请求,也就是本函数。当然,长轮询也需要后台配合,没有数据改变的时候就不用返回,或者约定好逻辑。

      • 资料来源

    2、使用

    ws常用前端库

    • ws(实现原生协议,特点:通用、性能高、定制性强)

    • socket.io(向下兼容,特点:适配性强、性能一般)

    学习地址

    第一个websocket应用

    前置:应用分客户端(client---浏览器端)和服务端(server---node作为服务端)

    • 服务端(node)

      空目录
      npm init -y 
      npm install ws
      服务端 index.js
      const WebSocket = require('ws')
      ​
      const wss = new WebSocket.Server({
        port: 3000
      })
      ​
      wss.on('connection', function connection (ws) {
        console.log('一个客户以连接');
      })
      客户端 index.html

    • 客户端

         var ws = new WebSocket('ws://localhost:3000')
         ws.onopen = function () {
            console.log(ws.readyState);
            ws.send('client:hello')
          }

    • 注意事项

      1. 当服务器接收到来自客户端消息的时候,出现编码问题,显示为GBK编码格式,例如:,这里安装依赖进行解码

        • npm i iconv-lite

        • 解码
          console.log(msg);
          解码前:
          console.log(iconv.decode(msg, 'gbk'));
          解码后:client : hello
        • 更多相关

      3、极简聊天室

      心跳检测

      • 判断客户端和服务端的连接是否牢靠、是否正常,二者可以互相判断,是一个双向的过程,服务端定时的向客户端发送消息,并且客户端及时的做出回应,保证连接正常 ,确保聊天室的在线人数实时封更新

      • 服务端

        1. 给当前用户一个初始化变量,维护当前用户是否登录,判断是否开始心跳检测,同时初始化一个是否处于聊天室的变量-作为在线状态(给定初始值0,登录后改为1,发送心跳检测定义为404,接收到用户的反馈后修改为2,表示当前用户连接状态良好)

        2. 开启定时器,判断用户当前状态为false时主动断开用户连接,更新数据

        3. 接收用户反馈,更新当前用户状态

      • 客户端

        1. 定义初始化函数方便重连

        2. 定义维护需要变量

        3. 定义定时器,用户检测服务端是否再规定时间内发起心跳检测

        4. 捕捉到异常,开启重连

      • 代码

        • 服务端(index.js)

          1. const WebSocket = require('ws');
          2. const iconv = require('iconv-lite'); //解析来自用户的消息
          3. const wss = new WebSocket.Server({
          4. port: 3000
          5. })
          6. let group = {}
          7. const timeInterval = 1000 //发送心跳请求间隔
          8. wss.on('connection', function (ws) {
          9. console.log("一个客户已连接");
          10. ws.isActive = 0 //初始连接状态
          11. ws.isLogin = false
          12. ws.on('message', function (msg) {
          13.   const msgObj = JSON.parse(iconv.decode(msg, 'gbk'))
          14.   if (msgObj.event == 'enter') {
          15.     ws.name = msgObj.name
          16.     ws.roomId = msgObj.roomId
          17.     console.log(msgObj);
          18.     if (typeof group[ws.roomId] === 'undefined') {
          19.       group[ws.roomId] = 1
          20.     } else {
          21.       group[ws.roomId]++
          22.     }
          23.     ws.isActive = 1
          24.     ws.isLogin = true
          25.   }
          26.   if (msgObj.event == 'heartbeat' && msgObj.message === 'pong') {
          27.     ws.isActive = 1
          28.     return
          29.   }
          30.   //实现广播
          31.   wss.clients.forEach((client) => {
          32.     if (client.readyState === WebSocket.OPEN && client.roomId === ws.roomId) {
          33.       msgObj.name = ws.name
          34.       msgObj.num = group[ws.roomId]
          35.       client.send(JSON.stringify(msgObj))
          36.     }
          37.   })
          38. })
          39. ws.on('close', function () {
          40.   if (ws.name) {
          41.     group[ws.roomId]--
          42.   }
          43.   let msgObj = {}
          44.   wss.clients.forEach((client) => {
          45.     if (client.readyState === WebSocket.OPEN && client.roomId === ws.roomId) {
          46.       msgObj.name = ws.name
          47.       msgObj.num = group[ws.roomId]
          48.       msgObj.event = 'exit'
          49.       client.send(JSON.stringify(msgObj))
          50.     }
          51.   })
          52. })
          53. })
          54. setInterval(() => {
          55. wss.clients.forEach((ws) => {
          56.   // 主动发送心跳检测请求
          57.   // 当客户端返回消息之后,主动设置flag为在线
          58.   if (ws.isLogin == true) {
          59.     // console.log(ws.isActive);
          60.     if (ws.isActive == '404') {
          61.       group[ws.roomId]--
          62.       ws.isLogin = false
          63.       return ws.terminate()
          64.     }
          65.     // console.log('---');
          66.     ws.isActive = '404'
          67.     ws.send(JSON.stringify({
          68.       event: 'heartbeat',
          69.       message: 'ping',
          70.       num: group[ws.roomId]
          71.     }))
          72.   }
          73. })
          74. }, timeInterval)

        • 客户端 (index.html)

          1. html>
          2. <html lang="en">
          3. <head>
          4. <meta charset="UTF-8">
          5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
          6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
          7. <title>Documenttitle>
          8. <style>
          9.   input {
          10.     outline-style: none;
          11.     border: 1px solid #ccc;
          12.     border-radius: 3px;
          13.     padding: 6px;
          14.     width: 300px;
          15.     font-size: 14px;
          16.     font-family: "Microsoft soft";
          17.   }
          18.   input:focus {
          19.     border-color: #66afe9;
          20.     outline: 0;
          21.     -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
          22.     box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6)
          23.   }
          24.   #login {
          25.     display: block;
          26.   }
          27.   #chatRoom {
          28.     display: none
          29.   }
          30. style>
          31. head>
          32. <body>
          33. <div id='login'>
          34.   <h1>进入聊天室h1>
          35.   <span>用户名:span><input type="text" class='msg'>
          36.   <span>房间号:span><input type="text" class='room'>
          37.   <button class='btn' id="loginBtn">登录button>
          38. div>
          39. <div id="chatRoom">
          40.   <h1>聊天室h1>
          41.   <p>在线人数:<span id="nums">0span>p>
          42.   <input type="text" class='msg'>
          43.   <button class='submitBtn' >发送消息button>
          44.   <button onclick="clsoeFun()">关闭连接button>
          45.   <ul id="content">
          46.   ul>
          47. div>
          48. <script>
          49.   var MyName, MyRoom //数据存储,重连时使用
          50.   var isLogin = false //判断是否为登录状态
          51.   var isReload = false //当监听到连接断开时,更新为true连接成功后恢复false
          52.   var content = document.getElementById('content')
          53.   var nums = document.getElementById('nums')
          54.   var handle       //作为定时器监听服务器状态,服务器断开开始尝试重连
          55.   webScoketInit() //初始化
          56.   function webScoketInit () {
          57.     var ws = new WebSocket('ws://localhost:3000')
          58.     ws.onopen = function () {
          59.       console.log(ws.readyState);
          60.        
          61.       if (isReload) {
          62.         // 如果重新连接服务器,将原本数据重新传入
          63.         let obj = {
          64.           event: 'enter',
          65.           name:MyName,
          66.           roomId:MyRoom,
          67.         }
          68.         ws.send(JSON.stringify(obj))
          69.         isReload = false
          70.       }
          71.     }
          72.     ws.onmessage = (event) => {
          73.       if (!isLogin) return;
          74.       let obj = JSON.parse(event.data)
          75.       showFun(obj)
          76.     }
          77.     ws.onclose = function () {
          78.       console.log("close" + ws.readyState);
          79.     }
          80.     ws.onerror = function () {
          81.       console.log('error' + ws.readyState);
          82.       // 连接失败后一秒进行短线重连
          83.       setTimeout(function () {
          84.         isReload = true
          85.         webScoketInit()
          86.       }, 1000)
          87.     }
          88.     function showFun (obj) {
          89.       let str = ''
          90.       switch (obj.event) {
          91.         case 'enter':
          92.           str = `欢迎${obj.name}进入聊天室`
          93.           nums.innerHTML = obj.num
          94.           break
          95.         case 'exit':
          96.           str = `${obj.name}离开进入聊天室`
          97.           nums.innerHTML = obj.num
          98.           break
          99.         case 'auth':
          100.           return
          101.         case 'heartbeat':
          102.           checkServer() //timeInterval+ t
          103.           ws.send(JSON.stringify({
          104.             event: 'heartbeat',
          105.             message: 'pong'
          106.           }))
          107.           return
          108.         default:
          109.           str = `${obj.name}:${obj.message}`
          110.       }
          111.       content.innerHTML += `
          112.        
          113. ${str}
  •       `
  •     }
  •     let submitBtn = document.querySelector(".submitBtn")
  •     console.log(submitBtn);
  •     submitBtn.addEventListener("click",submitFun)
  •     function submitFun () {
  •       let input = document.getElementsByClassName('msg')[1]
  •       let value = input.value
  •       let obj = {
  •         event: 'message',
  •         message: value
  •       }
  •       ws.send(JSON.stringify(obj))
  •       value.value = ''
  •     }
  •     let loginBtn = document.getElementById('loginBtn')
  •     loginBtn.addEventListener('click', login)
  •     function login () {
  •       let inputName = document.getElementsByClassName('msg')[0]
  •       let inputRoomId = document.getElementsByClassName('room')[0]
  •       let roomId = inputRoomId.value
  •       let name = inputName.value
  •       MyName = name
  •       MyRoom = roomId
  •       if (name.trim() == '') {
  •         alert('请输入名称')
  •       } else {
  •         let obj = {
  •           event: 'enter',
  •           name,
  •           roomId
  •         }
  •         ws.send(JSON.stringify(obj))
  •         inputName.value = ''
  •         inputRoomId.value = ''
  •         document.getElementById('login').style.display = 'none'
  •         document.getElementById('chatRoom').style.display = 'block'
  •         isLogin = true
  •       }
  •     }
  •     function clsoeFun () {
  •       ws.close()
  •     }
  •     /*
  •     接收到服务器发送的心跳检测,
  •     回复的同时开始开启检测(考虑延时t)如果服务端未再规定时间内再次发送请求,
  •     理解为服务端异常,触发重连
  •     */
  •     function checkServer () {
  •       clearTimeout(handle)
  •       handle = setTimeout(function () {
  •         isReload = true
  •         webScoketInit()
  •       }, 1000 + 500)
  •     }
  •   }
  • script>
  • body>
  • html>

4、socket.io

socket.io官网

W3Cschool

特点:

  • 易用性:socket.io封装了服务端和客户端,使用起来非常简单方便。

    • 内置心跳检测:短线重连

    • 广播自动过滤当前发消息用户

  • 跨平台:socket.io支持跨平台,这就意味着你有了更多的选择,可以在自己喜欢的平台下开发实时应用。

  • 自适应:它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5。

初步使用

  • 服务端

    空目录
    npm init -y
    npm i socket-io
    npm i express
    1. //server.js
    2. const app = require('express')();
    3. const http = require('http').createServer(app);
    4. const io = require('socket.io')(http)
    5. app.get('/', function (req, res) {
    6. res.sendFile(__dirname + '/index.html') //创建默认打开文件
    7. })
    8. io.on('connection', function (socket) {
    9. console.log('a socket is connected');
    10. socket.on('chatEvent', function (msg) { //监听客户端注册的charEvent事件,接收发送过来的消息
    11.   console.log(msg);
    12.   // socket.send('server 收到')
    13.   //广播,向聊天室其他成员发送消息,本人并不会接收,同时也无需接收
    14.   socket.broadcast.emit('SreverMsg', msg) //服务器注册ServerMsg事件,向所有用户广播,广播消息,实现群聊功能,自动过滤当前用户
    15. //单独向当前用户发送消息
    16.   socket.send(msg)
    17. })
    18. })
    19. http.listen(3000, function () {
    20. console.log("3000端口已开启");
    21. })

  • 客户端

    1. //index.html
    2. html>
    3. <html lang="en">
    4. <head>
    5. <meta charset="UTF-8">
    6. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    7. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    8. <title>Documenttitle>
    9. <style>
    10.   input {
    11.     outline-style: none;
    12.     border: 1px solid #ccc;
    13.     border-radius: 3px;
    14.     padding: 6px;
    15.     width: 300px;
    16.     font-size: 14px;
    17.     font-family: "Microsoft soft";
    18.   }
    19.   input:focus {
    20.     border-color: #66afe9;
    21.     outline: 0;
    22.     -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
    23.     box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6)
    24.   }
    25. style>
    26. head>
    27. <input type="text" id='msg'>
    28. <button id='btn'>发送消息button>
    29. <body>
    30. <script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.5.2/socket.io.js">script>
    31. <script>
    32.   var socket = io() //初始化
    33.   document.getElementById('btn').addEventListener('click', function (e) {
    34.     var value = document.getElementById('msg').value
    35.     socket.emit('chatEvent', value) //注册发送消息事件,向服务器发送消息
    36.     document.getElementById('msg').value = ''
    37.   })
    38. //监听服务器单独发送的消息    
    39.   socket.on('message', function (msg) {
    40.     console.log(msg);
    41.   })
    42.   //监听服务器注册的广播事件,接收消息
    43.   socket.on('SreverMsg', function (msg) {
    44.     console.log(msg);
    45.   })
    46. script>
    47. body>
    48. html>

  • socket.js引入方式

  • 相关阅读:
    Redis 之 SessionCallback & RedisCallback 使用
    java家用电器远程管理系统
    我的创作纪念日-五周年
    【flink报错】flink cdc无主键时的操作
    Linux远程工具专家推荐(二)
    Raft介绍
    VCS工具学习笔记(7)
    新手学习:ArcGIS对shp文件裁剪
    Hadoop发展史和生态圈介绍
    WebGPU 工具分享 - WGSL 代码高亮插件(VSCode)与预处理工具
  • 原文地址:https://blog.csdn.net/Flying____fish/article/details/126841950