一对一WebRTC视频通话系列(二)——websocket和join信令实现

news2025/1/14 18:35:09

本系列博客主要记录WebRtc实现过程中的一些重点,代码全部进行了注释,便于理解WebRTC整体实现。


一对一WebRTC视频通话系列往期博客:

一对一WebRTC视频通话系列(一)—— 创建页面并显示摄像头画面


websocket和join信令实现

    • 一、websocket实现
      • 1.1客户端
      • 1.2服务端
    • 二、join信令实现
      • 2.1 客户端
      • 2.2 服务端

一、websocket实现

1.1客户端

main.js文件中,定义了一个名为ZeroRTCEngine的类,该类使用WebSocket进行通信。ZeroRTCEngine类的实例化过程如下:

  1. 首先,声明并定义一个名为zeroRTCEngine的类,接受一个wsUrl参数。
  2. 在ZeroRTCEngine类中,定义一个init方法,用于初始化WebSocket地址。
  3. 定义ZeroRTCEngine类的方法createWebsocket,用于创建WebSocket对象。
  4. createWebsocket方法中,定义多种WebSocket事件处理函数.
  5. 定义onopenonmessageoncloseonerror方法,用于处理WebSocket连接状态和接收到的消息。

在实际使用中,通过调用ZeroRTCEngine类的createWebsocket方法来创建一个WebSocket实例,并监听各种事件(如打开、消息传递、关闭和错误)。

	//定义 ZeroRTCEngine 的 init 方法
	//参数 wsUrl:WebSocket 的地址
	ZeroRTCEngine.prototype.init = function(wsUrl) {
	    //设置 WebSocket url
	    this.wsUrl = wsUrl;
	    this.signaling =null;
	}
	
	//定义 ZeroRTCEngine 的 createWebsocket 方法
	ZeroRTCEngine.prototype.createWebsocket = function() {
	    //创建 WebSocket 对象
	    zeroRTCEngine = this;
	    zeroRTCEngine.signaling = new WebSocket(this.wsUrl);
	
	    //设置打开函数
	    zeroRTCEngine.signaling.onopen = function () {
	        zeroRTCEngine.onopen();
	    }
	
	    //设置关闭函数
	    zeroRTCEngine.signaling.onclose = function (event) {
	        zeroRTCEngine.onclose(event);
	    }
	    //设置错误函数
	    zeroRTCEngine.signaling.onerror = function (event) {
	        zeroRTCEngine.onerror(event);
	    }
	    //设置消息传递函数
	    zeroRTCEngine.signaling.onmessage = function (event) {
	        zeroRTCEngine.onmessage(event);
	    }
	}
	
	
	ZeroRTCEngine.prototype.onopen = function() {
	    console.log("WebSocket connection established.");
	}
	
	ZeroRTCEngine.prototype.onmessage = function(event) {
	    console.log("Received message:" + event.data);
	    //解析收到的消息
	    var message = JSON.parse(event.data);
	}
	
	ZeroRTCEngine.prototype.onclose = function(event) {
	    console.log("WebSocket connection closed." + event.data + ", reason" + EventCounts.reason);
	}
	
	ZeroRTCEngine.prototype.onerror = function(event) {
	    console.log("WebSocket connection error:" + event.data);  
	}
	
	zeroRTCEngine = new ZeroRTCEngine("ws://192.168.3.181:8099");
	zeroRTCEngine.createWebsocket();

1.2服务端

创建一个名为signal_server.jsjs新文件,搭建简单的WebSocket服务器,用于处理客户端连接、消息发送和接收、连接关闭等基本操作。在实际应用中,可能需要根据需求对代码进行修改和扩展。

  1. 使用ws.createServer()方法创建一个WebSocket服务器。这个方法接受一个回调函数作为参数,该回调函数在客户端连接到服务器时被调用。
  2. 在回调函数中,使用console.log()输出一条connection established的消息,表示连接已建立。然后,向客户端发送一条消息,例如"我收到你的连接了"。
  3. 使用conn.on("text", function (str) {...})监听客户端发送的消息。
    当客户端发送消息时,会触发这个回调函数。使用console.info()输出一条消息,表示收到的消息。
  4. 在连接关闭时,使用conn.on("close", function (code, reason) {...})监听连接关闭。
    当客户端关闭连接时,会触发这个回调函数。使用console.log()输出一条消息,表示连接已关闭。
  5. 使用了conn.on("error", function (err) {...})来监听连接错误。当发生错误时,会触发这个回调函数,输出错误信息。
  6. 使用server.listen()方法将服务器绑定到一个指定的端口。在实际应用中,通常需要根据不同的需求来修改这个端口。
	// 引入ws模块
	var ws = require("nodejs-websocket");
	// 定义端口号
	var port =8099;
	
	// 创建一个WebSocket服务器
	var server = ws.createServer(function (conn) {
	    // 连接建立时的输出
	    console.log("New connection");
	    // 向客户端发送消息
	    conn.sendText("我收到你的连接了")
	    // 监听客户端发送的消息
	    conn.on("text", function (str) {
	        console.info("Received msg:"+str);
	    });
	
	    // 连接关闭时的输出
	    conn.on("close", function (code, reason) {
	        console.log("Connection closed,code:" + code + " reason:" + reason);
	    });
	    // 监听连接错误
	    conn.on("error", function (error) {
	        console.error("发生错误:", error);
	    })
	}).listen(port);

