MediaStream使用webRtc多窗口传递

news2024/11/27 16:43:13

最近在做音视频通话,有个需求是把当前会话弄到另一个窗口单独展示,但是会话是属于主窗口的,多窗口通信目前不能直接传递对象,所以想着使用webRtc在主窗口和兄弟窗口建立连接,把主窗口建立会话得到的MediaStream传递给兄弟窗口;

主窗口界面

在这里插入图片描述

兄弟窗口界面

在这里插入图片描述

主窗口点击发送本地媒体后

在这里插入图片描述

呼叫封装_主窗口
// 主窗口WebRtc_呼叫
class CallWindowWebRtc {
    // 广播通道
    curBroadcas: BroadcastChannel;
    // webRtc点对点连接
    peerConnection: RTCPeerConnection;
    // 广播通道
    constructor({broadcastChannelName = 'yyh_text'}) {
        this.curBroadcas = CreateBroadcastChannel(broadcastChannelName);
        this.curBroadcas.onmessage = (event) => this.onMessage(event);
        // 处理页面刷新和关闭方法
        this.handlePageRefreshClose();
    }

    // 接收消息
    onMessage(event: any) {
        const msg = event.data;

        // 收到远端接听消息
        if (msg.type === 'answer') {
            this.handleSedRemoteSDP(msg);
        }
        if (msg.type === 'hangup') {
            this.hangup();
        }
    }
    // 发送消息_方法
    postMessage(msg: any) {
        this.curBroadcas.postMessage(msg);
    }
    // 处理页面刷新和关闭方法
    handlePageRefreshClose() {
        window.addEventListener('beforeunload', () => {
            this.postMessage({data: {event: 'mainPageRefresh', eventName: '主页面刷新了'}});
        });
        window.addEventListener('beforeunload', () => {
            this.postMessage({data: {event: 'mainPageClose', eventName: '主页面关闭了'}});
        });
    }
    // 处理媒体停止
    handleStreamStop() {
        if (!this.peerConnection) { return; }
        let localStream = this.peerConnection.getSenders();
        localStream.forEach((item: any) => {
            item.track.stop();
        })
    }
    // 卸载
    unmount() {
        if (this.peerConnection) {
            let localStream = this.peerConnection.getSenders();
            localStream.forEach((item: any) => {
                item.track.stop();
            })
            this.peerConnection.close();
            this.peerConnection.onicecandidate = null;
            this.peerConnection.ontrack = null;
            this.peerConnection.oniceconnectionstatechange = null;
            this.peerConnection = null;
        }
        if (this.curBroadcas) {
            this.curBroadcas.onmessage = null;
            this.curBroadcas = null;
        }
    }
    // ICE连接状态回调
    handleOniceconnectionstatechange(event) {
        // 1.检查网络配置
        if (this.peerConnection.iceConnectionState === 'checking') {
            // 发送订阅消息_给外部
            this.onSubscriptionMsg({event: 'iceConnectionState', code: 'checking', eventName: '检查网络配置'});
            // 2.ICE候选者被交换并成功建立了数据传输通道
        } else if (this.peerConnection.iceConnectionState === 'connected') {
            // 发送订阅消息_给外部
            this.onSubscriptionMsg({event: 'iceConnectionState', code: 'connected', eventName: 'ICE候选者被交换并成功建立了数据传输通道'});
            // 3.当连接被关闭或由于某种原因(如网络故障、对端关闭连接等)中断时
        } else if (this.peerConnection.iceConnectionState === 'disconnected') {
            this.hangup();
            this.onSubscriptionMsg({event: 'iceConnectionState', code: 'connected', eventName: 'ICE接被关闭或由于某种原因断开'});
        };
    }
    // 发送订阅消息给外部
    onSubscriptionMsg(msg: {}) {
    }
    // 创建全新的 RTCPeerConnection
    handleCreateNewPerrc() {
        // 停止媒体 
        this.handleStreamStop();
        // 最好每一次通话都单独创建一个RTCPeerConnection对象,防止复用导致ICE候选的收集受到之前连接的影响,导致画面延迟加载,或其它异常问题无法排查处理;
        this.peerConnection = new RTCPeerConnection();
        this.peerConnection.onicecandidate = (event) => this.onIcecandidate(event);
        this.peerConnection.ontrack = (event) => this.handleOnTrack(event);
        this.peerConnection.oniceconnectionstatechange  = (event) => this.handleOniceconnectionstatechange(event);
    }
    // 呼叫
    call(stream?: MediaStream) {
        return new Promise((resolve, reject) => {
            this.handleCreateNewPerrc();
            this.handleStreamAddPeerConnection(stream);
            this.handleCreateOffer().then((offer) => {
                // 存入本地offer
                this.handleLocalDes(offer).then(() => {
                    // 给远端发sdp
                    this.handleSendSDP();
                    resolve({code: 1, message: '发送sdp给远端'});
                }).catch(() => {
                    reject({code: 0, message: '存入本地offer失败'});
                });
            }).catch(() => {
                reject({code: 0, message: '创建offer失败'});
            });
        });
    }
    // 挂断
    hangup() {
        if (!this.peerConnection) {
            return;
        }
        if (this.peerConnection.signalingState === 'closed') {
            return;
        };
        this.postMessage({type: 'hangup'});
        // 停止媒体流
        let localStream = this.peerConnection.getSenders();
        localStream.forEach((item: any) => {
            item.track.stop();
        });
        // 关闭peerConection
        this.peerConnection.close();

    }
    // 1.获取本地媒体流
    getUserMediaToStream(audio: true, video: true) {
        return navigator.mediaDevices.getUserMedia({audio, video});
    }
    // 2.把媒体流轨道添加到 this.peerConnection 中
    handleStreamAddPeerConnection(stream?: MediaStream) {
        if (!stream) {
            stream = new MediaStream();
        }
        const tmpStream = new MediaStream();
        const audioTracks = stream.getAudioTracks();
        const videoTracks = stream.getVideoTracks();
        if (audioTracks.length) {
            tmpStream.addTrack(audioTracks[0]);
            this.peerConnection.addTrack(tmpStream.getAudioTracks()[0], tmpStream);
        }
        if (videoTracks.length) {
            tmpStream.addTrack(videoTracks[0]);
            this.peerConnection.addTrack(tmpStream.getVideoTracks()[0], tmpStream);
        }
    }
    // 3.创建createOffer
    handleCreateOffer() {
        return this.peerConnection.createOffer();
    }
    // 4.设置本地SDP描述
    handleLocalDes(offer) {
        return this.peerConnection.setLocalDescription(offer);
    }
    // 5.发送SDP消息给远端
    handleSendSDP() {
        if (this.peerConnection.signalingState === 'have-local-offer') {
            // 使用某种方式将offer传递给窗口B
            const answerData = {
                type: this.peerConnection.localDescription.type,
                sdp: this.peerConnection.localDescription.sdp
            };
            this.curBroadcas.postMessage(answerData);
        }
    }
    // 6.收到远端接听_存远端SDP
    handleSedRemoteSDP(msg: any) {
        // if (this.peerConnection.signalingState === 'stable') { return; }
        const answerData = msg;
        const answer = new RTCSessionDescription(answerData);
        return this.peerConnection.setRemoteDescription(answer);
    }
    // 7.用于处理ICE
    onIcecandidate(event) {
        // 如果event.candidate存在,说明有一个新的ICE候选地址产生了
        if (event.candidate) {  
            // 将ICE候选地址, 通常需要通过信令服务器发送给对端
            this.curBroadcas.postMessage({type: 'candidate', candidate: JSON.stringify(event.candidate)});  
        } else {  
            // 如果event.candidate不存在,则表示所有候选地址都已经收集完毕
            // 在某些情况下,这可能意味着ICE候选过程已完成,但并非总是如此
            // 因为在某些情况下,会有多轮ICE候选生成
        }
    }
    // 8.监听轨道赋值给video标签onTrack
    handleOnTrack(event: any) {
        let remoteStream = event.streams[0];
        // 发送订阅消息_给外部
        this.onSubscriptionMsg({event: 'remoteStreams', eventName: '远端视频准备好了', remoteStream})
    }
    
}
兄弟窗口封装
// 其它窗口WebRtc_接听
class AnswerWindowWebRtc {
    // 广播通道
    curBroadcas: BroadcastChannel;
    // webRtc点对点连接
    peerConnection: RTCPeerConnection;
    
