WebRTC直播间搭建记录

news2024/11/24 15:39:37

考虑到后续增加平台直播的可能性,笔记记录一下WebRTC相关.

在这里插入图片描述

让我们分别分析两种情况下的WebRTC连接建立过程:

情况一:AB之间可以直接通信

1.信令交换:
设备A和设备B首先通过信令服务器交换SDP(Session Description Protocol)信息和候选者(candidates)。SDP包含有关会话的信息,包括设备的媒体能力和网络地址。

2.ICE框架协商:
设备A和设备B使用ICE框架收集本地候选者(本地网络地址),然后交换候选者信息,包括通过STUN服务器获取的公共IP地址和端口号。

3.连接建立:
根据ICE框架收集的候选者信息,设备A和设备B尝试直接建立点对点连接。根据候选者优先级排序,选择最佳的候选者进行连接。

4.直接通信:
如果ICE框架成功建立了连接,设备A和设备B之间可以直接进行实时通信,例如音频、视频或数据传输。

情况二:AB之间不能直接通信
1.信令交换:
设备A和设备B通过信令服务器交换SDP信息和候选者。

2.ICE框架协商:
设备A和设备B分别收集本地候选者,并将候选者信息发送给对方。

3.无法直接建立连接:
如果ICE框架无法直接建立连接(例如由于双方都位于NAT环境或防火墙后),则ICE框架会返回无法建立连接的错误或超时。

4.使用TURN服务器:
在无法直接建立连接的情况下,设备A和设备B将使用TURN服务器作为中继器来中转数据流。设备A和设备B分别连接到TURN服务器,并将数据流通过TURN服务器进行中转,从而实现对等通信。

什么情况下AB不能直接建立点对点连接?

双方位于不同的私有网络:如果设备A和设备B都处于不同的私有网络(例如家庭网络),则无法直接访问对方的局域网地址。

AB之间无法直接建立点对点连接的情况通常是由于网络地址转换(NAT)或防火墙的存在,导致设备无法直接接收对方发送的数据包。具体情况包括:

NAT类型限制:如果设备A或设备B位于对称NAT或受限NAT网络中,会导致UDP包的发送和接收出现问题,从而无法建立直接连接。

防火墙限制:防火墙可以阻止对UDP或特定端口的访问,这会影响设备之间的直接通信。

公共IP地址不可用:有些设备可能没有公共IP地址,而是通过NAT路由器共享局域网IP地址。

在这些情况下,WebRTC利用ICE框架和STUN/TURN服务器来实现网络穿透,确保设备之间可以建立可靠的实时通信连接。通过STUN服务器获取公共网络地址,通过TURN服务器进行数据中转,解决了设备间无法直接通信的问题。

附一下个人直播间搭建部分代码

<html>

<head>
    <title>简单直播间</title>
    <style type="text/css">
    body {
        background: #888888 center center no-repeat;
        color: white;
    }

    button {
        cursor: pointer;
        user-select: none;
    }

    .room {
        border: 1px solid black;
        cursor: pointer;
        user-select: none;
        text-align: center;
        background: rgba(0, 0, 0, 0.5);
    }

    video {
        width: 75%;
        border: 2px solid black;
        border-image: linear-gradient(#F80, #2ED) 20 20;
    }

    #chat {
        position: fixed;
        top: 5px;
        right: 5px;
        width: calc(25% - 30px);
        height: calc(75% - 10px);
        background: rgba(0, 0, 0, 0.5);
    }

    .content {
        height: calc(100% - 66px);
        margin-top: 5px;
        margin-bottom: 5px;
        overflow-y: scroll;
    }

    #chatSend {
        white-space: nowrap;
    }

    #chatSend>input {
        width: calc(100% - 90px);
    }

    #chatSend>button {
        width: 80px;
    }

    #chatTag {
        margin-left: 10px;
        line-height: 30px;
    }

    .chatKuang {
        border: 1px solid white;
        margin: 3px;
        border-radius: 5px;
    }

    .hintKuang {
        margin: 3px;
        text-align: center;
    }
    </style>
</head>

