【一文读懂】WebRTC协议

WebRTC(Web Real-Time Communication)协议

WebRTC(Web Real-Time Communication)是一种支持浏览器和移动应用程序之间进行 实时音频、视频和数据通信 的协议。它使得开发者能够在浏览器中实现高质量的 P2P(点对点)实时通信,而无需安装插件或第三方软件。WebRTC 主要用于视频通话、语音聊天、在线协作和数据传输等应用场景。

核心功能

WebRTC 提供了以下几个关键功能:

  1. 音视频通信:WebRTC 支持通过浏览器进行音频和视频的实时传输,用户之间可以进行高质量的语音和视频通话。
  2. P2P 数据传输:WebRTC 不仅支持音视频流,还支持点对点的数据通道(Data Channel)传输,适合进行文件传输、屏幕共享和实时协作。
  3. 低延迟:WebRTC 专门优化了数据传输过程,以减少通信中的延迟,使得实时通信更加顺畅。

工作原理

WebRTC 使用了一系列底层协议和技术来实现点对点通信。WebRTC 的工作流程通常包括以下几个关键步骤:

  1. 建立连接(Signaling)
    • 在 WebRTC 中,信令(Signaling) 是客户端用于交换通信所需的元数据(如网络信息、音视频编解码信息、媒体能力等)的一种过程。信令不是 WebRTC 协议的一部分,但它是 WebRTC 通信的必要步骤。
    • 信令的内容包括:协商媒体(音视频)格式、网络路径、设备信息等。
    • 通常,信令使用 WebSockets、HTTP 或其他协议进行实现。
    • 信令过程包括:
      • Offer(提议):发起方创建会话请求,发送给接收方。
      • Answer(应答):接收方回应发起方的请求,确认会话设置。
      • ICE candidates(ICE 候选者):每个端点通过收集网络候选地址来交换,以帮助建立最佳的 P2P 连接。
  2. 网络连接(ICE、STUN 和 TURN)
    • ICE(Interactive Connectivity Establishment):用于在 NAT 后的网络环境中建立端到端的连接。ICE 是 WebRTC 连接的关键组成部分,它帮助客户端发现并连接到彼此。
    • STUN(Session Traversal Utilities for NAT):STUN 服务器帮助客户端了解自己在 NAT 后的公网 IP 地址。
    • TURN(Traversal Using Relays around NAT):TURN 服务器在 P2P 连接无法直接建立时提供数据转发服务,确保通信的可靠性。TURN 作为最后的解决方案,通常会导致更高的延迟,因此只有在需要时才使用。
  3. 媒体流传输(RTP/RTCP)
    • RTP(Real-Time Transport Protocol):RTP 是 WebRTC 用于音频和视频流的传输协议。它允许在网络中实时地传输数据包,并为这些数据包添加时间戳,确保音视频数据的正确顺序。
    • RTCP(Real-Time Control Protocol):RTCP 用于监控 RTP 会话的质量,并提供流控制和同步。
  4. 数据传输(DataChannel)
    • RTCDataChannel:WebRTC 支持数据通道(DataChannel),使得浏览器间可以通过 P2P 传输任意数据(包括文本、文件、图像等)。数据通道提供了低延迟的点对点数据传输能力,常用于文件传输、屏幕共享等应用。

关键技术

WebRTC 由多种技术组成,其中最重要的包括:

  1. getUserMedia:用于获取用户的音频和视频输入设备(如麦克风和摄像头)的权限。它会返回一个包含音视频流的对象。

    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
        .then(stream => {
            // 显示视频流
            const videoElement = document.getElementById('my-video');
            videoElement.srcObject = stream;
        })
        .catch(error => console.log('Error accessing media devices: ', error));
    
  2. RTCPeerConnection:用于建立、维护和管理 P2P 连接。它负责处理网络连接、音视频编解码、带宽管理等任务。

    const peerConnection = new RTCPeerConnection(configuration);
    peerConnection.addStream(localStream); // 添加本地音视频流
    
    // 建立连接后,发送媒体流
    peerConnection.createOffer()
        .then(offer => peerConnection.setLocalDescription(offer))
        .then(() => {
            // 将 offer 发送给接收方
        });
    
  3. RTCDataChannel:用于建立点对点的数据传输通道,可以传输任意类型的数据。

    javascript复制编辑const dataChannel = peerConnection.createDataChannel('chat');
    dataChannel.onopen = () => console.log('Data channel open');
    dataChannel.onmessage = (event) => console.log('Received message: ', event.data);
    
    // 发送数据
    dataChannel.send('Hello, WebRTC!');
    