    constructor({broadcastChannelName = 'yyh_text'}) {
        this.curBroadcas = CreateBroadcastChannel(broadcastChannelName);
        this.curBroadcas.onmessage = (event) => this.onMessage(event);
        this.handlePageRefreshClose();
    }

    
    // 接收消息
    onMessage(event: any) {
        const msg = event.data;
        // 收到远端SDP
        if (msg.type === 'offer') {
            this.handleCreateNewPerrc();
            this.onSubscriptionMsg({event: 'incomingCall', eventName: '收到新的来电', offer: msg});
        }
        // 保存这些 candidate,candidate 信息主要包括 IP 和端口号,以及所采用的协议类型等
        if (msg.type === 'candidate') {
            const candidate = new RTCIceCandidate(JSON.parse(event.data.candidate));
            this.peerConnection.addIceCandidate(candidate);
        }
        if (msg.type === 'hangup') {
            this.hangup();
        }
    }
    // 发送消息_方法
    postMessage(msg: any) {
        this.curBroadcas.postMessage(msg);
        
    }
    // 收到来电后创建全新的
    handleCreateNewPerrc() {
        // 停止媒体
        this.handleStreamStop();
        // 最好每一次通话都单独创建一个RTCPeerConnection对象,防止复用导致ICE候选的收集受到之前连接的影响,导致画面延迟加载,或其它异常问题无法排查处理;
        this.peerConnection = new RTCPeerConnection();
        this.peerConnection.ontrack = (event) => this.handleOnTrack(event);
        this.peerConnection.oniceconnectionstatechange  = (event) => this.handleOniceconnectionstatechange();
    }
    // 处理页面刷新和关闭方法
    handlePageRefreshClose() {
        window.addEventListener('beforeunload', () => {
            this.postMessage({data: {event: 'otherPageRefresh', eventName: '其它页面刷新了'}});
        });
        window.addEventListener('beforeunload', () => {
            this.postMessage({data: {event: 'otherPageClose', eventName: '其它页面关闭了'}});
        });
    }
    // 处理媒体停止
    handleStreamStop() {
        if (!this.peerConnection) { return; }
        let localStream = this.peerConnection.getSenders();
        localStream.forEach((item: any) => {
            item.track.stop();
        })
    }
    // 卸载
    unmount() {
        if (this.peerConnection) {
            let localStream = this.peerConnection.getSenders();
            localStream.forEach((item: any) => {
                item.track.stop();
            })
            this.peerConnection.close();
            this.peerConnection.onicecandidate = null;
            this.peerConnection.ontrack = null;
            this.peerConnection.oniceconnectionstatechange = null;
            this.peerConnection = null;
        }
        if (this.curBroadcas) {
            this.curBroadcas.onmessage = null;
            this.curBroadcas = null;
        }
    }
    // 挂断
    hangup() {
        if (!this.peerConnection) {
            return;
        }
        if (this.peerConnection.signalingState === 'closed') {
            return;
        };
        this.postMessage({type: 'hangup'});
        // 停止媒体流
        let localStream = this.peerConnection.getSenders();
        localStream.forEach((item: any) => {
            item.track.stop();
        });
        // 关闭peerConection
        this.peerConnection.close();
    }
    // ICE连接状态回调
    handleOniceconnectionstatechange() {
        // 1.检查网络配置
        if (this.peerConnection.iceConnectionState === 'checking') {
            // 发送订阅消息_给外部
            this.onSubscriptionMsg({event: 'iceConnectionState', code: 'checking', eventName: '检查网络配置'});
            // 2.ICE候选者被交换并成功建立了数据传输通道
        } else if (this.peerConnection.iceConnectionState === 'connected') {
            // 发送订阅消息_给外部
            this.onSubscriptionMsg({event: 'iceConnectionState', code: 'connected', eventName: 'ICE候选者被交换并成功建立了数据传输通道'});
            // 3.当连接被关闭或由于某种原因(如网络故障、对端关闭连接等)中断时
        } else if (this.peerConnection.iceConnectionState === 'disconnected') {
            this.hangup();
            this.onSubscriptionMsg({event: 'iceConnectionState', code: 'connected', eventName: 'ICE接被关闭或由于某种原因断开'});
        };
    }
    // 发送订阅消息给外部
    onSubscriptionMsg(msg: {}) {
    }
    // 接听
    answer(msg: any, stream?: MediaStream) {
        return new Promise((resolve, reject) => {
            this.handleStreamAddPeerConnection(stream);
            this.handleSedRemoteSDP(msg).then(() => {
                this.handleCreateAnswer().then((offer) => {
                    this.handleLocalDes(offer).then(() => {
                        this.handleSendAnswerRemoteMsg();
                        resolve({code: 1, message: '已发送接听消息给发送端,等待ice确认'});
                    }).catch(() => {
                        reject({code: 0, message: '本地sdp存储失败'});
                    });
                }).catch(() => {
                    reject({code: 0, message: '创建接听offer失败'});
                });
            }).catch(() => {
                reject({code: 0, message: '远端sdp存储失败'});
            })
        });
    }
    // 1.获取本地媒体流
    getUserMediaToStream(audio: true, video: true) {
        return navigator.mediaDevices.getUserMedia({audio, video});
    }
    // 2.把媒体流轨道添加到 this.peerConnection 中
    handleStreamAddPeerConnection(stream?: MediaStream) {
        if (!stream) {
            stream = new MediaStream();
        }
        const tmpStream = new MediaStream();
        const audioTracks = stream.getAudioTracks();
        const videoTracks = stream.getVideoTracks();
        if (audioTracks.length) {
            tmpStream.addTrack(audioTracks[0]);
            this.peerConnection.addTrack(tmpStream.getAudioTracks()[0], tmpStream);
        }
        if (videoTracks.length) {
            tmpStream.addTrack(videoTracks[0]);
            this.peerConnection.addTrack(tmpStream.getVideoTracks()[0], tmpStream);
        }
    }
    // 3.收到远端邀请_存远端SDP
    handleSedRemoteSDP(msg: any) {
        const answerData = msg;
        const answer = new RTCSessionDescription(answerData);
        return this.peerConnection.setRemoteDescription(answer);
    }
    // 4.接听
    handleCreateAnswer() {
        return this.peerConnection.createAnswer();
    }
    // 5.设置本地SDP描述
    handleLocalDes(offer) {
        return this.peerConnection.setLocalDescription(offer);
    }
    // 6.发送接听消息给远端
    handleSendAnswerRemoteMsg() {
        const answerData = {
            type: this.peerConnection.localDescription.type,
            sdp: this.peerConnection.localDescription.sdp
        };
        // 使用某种方式将answer传递回窗口A
        this.curBroadcas.postMessage(answerData);
    }
    // 7.监听轨道赋值给video标签onTrack
    handleOnTrack(event: any) {
        let remoteStream = event.streams[0];
        // 发送订阅消息_给外部
        this.onSubscriptionMsg({event: 'remoteStreams', eventName: '远端视频准备好了', remoteStream})
    }
}
导出方法
// 创建广播通道_建立两个窗口的广播通道,方便互发消息
function CreateBroadcastChannel(channelName: string) {
    return new BroadcastChannel(channelName);
};
export {CallWindowWebRtc, AnswerWindowWebRtc};
vue3主窗口使用
<template>
    <div class="root">
        <h1>主窗口</h1>
        <div class="loca_right_parent_wrap">
            <div class="loca_video_wrap">
                <div>
                    <button @click="methods.handleVideoToTracks()">发送本地视频给兄弟窗口</button>
                </div>
                <video id="locaVideo" autoplay controls src="./one.mp4" loop width="640" height="480" muted></video>
            </div>
            <div class="remote_video_wrap">
                <div class="tip_text">兄弟窗口视频预览 <button @click="methods.handleHangUp()">挂断</button> </div>
                <video id="remoteVideo" autoplay controls width="640" height="480"></video>
            </div>
        </div>
    </div>