<body>
    <div id="create">
        名称:<input id="name" type="text" />
        <button onclick="createRoom()">创建直播间</button>
        <span id="count"></span>
    </div>
    <video id="localVideo" autoplay controls="controls"></video>
    <br />
    <div id="chat">
        <div id="chatTag">
            <button onclick="changeTag(0)">房间列表</button>
            <button onclick="changeTag(1)">房间聊天</button>
        </div>
        <div id="roomContent" class="content"></div>
        <div id="chatContent" class="content" style="display: none"></div>
        <div id="chatSend">
            <input type="text" />
            <button onclick="chatSend()">发送</button>
        </div>
    </div>
    <script type="text/javascript">
    // 背景图片
    (function() {
        var img = new Image();
        img.addEventListener("load", function() {
            document.querySelector("body").style.background =
                "url('" + this.src + "') center center no-repeat";
            let _img = this;
            calculateBackgroundImageScale(_img);
            window.onresize = function(_img) {
                calculateBackgroundImageScale(_img);
            }
        });

        img.src = "https://parva.cool/share/sky043.jpg";
    })();

    //计算背景图片缩放(自适应窗口大小)
    function calculateBackgroundImageScale(img) {
        let w1 = document.body.clientWidth;
        let h1 = document.body.clientHeight;
        let w2 = img.width;
        let h2 = img.height;
        let scale1 = w1 / w2;
        let scale2 = h1 / h2;
        let scale = scale1 > scale2 ? scale1 : scale2;
        document.querySelector("body").style.backgroundSize =
            Math.ceil(w2 * scale) + "px " + Math.ceil(h2 * scale) + "px";
    }

    // 存储本地媒体流
    var localStream;

    // 与服务器的websocket通信
    var socket = new WebSocket("wss://parva.cool/rtc");

    // 判断自己是否正在直播
    var isMe;

    // 当前的标签页(0:房间列表, 1:房间聊天)
    var tag = 0;

    // 发送聊天信息
    function chatSend() {
        let input = document.querySelector("#chatSend input");
        let msg = input.value;
        input.value = "";
        if (Object.keys(pcs).length == 0) return;
        if (isMe) {
            socket.send(JSON.stringify({ event: "chatSend", msg: msg }));
        } else {
            socket.send(JSON.stringify({
                event: "chatSend",
                msg: msg,
                roomName: Object.keys(pcs)[0]
            }));
        }
    }

    // 切换标签
    function changeTag(t) {
        tag = t;
        document.querySelector("#roomContent").style.display = "none";
        document.querySelector("#chatContent").style.display = "none";
        if (tag == 0) {
            document.querySelector("#roomContent").style.display = "block";
        } else if (tag == 1) {
            document.querySelector("#chatContent").style.display = "block";
        }
    }

    // 创建直播间
    function createRoom() {
        let roomName = document.querySelector("#name").value;
        if (!localStream) {
            navigator.mediaDevices.getDisplayMedia({ video: true, audio: true })
                .then((stream) => {
                    localStream = stream;
                    socket.send(JSON.stringify({
                        event: "createRoom",
                        roomName: roomName
                    }));
                });
        } else {
            socket.send(JSON.stringify({
                event: "createRoom",
                roomName: roomName
            }));
        }
    }

    // 关闭直播间
    function closeRoom() {
        socket.send(JSON.stringify({ event: "closeRoom" }));
        document.querySelector("input").disabled = false;
        document.querySelector("button").innerHTML = "创建直播间";
        document.querySelector("button").setAttribute("onclick",
            "createRoom()");
        document.querySelector("video").srcObject = null;
        pcs = {};
        let content = document.querySelector("#chatContent");
        content.innerHTML = "";
        changeTag(0);
        let count = document.querySelector("#count");
        count.innerHTML = "";
        localStream.getTracks()[0].stop();
    }

    // 进入直播间
    var 防止双击;
    var 防止双击setTimeout;

    function joinRoom(roomName) {
        if (防止双击 == roomName) return;
        else 防止双击 = roomName;
        clearTimeout(防止双击setTimeout);
        防止双击setTimeout = setTimeout(function() { 防止双击 = ""; }, 800);
        let name = document.querySelector("#name").value;
        socket.send(JSON.stringify({
            event: "joinRoom",
            name: name,
            roomName: roomName
        }));
    }

    // 接收服务器的消息
    socket.onmessage = function(event) {
        let json = JSON.parse(event.data);

        // 接收聊天信息
        if (json.event === "chat") {
            let chatName = document.createElement("span");
            chatName.innerHTML = json.name + " : ";
            chatName.setAttribute("class", "chatName");
            let chatMessage = document.createElement("span");
            chatMessage.innerHTML = json.msg;
            chatName.setAttribute("class", "chatMessage");
            let chatKuang = document.createElement("div");
            chatKuang.setAttribute("class", "chatKuang");
            chatKuang.appendChild(chatName);
            chatKuang.appendChild(chatMessage);
            let content = document.querySelector("#chatContent");
            content.appendChild(chatKuang);
            content.scrollTop = 9999999;
        }

        // 通知新人加入房间
        if (json.event === "joinHint") {
            let hintKuang = document.createElement("div");
            hintKuang.setAttribute("class", "hintKuang");
            hintKuang.innerHTML = json.name + "加入直播房间!"
            let content = document.querySelector("#chatContent");
            content.appendChild(hintKuang);
            content.scrollTop = 9999999;
        }

        // 有人退出当前房间
        if (json.event === "quitHint") {
            let hintKuang = document.createElement("div");
            hintKuang.setAttribute("class", "hintKuang");
            hintKuang.innerHTML = json.name + "退出房间.."
            let content = document.querySelector("#chatContent");
            content.appendChild(hintKuang);
            content.scrollTop = 9999999;
        }

        // 接收房间人数
        if (json.event === "count") {
            let count = document.querySelector("#count");
            count.innerHTML = "\t在场众神数量 : " + json.count;
        }

        // 所有房间的信息
        if (json.event === "roomsInfo") {
            if (tag != 0) return;
            let content = document.querySelector("#roomContent");
            content.innerHTML = "";
            for (let i = 0; i < json.info.length; i++) {
                let div = document.createElement("div");
                div.innerHTML = json.info[i];
               
                //点击进入房间事件捆绑
                div.setAttribute("onclick", "joinRoom('" + json.info[i] + "')");
                div.setAttribute("class", "room");
                content.appendChild(div);
            }
        }

        // 创建房间失败
        if (json.event === "createRoomFailed") {
            alert("创建房间失败,名称已存在 或 名称格式有误");
            localStream.getTracks()[0].stop();
        }

        // 创建房间成功
        if (json.event === "createRoomOk") {
            document.querySelector("input").disabled = true;
            document.querySelector("button").innerHTML = "关闭直播间";
            document.querySelector("button").setAttribute("onclick",
                "closeRoom()");
           
           //将捕捉的本地媒体流赋值到直播窗口
            document.querySelector("video").srcObject = localStream;
            document.querySelector("video").volume = 0;
            isMe = true;
            changeTag(1);
            let hintKuang = document.createElement("div");
            hintKuang.setAttribute("class", "hintKuang");
            hintKuang.innerHTML = "创建直播房间成功!"
            let content = document.querySelector("#chatContent");
            content.appendChild(hintKuang);
            content.scrollTop = 9999999;
        }

        // 加入房间失败
        if (json.event === "joinRoomFailed") {
            alert("加入房间失败,名称已存在 或 名称格式有误");
        }

        // 主播下播,退出房间
        if (json.event === "roomClosed") {
            document.querySelector("video").srcObject = null;
            pcs = {};
            let content = document.querySelector("#chatContent");
            content.innerHTML = "";
            changeTag(0);
            document.querySelector("input").disabled = false;
            let count = document.querySelector("#count");
            count.innerHTML = "";
        }

        // 有人欲加入我的直播间
        if (json.event === "joinRoom") {
            // 为对方创建一个pc实例,并发送offer给他
            var pc = createRTCPeerConnection(json.name);
            // rtc建立连接:创建一个offer给对方
            pc.createOffer(function(desc) {
                pc.setLocalDescription(desc);
                socket.send(JSON.stringify({
                    event: "_offer",
                    data: {
                        sdp: desc,
                        nameB: json.name
                    }
                }));
            }, function(error) {
                console.log("CreateOffer Failure callback: " + error);
            });
        }

        // rtc建立连接:接收到offer
        if (json.event === "_offer") {
            // 为对方创建一个pc实例,并发送offer给他
            var pc = createRTCPeerConnection(json.data.nameA);
            pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
            // rtc建立连接:创建一个answer给对方
            pc.createAnswer(function(desc) {
                pc.setLocalDescription(desc);
                socket.send(JSON.stringify({
                    event: "_answer",
                    data: {
                        sdp: desc,
                        nameA: json.data.nameA
                    }
                }));
            }, function(error) {
                console.log("CreateAnswer Failure callback: " + error);
            });
        }

        // rtc建立连接:接收到answer   --A收到B的answer
        // 将来自对方的 Answer SDP 设置为本地 WebRTC 连接的远程描述,以便双方能够正确地理解和处理对方的媒体数据,从而建立成功的实时通信连接。
        if (json.event === "_answer") {

            pcs[json.data.nameB].setRemoteDescription(
                new RTCSessionDescription(json.data.sdp));
        }

        // rtc建立连接:接收到_ice_candidate
        if (json.event === "_ice_candidate") {
            pcs[json.data.from].addIceCandidate(
                new RTCIceCandidate(json.data.candidate));
        }
    }

    // Q:一对多直播情况下 直播用户A的远程描述需要怎么变化?
    // A:连接都是独立的 分别设置对应的远端描述符 A的描述符不变



    // 存储pc实例
    var pcs = {};

    // stun和turn服务器URL及配置
    var iceServer = {
        iceServers: [
            { urls: "stun:parva.cool:3478" },
            {
                urls: "turn:parva.cool:3478",
                username: "parva",
                credential: "Parva089"
            }
        ]
    };

    // 创建RTCPeerConnection实例
    function createRTCPeerConnection(name) {
        let pc = new RTCPeerConnection(iceServer);
        if (!isMe) {
            for (let n in pcs) pcs[n].close();
            pcs = {};
        }
        // 以{对方的名字:PC实例}键值对形式把PC实例存储起来
        pcs[name] = pc;
        if (localStream) pc.addStream(localStream);
        else pc.addStream(new MediaStream());

        pc.onicecandidate = function(event) {
            if (event.candidate !== null)
                socket.send(JSON.stringify({
                    event: "_ice_candidate",
                    data: {
                        candidate: event.candidate,
                        to: Object.keys(pcs).find(k => pcs[k] == pc)
                    }
                }));
        }

        pc.ontrack = function(event) {
            if (isMe) return;
            changeTag(1);
            document.querySelector("video").srcObject = event.streams[0];
            document.querySelector("input").disabled = true;
            let hintKuang = document.createElement("div");
            hintKuang.setAttribute("class", "hintKuang");
            hintKuang.innerHTML = "成功进入直播间!"
            let content = document.querySelector("#chatContent");
            content.innerHTML = "";
            content.appendChild(hintKuang);
            content.scrollTop = 9999999;
        }

        return pc;
    }
    </script>