应用场景

  1. 视频通话:WebRTC 可以用于构建视频会议应用,如 Zoom、Google Meet 等。
  2. 语音通话:WebRTC 支持语音通话,广泛应用于 IP 电话、语音助手等。
  3. 文件传输:通过 RTCDataChannel,WebRTC 可以用于点对点的文件传输。
  4. 实时协作:WebRTC 用于多人在线编辑、白板共享等实时协作工具。
  5. 直播:WebRTC 可以支持低延迟的视频直播,适用于游戏直播、网络教学等领域。

实现过程

1. 获取音视频流(getUserMedia)

getUserMedia 是 WebRTC 中用于访问用户音频和视频设备的 API。通过它,你可以获取麦克风和摄像头的权限,从而获取用户的音视频流。

示例:获取视频和音频流
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then(stream => {
        // 获取视频流后,可以将其显示在视频标签上
        const videoElement = document.getElementById('localVideo');
        videoElement.srcObject = stream;

        // 创建 RTCPeerConnection 实例(将在后面讨论)
        const peerConnection = new RTCPeerConnection();
        // 将本地流添加到连接
        stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
    })
    .catch(error => {
        console.error('Error accessing media devices.', error);
    });
  • getUserMedia:请求用户设备的音视频流。
  • 返回的 MediaStream 可以用于显示、录制或传输。
2. 创建点对点连接(RTCPeerConnection)

WebRTC 使用 RTCPeerConnection 来管理媒体流的传输。它代表了与另一个客户端的点对点连接。

示例:创建 RTCPeerConnection 并添加本地流
const peerConnection = new RTCPeerConnection({
    iceServers: [
        { urls: 'stun:stun.l.google.com:19302' } // 使用 STUN 服务器
    ]
});

// 添加本地流到连接
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then(stream => {
        const localVideo = document.getElementById('localVideo');
        localVideo.srcObject = stream;
        stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
    });
  • STUN 服务器:STUN(Session Traversal Utilities for NAT)帮助客户端发现自己的公共 IP 地址,用于 NAT 穿透。
3. 信令交换(Signal)

WebRTC 协议并不直接定义信令交换的方式,因此你需要自己实现信令交换。信令过程用于交换连接的元数据,如会话描述(SDP)和 ICE 候选者等。

  1. 创建 Offer(发起方)
peerConnection.createOffer()
    .then(offer => {
        return peerConnection.setLocalDescription(offer);  // 设置本地 SDP
    })
    .then(() => {
        // 将 offer 发送给对方(通过信令服务器)
        signalingServer.send({ type: 'offer', offer: peerConnection.localDescription });
    });
  • SDP(Session Description Protocol):描述了音视频流的编码、传输等信息。
  1. 设置 Answer(接收方)

接收方收到 Offer 后,创建 Answer 并回复:

signalingServer.on('offer', offer => {
    peerConnection.setRemoteDescription(new RTCSessionDescription(offer))
        .then(() => peerConnection.createAnswer())
        .then(answer => {
            return peerConnection.setLocalDescription(answer);  // 设置本地 SDP
        })
        .then(() => {
            // 将 answer 发送给发起方
            signalingServer.send({ type: 'answer', answer: peerConnection.localDescription });
        });
});
  1. 交换 ICE 候选者

在连接过程中,客户端会收集并交换 ICE 候选者(候选网络路径)。这些候选者用于寻找最佳的连接路径。

peerConnection.onicecandidate = event => {
    if (event.candidate) {
        // 发送 ICE 候选者到对方
        signalingServer.send({ type: 'ice-candidate', candidate: event.candidate });
    }
};

// 接收对方的 ICE 候选者
signalingServer.on('ice-candidate', candidate => {
    peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
});
  1. 建立连接并处理媒体流

当信令交换完成并且 ICE 候选者交换完毕后,WebRTC 将会建立一个完整的点对点连接,音视频流会开始传输。

示例:显示远端视频流

peerConnection.ontrack = event => {
    const remoteVideo = document.getElementById('remoteVideo');
    remoteVideo.srcObject = event.streams[0];  // 获取远程流并显示
};

5. 数据通道(RTCDataChannel)