在ubuntu中使用下列指令开启服务端:

	node signal_server.js

在这之前切记先用以下两行命令初始化:

	sudo npm init -y
	sudo npm install nodejs-websocket

在这里插入图片描述
在这里插入图片描述

二、join信令实现

2.1 客户端

修改按钮点击事件

document.getElementById('joinBtn').onclick = function () {
    roomId = document.getElementById('roomId').value;
    if(roomId == '' || roomId == "Please enter the room ID"){
        alert('Please enter the room ID');
        return;
    }

    console.log("joinBtn clicked,roomId: " + roomId);
    //初始化本地码流
    initLocalStream();
}

首先创建一个JSON对象,包含命令(cmd)、房间ID(roomId)和用户ID(uid),然后将这个JSON对象转换为字符串。接着,调用zeroRTCEngine.sendMessage方法将这个字符串发送给服务器,表示用户要加入房间。最后,在控制台输出一条消息,表明用户已经成功加入房间。

// join 主动加入房间
// leave 主动离开房间
// new-peer 有人加入房间,通知已经在房间的人
// peer-leave 有人离开房间,通知已经在房间的人
// offer 发送offer给对端peer
// answer发送offer给对端peer
// candidate 发送candidate给对端peer
const SIGNAL_TYPE_JOIN = "join";
const SIGNAL_TYPE_RESP_JOIN = "resp-join";  // 告知加入者对方是谁
const SIGNAL_TYPE_LEAVE = "leave";
const SIGNAL_TYPE_NEW_PEER = "new-peer";
const SIGNAL_TYPE_PEER_LEAVE = "peer-leave";
const SIGNAL_TYPE_OFFER = "offer";
const SIGNAL_TYPE_ANSWER = "answer";
const SIGNAL_TYPE_CANDIDATE = "candidate";

var localUserId = Math.random().toString(36).substr(2);//本地ID   
var remoteUserId = -1; //对方用户ID
var roomId = -1; //房间ID

ZeroRTCEngine.prototype.sendMessage = function(message) {
    this.signaling.send(message);
}

function doJoin(roomId) {
    var jsonMsg = {
        'cmd': 'join',
        'roomId': roomId,
        'uid': localUserId,
    }; 
    var message = JSON.stringify(jsonMsg); //将json对象转换为字符串
    zeroRTCEngine.sendMessage(message);   //设计方法:用实现方法而不是直接用变量    
    console.info("doJoin message: " + message);
}

运行效果:
在这里插入图片描述

2.2 服务端

修改signal_server.js
1.添加相关宏定义,以及WebRTC所需要用到的Map类。

// join 主动加入房间
// leave 主动离开房间
// new-peer 有人加入房间,通知已经在房间的人
// peer-leave 有人离开房间,通知已经在房间的人
// offer 发送offer给对端peer
// answer发送offer给对端peer
// candidate 发送candidate给对端peer
const SIGNAL_TYPE_JOIN = "join";
const SIGNAL_TYPE_RESP_JOIN = "resp-join";  // 告知加入者对方是谁
const SIGNAL_TYPE_LEAVE = "leave";
const SIGNAL_TYPE_NEW_PEER = "new-peer";
const SIGNAL_TYPE_PEER_LEAVE = "peer-leave";
const SIGNAL_TYPE_OFFER = "offer";
const SIGNAL_TYPE_ANSWER = "answer";
const SIGNAL_TYPE_CANDIDATE = "candidate";