</template>


<script lang="ts">
import {onMounted, onBeforeUnmount} from 'vue';
import {CallWindowWebRtc} from '@/Util/MultiWindowSharingStream';
let curWebRtc: any = null;
export default {
    setup() {
        const methods = {
            handleVideoToTracks() {
                if (curWebRtc) {
                    curWebRtc.unmount && curWebRtc.unmount();
                }
                curWebRtc = new CallWindowWebRtc({});
                // 获取轨道
                const myVideo = document.getElementById('locaVideo');
                const myVideoStream = (myVideo as any).captureStream(30);
                // 呼叫
                curWebRtc.call(myVideoStream);
                
                // 拦截订阅消息
                curWebRtc.onSubscriptionMsg = (msg) => {
                    if (msg.event && msg.event === 'remoteStreams') {
                        const {remoteStream} = msg;
                        const remoteRef = document.getElementById('remoteVideo');
                        (remoteRef as HTMLVideoElement).srcObject = remoteStream;
                        // (remoteRef as HTMLVideoElement).play();
                    }
                }

            },
            handleHangUp() {
                if (curWebRtc) {
                    curWebRtc.hangup && curWebRtc.hangup();
                }
            },
            // 处理组件卸载
            handleUnmount(){
                if (curWebRtc) {
                    curWebRtc.unmount && curWebRtc.unmount();
                }
            }
        }
        onMounted(() => {
           
        });
        onBeforeUnmount(() => {
            methods.handleUnmount();
            
        })
        return {
            methods,
        }
    }
}
</script>

