最新文章
  • emqx通过REST API获取设备是否在线

    emqx通过REST API获取设备是否在线

    本文通过emqx REST API的方式来进行配置 创建一个API密钥 记录密钥的名称 API key是用户名称,Secret KEY是密码 浏览器登录验证 用户名就是 API Key,密码就是Secret KEY. 登录上之后就可以获取到设备的信息了。 可以参考: http://localhost:18083/api-docs/index.html 支持哪些API 官网参考: https://docs.emqx.com/zh/emqx/latest/admin/api.html
  • 基于nodejs搭建jsonp环境

    基于nodejs搭建jsonp环境

    安装 安装 Node.js 和 npm:执行以下命令安装 Node.js 和 npm: yum install -y nodejs 创建工程 步骤1: 创建一个新的Node.js项目目录,并进入该目录: mkdir jsonp-server cd jsonp-server 步骤2:初始化Node.js项目,并安装Express框架 npm init -y npm install express --save 步骤3:创建一个名为server.js的文件,并添加以下代码: const express = require(\'express\'); const app = express(); const port = 3000; app.get(\'/jsonp\', function(req, res) { const callback = req.query.callback; const data = { name: \'John Doe\', age: 30 }; res.send(`${callback}(${JSON.stringify(data)})`); }); app.listen(port, () => { console.log(`JSONP server listening at http://localhost:${port}`); }); 步骤4:运行你的JSONP服务器: node server.js 步骤5:验证 浏览器中访问https://www.xxx.xxx:3000/jsonp?callback=handleResponse
  • webrtc网页代码分析二

    webrtc网页代码分析二

    交互流程 上图是完整的处理流程。 获取URL参数 const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const deviceId = urlParams.get('deviceId'); 这段代码先获取当前页面 URL 中的查询字符串部分(window.location.search),然后使用 URLSearchParams 对象解析查询字符串,尝试获取名为 deviceId 的参数值,后续很多操作会根据这个 deviceId 的有无来进行不同的逻辑处理。 页面加载时的初始化操作 window.onload = function () { if (deviceId == undefined) { document.getElementById('video').style.display = 'none'; document.getElementById('getting-started').style.display = 'block'; } const canvas = document.createElement('canvas') canvas.width = 640 canvas.height = 480 const ctx = canvas.getContext('2d') ctx.fillStyle = 'rgba(200, 200, 200, 100)' ctx.fillRect(0, 0, 640, 480) const img = new Image(640, 480) img.src = canvas.toDataURL() document.getElementById('imgStream').src = img.src; } window.onload是页面加载完成后的回调函数,上面使用的是匿名函数,即当页面所有内容加载完成后执行的逻辑。具体逻辑如下 这里判断如果deviceId未定义(也就是可能页面直接访问,没有带上对应的参数),则隐藏视频相关区域(id为video的div),显示初始引导内容区域(id 为 getting-started 的 div)。在函数内创建了一个 canvas 元素,绘制了一个半透明的矩形,将其转换为图片数据后设置给img id=\\\\"imgStream\\\\"元素作为初始显示内容。 MQTT操作 发起连接 var options = { keepalive: 600, username: 'xxx', password: 'xxxx', }; const client = mqtt.connect('wss://xxx.xxx.com:8084/mqtt', options); 定义了 MQTT连接的相关选项(如保活时间、用户名、密码等),然后尝试连接到指定的MQTT服务器地址(wss://xxx.xxx.xxx:8084/mqtt),后续通过client对象来处理 MQTT 的各种消息收发以及相关操作。 事件处理 connect事件 client.on('connect', function () { console.log("connected") client.subscribe('webrtc/' + deviceId + '/jsonrpc-reply', function (err) { if (!err) { } }); console.log('publish to webrtc/' + deviceId + '/jsonrpc'); client.publish('webrtc/' + deviceId + '/jsonrpc', JSON.stringify({ jsonrpc: '2.0', method: 'offer', id: offerId, }), { qos: 2 }); }) 结构为client.on(\\\\'connect\\\\', function () {... }); client.on这里是mqtt对象的事件回调,这里将function(){...}作为回调函数注册进去,funciton实现是匿名函数的方式。当前收到connect事件时,就会执行function的内容。具体逻辑如下。 当成功连接到MQTT服务器后,先在控制台输出 connected 表示连接成功,然后订阅一个特定主题(webrtc/\\\\' + deviceId + \\\\'/jsonrpc-reply)用于接收回复消息,接着向另一个特定主题(webrtc/\\\\' + deviceId + \\\\'/jsonrpc)发布一条包含offer信息的 JSON 格式消息(设置了消息质量等级 qos 为 2),用于发起某种交互(可能与 WebRTC 相关的会话建立等操作)。 (1)订阅函数subscribe client.subscribe(topic, options, callback); topic: 需要订阅的主题,可以是一个字符串或者一个主题数组。主题是 MQTT 中用于分类消息的方式。主题的命名规则通常是层级的,使用斜杠(/)分隔每一层级,例如 \\\\'home/livingroom/temperature\\\\'。 options: 可选参数,配置订阅的一些选项,如 QoS(服务质量)级别。常见的选项包括: -- qos: 服务质量(Quality of Service)级别,取值范围是: -- 0 - 至多一次:消息最多发送一次,不做确认。 -- 1 - 至少一次:消息至少发送一次,可能会重复。 -- 2 - 只有一次:消息只能传递一次,不会丢失,也不会重复。 callback: 可选的回调函数,当订阅成功时会被调用。回调函数接受一个错误对象作为参数,如果订阅成功,err 为 null;如果订阅失败,则返回相应的错误信息。 (2)发布函数publish client.publish(topic, message, options, callback); topic: 需要发布消息的主题,可以是一个字符串,表示主题名称。 message: 需要发布的消息内容,可以是字符串、Buffer、对象等。一般情况下,消息会被自动转换为字符串或 Buffer(如果传入对象,会自动通过 JSON.stringify() 转换为字符串)。 options: 可选参数,配置发布消息的选项,常见的选项包括: -- qos: 服务质量(Quality of Service)级别(如前述),默认是 0。 -- retain: 是否保留消息,若为 true,代理服务器会保存消息并在新订阅者连接时发送该消息。 callback: 可选的回调函数,发布消息成功后会被调用。回调函数接受一个错误对象作为参数,如果发布成功,err 为 null;如果发布失败,则返回相应的错误信息。 messange事件 MQTT消息接收处理(client.on(\\\\'message\\\\', function (topic, message) {... })),语法与上面类似,逻辑如下。 client.on('message', function (topic, message) { let msg = JSON.parse(message.toString()); if (msg.id == offerId) { let sdp = msg.result; let offer = { type: 'offer', sdp: sdp }; log(offer); pc.setRemoteDescription(offer); pc.createAnswer().then(d => pc.setLocalDescription(d)).catch(log); } else if (msg.id == answerId) { log('receive answer ok'); } }) 当接收到MQTT消息时,先将消息内容解析为 JSON 对象,然后根据消息中的 id 判断消息类型,如果 id 与之前定义的 offerId 相等,说明是对之前 offer 的回复,会提取其中的 sdp 信息设置为远程描述(setRemoteDescription),并创建一个本地回答(createAnswer)然后设置本地描述(setLocalDescription);如果 id 等于 answerId,则只是在控制台输出表示接收到了回答消息。 WebRTC操作 创建对象 var pc = new RTCPeerConnection({ iceServers: [ { urls: "turn:rtpeer.allwinnertech.com:3478", username: "pctest", credential: "Aa123456", }, ], }); 创建了一个 RTCPeerConnection 对象用于建立 WebRTC 连接,配置了 iceServers,这里指定了一个 TURN 服务器的相关信息(地址、用户名、密码),有助于在复杂网络环境(如存在 NAT 等情况)下建立稳定的点对点连接,确保音视频数据能顺利传输。 获取PC录音 navigator.mediaDevices.getUserMedia({ video: false, audio: true }) .then(stream => { stream.getTracks().forEach(track => pc.addTrack(track, stream)); }).catch(log); 通过 navigator.mediaDevices.getUserMedia 方法请求获取音频媒体流(这里明确只要音频,video: false, audio: true),如果获取成功,将音频轨道添加到 RTCPeerConnection 对象(pc)中,以便后续传输音频数据,如果出现错误则通过 log 函数(也就是在控制台输出错误信息)进行处理。 连接状态 pc.oniceconnectionstatechange = e => { log(pc.iceConnectionState); document.getElementById('status').innerHTML = pc.iceConnectionState; if (pc.iceConnectionState == 'connected') { // default to muted if (!isMuted) onMuted(); } } 当 WebRTC 连接的 ICE 连接状态发生变化时,先在控制台输出当前连接状态(通过 log 函数),然后更新页面上 id 为 status 的元素文本内容为当前连接状态,并且当连接状态变为 connected(已连接)时,如果当前不是静音状态(!isMuted),则调用 onMuted 函数将音频静音。 ICE候选者 pc.onicecandidate = event => { if (event.candidate === null) { console.log(pc.localDescription.sdp) let json = { jsonrpc: \\'2.0\\', method: \\'answer\\', params: pc.localDescription.sdp, id: answerId, } console.log(json) client.publish(\\'webrtc/\\' + deviceId + \\'/jsonrpc\\', JSON.stringify(json)) setInterval(() => {....}, 1000); } } 当有 ICE 候选者信息产生并且当候选者为 null 时(意味着 ICE 收集过程结束),执行一系列操作,包括打印 RTCPeerConnection 的本地描述中的 sdp 信息,向 MQTT 特定主题发布包含本地描述 sdp 信息的 JSON 消息,并且每隔 1 秒获取连接的统计信息(通过 pc.getStats 方法),整理后更新到页面中 id 为 stats-box 的元素内展示出来。 当收到对端的offer候选信息后,本地会进行与stun/turn服务交互,交互获取到ICE信息后,就会触发回调这个函数,将自己SDP信息组装成json通过mqtt publish发送给对端。 接收音视频 pc.ontrack = function (event) { if (event.track.kind == \\'video\\') { var el = document.getElementById(\\'videoStream\\'); var newStream = new MediaStream(); newStream.addTrack(event.track); el.srcObject = newStream; el.autoplay = true el.controls = false el.muted = true document.getElementById(\\'imgStream\\').style.display = \\'none\\'; document.getElementById(\\'videoStream\\').style.display = \\'block\\'; // 当视频流添加成功后,获取视频分辨率和码率信息并更新显示 el.addEventListener(\\'loadedmetadata\\', function () { updateVideoInfo(event.track); }); } else if (event.track.kind == \\'audio\\') { var el = document.getElementById(\\'audioStream\\'); var newStream = new MediaStream(); newStream.addTrack(event.track); el.srcObject = newStream; el.controls = false el.muted = false } } 当通过 RTCPeerConnection 接收到远端传来的媒体轨道(视频或音频轨道)时,根据轨道类型进行不同的处理。对于视频轨道,获取页面中的视频元素(id 为 videoStream),创建新的 MediaStream 对象添加轨道后设置为视频元素的播放源,同时设置自动播放、隐藏控制条、静音等属性,并在视频元数据加载完成后(通过 loadedmetadata 事件)调用 updateVideoInfo 函数更新视频分辨率和码率信息显示;对于音频轨道,类似地获取音频元素(id 为 audioStream),添加轨道到新的 MediaStream 对象并设置为音频元素播放源,设置不显示控制条且不静音。 数据通道 const datachannel = pc.createDataChannel(\\'pear\\') datachannel.onclose = () => console.log(\\'datachannel has closed\\'); datachannel.onopen = () => { console.log(\\'datachannel has opened\\'); console.log(\\'sending ping\\'); setInterval(() => { console.log(\\'sending ping\\'); datachannel.send(\\'ping\\'); }, 1000); } datachannel.onmessage = e => { if (e.data.byteLength === undefined) { console.log(e.data); } else { // is binary data. mjpeg stream // console.log(e.data.byteLength); var arrayBufferView = new Uint8Array(e.data); var blob = new Blob([arrayBufferView], { type: \\"image/jpeg\\" }); var urlCreator = window.URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL(blob); var imageElement = document.getElementById(\\'imgStream\\'); imageElement.src = imageUrl; } } 创建了一个名为pear的数据通道(const datachannel = pc.createDataChannel(\\'pear\\')),并分别对数据通道的打开、关闭、接收消息等事件绑定了相应的回调函数。例如在数据通道打开时(datachannel.onopen),会定期(每隔 1 秒)发送 ping 消息用于保持连接或检测连接状态等;在接收到消息时(datachannel.onmessage),根据消息数据类型判断,如果是文本消息直接在控制台输出,如果是二进制数据(判断为 MJPEG 流),则将其转换为图片的 URL 并设置给页面中的图片元素(id 为 imgStream)用于显示。 所以这里支持mjpeg的方式,但是是通过数据通道来实现的。
  • webrtc网页代码分析一

    webrtc网页代码分析一

    文档结构 <!doctype html> <html lang="en"> <head> ... </head> <body> ... </body> </html> 文档类型声明():声明文档使用HTML5标准。 html标签:设置文档的语言为英语(lang=\"en\")。 head标签:包含元数据、样式、外部链接等,定义网页的头部信息。 body标签:页面的主体部分,包含所有网页内容。 head标签 字符集和窗口设置 <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> meta标签:用于定义页面的字符集、视口设置、作者信息等。 charset=\\"utf-8\\":指定字符编码为UTF-8,确保网页能够正确显示多种语言的字符。 viewport 设置:用于响应式设计,确保页面在不同设备上显示良好。width=device-width 表示页面宽度等于设备的屏幕宽度,initial-scale=1 表示初始缩放比例为 1。 外部资源引入 <script src="https://unpkg.com/mqtt@4.1.0/dist/mqtt.min.js"></script> <script src="https://kit.fontawesome.com/8c8bbe3334.js" crossorigin="anonymous"></script> MQTT库:引入了 MQTT JavaScript 客户端库(版本 4.1.0)。MQTT 是一种轻量级的消息发布/订阅协议,通常用于 IoT(物联网)应用。这表明页面可能涉及到实时消息传递或设备间通信。 FontAwesome图标库:引入了 FontAwesome 的 JavaScript 库,用于提供图标支持。crossorigin=\"anonymous\" 允许浏览器在不发送凭据的情况下访问该资源。 CSS样式 style标签包含了许多样式规则,以下是主要部分的分析。 全局设置 html { font-family:"Calibri", sans-serif; box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } font-family: \"Calibri\", sans-serif;:设置了页面字体为 \"Calibri\",如果不支持则使用 sans-serif。 box-sizing: border-box;:使得所有元素的 padding 和 border 都包含在 width 和 height 的计算中,避免了布局问题。 响应式设计 @media screen and (max-width: 650px) { .column { width: 100%; display: block; } } 使用 @media 媒体查询来调整页面布局。在屏幕宽度小于或等于 650px 时,将 .column 类的元素的宽度设置为 100%,并显示为块级元素。这样有助于在小屏幕设备(如手机)上优化布局。 卡片样式和容器 .card { box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); } .container { position: relative; text-align: center; color: white; } .card:为卡片元素设置了一个阴影效果,使其在页面上看起来更立体。 .container:设置容器为相对定位,并使文本居中。文本颜色为白色。 按钮样式 .title { color: grey; } .bottom-left, .top-left, .top-right, .bottom-right { position: absolute; padding: 10px; } .button { border: none; outline: 0; display: inline-block; padding: 8px; color: white; background-color: #000; text-align: center; cursor: pointer; } .button:hover { background-color: #555; } .title:为标题设置灰色字体颜色。 .bottom-left, .top-left, .top-right, .bottom-right:为定位在四个角的元素提供了绝对定位样式。通过定位,它们可以被放置在容器的四个角。 .button:定义了按钮的基本样式,包括无边框、白色文字、黑色背景以及光标指针。hover 样式将按钮背景色更改为灰色(#555)。 导航栏样式 .topnav { overflow: hidden; background-color: #333; } .topnav a { float: left; display: block; color: #f2f2f2; text-align: center; padding: 14px 16px; text-decoration: none; font-size: 17px; } .topnav a:hover { background-color: #ddd; color: black; } .topnav a.active { background-color: #04AA6D; color: white; } .topnav:定义了一个水平的导航栏,背景色为深灰色,且隐藏溢出的内容。 .topnav a:定义了导航栏中每个链接的样式,包括文本颜色、对齐方式、内边距等。 .topnav a:hover:设置链接在悬停时背景色变为浅灰色,文字颜色变为黑色。 .topnav a.active:当链接处于 \"活动\" 状态时,背景色变为绿色(#04AA6D),文字变为白色。 按钮样式 .btn-group button { margin-top: -4px; background-color: white; border: 1px solid white; color: black; padding: 10px 24px; cursor: pointer; } .btn-group button:hover { background-color: #f2f2f2; } .btn-group button:定义了按钮组中的按钮样式,包括背景色为白色,边框为白色,文本为黑色,且在悬停时背景色变为浅灰色。 普通按钮样式 .btn { background-color: DodgerBlue; border: none; color: white; padding: 12px 16px; font-size: 16px; cursor: pointer; } .btn:hover { background-color: RoyalBlue; } .btn:定义了一个蓝色的按钮,包含内边距、字体颜色等样式。在悬停时,按钮的背景色会变成更深的蓝色(RoyalBlue)。 body标签 body包含页面部分显示部分和script部分。 页面内容 导航栏区域 <div class="topnav" id="myTopnav"> <a href="#">Peer Project</a> <a href="https://ko-fi.com/sepfy95" style="float: right">Sponsor</a> <a href="https://github.com/sepfy/pear" style="float: right">Github</a> </div> 这是一个顶部导航栏(topnav)元素,包含三个链接: Peer Project:这是主页面的链接,指向当前页面(#)。可以是应用的标题或者主页面链接。 ko-fi.com:链接到 ko-fi.com,可能是用于捐赠或支持项目。 github: GitHub 项目的链接,用户可以访问该链接查看源代码或参与项目。 这些链接使用了 float: right 样式,使得“Sponsor”和“Github”链接都被浮动到右侧,而“Peer Project”链接则停留在左侧。 音视频展示区域 //这是包含视频内容的容器 <div class="column" id="video"> //用来包裹所有显示视频、音频及空间的内容,创建一个卡片式布局 <div class="card"> //容器内放置了视频和音频元素,包含一些展示信息的<p>标签 <div class="container"> // 媒体元素信息 //图片元素,在没有视频流可用时显示,他的id是imgStream <img id="imgStream" style="width: 100%"> //video元素,用于显示视频流,display样式为none,默认视频流不可见。通过js控制 <video id="videoStream" playsinline style="width:100%; display: none"></video> //audio元素,用于播放音频流的,默认被隐藏,通过js控制。 <audio id="audioStream" style="display: none"></audio> //用于设置设备信息 //段落元素用于显示设备信息,这里是device id <p class="top-left" id="device-id"></p> //段落元素,用于显示设备当的状态。 <p class="top-right" id="status">waiting</p> </div> //控制按钮,包括音量、录音、旋转、暂停 <div class="btn-group" style="width:100%"> //控制音量的按钮,调用onVolume函数,图标为fa-solid fa-volume-xmark <button style="width:25%" class="btn" onclick="onVolume()"><i id="volume-icon" class="fa-solid fa-volume-xmark"></i></button> //控制是否静音,调用onMuted函数,图标为fa-solid fa-microphone-slash <button style="width:25%" class="btn" onclick="onMuted()"><i id="mute-icon" class="fa-solid fa-microphone-slash"></i></button> //用于旋转视频,点击调用onRotate函数,图标为solid fa-arrows-rotate <button style="width:25%" class="btn" onclick="onRotate()"><i class="fa-solid fa-arrows-rotate"></i></button> //用于停止视频或音频流,点击调用onStop函数,图标为fa-solid fa-circle-stop <button style="width:25%" class="btn" onclick="onStop()"><i id="stop-icon" class="fa-solid fa-circle-stop"></i></button> </div> </div> </div>
  • javascript之dom

    javascript之dom

    什么是dom DOM(Document Object Model)是一个编程接口,它将 HTML 或 XML 文档呈现为一个由节点和对象(这些节点和对象其实是文档的各种元素、属性和文本内容等)组成的树形结构。这个树形结构允许开发者使用编程语言(如 JavaScript)来访问、修改和操作文档的内容、结构和样式。对于以下简单的 HTML 文档: <!DOCTYPE html> <html> <head> <title>My Page</title> </head> <body> <h1>Hello, World!</h1> <p id=\\\"myParagraph\\\">This is a paragraph.</p> </body> </html> 它的 DOM 树结构大致如下: - html 节点是根节点,它包含两个子节点:head 和 body。 - head 节点包含一个子节点 title,title 节点的文本内容是 \\\\\\\"My Page\\\\\\\"。 - body 节点包含两个子节点:h1 和 p。h1 节点的文本内容是 \\\\\\\"Hello, World!\\\\\\\",p 节点有一个属性 id 为 \\\\\\\"myParagraph\\\\\\\",其文本内容是 \\\\\\\"This is a paragraph.\\\\\\\"。 获取dom元素 getElementById 这是最常用的方法之一,用于通过元素的id属性获取单个元素。id在HTML文档中应该是唯一的,语法如下。 document.getElementById(\\\"elementId\\\"); 在上面的 HTML 文档中,如果要获取p元素,可以使用以下 JavaScript 代码: var paragraph = document.getElementById(\\\"myParagraph\\\"); console.log(paragraph.textContent); // 输出:This is a paragraph. getElementsByTagName 根据标签名获取元素集合。它返回一个类似数组的对象(HTMLCollection),包含所有匹配标签名的元素。语法如下: document.getElementsByTagName(\\\"tagName\\\"); 要获取文档中的所有 h1 元素: var h1Elements = document.getElementsByTagName(\\\"h1\\\"); for (var i = 0; i < h1Elements.length; i++) { console.log(h1Elements[i].textContent); // 输出:Hello, World! } getElementsByClassName 通过类名获取元素集合。同样返回一个 HTMLCollection,包含所有具有指定类名的元素。 document.getElementsByClassName(\\\"className\\\"); 假设HTML中有多个元素有highlight类,如下: <p class=\\\"highlight\\\">This is a highlighted paragraph.</p> <span class=\\\"highlight\\\">This is a highlighted span.</span> 可以使用以下代码获取这些元素: var highlightedElements = document.getElementsByClassName(\\\"highlight\\\"); for (var i = 0; i < highlightedElements.length; i++) { console.log(highlightedElements[i].textContent); // 输出两个元素的文本内容 } 修改元素 修改元素内容 textContent属性 用于获取或设置元素的文本内容,如改变前面获取的p元素的内容 var paragraph = document.getElementById(\\\"myParagraph\\\"); paragraph.textContent = \\\"This is a new paragraph content.\\\"; innerHTML属性 用于获取或设置元素内部的HTML内容。可以用于添加或修改元素内部的标签和文本。如在前面p元素内部添加一个strong标签: var paragraph = document.getElementById(\\\"myParagraph\\\"); paragraph.innerHTML = \\\"This is a <strong>modified</strong> paragraph.\\\"; 修改元素属性 获取和设置属性 可以通过元素对象的属性来获取和设置大多数 HTML 属性。例如,对于一个 元素的 src 属性。假设 HTML 中有一个img元素,可以这样修改其src属性: <img id=\\\"myImage\\\" src=\\\"original.jpg\\\"> var image = document.getElementById(\\\"myImage\\\"); image.src = \\\"new.jpg\\\"; 使用setAttribute和getAttribute setAttribute 用于设置元素的属性,getAttribute 用于获取元素的属性。如对于上面的img元素,也可以这样操作: var image = document.getElementById(\\\"myImage\\\"); image.setAttribute(\\\"src\\\", \\\"new.jpg\\\"); var currentSrc = image.getAttribute(\\\"src\\\"); console.log(currentSrc); // 输出:new.jpg 创建与添加DOM元素 创建元素 使用createElement方法。具体为先使用 document.createElement(\\\\\\\"tagName\\\\\\\") 创建一个新的 HTML元素。如创建一个新的div元素并设置其 id 和内容,如下。 var newDiv = document.createElement(\\\"div\\\"); newDiv.id = \\\"newDiv\\\"; newDiv.textContent = \\\"This is a new div.\\\"; 添加元素 appendChild 使用appendChild方法,将一个元素添加为另一个元素的子元素。例如,将新创建的div元素添加到 body元素中。 var body = document.getElementsByTagName(\\\"body\\\")[0]; var newDiv = document.createElement(\\\"div\\\"); newDiv.id = \\\"newDiv\\\"; newDiv.textContent = \\\"This is a new div.\\\"; body.appendChild(newDiv); insertBefore 在指定的子元素之前插入一个新元素。它需要两个参数:要插入的新元素和参考元素(新元素将插入在参考元素之前)。如假设有两个 p 元素在 body 中,在第一个 p 元素之前插入新的 div 元素。 <body> <p>First Paragraph</p> <p>Second Paragraph</p> </body> var body = document.getElementsByTagName(\\\"body\\\")[0]; var newDiv = document.createElement(\\\"div\\\"); newDiv.id = \\\"newDiv\\\"; newDiv.textContent = \\\"This is a new div.\\\"; var firstParagraph = document.getElementsByTagName(\\\"p\\\")[0]; body.insertBefore(newDiv, firstParagraph); 删除DOM元素 用于从父元素中删除指定的子元素,语法如下。 parentElement.removeChild(childElement); 如假设要删除前面添加的div元素(假设它有 id 为 \\\\\\\"newDiv\\\\\\\")。 var divToRemove = document.getElementById(\\\"newDiv\\\"); var body = document.getElementsByTagName(\\\"body\\\")[0]; body.removeChild(divToRemove);
  • 快速搭建一个可访问的网页

    快速搭建一个可访问的网页

    简介 搭建网站需要以下几个组件, Web服务器(如Apache、Nginx):它是网页能够被访问的核心组件。当用户在浏览器中输入网站的域名或 IP 地址并请求访问网页时,Web 服务器软件会接收这些 HTTP 请求。负责从服务器的存储设备(如硬盘)中找到对应的网页文件(如 HTML、CSS、JavaScript 文件等),并将这些文件发送回用户的浏览器。 编程语言支持(如PHP、Python、Node.js):实现网页的动态生成和业务逻辑,如果只是静态网页或不需要服务器端脚本处理PHP也不需要安装。 SSL证书(可选):用于加密HTTP通信,提升安全性。如果不需要安全链接,也不需要安装。 数据库(如MySQL、PostgreSQL)(可选):如果网页需要存储和管理大量的数据(如用户信息、文章内容、产品信息等),数据库就非常重要。 安装 安装nginx apache和nginx可以二选一 软件安装步骤 步骤1: 执行命令安装nginx sudo yum install nginx 步骤2: 启动nginx sudo systemctl start nginx 步骤3:查看启动状态 sudo systemctl status nginx 配置证书支持HTTPS 步骤1: 打开nginx.conf文件 /etc/nginx/nginx.conf 步骤2: 配置证书路径 步骤3: 重启nginx sudo systemctl restart nginx 根页面 在nginx.conf文件中,可以配置根页面,如上图的root /usr/share/nginx/html。 安装Apache 软件安装步骤 步骤1:执行命令安装Apache及其扩展包。 sudo yum -y install httpd httpd-manual httpd-devel mod_ssl mod_perl php-mysqli 如果回显信息显示Complete!,则表示Apache安装成功。 步骤2:依次执行命令启动Apache并设置自启动。 sudo systemctl start httpd sudo systemctl enable httpd 步骤3:执行命令查看Apache运行状态。如果回显信息显示active(running)时,表示Apache已启动。 systemctl status httpd 4.在本地电脑浏览器地址栏中输入http://<ECS服务器的公网IP>,测试Apache服务是否安装成功。如果显示如图所示的测试页面,表示Apache服务已安装成功。ECS服务器的弹性公网IP可以从ECS实例页面获取。 至此,按照上面流程,就可以访问一个页面了。页面的位置在;/usr/share/httpd/noindex/index.html 证书更新步骤 步骤1:找到ssl.conf文件 cd /etc/httpd/conf.d 步骤2: 编辑ssl.conf更新SSLCertificateFile、SSLCertificateKeyFile、SSLCertificateChainFile。 步骤3: 重启Apache。 sudo systemctl restart httpd 通过以上的步骤,把你的网页放到/var/www/html下面,远端就可以访问了。 数据库 暂时用不到,后续再补充。 PHP 暂时用不到,后续再补充。 其他 关于开机自启动 设置应用开机自启动 systemctl enable nginx 查询应用是否开机 systemctl list-unit-files | grep nginx 或者systemctl is-enabled nginx 关闭应用自启动 systemctl disable httpd
  • openwrt规则编译cmake软件包

    openwrt规则编译cmake软件包

    软件包中有完整CMakelists.txt include $(TOPDIR)/rules.mk PKG_NAME:=usrsctp PKG_VERSION:=0.9.5.0 PKG_RELEASE:=3 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:= https://codeload.github.com/sctplab/usrsctp/tar.gz/v$(PKG_VERSION)? PKG_HASH:=260107caf318650a57a8caa593550e39bca6943e93f970c80d6c17e59d62cd92 PKG_LICENSE:=MIT PKG_LICENSE_FILES:=LICENSE PKG_BUILD_PARALLEL:=1 include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/cmake.mk define Package/usrsctp SECTION:=libs CATEGORY:=Libraries TITLE:=sctp stack URL:=https://github.com/sctplab/usrsctp endef define Package/cJSON/description this is a userland SCTP stack endef CMAKE_OPTIONS += -DBUILD_SHARED_LIBS=on #Provide compilation dependencies for other modules to call define Build/InstallDev $(INSTALL_DIR) $(1)/usr/include $(CP) $(PKG_INSTALL_DIR)/usr/include/usrsctp.h $(1)/usr/include $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/libusrsctp.so* $(1)/usr/lib/ $(INSTALL_DIR) $(1)/usr/lib/pkgconfig $(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/usrsctp.pc $(1)/usr/lib/pkgconfig $(SED) 's,/usr,$(STAGING_DIR)/usr,g' $(1)/usr/lib/pkgconfig/usrsctp.pc endef #Installed to the root file system for call at runtime define Package/usrsctp/install $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/libusrsctp.so* $(1)/usr/lib/ endef $(eval $(call BuildPackage,usrsctp)) 如上是示例,这种类型比较简单,包含了cmake.mk后,openwrt会自动生成编译,只需要安装需要被其他代码依赖的库和头文件,以及运行时需要的库即可。 软件包中有完整CMakelists.txt但需要创建build目录 include $(TOPDIR)/rules.mk PKG_NAME:=libsrtp PKG_VERSION:=2.6.0 PKG_RELEASE:=3 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/cisco/libsrtp/tar.gz/v$(PKG_VERSION)? PKG_HASH:=bf641aa654861be10570bfc137d1441283822418e9757dc71ebb69a6cf84ea6b PKG_LICENSE:=MIT PKG_LICENSE_FILES:=LICENSE PKG_BUILD_PARALLEL:=1 include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/cmake.mk define Package/libsrtp SECTION:=libs CATEGORY:=Libraries TITLE:=libsrtp Client Library URL:=https://github.com/cisco/libsrtp endef define Package/libsrtp/description srtp Library endef CMAKE_BINARY_SUBDIR=build CMAKE_OPTIONS += -DBUILD_SHARED_LIBS=on -DTEST_APPS=off define Build/InstallDev $(INSTALL_DIR) $(1)/usr/include/srtp2 $(CP) $(PKG_INSTALL_DIR)/usr/include/srtp2/*.h $(1)/usr/include/srtp2 $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/lib*.so* $(1)/usr/lib/ endef define Package/libsrtp/install $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/lib*.so* $(1)/usr/lib/ endef $(eval $(call BuildPackage,libsrtp)) 这种相对前面的方式类似,只是这种软件包CMakelist.text要求需要创建build目录进行编译,因此在Makefile中添加CMAKE_BINARY_SUBDIR=build进行声明。因此在编译输出目录,可以看到openwrt会先创建build目录,而第一种方式是没有创建build目录。 软件包中只有部分CMakelist.text 这种方式就是软件包中没有提供完整的CMakelist.txt,但是提供了部分,需要用户包含提供部分的Cmakelist.txt实现,这种方式目前是通过打patches的方式生成主要CMakelist.txt。 Makefile文件 include $(TOPDIR)/rules.mk PKG_NAME:=coreMQTT PKG_VERSION:=2.3.1 PKG_RELEASE:=3 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:= https://codeload.github.com/FreeRTOS/coreMQTT/tar.gz/v$(PKG_VERSION)? PKG_HASH:=b8e95044e6ef8381610949b7fe546c5ddf4e52989ce9938209d5dd5f3371b5d7 PKG_LICENSE:=MIT PKG_LICENSE_FILES:=LICENSE PKG_BUILD_PARALLEL:=1 include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/cmake.mk define Package/coreMQTT SECTION:=libs CATEGORY:=Libraries TITLE:=coreMQTT Client Library URL:=https://github.com/FreeRTOS/coreMQTT endef define Package/coreMQTT/description coreMQTT Client Library endef define Build/InstallDev $(INSTALL_DIR) $(1)/usr/include $(CP) $(PKG_INSTALL_DIR)/*.h $(1)/usr/include $(CP) $(PKG_INSTALL_DIR)/include/*.h $(1)/usr/include $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/lib*.so* $(1)/usr/lib/ endef define Package/coreMQTT/install $(INSTALL_DIR) $(1)/usr/lib $(CP) $(PKG_INSTALL_DIR)/usr/lib/libcoreMQTT.so* $(1)/usr/lib/ endef $(eval $(call BuildPackage,coreMQTT)) patches diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..754a6a2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.10) + +project(CoreMQTT) + +include(${CMAKE_CURRENT_LIST_DIR}/mqttFilePaths.cmake) + +add_definitions("-DMQTT_DO_NOT_USE_CUSTOM_CONFIG") + +include_directories ( + ${MQTT_INCLUDE_PUBLIC_DIRS} +) + +add_library(coreMQTT SHARED ${MQTT_SOURCES} ${MQTT_SERIALIZER_SOURCES}) + +install(TARGETS coreMQTT + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(DIRECTORY ${MQTT_INCLUDE_PUBLIC_DIRS}/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ + FILES_MATCHING PATTERN "*.h" +) -- 2.25.1 以上是通过打patches生成主CMakelist.txt,可以看到包含了mqttFilePaths.cmake文件。
  • Cmake构建模版

    Cmake构建模版

    基础示例 # 最低 CMake 版本要求 cmake_minimum_required(VERSION 3.16) # 定义工程名称和语言 project(mlink_device VERSION 1.0.0 LANGUAGES C) # ============================================ # 收集源码文件 # ============================================ # 只收集 src/ 目录下的实现文件 file(GLOB_RECURSE MLINK_DEVICE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c ) # ============================================ # 创建库目标(可选静态 / 可选共享) # ============================================ option(MLINK_DEVICE_BUILD_SHARED "Build mlink_device as shared library" ON) if(MLINK_DEVICE_BUILD_SHARED) add_library(mlink_device SHARED ${MLINK_DEVICE_SOURCES}) else() add_library(mlink_device STATIC ${MLINK_DEVICE_SOURCES}) endif() # 给库取一个 namespace 别名,让别人可以用 components::mlink_device add_library(components::mlink_device ALIAS mlink_device) # ============================================ # 设置 C 标准 # ============================================ target_compile_features(mlink_device PUBLIC c_std_99) # ============================================ # PRIVATE 宏定义(不会影响其他 target) # ============================================ target_compile_definitions(mlink_device PRIVATE _GNU_SOURCE ) # ============================================ # 头文件路径设置(现代写法) # ============================================ # PUBLIC:构建时 + 安装后供使用者使用 # PRIVATE:仅本库自己使用,不导出 target_include_directories(mlink_device PUBLIC # 本地构建时使用的 include 路径 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # 安装后,头文件会被安装到 ${CMAKE_INSTALL_PREFIX}/include # 所以使用相对路径 include(自动加上 install prefix) $<INSTALL_INTERFACE:include> PRIVATE # 库内部使用的私有头文件,不暴露给外部项目 ${CMAKE_CURRENT_SOURCE_DIR}/private ) # ============================================ # 链接依赖(示例) # ============================================ # target_link_libraries(mlink_device # PUBLIC some_other_lib # PRIVATE pthread # ) # ============================================ # 安装规则(核心) # ============================================ # 安装库文件 install( TARGETS mlink_device EXPORT mlink_deviceTargets # 导出 target 列表 ARCHIVE DESTINATION lib # 静态库 .a 安装位置 LIBRARY DESTINATION lib # 共享库 .so 安装位置 RUNTIME DESTINATION bin # 可执行文件(如果是 EXE) ) # 安装公共头文件到 include/ install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION include ) # ============================================ # 导出 CMake package(下游使用 find_package) # ============================================ # 生成 mlink_deviceTargets.cmake install( EXPORT mlink_deviceTargets NAMESPACE components:: # 别名前缀 DESTINATION lib/cmake/mlink_device # 包的安装位置 ) # 生成 mlink_deviceConfig.cmake & version 文件 include(CMakePackageConfigHelpers) # 自动生成版本文件,包含 version 检查逻辑 write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/mlink_deviceConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY ExactVersion ) # 生成 Config 文件(下游项目 find_package 用) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/mlink_deviceConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/mlink_deviceConfig.cmake INSTALL_DESTINATION lib/cmake/mlink_device ) # 安装 config 与 version 文件 install( FILES ${CMAKE_CURRENT_BINARY_DIR}/mlink_deviceConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/mlink_deviceConfigVersion.cmake DESTINATION lib/cmake/mlink_device ) 收集源代码 file(GLOB_RECURSE MLINK_DEVICE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/core/*.c" ) 自动递归扫描 core/ 目录下所有 .c 文件,并把结果放到变量 MLINK_DEVICE_SOURCES 中。 创建库目标 option(MLINK_DEVICE_BUILD_SHARED "Build mlink_device as shared library" ON) if(MLINK_DEVICE_BUILD_SHARED) add_library(mlink_device SHARED ${MLINK_DEVICE_SOURCES}) else() add_library(mlink_device STATIC ${MLINK_DEVICE_SOURCES}) endif() # 给库取一个 namespace 别名,让别人可以用 components::mlink_device add_library(components::mlink_device ALIAS mlink_device) option:添加一个Bool的开关,决定是编译动态库还是静态库。 add_library:创建一个库,语法add_library(name [STATIC | SHARED | MODULE] [sources...]) ALIAS是给库创建一个新的别名,方便其他模块调用。 编译参数 target_compile_features(mlink_device PUBLIC c_std_99) # ======== # PRIVATE 宏定义(不会影响其他 target) # ======== target_compile_definitions(mlink_device PRIVATE _GNU_SOURCE ) target_compile_features:如上示例,gcc -std=c99 target_compile_definitions:添加编译宏,-Dxxx,如上-D_GNU_SOURCE,但PRIVATE设置了作用于只对taget自己游侠,下游不继承。 作用域 设置方式 A 本身用 A 的用户(B)用 PRIVATE ✔ ✘ PUBLIC ✔ ✔(传播) INTERFACE ✘ ✔(只传播) 头文件搜索 target_include_directories(mlink_device PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/mcp ${CMAKE_CURRENT_SOURCE_DIR}/include/transport ${CMAKE_CURRENT_SOURCE_DIR}/include/utils ) target_include_directories:给目标mlink_device配置编译时要搜索的头文件目录,类似加上-I....。 PRIVATE:在编译mlink_device模块编译自己这个库时寻找的头文件路径,不会传给它的依赖目标。 PUBLIC:这是mlink_device对外提供API的头文件目录,其他模块链接mlink_device库时也会自动继承这些include目录。 PRIVATE搜索的头文件路径不会传播,只会影响当前的mlink_device模块这个编译目标。而PUBLIC会传播给他一来的target,然后这个传播又分为两类一个是构建期和安装后。 PUBLIC + BUILD_INTERFACE:构建阶段,适用于同一个构建Cmake,提供的头文件路径为源码路径,${CMAKE_CURRENT_SOURCE_DIR}/include就是srobot/components/mlink/include。 PUBLIC + INSTALL_INTERFACE:安装阶段,适用于不同的构建Cmake,给其他模块find_package() 后使用的 include 路径。:include就会变成 ${PREFIX}/include,如srobot/outpu/sdk/include。 库搜索 find_package+target_link_libraries组合。 find_package(OpenSSL REQUIRED) Cmake就会自动搜索 /usr/lib/cmake/openssl/... /usr/local/lib/cmake/openssl/... /opt/sdk/lib/cmake/openssl/... 然后只需要: target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto) 库和头文件导出 #导出编译产物如库、可执行文件 install( TARGETS mlink_device EXPORT ComponentsTargets ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin ) #导出普通文件,如头文件 install( FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/mlink.h DESTINATION include ) EXPORT:mlink_device 这个 target 加入一个“targets 导出列表”,后面install(EXPORT...) 把它写入 CMake 模块文件,让外部项目 find_package 时导入 components::mlink_device。 ARCHIVE DESTINATION lib:静态库(.a)安装到哪里?——> prefix/lib。编译cmake指定的-DCMAKE_INSTALL_PREFIX LIBRARY DESTINATION lib:共享库(.so / .dylib)安装到哪里?——>prefix/lib。 RUNTIME DESTINATION bin:可执行文件安装到哪里?——>prefix/bin。 FILES:导出头文件到——>prefix/include
  • 部署MQTT云服务

    部署MQTT云服务

    Centos安装 配置emqx的源 curl -s https://assets.emqx.com/scripts/install-emqx-rpm.sh | sudo bash 安装emqx sudo yum install emqx -y 启动emqx sudo systemctl start emqx 如果要卸载 sudo yum remove emqx 后台配置 访问前,服务端的端口18083权限打开。 截屏2024-12-01 08.53.43 xxx.xxx.xxx:18083 默认登录账号:admin,public。首次登录会要求更新密码。 如果要支持tcp/ssl/ws/wss这几种监听,也需要把相应的端口打开以及服务器端口权限打开。 测试访问 使用mosquitto测试 这里使用的是ubuntu系统上的mosquito_sub/pub工具测试。先安装工具: sudo apt-get install mosquitto-clients 订阅 mosquitto_sub -h www.xxx.xxx -t \"test\" -v 发布 mosquitto_pub -h www.laumy.tech -t \"test\" -m \"hello world\" 使用MQTTX软件测试 下载地址:https://mqttx.app/zh/downloads 建立一个回话连接用于订阅消息 接着对填写订阅主题 再建立一个回话用于发布消息 连接上后进行发布主题 这样订阅的回话就收到订阅消息了 开启SSL/TLS连接 参考:https://docs.emqx.com/zh/emqx/v5.8/network/overview.html 启动异常排查 查找emqx.log cd / find -name \"emqx.log*\" 参考: https://docs.emqx.com/zh/cloud/latest/connect_to_deployments/react_sdk.html https://docs.emqx.com/zh/emqx/v5.8/connect-emqx/developer-guide.html
  • coturn安装

    coturn安装

    安装 yum install coturn -y 配置 vim /etc/coturn/turnserver.conf listening-port=3478 #指定 coturn 监听的端口。默认的 TURN 协议端口是 3478,用于接收 TURN 客户端的连接请求。 tls-listening-port=5349 #指定用于 TLS (传输层安全协议) 加密连接的监听端口。TURN 支持通过 TLS 来增强通信的安全性,默认的端口是 5349。 relay-ip=172.20.65.5 #指定 TURN 服务器用于中继流量的 IP 地址。这个 IP 地址通常是内部的局域网地址,用于数据中继传输。 relay-ip=fe80::216:3eff:fe01:105b #指定 TURN 服务器的 IPv6 地址,通常用于支持 IPv6 网络的环境中。 external-ip=8.134.108.235 #指定TURN服务器的外部公网 IP 地址。当 TURN 服务器位于 NAT 后面时,external-ip 用于告知客户端如何访问该服务器。 relay-threads=50 #指定服务器处理中继流量时使用的线程数量。增加线程数有助于提高并发连接的处理能力。 min-port=40000 max-port=65535 #指定 coturn 使用的最小和最大端口范围。coturn 会在该范围内选择端口来建立临时的媒体连接。这个端口范围通常需要开放,供客户端进行通讯。 server-name=www.laumy.tech #指定 TURN 服务器的名称。这通常用于身份验证或显示给客户端的服务器标识。 lt-cred-mech # 打开这个表示要使用加密,默认是打开的,加密的话就需要验证username和密码,如下的user字段。 no-auth #如果打开这个,表示不需要加密,和lt-cred-mech是二选一。 user=laumy:A8xxxx22 #指定允许连接到 TURN 服务器的用户名和密码。格式为 username:password,用于身份验证。laumy 是用户名,A8xxxx22 是密码。 realm=www.laumy.tech #指定 TURN 服务器的“域”名,用于标识一个身份验证域。这个域通常与 user 结合使用,确保身份验证的正确性。 cert=/etc/xxx/cert/www.laumy.tech.pem pkey=/etc/nginx/cert/www.laumy.tech.key # 设置证书密钥 启停 启动: systemctl start coturn ps -ef |grep turnserver 关闭: systemctl stop coturn 调试 查看启动日志 /var/log/coturn/turnserver.log 测试 https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/ 参考: https://www.zhiboblog.com/2105.html
\t