/** ----- ZeroRTCMap ----- */
var ZeroRTCMap = function () {
    this._entrys = new Array();

    this.put = function (key, value) {
        if (key == null || key == undefined) {
            return;
        }
        var index = this._getIndex(key);
        if (index == -1) {
            var entry = new Object();
            entry.key = key;
            entry.value = value;
            this._entrys[this._entrys.length] = entry;
        } else {
            this._entrys[index].value = value;
        }
    };
    this.get = function (key) {
        var index = this._getIndex(key);
        return (index != -1) ? this._entrys[index].value : null;
    };
    this.remove = function (key) {
        var index = this._getIndex(key);
        if (index != -1) {
            this._entrys.splice(index, 1);
        }
    };
    this.clear = function () {
        this._entrys.length = 0;
    };
    this.contains = function (key) {
        var index = this._getIndex(key);
        return (index != -1) ? true : false;
    };
    this.size = function () {
        return this._entrys.length;
    };
    this.getEntrys = function () {
        return this._entrys;
    };
    this._getIndex = function (key) {
        if (key == null || key == undefined) {
            return -1;
        }
        var _length = this._entrys.length;
        for (var i = 0; i < _length; i++) {
            var entry = this._entrys[i];
            if (entry == null || entry == undefined) {
                continue;
            }
            if (entry.key === key) {// equal
                return i;
            }
        }
        return -1;
    };
}

2.定义Client函数,用于初始化用户信息

function Client(uid, conn, roomId) {
    this.uid = uid;     // 用户所属的id
    this.conn = conn;   // uid对应的websocket连接
    this.roomId = roomId;
}

3.处理加入房间请求
监听客户端发送的消息,如果时加入房间的,利用handleJoin()函数进行处理

// 监听客户端发送的消息
    conn.on("text", function (str) {
        console.info("Received msg:"+str);
        var jsonMsg = JSON.parse(str);

        switch(jsonMsg.cmd){
            case SIGNAL_TYPE_JOIN:
                handleJoin(jsonMsg, conn); 
            break;
            
        }
    });

定义handleJoin()函数,处理加入房间请求。它首先获取房间ID用户ID,然后获取房间Map。如果房间不存在,则创建一个新的房间。如果房间已满,给出提示并拒绝加入请求。如果房间未满,创建一个新的客户端并将其添加到房间。如果房间中用户数大于1,则通知房间中的其他用户有新人加入。

实现原理:
1.从传入的messageconn参数中获取房间ID用户ID
2.尝试获取roomId对应的房间Map。如果房间Map不存在,则创建一个新的房间Map。
3.检查房间是否已满。如果已满,给出提示并返回;否则,创建一个新的客户端并将其添加到房间。
4.如果房间中用户数大于1,则遍历房间中的所有用户,通知他们有新人加入。同时,通知自己房间中有新人加入。

function handleJoin(message, conn){
    // 获取房间ID和用户ID
    var roomId = message.roomId;
    var uid = message.uid;
    console.log(" uid:"+uid + "try to join room: "+roomId);

    // 获取房间Map
    var roomMap = roomTableMap.get(roomId);
    // 如果房间不存在,则创建一个新的房间
    if(roomMap == null){
        roomMap = new ZeroRTCMap();
        roomTableMap.put(roomId, roomMap);
    }

    // 如果房间已满,给出提示
    if(roomMap.size() >= 1){
        console.error("roomId:" + roomId + " is full");

        conn.sendText('roomId:' + roomId + ' is full');
        return;
    }

    // 创建一个新的客户端
    var client = new Client(uid, conn, roomId);
    // 将用户添加到房间
    roomMap.put(uid, client);
    // 如果房间中的用户数大于1,则通知房间中的其他用户
    if(roomMap.size() > 1){
        
        // 获取房间中的所有用户
        var clients = roomMap.getEntrys();
        for(var i in clients){
            var remoteUid = clients[i].key;
            if(remoteUid != uid){
                // 通知已经在房间的人,有新人加入
                var jsonMsg = {
                    'cmd':SIGNAL_TYPE_NEW_PEER,
                    'remoteUid':uid
                };
                var msg = JSON.stringify(jsonMsg);

                // 通知自己,房间里有的人
                jsonMsg = {
                    'cmd':SIGNAL_TYPE_RESP_JOIN,
                    'remoteUid':remoteUid
                };
                msg = JSON.stringify(jsonMsg);
                console.info("resp-join"+msg);
                conn.sendText(msg);
            }
        }
    }
}

整体代码:

// 引入ws模块
var ws = require("nodejs-websocket");
// 定义端口号
var port =8099;