</body>

</html>

建立 BC 和 A 之间的 WebRTC 连接涉及以下步骤和流程:

前提条件

A 是直播主播,已经在直播间创建了媒体流并开始直播。
B 和 C 是观众,希望加入直播间并观看 A 的直播。

建立 WebRTC 连接的过程:
1.B 或 C 加入直播间:
B 或 C 在前端页面选择要加入的直播间并点击加入按钮。
前端通过 WebSocket 向服务器发送加入房间的请求,包括用户信息和房间名称。

2.服务器收到加入房间请求:
后端服务器接收到 B 或 C 的加入房间请求,将其添加到对应房间的用户列表中。

3.A 发送 WebRTC offer 给 B 或 C:
当 B 或 C成功加入房间后,后端服务器会通知 A(主播)有新用户加入了直播间。
A 在前端收到加入房间的通知后,使用 WebRTC 创建一个 PeerConnection 对象(pc),并生成一个 SDP offer。
A 将这个 SDP offer 通过 WebSocket 发送给服务器。

4.服务器转发 WebRTC offer 给 B 或 C:
后端服务器收到 A 发送的 SDP offer。
后端服务器将 SDP offer 转发给房间内除 A 之外的其他用户(即 B 或 C)。

5.B 或 C 接收 WebRTC offer:
B 或 C 前端收到 A 发送的 SDP offer。
B 或 C 使用 WebRTC 创建一个 PeerConnection 对象(pc),并设置 A 的 SDP offer 作为远端描述(setRemoteDescription)。

