• 深入理解WebSocket,让你入门音视频


    😄作者简介: 小曾同学.com,一个致力于测试开发的博主⛽️,主要职责:测试开发、CI/CD
    如果文章知识点有错误的地方,还请大家指正,让我们一起学习,一起进步。😊 座右铭:不想当开发的测试,不是一个好测试✌️。
    如果感觉博主的文章还不错的话,还请点赞、收藏哦!👍

    原文在这里https://testerhome.com/topics/34427

    WebSocket浅析

    一、 WebSocket概念

    WebSocket是一种在单个TCP连接上进行全双工通信的网络协议。意为:经过一次TCP握手就可以直接创建持久性连接,进而可实现服务端和客户端双向数据传输。websocket的协议标识是ws和wss

    websocket的应用场景:

    • 在线聊天
    • 协作文档编辑
    • 大型多人在线游戏
    • 股票交易应用
    • webrtc

    二、为什么需要WebSocket协议

    2.1 WebSocket的出现主要是为了弥补HTTP半双工通信的缺陷。

    在websocket没有出现之前,为了让http能够实现即时通信,前辈们也做了一些研究,常用的有三种方法:

    1. HTTP轮询

      HTTP轮询(polling):在固定的时间间隔,由浏览器向服务器发起http请求,无论服务器中的数据有没有更新,都会给客户端作出响应。

      但如果知道信息交付的精确间隔,那么轮询也是一个好的方案,但对于一些实时的数据是不能预测的,所有就会导致发出一些不必要的请求。

    2. 长轮询

      长轮询( long polling):客户端向服务端请求信息,并在设定的时间段内打开一个连接。服务器如果没有任何信息,会保持请求打开,直到有客户端可用的信息,或者直到指定的超时时间用完为止。

      长轮询中客户端必须频繁地重连到服务器以读取服务端的信息,会增大服务端到压力。

    3. 流化技术

      客户端向服务端发起一个长连接请求,服务端收到请求后响应它并不断更新连接状态,以确保连接在客户端与服务端之间一直有效。服务端可以通过这个连接将数据主动推送到客户端。

      但存在一个问题:每当服务器有需要交付给客户端的信息时,它就会更新响应,但是服务器从不发出完成http响应,从而导致连接一直打开,在这种情况下,代理和防火墙可能会缓存一个响应,就会导致信息交付的延迟增加。

    以上三种方法都实现了近乎实时的通信,但都涉及HTTP请求和响应,当然也包含了许多附加和不必要的延迟,此外,在每一种情况下,客户端必须主动给服务器发送消息,且客户端都必须等待请求返回,才能发出后续的请求,再一次增加了延迟。

    2.2 websocket与http有着良好的兼容性

    默认端口是80和443, 并且握手阶段采用HTTP协议,因此握手的时候不容易屏蔽,能通过各种的HTTP代理。

    三、WebSocket通信原理

    以七牛webrtc demo为例:https://demo-rtc.qnsdk.com/

    在这里插入图片描述

    详解:

    每个WebSocket连接都开始于一个http请求,这个请求和其他请求类似,但是websocket连接请求中包含一个特殊的首标,Upgrade:websocket,意为:客户端想将HTTP协议升级为websocket协议。如果服务端同意,则响应Connection:Upgrade,同时 101 Switching Protocols 也表示协议切换成功,这个过程叫做初始握手

    但为了成功地完成握手,websocket服务器必须根据客户端请求消息中的Sec-WebSocket-Key,响应SHA-1的信息摘要,即:Sec-WebSocket-Accept 。其中:Sec-WebSocket-Key是一个随机字符串,服务端接收到Key之后,会对其进行加密,并进行base-64编码,然后将结果响应给客户端;客户端将Key使用同样的加密算法进行加密并进行base-64编码,当得到的值与服务端响应的值保持一致时,表示真正的握手成功。

    至此,HTTP已经完成了它所有的工作,接下来就是完全按照Websocket协议进行通信。

    四、WebRTC中websocket的使用

    在webrtc中websocket充当信令服务器,那何为信令服务器?信令可理解为信息的传递或者命令的执行,主要是传输用户的一些信息。在webrtc中如果没有信令服务器,webrtc之间是不能够通信的。

    在这里插入图片描述

    蓝色区域表示发送端(Caller)和接收端(Callee),如果两者想要传递媒体数据,那么有两个信息必须经过信令服务器交换;

    1)媒体信息:通过SDP协议进行交换,SDP是一个描述多媒体连接内容的协议,其中包含了分辨率、编解码方式、格式、是否支持音频、视频等。例如,Caller想给Callee发一个H264的视频,需要先问一下Callee能不能解H264的视频,如果可以解码,则可以通信;如果Callee只能解H265的视频,则不可通信。

    2)网络信息:通常指的是ip地址、端口、以及数据存放地址,我们称之为ICE,这是一个基于offer/answer模式解决NAT穿越的协议集合。在ICE中主要包含STUN+TURN主要协议。当Callee想要接收数据时,需要将所有的网络相关的信息传到信令服务器,信令服务器再转发给Caller,Caller拿到信息之后,发现处于同一个局域网,则可通信,如果不在同一个局域网,则通过TURN协议进行NAT穿越,再利用Relay转发,两者即可通信。

    因为TCP的超时时间为60s,如果要保持长连接的话,最好加一个ping/pong的心跳检测,就是服务端给客户端发一个ping的消息(绿色),客户端再给服务端发送一个pong的消息(红色),就是在server端加一个定时调用函数setInterval,即可实现

    setInterval(() => {
            connect.send('ping');
        }, 3000);
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    五、使用 node.js 实现简易聊天室

    第一步:实现服务器

    安装第三方依赖库:nodejs-websocket

    具体实现如下

    const ws = require('nodejs-websocket')
    const PORT = 3003
    const TYPE_ENTER = 0
    const TYPE_LEAVE = 1
    const TYPE_MSG = 2
    
    //1. 记录当前连接上来的总的用户数量
    let count = 0
    //2.conn每个连接到服务器的用户,都会有一个conn
    const server = ws.createServer(conn => {
        console.log('有用户进来')
        count++
        conn.userName = `用户${count}`
       
        broadcast({
            type:TYPE_ENTER,
            msg:`${conn.userName}进入了聊天室`,
            time:new Date().toLocaleTimeString()
        })
    
        //每当接收到用户传递过来的数据,这个text事件会被触发
        conn.on('text',data =>{
            console.log('接受到用户的数据',data)  
            broadcast({
                type:TYPE_MSG,
                msg:data,
                time:new Date().toLocaleTimeString()
            })
        })
      
        conn.on('close',() => {
            console.log('连接断开了')
            count--
            broadcast({
                type:TYPE_LEAVE,
                msg:`${conn.userName}离开了聊天室`,
                time:new Date().toLocaleTimeString()
            })
    
        })
        conn.on('error',() => {
            console.log('用户连接异常')
        }) 
    })
    
    // 通过广播,给所有的用户发送消息
    function broadcast(msg){
        //server.connections:表示所有用户
        server.connections.forEach(item => {
            item.send(JSON.stringify(msg))
        })
    }
    server.listen(PORT,() => {
        console.log('websocket服务启动成功了,监听了端口' + PORT)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    第二步:实现客户端

    <html>
        <head>
            <title>在线聊天室title>
        head>
        <body>   
            <input type="text" placeholder="输入内容">
            <button>发送请求button>
            
            <div>div>
            <script>     
                var input = document.querySelector('input');
                var button = document.querySelector('button');
                var div = document.querySelector('div');
                const TYPE_ENTER = 0
                const TYPE_LEAVE = 1
                const TYPE_MSG = 2
                 //创建websocket对象
                var socket = new WebSocket('ws://localhost:3003');
                socket.addEventListener('open',function(){
                    div.innerHTML = '连接服务器成功'
    
                })
                //主动给websocket服务发送消息
                button.addEventListener('click',function(){
                    var value = input.value
                    socket.send(value)
                    input.value = ''
                })
                socket.addEventListener('message',function(e){
                    var data = JSON.parse(e.data)
                    var dv = document.createElement('div')
                    dv.innerText = data.msg +'------'+data.time
                    if(data.typy === TYPE_ENTER){
                        dv.style.color = 'green'
                    }else if(data.typy === TYPE_LEAVE){
                        dv.style.color = 'red'
                    }else{
                        dv.style.color = 'blue'
                    }
                    div.appendChild(dv)
                })
                socket.addEventListener('close',function(){
                    div.innerHTML = '服务断开链接'
                })
            script>
    
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    demo展示:

    在这里插入图片描述

  • 相关阅读:
    HashMap略解
    砖家测评:腾讯云标准型S5服务器和s6性能差异和租用价格
    python:绘制回归预测结果真实值和预测值之间的散点密度图
    SpringBoot+Vue项目校园防疫管理系统
    解决问题:Replace `‘vue‘;⏎` with `“vue“;`
    【RocketMQ】集群的搭建与高可用
    第一期 微信云开发小程序介绍-生活智打卡
    particles.js粒子特效(常用登录页背景)
    使用Docker安装和部署Elasticsearch出现问题以及解决方案
    【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 游乐园门票 (200分) - 三语言AC题解(Python/Java/Cpp)
  • 原文地址:https://blog.csdn.net/weixin_42182599/article/details/131343152