WebRTC 还支持 RTCDataChannel,用于在两个客户端之间进行低延迟的点对点数据传输(例如文件传输、聊天信息等)。

示例:创建并使用数据通道
const dataChannel = peerConnection.createDataChannel('chat');

// 监听数据通道的消息
dataChannel.onmessage = event => {
    console.log('Received message:', event.data);
};

// 发送数据
dataChannel.send('Hello from WebRTC!');

6. 断开连接

当通信结束时,你可以通过关闭 PeerConnection 来断开连接并释放资源。

peerConnection.close();

WebRTC-Streamer开源项目

项目介绍

WebRTC-Streamer 是一个开源工具集,旨在简化实时音视频数据流的传输与集成,主要通过 WebRTC 技术实现低延迟的音视频流传输。开发者无需深入理解复杂的底层协议即可轻松将实时音视频功能集成到自己的应用中。该项目特别设计了高效的音视频流处理功能,支持多种数据来源,如 V4L2 捕获设备RTSP 流屏幕捕捉 等,适用于多种实时传输场景。

快速启动

WebRTC-Streamer 提供了简便的集成方式。以下是一个通过 HTML 和 JavaScript 快速搭建基本实时音视频流服务的示例代码:

<html>
<head>
    <script src="libs/adapter.min.js"></script>
    <script src="webrtcstreamer.js"></script>
</head>
<body>
<script>
var webRtcServer;
window.onload = function() {
    webRtcServer = new WebRtcStreamer(document.getElementById("video"), location.protocol+"//" + location.hostname + ":8000");
    webRtcServer.connect("rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov");
}
window.onbeforeunload = function() {
    if (webRtcServer !== null) {
        webRtcServer.disconnect();
    }
}
</script>
<video id="video" controls autoplay muted></video>
</body>
</html>
代码解析
  • 引入了 adapter.min.jswebrtcstreamer.js 两个必要的 JavaScript 库。
  • 创建一个 WebRtcStreamer 实例,指定本地服务器地址及目标 RTSP 视频流地址。
  • 页面加载时自动连接至 RTSP 流,播放视频。
  • 页面卸载时,断开连接,释放资源。
应用案例与最佳实践

示例 1:直播演示

  • 使用 WebRTC-Streamer 可以通过简化的 Web 组件方式轻松展示来自 RTSP 源的实时视频流。
<webrtc-streamer url="rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov">

示例 2:地图上的直播流

  • 配合 Google Map APIWebRTC-Streamer 可以在地图上显示多个实时视频流,适用于 监控交通管理 等领域。
其它生态项目

WebRTC-Streamer 还支持一系列相关生态项目,以扩展其功能和适用范围:

  • webrtc-streamer-card:为 Home Assistant 提供的卡片插件,允许从 WebRTC-Streamer 服务中拉取零延迟视频流,适用于智能家居。
  • rpi-webrtc-streamer:面向 树莓派 系列微控制器的 WebRTC 流媒体软件包,支持在边缘设备上实现高效的音视频处理。
  • Live555 Integration:通过整合 Live555 Media Server,增强 WebRTC-Streamer 在处理非标准音视频格式方面的能力,扩展其应用场景。

附录:WebRTC-Streamer项目地址

https://gitcode.com/gh_mirrors/we/webrtc-streamer/?utm_source=artical_gitcode&index=bottom&type=card&webUrl&isLogin=1

附录:webrtcstreamer.js源码