6.B 或 C 创建 WebRTC answer 给 A:
B 或 C 使用自己的本地媒体流(视频和音频)创建一个 SDP answer。
B 或 C 将这个 SDP answer 发送给服务器。

7.服务器转发 WebRTC answer 给 A:
后端服务器收到 B 或 C 发送的 SDP answer。
后端服务器将 SDP answer 转发给 A。

8.A 接收 WebRTC answer:
A 前端收到 B 或 C 发送的 SDP answer。
A 设置 B 或 C 的 SDP answer 作为远端描述(setRemoteDescription)。

9.ICE 候选者交换:
PeerConnection 开始收集和交换 ICE 候选者信息(网络地址、端口等)。

10.B、C 和 A 通过服务器交换 ICE 候选者信息,以便彼此建立直接的通信路径。
建立直播观看连接:

完成以上步骤后,B 或 C 和 A 之间的 WebRTC 连接就建立起来了。
B 或 C 的本地媒体流通过 ICE 候选者的协商直接传输到 A,A 的直播内容会被 B 或 C 观看。

总结:

通过以上步骤,B 或 C 可以加入 A 创建的直播间,并与 A 建立起 WebRTC 连接,实现实时的音视频传输和观看直播内容。整个过程涉及前端的用户交互和媒体流处理,以及后端的 WebRTC 信令传递和 ICE 候选者交换,共同实现了观众与主播之间的实时通信和直播功能。

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

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