<style lang="scss" scoped>
.root{
    .loca_right_parent_wrap{
        display: flex;
    }
    .loca_video_wrap{
        box-sizing: border-box;
        padding: 0 5px;
        video{
            background: #000;
        }
    }
    .remote_video_wrap{
        box-sizing: border-box;
        padding: 0 5px;
        .tip_text{
            height: 28px;
        }
        video{
            background: #000;
        }
    }
}
</style>
vue3兄弟窗口使用
<template>
    <div class="root">
        <h1>兄弟窗口</h1>
        <div class="loca_right_parent_wrap">
            <div class="loca_video_wrap">
                <div class="tip_text">本地视频预览</div>
                <video id="locaVideo" autoplay controls src="./two.mp4" loop width="640" height="480"></video>
            </div>
            <div class="remote_video_wrap">
                <div class="tip_text">主窗口视频预览 <button @click="methods.handleHangUp()">挂断</button></div>
                <video id="remoteVideo" autoplay controls width="640" height="480"></video>
            </div>
        </div>
    </div>
</template>


<script lang="ts">
import {onMounted, onBeforeUnmount} from 'vue';
import {AnswerWindowWebRtc} from '@/Util/MultiWindowSharingStream';
let curWebRtc: any = null;
export default {
    setup() {
        const methods = {
            handleVideoToTracks() {
            },
            handleInitAnswerOne() {
                if (curWebRtc) {
                    curWebRtc.close && curWebRtc.close();
                    curWebRtc = null;
                }
                curWebRtc = new AnswerWindowWebRtc({});
                const remoteRef = document.getElementById('remoteVideo');
                const myVideo = document.getElementById('locaVideo');
                // 拦截订阅消息
                curWebRtc.onSubscriptionMsg = (msg) => {
                    // 收到远端媒体流
                    if (msg.event && msg.event === 'remoteStreams') {
                        const {remoteStream} = msg;
                        const remoteRef = document.getElementById('remoteVideo');
                        (remoteRef as HTMLVideoElement).srcObject = remoteStream;
                        // (remoteRef as HTMLVideoElement).play();
                    }
                    // 收到新的来电
                    if (msg.event && msg.event === 'incomingCall') {
                        (remoteRef as HTMLVideoElement).srcObject = null;
                        // 获取轨道
                        const locaVideoStream = (myVideo as any).captureStream(30);
                        const {offer} = msg;
                        curWebRtc.answer(offer, locaVideoStream);
                    }
                }

            },
            handleHangUp() {
                if (curWebRtc) {
                    curWebRtc.hangup && curWebRtc.hangup();
                }
            },
            handleUnmount() {
                if (curWebRtc) {
                    curWebRtc.unmount && curWebRtc.unmount();
                    curWebRtc = null;
                }
            }
        }
        onMounted(() => {
            methods.handleInitAnswerOne();
        });
        onBeforeUnmount(() => {
            methods.handleUnmount();
        });
        return {
            methods
        }
    }
}
</script>