// webrtcstreamer.js
var WebRtcStreamer = (function() {

    /** 
     * Interface with WebRTC-streamer API
     * @constructor
     * @param {string} videoElement - id of the video element tag
     * @param {string} srvurl -  url of webrtc-streamer (default is current location)
    */
    var WebRtcStreamer = function WebRtcStreamer (videoElement, srvurl) {
        if (typeof videoElement === "string") {
            this.videoElement = document.getElementById(videoElement);
        } else {
            this.videoElement = videoElement;
        }
        this.srvurl           = srvurl || location.protocol+"//"+window.location.hostname+":"+window.location.port;
        this.pc               = null;    
    
        this.mediaConstraints = { offerToReceiveAudio: true, offerToReceiveVideo: true };
    
        this.iceServers = null;
        this.earlyCandidates = [];
    }
    
    WebRtcStreamer.prototype._handleHttpErrors = function (response) {
        if (!response.ok) {
            throw Error(response.statusText);
        }
        return response;
    }
    
    /** 
     * Connect a WebRTC Stream to videoElement 
     * @param {string} videourl - id of WebRTC video stream
     * @param {string} audiourl - id of WebRTC audio stream
     * @param {string} options -  options of WebRTC call
     * @param {string} stream  -  local stream to send
    */
    WebRtcStreamer.prototype.connect = function(videourl, audiourl, options, localstream) {
        this.disconnect();
        
        // getIceServers is not already received
        if (!this.iceServers) {
            console.log("Get IceServers");
            
            fetch(this.srvurl + "/api/getIceServers")
                .then(this._handleHttpErrors)
                .then( (response) => (response.json()) )
                .then( (response) =>  this.onReceiveGetIceServers(response, videourl, audiourl, options, localstream))
                .catch( (error) => this.onError("getIceServers " + error ))
                    
        } else {
            this.onReceiveGetIceServers(this.iceServers, videourl, audiourl, options, localstream);
        }
    }
    
    /** 
     * Disconnect a WebRTC Stream and clear videoElement source
    */
    WebRtcStreamer.prototype.disconnect = function() {		
        if (this.videoElement?.srcObject) {
            this.videoElement.srcObject.getTracks().forEach(track => {
                track.stop()
                this.videoElement.srcObject.removeTrack(track);
            });
        }
        if (this.pc) {
            fetch(this.srvurl + "/api/hangup?peerid=" + this.pc.peerid)
                .then(this._handleHttpErrors)
                .catch( (error) => this.onError("hangup " + error ))
    
            
            try {
                this.pc.close();
            }
            catch (e) {
                console.log ("Failure close peer connection:" + e);
            }
            this.pc = null;
        }
    }    
    
    /*
    * GetIceServers callback
    */
    WebRtcStreamer.prototype.onReceiveGetIceServers = function(iceServers, videourl, audiourl, options, stream) {
        this.iceServers       = iceServers;
        this.pcConfig         = iceServers || {"iceServers": [] };
        try {            
            this.createPeerConnection();
    
            var callurl = this.srvurl + "/api/call?peerid=" + this.pc.peerid + "&url=" + encodeURIComponent(videourl);
            if (audiourl) {
                callurl += "&audiourl="+encodeURIComponent(audiourl);
            }
            if (options) {
                callurl += "&options="+encodeURIComponent(options);
            }
            
            if (stream) {
                this.pc.addStream(stream);
            }
    
                    // clear early candidates
            this.earlyCandidates.length = 0;
            
            // create Offer
            this.pc.createOffer(this.mediaConstraints).then((sessionDescription) => {
                console.log("Create offer:" + JSON.stringify(sessionDescription));
                
                this.pc.setLocalDescription(sessionDescription)
                    .then(() => {
                        fetch(callurl, { method: "POST", body: JSON.stringify(sessionDescription) })
                            .then(this._handleHttpErrors)
                            .then( (response) => (response.json()) )
                            .catch( (error) => this.onError("call " + error ))
                            .then( (response) =>  this.onReceiveCall(response) )
                            .catch( (error) => this.onError("call " + error ))
                    
                    }, (error) => {
                        console.log ("setLocalDescription error:" + JSON.stringify(error)); 
                    });
                
            }, (error) => { 
                alert("Create offer error:" + JSON.stringify(error));
            });
    
        } catch (e) {
            this.disconnect();
            alert("connect error: " + e);
        }	    
    }
    
    
    WebRtcStreamer.prototype.getIceCandidate = function() {
        fetch(this.srvurl + "/api/getIceCandidate?peerid=" + this.pc.peerid)
            .then(this._handleHttpErrors)
            .then( (response) => (response.json()) )
            .then( (response) =>  this.onReceiveCandidate(response))
            .catch( (error) => this.onError("getIceCandidate " + error ))
    }
                        
    /*
    * create RTCPeerConnection 
    */
    WebRtcStreamer.prototype.createPeerConnection = function() {
        console.log("createPeerConnection  config: " + JSON.stringify(this.pcConfig));
        this.pc = new RTCPeerConnection(this.pcConfig);
        var pc = this.pc;
        pc.peerid = Math.random();		
        
        pc.onicecandidate = (evt) => this.onIceCandidate(evt);
        pc.onaddstream    = (evt) => this.onAddStream(evt);
        pc.oniceconnectionstatechange = (evt) => {  
            console.log("oniceconnectionstatechange  state: " + pc.iceConnectionState);
            if (this.videoElement) {
                if (pc.iceConnectionState === "connected") {
                    this.videoElement.style.opacity = "1.0";
                }			
                else if (pc.iceConnectionState === "disconnected") {
                    this.videoElement.style.opacity = "0.25";
                }			
                else if ( (pc.iceConnectionState === "failed") || (pc.iceConnectionState === "closed") )  {
                    this.videoElement.style.opacity = "0.5";
                } else if (pc.iceConnectionState === "new") {
                    this.getIceCandidate();
                }
            }
        }
        pc.ondatachannel = function(evt) {  
            console.log("remote datachannel created:"+JSON.stringify(evt));
            
            evt.channel.onopen = function () {
                console.log("remote datachannel open");
                this.send("remote channel openned");
            }
            evt.channel.onmessage = function (event) {
                console.log("remote datachannel recv:"+JSON.stringify(event.data));
            }
        }
        pc.onicegatheringstatechange = function() {
            if (pc.iceGatheringState === "complete") {
                const recvs = pc.getReceivers();
            
                recvs.forEach((recv) => {
                  if (recv.track && recv.track.kind === "video") {
                    console.log("codecs:" + JSON.stringify(recv.getParameters().codecs))
                  }
                });
              }
        }
    
        try {
            var dataChannel = pc.createDataChannel("ClientDataChannel");
            dataChannel.onopen = function() {
                console.log("local datachannel open");
                this.send("local channel openned");
            }
            dataChannel.onmessage = function(evt) {
                console.log("local datachannel recv:"+JSON.stringify(evt.data));
            }
        } catch (e) {
            console.log("Cannor create datachannel error: " + e);
        }	
        
        console.log("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig) );
        return pc;
    }
    
    
    /*
    * RTCPeerConnection IceCandidate callback
    */
    WebRtcStreamer.prototype.onIceCandidate = function (event) {
        if (event.candidate) {
            if (this.pc.currentRemoteDescription)  {
                this.addIceCandidate(this.pc.peerid, event.candidate);					
            } else {
                this.earlyCandidates.push(event.candidate);
            }
        } 
        else {
            console.log("End of candidates.");
        }
    }
    
    
    WebRtcStreamer.prototype.addIceCandidate = function(peerid, candidate) {
        fetch(this.srvurl + "/api/addIceCandidate?peerid="+peerid, { method: "POST", body: JSON.stringify(candidate) })
            .then(this._handleHttpErrors)
            .then( (response) => (response.json()) )
            .then( (response) =>  {console.log("addIceCandidate ok:" + response)})
            .catch( (error) => this.onError("addIceCandidate " + error ))
    }
                    
    /*
    * RTCPeerConnection AddTrack callback
    */
    WebRtcStreamer.prototype.onAddStream = function(event) {
        console.log("Remote track added:" +  JSON.stringify(event));
        
        this.videoElement.srcObject = event.stream;
        var promise = this.videoElement.play();
        if (promise !== undefined) {
          promise.catch((error) => {
            console.warn("error:"+error);
            this.videoElement.setAttribute("controls", true);
          });
        }
    }
            
    /*
    * AJAX /call callback
    */
    WebRtcStreamer.prototype.onReceiveCall = function(dataJson) {
    
        console.log("offer: " + JSON.stringify(dataJson));
        var descr = new RTCSessionDescription(dataJson);
        this.pc.setRemoteDescription(descr).then(() =>  { 
                console.log ("setRemoteDescription ok");
                while (this.earlyCandidates.length) {
                    var candidate = this.earlyCandidates.shift();
                    this.addIceCandidate(this.pc.peerid, candidate);				
                }
            
                this.getIceCandidate()
            }
            , (error) => { 
                console.log ("setRemoteDescription error:" + JSON.stringify(error)); 
            });
    }	
    
    /*
    * AJAX /getIceCandidate callback
    */
    WebRtcStreamer.prototype.onReceiveCandidate = function(dataJson) {
        console.log("candidate: " + JSON.stringify(dataJson));
        if (dataJson) {
            for (var i=0; i<dataJson.length; i++) {
                var candidate = new RTCIceCandidate(dataJson[i]);
                
                console.log("Adding ICE candidate :" + JSON.stringify(candidate) );
                this.pc.addIceCandidate(candidate).then( () =>      { console.log ("addIceCandidate OK"); }
                    , (error) => { console.log ("addIceCandidate error:" + JSON.stringify(error)); } );
            }
            this.pc.addIceCandidate();
        }
    }
    
    
    /*
    * AJAX callback for Error
    */
    WebRtcStreamer.prototype.onError = function(status) {
        console.log("onError:" + status);
    }
    
    return WebRtcStreamer;
    })();
    
    if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
        window.WebRtcStreamer = WebRtcStreamer;
    }
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = WebRtcStreamer;
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/970068.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