相关文章

负载均衡集群——LVS

目录 1.LVS简介 2.LVS体系结构 3.LVS相关术语 4. LVS工作模式 5. LVS调度算法 6.LVS集群介绍 6.1 LVS-DR模式 6.2 LVS – NAT 模式 6.3 LVS – TUN 模式 7.LVS 集群构建 7.1 LVS/NAT 模式配置 实验操作步骤 步骤 1 Nginx1 和 Nginx2 配置 步骤 2 安装和配置 LVS …

R语言使用installr包对R包进行整体迁移

今天分享一个R语言的实用小技巧&#xff0c;如果咱们重新安装了电脑&#xff08;我重装了电脑&#xff09;或者因为需要卸载旧版本的R软件&#xff0c;安装新版本的R&#xff0c;那么必然会造成R包的库缺失&#xff0c;需要重新下载&#xff0c;有些还不是官方的R包&#xff0c…

如何从零开始创建React应用:简易指南

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

认识异常(1)

❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&a…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之十三 简单去除图片水印效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之十三 简单去除图片水印效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之十三 简单去除图片水印效果 一、简单介绍 二、简单去除图片水印效果实现原理 三、简单去除图片水印效果案例…

stm32实现hid键盘

前面的cubelmx项目配置参考 stm32实现hid鼠标-CSDN博客https://blog.csdn.net/anlog/article/details/137814494?spm1001.2014.3001.5502两个项目的配置完全相同。 代码 引用 键盘代码&#xff1a; 替换hid设备描述符 先屏蔽鼠标设备描述符 替换为键盘设备描述符 修改宏定…

Springboot框架——3.整合SpringMVC

1.修改端口号&#xff1a; 在application.properties中添加如下配置即可&#xff1a; server.port8088 2.静态资源访问&#xff1a; 首先打开ResourceProperties这个类的源码&#xff1a; 将静态资源放到类中默认位置即可实现访问&#xff1a; http://localhost:8088/erth.jp…

Docker安装xxl-job分布式任务调度平台

文章目录 Docker安装xxl-job分布式任务调度平台1.xxl-job介绍2. 初始化“调度数据库”3、docker挂载运行xxl-job容器3.1、在linux的opt目录下创建xxl_job文件夹&#xff0c;并在里面创建logs文件夹和application.properties文件3.2、配置application.properties文件&#xff0c…

springboot整合dubbo实现RPC服务远程调用

一、dubbo简介 1.什么是dubbo Apache Dubbo是一款微服务开发框架&#xff0c;他提供了RPC通信与微服务治理两大关键能力。有着远程发现与通信的能力&#xff0c;可以实现服务注册、负载均衡、流量调度等服务治理诉求。 2.dubbo基本工作原理 Contaniner:容器Provider&#xf…

