当前位置:   article > 正文

WebSocket入门篇(一)_websocket学习

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编码格式,例如:<Buffer 63 6c 69 65 6e 74 20 3a 20 68 65 6c 6c 6f>,这里安装依赖进行解码

      • npm i iconv-lite

      • 解码
        console.log(msg);
        解码前:<Buffer 63 6c 69 65 6e 74 20 3a 20 68 65 6c 6c 6f>
        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. <!DOCTYPE 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>Document</title>
        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">0</span></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.         <li>${str}</li>
        113.       `
        114.     }
        115.     let submitBtn = document.querySelector(".submitBtn")
        116.     console.log(submitBtn);
        117.     submitBtn.addEventListener("click",submitFun)
        118.     function submitFun () {
        119.       let input = document.getElementsByClassName('msg')[1]
        120.       let value = input.value
        121.       let obj = {
        122.         event: 'message',
        123.         message: value
        124.       }
        125.       ws.send(JSON.stringify(obj))
        126.       value.value = ''
        127.     }
        128.     let loginBtn = document.getElementById('loginBtn')
        129.     loginBtn.addEventListener('click', login)
        130.     function login () {
        131.       let inputName = document.getElementsByClassName('msg')[0]
        132.       let inputRoomId = document.getElementsByClassName('room')[0]
        133.       let roomId = inputRoomId.value
        134.       let name = inputName.value
        135.       MyName = name
        136.       MyRoom = roomId
        137.       if (name.trim() == '') {
        138.         alert('请输入名称')
        139.       } else {
        140.         let obj = {
        141.           event: 'enter',
        142.           name,
        143.           roomId
        144.         }
        145.         ws.send(JSON.stringify(obj))
        146.         inputName.value = ''
        147.         inputRoomId.value = ''
        148.         document.getElementById('login').style.display = 'none'
        149.         document.getElementById('chatRoom').style.display = 'block'
        150.         isLogin = true
        151.       }
        152.     }
        153.     function clsoeFun () {
        154.       ws.close()
        155.     }
        156.     /*
        157.     接收到服务器发送的心跳检测,
        158.     回复的同时开始开启检测(考虑延时t)如果服务端未再规定时间内再次发送请求,
        159.     理解为服务端异常,触发重连
        160.     */
        161.     function checkServer () {
        162.       clearTimeout(handle)
        163.       handle = setTimeout(function () {
        164.         isReload = true
        165.         webScoketInit()
        166.       }, 1000 + 500)
        167.     }
        168.   }
        169. </script>
        170. </body>
        171. </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. <!DOCTYPE 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>Document</title>
      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="/socket.io/socket.io.js"></script> -->
      31. <script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.5.2/socket.io.js"></script>
      32. <script>
      33.   var socket = io() //初始化
      34.   document.getElementById('btn').addEventListener('click', function (e) {
      35.     var value = document.getElementById('msg').value
      36.     socket.emit('chatEvent', value) //注册发送消息事件,向服务器发送消息
      37.     document.getElementById('msg').value = ''
      38.   })
      39. //监听服务器单独发送的消息    
      40.   socket.on('message', function (msg) {
      41.     console.log(msg);
      42.   })
      43.   //监听服务器注册的广播事件,接收消息
      44.   socket.on('SreverMsg', function (msg) {
      45.     console.log(msg);
      46.   })
      47. </script>
      48. </body>
      49. </html>

    • socket.js引入方式

      • <script src="/socket.io/socket.io.js"></script>

      • <script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.5.2/socket.io.js"></script>

      • 方式二地址

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/241893
推荐阅读
相关标签
  

闽ICP备14008679号