// join 主动加入房间
// leave 主动离开房间
// new-peer 有人加入房间,通知已经在房间的人
// peer-leave 有人离开房间,通知已经在房间的人
// offer 发送offer给对端peer
// answer发送offer给对端peer
// candidate 发送candidate给对端peer
const SIGNAL_TYPE_JOIN = "join";
const SIGNAL_TYPE_RESP_JOIN = "resp-join";  // 告知加入者对方是谁
const SIGNAL_TYPE_LEAVE = "leave";
const SIGNAL_TYPE_NEW_PEER = "new-peer";
const SIGNAL_TYPE_PEER_LEAVE = "peer-leave";
const SIGNAL_TYPE_OFFER = "offer";
const SIGNAL_TYPE_ANSWER = "answer";
const SIGNAL_TYPE_CANDIDATE = "candidate";


/** ----- ZeroRTCMap ----- */
var ZeroRTCMap = function () {
    this._entrys = new Array();

    this.put = function (key, value) {
        if (key == null || key == undefined) {
            return;
        }
        var index = this._getIndex(key);
        if (index == -1) {
            var entry = new Object();
            entry.key = key;
            entry.value = value;
            this._entrys[this._entrys.length] = entry;
        } else {
            this._entrys[index].value = value;
        }
    };
    this.get = function (key) {
        var index = this._getIndex(key);
        return (index != -1) ? this._entrys[index].value : null;
    };
    this.remove = function (key) {
        var index = this._getIndex(key);
        if (index != -1) {
            this._entrys.splice(index, 1);
        }
    };
    this.clear = function () {
        this._entrys.length = 0;
    };
    this.contains = function (key) {
        var index = this._getIndex(key);
        return (index != -1) ? true : false;
    };
    this.size = function () {
        return this._entrys.length;
    };
    this.getEntrys = function () {
        return this._entrys;
    };
    this._getIndex = function (key) {
        if (key == null || key == undefined) {
            return -1;
        }
        var _length = this._entrys.length;
        for (var i = 0; i < _length; i++) {
            var entry = this._entrys[i];
            if (entry == null || entry == undefined) {
                continue;
            }
            if (entry.key === key) {// equal
                return i;
            }
        }
        return -1;
    };
}

var roomTableMap = new ZeroRTCMap();

function Client(uid, conn, roomId) {
    this.uid = uid;     // 用户所属的id
    this.conn = conn;   // uid对应的websocket连接
    this.roomId = roomId;
}

// 处理加入房间请求
function handleJoin(message, conn){
    // 获取房间ID和用户ID
    var roomId = message.roomId;
    var uid = message.uid;
    console.log(" uid:"+uid + "try to join room: "+roomId);

    // 获取房间Map
    var roomMap = roomTableMap.get(roomId);
    // 如果房间不存在,则创建一个新的房间
    if(roomMap == null){
        roomMap = new ZeroRTCMap();
        roomTableMap.put(roomId, roomMap);
    }

    // 如果房间已满,给出提示
    if(roomMap.size() >= 1){
        console.error("roomId:" + roomId + " is full");

        conn.sendText('roomId:' + roomId + ' is full');
        return;
    }

    // 创建一个新的客户端
    var client = new Client(uid, conn, roomId);
    // 将用户添加到房间
    roomMap.put(uid, client);
    // 如果房间中的用户数大于1,则通知房间中的其他用户
    if(roomMap.size() > 1){
        
        // 获取房间中的所有用户
        var clients = roomMap.getEntrys();
        for(var i in clients){
            var remoteUid = clients[i].key;
            if(remoteUid != uid){
                // 通知已经在房间的人,有新人加入
                var jsonMsg = {
                    'cmd':SIGNAL_TYPE_NEW_PEER,
                    'remoteUid':uid
                };
                var msg = JSON.stringify(jsonMsg);

                // 通知自己,房间里有的人
                jsonMsg = {
                    'cmd':SIGNAL_TYPE_RESP_JOIN,
                    'remoteUid':remoteUid
                };
                msg = JSON.stringify(jsonMsg);
                console.info("resp-join"+msg);
                conn.sendText(msg);
            }
        }
    }
}