安全中级-环境安装(手动nginx以及自动安装php,mysql)

为了方便大家跟bilibili课程&#xff0c;出了第一节环境 bilibili搜凌晨五点的星可以观看相关的教程 一、环境 ubentu 二、nginx手动安装 2.1第一步 wget https://nginx.org/download/nginx-1.24.0.tar.gz 2.2下载好安装包以后解压 tar -zxvf nginx-1.21.6.tar.gz2.3安…

Python零基础从小白打怪升级中~~~~~~~TCP网络编程

TCP网络编程 一、什么是TCP协议 TCP( Transmission control protocol )即传输控制协议&#xff0c;是一种面向连接、可靠的数据传输协议&#xff0c;它是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。 面向连接 &#xff1a;数据传输之前客户端和…

文心一言用户数突破2亿 百度官宣三大AI开发神器

在日益激烈的竞争中&#xff0c;百度正在中国AI市场努力保持领导者地位&#xff0c;文心一言用户规模突破2亿&#xff0c;较去年年底翻了一番。 4月16日周二&#xff0c;以“创造未来”为主题的Create 2024百度AI开发者大会在深圳国际会展中心举办。百度CEO李彦宏在会议上指出…

如何用JAVA如何实现Word、Excel、PPT在线前端预览编辑的功能?

背景 随着信息化的发展&#xff0c;在线办公也日益成为了企业办公和个人学习不可或缺的一部分&#xff0c;作为微软Office的三大组成部分&#xff1a;Word、Excel和PPT也广泛应用于各种在线办公场景&#xff0c;但是由于浏览器限制及微软Office的不开源等特性&#xff0c;导致…

PLSQL中文乱码问题 + EZDML导入数据库模型乱码

PLSQL中文乱码问题 EZDML导入数据库模型乱码 查询数据库字符集 select userenv(language) from dual;查询本地字符集编码 select * from V$NLS_PARAMETERS;理论上 数据库字符集 跟 本地字符集编码 是一致的 本地字符集编码需要拼接字段值 NLS_LANGUAGE NLS_TERRITORY NLS…

PySpark预计算ClickHouse Bitmap实践

1. 背景 ClickHouse全称是Click Stream&#xff0c;Data WareHouse&#xff0c;是一款高性能的OLAP数据库&#xff0c;既使用了ROLAP模型&#xff0c;又拥有着比肩MOLAP的性能。我们可以用ClickHouse用来做分析平台快速出数。其中的bitmap结构方便我们对人群进行交并。Bitmap位…

【muzzik 分享】关于 MKFramework 的设计想法

MKFramework是我个人维护持续了几年的项目&#xff08;虽然公开只有一年左右&#xff09;&#xff0c;最开始由于自己从事QP类游戏开发&#xff0c;我很喜欢MVVM&#xff0c;于是想把他做成 MVVM 框架&#xff0c;在论坛第一个 MVVM 框架出来的时候&#xff0c;我的框架已经快完…

uniapp--登录和注册页面-- login

目录 1.效果展示 2.源代码展示 测试登录 login.js 测试请求 request.js 测试首页index.js 1.效果展示 2.源代码展示 <template><view><f-navbar title"登录" navbarType"4"></f-navbar><view class"tips"><…

基于JSP的电器网上订购系统

本系统利用现在比较广泛的JSP结合后台SpringMybatisAjax编写程序的方式实现的。 在意见箱板块中&#xff0c;运用JSP通过JDBC技术和后台的数据库进行交互的方式将数据信息反馈给用户和管理员&#xff1b;在登录系统中&#xff0c;使用Ajax技术实现异步交互&#xff0c;在不更新…

VulBG: 构建行为图加强基于深度学习的漏洞检测模型

近年来&#xff0c;人们提出了基于深度学习&#xff08;DL&#xff09;的漏洞检测系统&#xff0c;用于从源代码中自动提取特征。这些方法在合成数据集上可以实现理想的性能&#xff0c;但在检测真实世界的漏洞数据集时&#xff0c;准确率却大幅下降。此外&#xff0c;这些方法…

基于Zookeeper 简单实现分布式任务协调组件

一、什么是 Zookeeper ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;是Hadoop和Hbase的重要组件。 它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维…