M3U8工作原理以及key解密视频流详解

文章目录 前言一、M3U8是什么&#xff1f;二、HLS—M3U8的工作原理1.分段视频流2.生成播放列表3.客户端请求和解析4.片段下载和播放 三、.m3u8文件内部是什么样的&#xff1f;四、简单介绍下AES-128算法五、拿到KEY后如何去解密&#xff1f;1.手动解密.ts文件2.前人栽树&#x…

重读《Java面试题,10万字208道Java经典面试题总结(附答案)》

最近重读了这篇文章&#xff0c;对很多概念模糊的地方加了拓展和补充。 目录 1、JDK 和 JRE 有什么区别&#xff1f; 2、 和 equals 的区别是什么&#xff1f; 3、final 在 java 中有什么作用&#xff1f; 4、java 中的 Math.round(-1.5) 等于多少&#xff1f; 5、String…

AI知识库 - Cherry Studio

1 引言&#xff1a; 最近 DeepSeek 很火啊&#xff0c;想必大家都知道&#xff0c;DeepSeek 这个开源的模型出来后&#xff0c;因其高质量能力和R1 的思维链引发了大家本地部署的热潮。我也不例外&#xff0c;本地部署了一个 14B 的模型&#xff0c;然后把&#xff0c;感觉傻傻…