// 创建一个WebSocket服务器
var server = ws.createServer(function (conn) {
    // 连接建立时的输出
    console.log("New connection");
    // 向客户端发送消息
    conn.sendText('我收到你的连接了')
    // 监听客户端发送的消息
    conn.on("text", function (str) {
        console.info("Received msg:"+str);
        var jsonMsg = JSON.parse(str);

        switch(jsonMsg.cmd){
            case SIGNAL_TYPE_JOIN:
                handleJoin(jsonMsg, conn); 
            break;
            
        }
    });
    
    // 连接关闭时的输出
    conn.on("close", function (code, reason) {
        console.log("Connection closed,code:" + code + " reason:" + reason); 
    });
    // 监听连接错误
    conn.on("error", function (error) {
        console.error("发生错误:", error);
    })
}).listen(port);

运行效果:

客户1:
第一个创建,刚开始没有红框的内容。在客户2新加入后,收到服务端发来的信令。
在这里插入图片描述

客户2:
创建后,会收到服务端发来的,房间内已有的客户1的信息。
在这里插入图片描述
客户3:
房间内已经有2个客户了,会提示房间已满,无法加入房间。
在这里插入图片描述
服务端:
在这里插入图片描述

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

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

相关文章

下载Node.js及其他环境推荐nvm

文章目录 项目场景&#xff1a;下载Node.js环境配置配置环境变量 安装脚手架安装依赖安装淘宝镜像安装 cnpm&#xff08;我需要安装&#xff09;nvm 安装 Node.js &#xff08;推荐&#xff09; 项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 项目…

Java毕业设计 基于SSM SpringBoot vue宠物领养平台

Java毕业设计 基于SSM SpringBoot vue宠物领养平台 SSM 宠物领养平台 功能介绍 首页 图片轮播 新闻信息 新闻类型 新闻详情 宠物百科 宠物百科类型 宠物百科详情 宠物 宠物类型 宠物详情 立即领养 留言 论坛 发布帖子 登录 个人中心 宠物收藏 宠物领养订单 后台管理 登录注…

pymeshlab创建给定水平半径、垂直半径和水平细分以及垂直细分的圆环并保存(torus)

一、关于环境 请参考&#xff1a;pymeshlab遍历文件夹中模型、缩放并导出指定格式-CSDN博客 二、关于代码 本文所给出代码仅为参考&#xff0c;禁止转载和引用&#xff0c;仅供个人学习。 # pymeshlab需要导入&#xff0c;其一般被命名为ml import pymeshlab as ml# 首先需…

TCP重传机制——快速重传

TCP 有一种快速重传机制&#xff0c;它不以时间为驱动&#xff0c;而是以数据驱动重传。 在上图&#xff0c;发送方发出了 1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5 份数据&#xff1a; 第一份 Seq1 先送到了&#xff0c;于是就 Ack 回 2&#xff1b;结果 Seq2…

Nutch库入门指南:利用Java编写采集程序,快速抓取北京车展重点车型

概述 在2024年北京车展上&#xff0c;电动汽车成为全球关注的焦点之一。这一事件不仅吸引了全球汽车制造商的目光&#xff0c;也突显了中国市场在电动汽车领域的领先地位。117台全球首发车的亮相&#xff0c;其中包括30台跨国公司的全球首发车和41台概念车&#xff0c;彰显了中…

buuctf-misc-26.后门查杀

26.后门查杀 题目&#xff1a;火绒D盾查杀关键文件获取flag 下载完文件&#xff0c;我们可以用火绒进行查杀后门&#xff0c;一般解压后&#xff0c;火绒会自动查杀到病毒文件 病毒查杀-自定义查杀 找到了需要注意的文件&#xff0c;用vscode打开这个html,可以发现和md5比较相…

【副本向】Lua副本逻辑

副本生命周期 OnCopySceneTick() 子线程每次心跳调用 --副本心跳 function x3323_OnCopySceneTick(elapse)if x3323_g_IsPlayerEnter 0 thenreturn; -- 如果没人进入&#xff0c;则函数直接返回endif x3323_g_GameOver 1 thenif x3323_g_EndTick > 0 thenx3323_CountDown…

Vue进阶之Vue项目实战(一)

Vue项目实战 项目搭建初始化eslint版本约束版本约束eslint配置 stylelintcspellcz-githusky给拦截举个例子 zx 项目搭建 node版本&#xff1a;20.11.1 pnpm版本&#xff1a;9.0.4 初始化 vue3最新的脚手架 pnpm create vite byelide-demo --template vue-ts pnpm i pnpm dev…

020、Python+fastapi,第一个Python项目走向第20步:ubuntu 24.04 docker 安装mysql8、redis(一)