<style lang="scss" scoped>
.root{
    .loca_right_parent_wrap{
        display: flex;
    }
    .loca_video_wrap{
        box-sizing: border-box;
        padding: 0 5px;
        .tip_text{
            height: 28px;
        }
        video{
            background: #000;
        }
    }
    .remote_video_wrap{
        box-sizing: border-box;
        padding: 0 5px;
        .tip_text{
            height: 28px;
        }
        video{
            background: #000;
        }
    }
}
</style>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系统稳定性建设

说到系统稳定性&#xff0c;不知道大家会想起什么&#xff1f;大多数人会觉得这个词挺虚的&#xff0c;不知道系统稳定性指的是什么。 一年前看到这个词&#xff0c;也是类似于这样的感受&#xff0c;大概只知道要消除单点、做好监控报警&#xff0c;但却并没有一个体系化的方…

ChatGLM-6B的部署步骤

2022年8月&#xff0c;清华背景的智谱AI基于GLM框架&#xff0c;正式推出拥有1300亿参数的中英双语稠密模型 GLM-130B(论文地址、代码地址&#xff0c;论文解读之一&#xff0c;GLM-130B is trained on a cluster of 96 DGX-A100 GPU (840G) servers with a 60-day&#xff0c;…

【Excel如何在表格中筛选重复的值之条件格式】