Ai笔记本-Ainote(IOS 应用)帮助支持页面

简介 一个 iCloud 实时同步的笔记工具&#xff0c;支持markdown 格式解析、分享 PDF文件。 方便存储各种AI生成的markdown 格式回答&#xff0c;自动保存到 iCloud 永不丢失&#xff0c;支持分享为 PDF 格式笔记。 联系方式 如果您在使用过程中有任何问题或建议&#xff0c;…

1、Prometheus 监控系统(上)

Prometheus 监控系统&#xff08;上&#xff09; 认识一下 PrometheusPrometheus 的特点Prometheus 的生态组件Prometheus 的工作模式Prometheus 的工作流程Prometheus 的局限性&#xff1a; 部署 PrometheusPrometheust Server 端安装和相关配置部署 Exporters部署 Node Expor…

【设计模式】-工厂模式(简单工厂、工厂方法、抽象工厂)

工厂模式(简单工厂、工厂方法、抽象工厂) 介绍 简单工厂模式 简单工厂模式不属于23种GoF设计模式之一&#xff0c;但它是一种常见的设计模式。它提供了一种创建对象的接口&#xff0c;但由子类决定要实例化的类是哪一个。这样&#xff0c;工厂方法模式让类的实例化推迟到子类…

应急响应(linux 篇,以centos 7为例)

一、基础命令 1.查看已经登录的用户w 2.查看所有用户最近一次登录&#xff1a;lastlog 3.查看历史上登录的用户还有登录失败的用户 历史上所有登录成功的记录 last /var/log/wtmp 历史上所有登录失败的记录 Lastb /var/log/btmp 4.SSH登录日志 查看所有日志&#xff1a;…

【实测】用全志A733平板搭建一个端侧Deepseek算力平台

随着DeepSeek 的蒸馏技术的横空出世&#xff0c;端侧 SoC 芯片上运行大模型成为可能。那么端侧芯片跑大模型的效果如何呢&#xff1f;本文将在全志 A733 芯片平台上部署一个 DeepSeek-R1:1.5B 模型&#xff0c;并进行实测效果展示。 端侧平台环境 设备&#xff1a;全志A733平板…

nuxt中引入element-ui组件控制台报错问题

在使用element-ui组件的外层加一层 <client-only placeholder"Loading..."><van-button type"primary">主要按钮</van-button> </client-only> 实际使用&#xff1a; <div class"tab"><client-only placehol…

数据结构(考研)