系列文章 pythonvue3fastapiai 学习_浪淘沙jkp的博客-CSDN博客https://blog.csdn.net/jiangkp/category_12623996.html 前言 docker安装起来比较方便&#xff0c;不影响系统整体&#xff0c;和前面虚拟环境有异曲同工之妙&#xff0c;今天把老笔记本T400拿出来装了个ubuntu24…

【分布式系统】FLP、CAP、BASE、ACID理论简介

分布式系统一致性模型 在说FLP&#xff0c;CAP&#xff0c;BASE&#xff0c;ACID理论前&#xff0c;必须先说说分布式系统的一致性模型&#xff0c;它是其他理论的基础知识。 依次介绍几个相关的概念&#xff1a; 分布式系统是由多个不同的服务节点组成&#xff0c;节点与节…

VMware虚拟机安装Linux(CentOS)【超详细】

参考大佬文章&#xff1a;VMware虚拟机安装Linux教程(超详细)_vmware安装linux虚拟机-CSDN博客 目录 一、获取映射文件 二、新建虚拟机 三、安装操作系统 四、切换系统用户 一、获取映射文件 参考大佬文章获取映射文件&#xff0c;以及对应修改后缀名的方法 二、新建虚拟…

电阻 电容 电感

电阻理论基础 电阻定义 电阻决定式 温度对电阻的影响 一般电阻都是在-200-500ppm这个范围内 电阻选型 贴片电阻的标值 数字位数 3位和4位 字母R 除了数字和字母R的其他标注 需要查表 电阻精度 电阻功率和温度的关系 电阻的额定电压 零欧姆电阻 零欧姆电阻又称为跨…

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(三)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 继续接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 当我们点击 submit 提…

【云原生】Docker 实践(四):使用 Dockerfile 文件的综合案例

【Docker 实践】系列共包含以下几篇文章&#xff1a; Docker 实践&#xff08;一&#xff09;&#xff1a;在 Docker 中部署第一个应用Docker 实践&#xff08;二&#xff09;&#xff1a;什么是 Docker 的镜像Docker 实践&#xff08;三&#xff09;&#xff1a;使用 Dockerf…

【多数组合 数学 字符串】2514. 统计同位异构字符串数目

本文涉及知识点 多数组合 数学 字符串 LeetCode2514. 统计同位异构字符串数目 给你一个字符串 s &#xff0c;它包含一个或者多个单词。单词之间用单个空格 ’ ’ 隔开。 如果字符串 t 中第 i 个单词是 s 中第 i 个单词的一个 排列 &#xff0c;那么我们称字符串 t 是字符串…

Web Storage 笔记12 操作购物车

相关内容&#xff1a;购物车实例 WebStorage存储空间足够大&#xff0c;访问都在客户端(Client)完成。有些客户端先处理或检查数据&#xff0c;就可以直接使用WebStorage进行存储&#xff0c;不仅可以提高访问速度&#xff0c;还可以降低服务器的练习。负担。例如&#xff0c;购…

如何访问公司内网?

访问公司内网是现代企业中的一个重要需求。无论是员工在外办公&#xff0c;还是远程技术支持&#xff0c;都需要能够安全、稳定地访问公司内部的网络资源。为了解决这一问题&#xff0c;北京金万维科技有限公司自主研发了一款名为【天联】的组网产品。 【天联】组网是一款异地组…

Linux下Palabos源码编译安装及使用

目录 软件介绍 基本依赖 其它可选依赖 一、源码下载 二、解压缩&#xff08;通过方式1下载源码.zip格式&#xff09; 三、编译安装 3.1 自带算例 ​编辑3.2 自行开发算例 四、简单使用 4.1 串行运行 4.2 并行运行 4.3 查看结果 软件介绍 Palabos是一款基于LBM&…

平平科技工作室-Python-步步惊心

一.准备图片 放在文件夹取名为imgs,分为两种boys和girls 二.编写程序 首先创建一个文件名为index.py 其次编写程序 # coding:utf-8 import sys, time, easygui, os, pygame from pygame.locals import * pygame.init() # 设置窗口显示位置、大小、颜色、标题 os.environ[ S…

【基于MAX98357的Minimax(百度)长文本语音合成TTS 接入教程】

【基于MAX98357的Minimax&#xff08;百度&#xff09;长文本语音合成TTS 接入教程】 1. 前言2. 先决条件2.1 硬件准备2.2 软件准备2.3 接线 3. 核心代码3.1 驱动实现3.2 代码解析 4. 播放文本5. 结论 视频地址&#xff1a; SeeedXIAO ESP32S3 Sense【基于MAX98357的Minimax&am…