在使用excel进行统计时经常会遇到&#xff0c;数据统计出现重复的现象&#xff0c;为了确保数据的唯一性&#xff0c;可以用到条件格式筛选出重复值&#xff0c;以确保数据的正确性。 筛选重复值&#xff1a; 选中要筛选的范围&#xff0c;行或列或整个表选中【开始】-【条件…

记录一次k8s pod之间ip无法访问,问题排查与定位

记录一次k8s pod之间ip无法访问&#xff0c;问题排查与定位 问题展现现象 node之间通信正常 部分node上的pod无法通信 排查有问题node 使用启动网络测试工具 环境准备 docker 数据库mysql 使用有状态副本集合 --- apiVersion: apps/v1 kind: StatefulSet metadata:anno…

MATLAB实现图片栅格化

MATLAB实现图片栅格化 1.读取图片&#xff1a;首先&#xff0c;你需要使用imread函数读取要栅格化的图片。 2.设置栅格大小&#xff1a;确定你希望将图片划分成的栅格大小&#xff0c;即每个栅格的宽度和高度。 3.计算栅格数量&#xff1a;根据图片的总尺寸和栅格大小&#…

pycharm 更换Eclipse 的按键模式 keymap

流程 整体来说比较简单&#xff0c;其实只要下载一个eclipse keymap插件就可以完成 首先 ctrl alt s 打开设置页面&#xff0c;找到 plugin 安装完成后还是在 settings 下切换到 keymap即可以看到eclipse 的按键设置出现了&#xff0c;应用后ok 即可完成 再去试试&#x…

绿色自适应网址发布页源码

源码介绍 绿色自适应网址发布页源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果截图 源码下载 绿色自适应网址…

Adobe Bridge 2024 v14.0.3 (macOS, Windows) - 集中管理创意资源

Adobe Bridge 2024 v14.0.3 (macOS, Windows) - 集中管理创意资源 Acrobat、After Effects、Animate、Audition、Bridge、Character Animator、Dimension、Dreamweaver、Illustrator、InCopy、InDesign、Lightroom Classic、Media Encoder、Photoshop、Premiere Pro、Adobe XD…

51单片机入门_江协科技_31~32_OB记录的自学笔记_LCD1602液晶显示屏

31. LCD1602 31.1. LCD1602介绍 •LCD1602&#xff08;Liquid Crystal Display&#xff09;液晶显示屏是一种字符型液晶显示模块&#xff0c;可以显示ASCII码的标准字符和其它的一些内置特殊字符&#xff0c;还可以有8个自定义字符 •显示容量&#xff1a;162个字符&#xff0c…

【VIC水文模型】准备工作:平台软件安装