线性表 顺序表 顺序表的静态分配 //线性表的元素类型为 ElemType//顺序表的静态分配 #define MaxSize10 typedef int ElemType; typedef struct{ElemType data[MaxSize];int length; }SqList;顺序表的动态分配 //顺序表的动态分配 #define InitSize 10 typedef struct{El…

【广州大学主办,发表有保障 | IEEE出版,稳定EI检索,往届见刊后快至1个月检索】第二届电气技术与自动化工程国际学术会议 (ETAE 2025)

第二届电气技术与自动化工程国际学术会议 (ETAE 2025) The 2nd International Conference on Electrical Technology and Automation Engineering 大会官网&#xff1a;http://www.icetae.com/【更多详情】 会议时间&#xff1a;2025年4月25-27日 会议地点&#xff1a…

【弹性计算】弹性计算的技术架构

弹性计算的技术架构 1.工作原理2.总体架构3.控制面4.数据面5.物理设施层 虽然弹性计算的产品种类越来越多&#xff0c;但不同产品的技术架构大同小异。下面以当前最主流的产品形态 —— 云服务器为例&#xff0c;探查其背后的技术秘密。 1.工作原理 云服务器通常以虚拟机的方…

EasyRTC轻量级SDK:智能硬件音视频通信资源的高效利用方案

在智能硬件这片广袤天地里&#xff0c;每一份资源的精打细算都关乎产品的生死存亡。随着物联网技术的疾速演进&#xff0c;实时音视频通信功能已成为众多设备的标配。然而&#xff0c;硬件资源的捉襟见肘&#xff0c;让开发者们常常陷入两难境地。EasyRTC&#xff0c;以它的极致…

Linux | 进程相关概念(进程、进程状态、进程优先级、环境变量、进程地址空间)

文章目录 进程概念1、冯诺依曼体系结构2、进程2.1基本概念2.2描述进程-PCB2.3组织进程2.4查看进程2.5通过系统调用获取进程标识符2.6通过系统调用创建进程-fork初识fork の 头文件与返回值fork函数的调用逻辑和底层逻辑 3、进程状态3.1状态3.2进程状态查看命令3.2.1 ps命令3.2.…

【ESP32接入国产大模型之Deepseek】

【ESP32接入国产大模型之Deepseek】 1. Deepseek大模型1.1 了解Deepseek api1.2 Http接口鉴权1.3. 接口参数说明1.3.1 请求体(request)参数1.3.2 模型推理 2. 先决条件2.1 环境配置2.2 所需零件 3. 核心代码3.1 源码分享3.2 源码解析3.3 连续对话修改后的代码代码说明示例输出注…

OSI 参考模型和 TCP/IP 参考模型

数据通信是很复杂的&#xff0c;很难在一个协议中完成所有功能。因此在制定协议时经常采用的思路是将复杂的数据通信功能由若干协议分别完成&#xff0c;然后将这些协议按照一定的方式组织起来。最典型的是采用分层的方式来组织协议&#xff0c;每一层都有一套清晰明确的功能和…

C# CultureInfo 地区影响字符串

问题 线上遇到有玩家资源加载异常&#xff0c;发现资源路径出现异常字符&#xff1a; 发现是土耳其语下字符串转小写不符合预期&#xff1a; "I".ToLower() -> ı 解决方案 String.ToLower 改成 String.ToLowerInvariant 全局修改禁用文化差异&#xff1a;ht…

蓝桥与力扣刷题(108 将有序数组转换成二叉搜索树)

题目&#xff1a;给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡二叉搜索树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,-3,null,9]…

python学opencv|读取图像(六十二)使用cv2.morphologyEx()形态学函数实现图像梯度处理

【1】引言 前序已经学习了腐蚀和膨胀的单独作用函数&#xff0c;还研究了按照不同顺序调用腐蚀和膨胀函数调整图像效果&#xff0c;相关文章包括且不限于&#xff1a; python学opencv|读取图像&#xff08;六十一&#xff09;先后使用cv2.dilate()函数和cv2.erode()函数实现图…

(萌新入门)如何从起步阶段开始学习STM32 —— 0.碎碎念

目录 前言与导论 碎碎念 所以&#xff0c;我到底需要知道哪些东西呢 从一些基础的概念入手 常见的工具和说法 ST公司 MDK5 (Keil5) CubeMX 如何使用MDK5的一些常用功能 MDK5的一些常见的设置 前言与导论 非常感谢2301_77816627-CSDN博客的提问&#xff0c;他非常好奇…