VIC水文模型所需平台软件安装 1 Arcgis安装2 Cygwin安装&#xff08;Linux系统&#xff09;3 Matlab/R/Fortran的安装Notepad 4 VIC模型程序代码获取参考 由于VIC模型的编程语言为C语言&#xff0c;交互方式为控制台输指令&#xff0c;需要在Linux系统上运行。Windows 上使用 …

Linux--进程间的通信-命名管道

前文&#xff1a; Linux–进程间的通信-匿名管道 Linux–进程间的通信–进程池 命名管道的概念 命名管道是一种进程间通信&#xff08;IPC&#xff09;机制&#xff0c;运行不同进程之间进行可靠的、单向或双向的数据通信。 特点和作用&#xff1a; 跨平台性&#xff1a;在W…

Vanna-ai 大模型开源项目 基于RAG的TextToSql框架 安装和使用教程

github项目地址&#xff1a;vanna-ai/vanna: &#x1f916; 与 SQL 数据库聊天&#x1f4ca;。通过 LLMs使用RAG进行准确的TextToSQL的生成工作 &#x1f504; 。 Vanna 是 MIT 许可的开源 Python RAG&#xff08;检索增强生成&#xff09;框架&#xff0c;用于 SQL 生成和相关…

锐捷云桌面的安装

按下 <DEL> 键进入 BIOS setup 界面&#xff08;初始密码为 admin &#xff09;。 输入密码之后就进入 BIOS 的 Main 界面 设置服务器 BMC IP 地址。 a 云服务器启动后&#xff0c;在 BIOS 的主页面&#xff0c;把光标移到 [Server Mgmt] 项。 b 选择 [BMC Network C…

C++11 数据结构3 线性表的循环链式存储,实现,测试

上一节课&#xff0c;我们学了线性表 单向存储结构&#xff08;也就是单链表&#xff09;&#xff0c;这个是企业常用的技术&#xff0c;且是后面各种的基本&#xff0c;一定要牢牢掌握&#xff0c;如果没有掌握&#xff0c;下面的课程会云里雾里。 一 &#xff0c;循环链表 1…

腾讯AI Lab:“自我对抗”提升大模型的推理能力

本文介绍了一种名为“对抗性禁忌”&#xff08;Adversarial Taboo&#xff09;的双人对抗语言游戏&#xff0c;用于通过自我对弈提升大型语言模型的推理能力。 &#x1f449; 具体的流程 1️⃣ 游戏设计&#xff1a;在这个游戏中&#xff0c;有两个角色&#xff1a;攻击者和防守…

【七 (2)FineBI FCP模拟试卷-平台新增用户留存分析】

目录 文章导航一、字段解释1、用户平台登录信息表格2、用户平台激活信息表格 二、需求三、操作步骤1、建立用户平台登录信息表格和用户平台激活信息表格的关联关系2、将用户平台激活信息表格的激活日期添加到用户平台登录信息表格3、新增公式列&#xff0c;计算激活时间和登录时…

SpringCloud(一)

微服务框架 一、分布式架构 分布式架构︰根据业务功能对系统进行拆分&#xff0c;每个业务模块作为独立项目开发&#xff0c;称为一个服务。 优点: 降低服务耦合有利于服务升级拓展 微服务是一种经过良好架构设计的分布式架构方案&#xff0c;微服务架构特征: 单一职责:微…

源码解读——SplitFed: When Federated Learning Meets Split Learning

源码地址 1. 源码概述 源码里一共包含了5个py文件 单机模型&#xff08;Normal_ResNet_HAM10000.py&#xff09;联邦模型&#xff08;FL_ResNet_HAM10000.py&#xff09;本地模拟的SFLV1&#xff08;SFLV1_ResNet_HAM10000.py&#xff09;网络socket下的SFLV2&#xff08;SF…

MySQL的内外连接

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;MySQL &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容主要介绍了MySQL中的内外连接 文章目录 MySQL的内外连接…

如何用ChatGPT进行论文撰写?

原文链接&#xff1a;如何用ChatGPT进行论文撰写&#xff1f;https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247601619&idx1&snb686fbe87dedfac2df3a6afe780b2ffe&chksmfa820c34cdf5852251dca64597024ea62ddbde280086535ec251f4b62b848d9f9234688384e6…