当前位置: 首页 > news >正文

深入浅出mediasoup—通信框架

libuv 是一个跨平台的异步事件驱动库,用于构建高性能和可扩展的网络应用程序。mediasoup 基于 libuv 构建了包括管道、信号和 socket 在内的一整套通信框架,具有单线程、事件驱动和异步的典型特征,是构建高性能 WebRTC 流媒体服务器的重要基础,本文主要分析 mediasoup 对 libuv 的封装。

1. Pipe 通信

Node.js 进程与 worker 进程之间使用管道通信,而且是双向通信。node.js 进程通过管道向 worker 进程发送请求,并接收响应。worker 进程也可以主动向 node.js 进程发送通知消息。

1.1. 文件描述符

管道通信需要使用两个文件描述符,node.js 进程的文件描述符定义如下:

this.#channel = new Channel({producerSocket: this.#child.stdio[3],consumerSocket: this.#child.stdio[4],pid: this.#pid,
});

worker 进程的文件描述符定义如下:

static constexpr int ConsumerChannelFd{ 3 };
static constexpr int ProducerChannelFd{ 4 };

1.2. 静态结构

worker 进程对管道通信的封装看起来比较复杂,涉及到多个类,如下图所示。由于这里面糅合了几个逻辑,拆解以后会更好理解:

1)UnixStreamSocketHandle 封装了基于 libuv 的 pipe 通信能力,内部包含 libuv 句柄。

2)ChannelSocket 内部包含的 ConsumerSocket 和 ProducerSocket 对应管道通信的读和写两个方向。ChannelSocket 继承了 ConsumerSocket::Listener,从 ConsumerSocket 收到的管道消息,都会回调到 ChannelSocket。

3)全局只有一个 ChannelSocket 对象,被 Worker 持有。Worker 继承了 ChannelSocekt::Listener,ChannelSocket 收到的所有管道消息都会回调 Worker。

4)Worker 包含了一个 Shared 对象,从名字上能看出,这是一个“共享对象”,通过传参的方式共享给各个对象,本质上就是一个全局对象。

5)Shared 内部包含两个对象:ChannelMessageRegistor 和 ChannelNotifier。ChannelMessageRegistor 用来管理管道消息处理器,因为全局就一个 ChannelSocket 对象,所有需要处理管道消息的对象都要把自己注册到 ChannelMessageRegistor,Worker 根据注册信息把管道消息分发给各个处理器。ChannelNotifier 用来发送管道消息,其内部也是使用 ChannelSocket 来发送消息,所有对象需要向 Node.js 进程发送管道消息调用 ChannelNotifier 接口即可。

1.3. 数据流

管道通信的数据流如下图所示。接收到的管道消息会一层层回调到 Worker 对象,Worker 先对消息进行过滤,如果是 Worker 自己关注的消息,自己先处理,其他消息则根据“注册表”进行路由。发送管道消息,调用 ChannelNotifier::Emit 接口,最终通过 libuv 发送出去。

2. Socket 通信

Socket 通信主要用来处理 mediasoup worker 与 WebRTC 客户端之间的媒体通信,支持 TCP 和 UDP。

2.1. 静态结构

2.1.1. UDP

1)UdpSocketHandle 封装了基于 libuv 的 UDP 通信能力,内部包含 libuv 句柄。

2)UdpSocket 继承自 UdpSocketHandle,内部包含了一个数据监听对象,用来接收 UDP 消息。

2)PipeTransport、PlainTransport、WebRtcTransport 和 WebRtcServer 都支持 UDP 通信,它们内部都包含一个指向 UdpSocket 的指针,用来发送 UDP 消息。

【注】这里的 PipeTransport 并不是使用管道通信的 transport。

2.1.2. TCP

1)TcpServerHandle 封装了基于 libuv 的 TCP 监听能力,内部包含 libuv 句柄。

2)TcpConnectionHandle 封装了基于 libuv 的 TCP 通信能力,内部包含 libuv 句柄。TCP 连接中断会通过 OnTcpConnectionClosed 通知 TcpServerHandle。

3)TcpConnection 继承自 TcpConnectionHandle,收到 TCP 报文会回调连接监听者。

4)当前只有 WebRtcServer 和 WebRtcTransport 支持 TCP 通信。

【注】WebRtcServer 用来实现端口聚合,其上可以承载多个 WebRtcTransport。

2.2. Socket 创建

2.2.1. WebRtcServer

WebRtcServer 用来实现 WebRTC 连接的端口聚合,WebRtcTransport 可以运行在 WebRtcServer 之上,共享 WebRtcServer 的端口。

WebRtcServer 根据传入的参数,决定创建 UdpSocket 还是 TcpServer,支持指定端口或端口范围。

WebRtcServer::WebRtcServer(RTC::Shared* shared, const std::string& id,const flatbuffers::Vector<flatbuffers::Offset<Transport::ListenInfo>>* listenInfos): id(id), shared(shared)
{...// 遍历所有地址for (const auto* listenInfo : *listenInfos){auto ip = listenInfo->ip()->str();...// UDP 协议if (listenInfo->protocol() == FBS::Transport::Protocol::UDP){RTC::UdpSocket* udpSocket;// 指定端口范围,从中选择一个if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0){uint64_t portRangeHash{ 0u };udpSocket = new RTC::UdpSocket(this,ip,listenInfo->portRange()->min(),listenInfo->portRange()->max(),flags,portRangeHash);}// 指定端口else if (listenInfo->port() != 0){udpSocket = new RTC::UdpSocket(this, ip, listenInfo->port(), flags);}// 未指定端口,使用配置中的端口else{uint64_t portRangeHash{ 0u };udpSocket = new RTC::UdpSocket(this,ip,Settings::configuration.rtcMinPort,Settings::configuration.rtcMaxPort,flags,portRangeHash);}...}// TCP 协议else if (listenInfo->protocol() == FBS::Transport::Protocol::TCP){RTC::TcpServer* tcpServer;// 指定端口范围if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0){uint64_t portRangeHash{ 0u };tcpServer = new RTC::TcpServer(this,this,ip,listenInfo->portRange()->min(),listenInfo->portRange()->max(),flags,portRangeHash);}// 指定端口else if (listenInfo->port() != 0){tcpServer = new RTC::TcpServer(this, this, ip, listenInfo->port(), flags);}// 未指定端口,使用配置中的端口else{uint64_t portRangeHash{ 0u };tcpServer = new RTC::TcpServer(this,this,ip,Settings::configuration.rtcMinPort,Settings::configuration.rtcMaxPort,flags,portRangeHash);}...}}...
}

2.2.2. WebRtcTransport

如果 WebRtcTransport 运行在 WebRtcServer 之上,则 WebRtcTransport 不会再创建 Socket。

WebRtcTransport::WebRtcTransport(...)
{...// 将 WebRtcTransport 加入到 WebRtcServer 的转发列表this->webRtcTransportListener->OnWebRtcTransportCreated(this);...
}

否则,还需自食其力,WebRtcTransport  创建 Socket 的逻辑与 WebRtcServer 类似,不再赘述。

2.2.3. PlainTransport

PlainTransport 用来对接像 FFMPEG 这种第三方编码器和工具的推拉流, 只支持 UDP 协议,创建逻辑类似,也支持指定端口或端口范围。

PipeTransport::PipeTransport(RTC::Shared* shared,const std::string& id,RTC::Transport::Listener* listener,const FBS::PipeTransport::PipeTransportOptions* options): RTC::Transport::Transport(shared, id, listener, options->base())
{...// 指定端口范围if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0){uint64_t portRangeHash{ 0u };this->udpSocket = new RTC::UdpSocket(this,this->listenInfo.ip,this->listenInfo.portRange.min,this->listenInfo.portRange.max,this->listenInfo.flags,portRangeHash);}// 指定端口else if (this->listenInfo.port != 0){this->udpSocket = new RTC::UdpSocket(this, this->listenInfo.ip, this->listenInfo.port, this->listenInfo.flags);}// 未指定端口,使用配置else{uint64_t portRangeHash{ 0u };this->udpSocket = new RTC::UdpSocket(this,this->listenInfo.ip,Settings::configuration.rtcMinPort,Settings::configuration.rtcMaxPort,this->listenInfo.flags,portRangeHash);}...
}

2.2.4. PipeTransport

PipeTransport 的设计目的是为了使位于同一主机上或不同主机上的两个Router实例之间进行通信,只支持 UDP 协议,创建逻辑类似,也支持指定端口或端口范围。

PipeTransport::PipeTransport(RTC::Shared* shared,const std::string& id,RTC::Transport::Listener* listener,const FBS::PipeTransport::PipeTransportOptions* options): RTC::Transport::Transport(shared, id, listener, options->base())
{...if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0){uint64_t portRangeHash{ 0u };this->udpSocket = new RTC::UdpSocket(this,this->listenInfo.ip,this->listenInfo.portRange.min,this->listenInfo.portRange.max,this->listenInfo.flags,portRangeHash);}else if (this->listenInfo.port != 0){this->udpSocket = new RTC::UdpSocket(this, this->listenInfo.ip, this->listenInfo.port, this->listenInfo.flags);}else{uint64_t portRangeHash{ 0u };this->udpSocket = new RTC::UdpSocket(this,this->listenInfo.ip,Settings::configuration.rtcMinPort,Settings::configuration.rtcMaxPort,this->listenInfo.flags,portRangeHash);}...
}

2.3. 数据流

2.3.1. UDP

2.3.1.1. 接收数据

以 WebRtcServer 为例,libuv 收到 UDP 消息会回调 UdpSocketHandle::OnUvRecv,UdpSocketHandle 再回调 UdpSocket::UserOnUdpDatagramReceived,最终将消息回调给数据监听者 WebRtcServer。

2.3.1.2. 发送数据

需要发送 UDP 消息的模块持有 TransportTuple 对象,调用 TransportTuple:: Send 方法,内部调用 UdpSocketHandle::Send,最终通过 libuv 接口将数据发送到网络。

需要注意,UDP 报文发送有一个特殊机制,mediaoup 会先调用 libuv 同步发送接口,如果同步发送接口出错,mediasoup 不是立即返回,而是拷贝发送数据,继续调用 libuv 的异步发送接口。这在某些极端场景下,可能会大量消耗服务器内存。

void UdpSocketHandle::Send(const uint8_t* data, size_t len, const struct sockaddr* addr,   UdpSocketHandle::onSendCallback* cb)
{...// 使用待发送送数据初始化一块uv缓冲区uv_buf_t buffer = uv_buf_init(reinterpret_cast<char*>(const_cast<uint8_t*>(data)), len);// 调用同步接口发送const int sent  = uv_udp_try_send(this->uvHandle, &buffer, 1, addr);// 所有数据都发送完成if (sent == static_cast<int>(len)){// Update sent bytes.this->sentBytes += sent;if (cb){(*cb)(true); // 回调返回成功delete cb;}return;}// 发送了部分数据else if (sent >= 0){this->sentBytes += sent;if (cb){(*cb)(false); // 回调返回失败delete cb;}return;}// 出错了,可能是网络繁忙,使用异步接口uv_udp_send发送else if (sent != UV_EAGAIN){MS_WARN_DEV("uv_udp_try_send() failed, trying uv_udp_send(): %s", uv_strerror(sent));}// 创建一个异步处理数据结构auto* sendData = new UvSendData(len);// 作为自定义数据挂载到uv数据结构中sendData->req.data = static_cast<void*>(sendData);// 拷贝待发送数据std::memcpy(sendData->store, data, len);// 保存回调函数指针sendData->cb = cb;// 使用待发送数据的拷贝初始化uv缓冲区buffer = uv_buf_init(reinterpret_cast<char*>(sendData->store), len);// 调用异步接口发送,设置回调接口onSendconst int err = uv_udp_send(&sendData->req, this->uvHandle, &buffer, 1, addr, static_cast<uv_udp_send_cb>(onSend));if (err != 0){if (cb){(*cb)(false);}delete sendData;}else{this->sentBytes += len;}
}

UvSendData 定义如下:

struct UvSendData
{uv_udp_send_t req{};uint8_t* store{ nullptr };UdpSocketHandle::onSendCallback* cb{ nullptr };
};

libuv 发送完成后会回调 onSend,在 onSend 函数中处理善后事宜。

inline static void onSend(uv_udp_send_t* req, int status)
{auto* sendData = static_cast<UdpSocketHandle::UvSendData*>(req->data);auto* handle   = req->handle;auto* socket   = static_cast<UdpSocketHandle*>(handle->data);const auto* cb = sendData->cb;if (socket){socket->OnUvSend(status, cb);}// Delete the UvSendData struct (it will delete the store and cb too).delete sendData;
}

2.3.2. TCP

2.3.2.1. 监听连接

1)TcpServer 调用 libuv 接口建立监听。

2)客户端与服务器完成三次握手后,libuv 会回调 TcpServerHandle::OnUvConnection。

3)TcpServerHandle 回调 TcpServer::UserOnTcpConnectionAlloc。

4)TcpServer 创建 TcpConnection 并调用 TcpServerHandle::AcceptTcpConnection 告知要接受这个连接。

5)TcpServerHandle 对 TcpConnection 进行初始化,调用 libuv 的 uv_accpet 方法完成新连接的创建。

6)调用 TcpConnectionHandle::Start 开始接收数据。

2.3.2.2. 接收数据

接收数据逻辑非常简单,以 WebRtcServer 为例,libuv 收到 TCP 数据后会层层回调到 WebRtcServer。

2.3.2.3. 发送数据

发送 TCP 数据的逻辑也很简单,调用 TransportTuple 接口,内部最终调用 libuv 将数据发送到网络。

3. 定时器

定时器在很多地方都会被用到,mediasoup 使用 TimerHanlde 封装 libuv 的定时器能力。需要使用定时器的类需要继承 TimerHandle::Listener,实现 OnTimer 虚拟方法。然后创建一个 TimerHandle 对象,传入 this 指针,调用 TimerHandle::Start 方法启动定时器即可。

4. 信号处理

信号是进程间通信的一种机制,也是操作系统用来通知进程有关系统事件或异常状况的重要手段。信号可以由系统内核发送给进程,也可以由一个进程发送给另一个进程。在 Worker 进程中,Worker 类是唯一处理 signal 的类,它继承 SignalHandle::Listener,实现 OnSignal 虚拟方法,进程接收的所有信号都会回调给 Worker 处理。

mediasoup 当前只处理了 SIGINT 和 SIGTERM 两个信号,用来优雅的关闭 mediasoup 进程。

void Worker::OnSignal(SignalHandle* /*signalHandle*/, int signum)
{if (this->closed){return;}switch (signum){case SIGINT:{if (this->closed){return;}Close();break;}case SIGTERM:{if (this->closed){return;}Close();break;}default:{MS_WARN_DEV("received a non handled signal [signum:%d]", signum);}}
}

5. 总结

熟悉 mediasoup 的底层通信机制,是深入阅读 mediasoup 源码的基础。本文详细描述了 mediasoup 对 libuv 的封装,覆盖了 pipe、socket、signal 等几种通信方式,重点分析了 Socket 通信的静态结构和数据流,补充分析了 UDP 报文的异步发送机制。mediasoup 对 libuv 的封装简洁清晰,是一个优秀的设计方案,值得大家借鉴。

相关文章:

深入浅出mediasoup—通信框架

libuv 是一个跨平台的异步事件驱动库&#xff0c;用于构建高性能和可扩展的网络应用程序。mediasoup 基于 libuv 构建了包括管道、信号和 socket 在内的一整套通信框架&#xff0c;具有单线程、事件驱动和异步的典型特征&#xff0c;是构建高性能 WebRTC 流媒体服务器的重要基础…...

每日一题 LeetCode03 无重复字符的最长字串

1.题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的最长字串的长度。 2 思路 可以用两个指针, 滑动窗口的思想来做这道题,即定义两个指针.一个left和一个right 并且用一个set容器,一个length , 一个maxlength来记录, 让right往右走,并且用一个set容器来…...

栈和队列(C语言)

栈的定义 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&#xff1a;…...

swagger-ui.html报错404

问题1&#xff1a;权限受限无法访问 由于采用的Shiro安全框架&#xff0c;需要在配置类ShiroConfig下的Shiro 的过滤器链放行该页面&#xff1a;【添加&#xff1a;filterChainDefinitionMap.put("/swagger-ui.html", "anon");】 public ShiroFilterFact…...

Milvus 核心组件(3)--- MinIO详解

目录 背景 MinIO 安装 docker desktop 安装 Ubuntu UI 在 docker 中的安装 Minio 下载及安装 启动minio docker image 保存 启动 minio web 网页 下一次启动 MinIO基本概念 基本概述 主要特性 应用场景 MinIO 使用 连接server 创建bucket 查询bucket 上传文件…...

[数据集][目标检测]婴儿车检测数据集VOC+YOLO格式1073张5类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1073 标注数量(xml文件个数)&#xff1a;1073 标注数量(txt文件个数)&#xff1a;1073 标注…...

JAVASE进阶day14(网络编程续TCP,日志)

TCP 三次握手 四次挥手 package com.lu.day14.tcp;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket;public class Client {public static void main(String[] args) {try(Socket socket new Socket("192.…...

机器学习(五) -- 无监督学习(1) --聚类1

系列文章目录及链接 上篇&#xff1a;机器学习&#xff08;五&#xff09; -- 监督学习&#xff08;7&#xff09; --SVM2 下篇&#xff1a;机器学习&#xff08;五&#xff09; -- 无监督学习&#xff08;1&#xff09; --聚类2 前言 tips&#xff1a;标题前有“***”的内容…...

leetcode 116. 填充每个节点的下一个右侧节点指针

leetcode 116. 填充每个节点的下一个右侧节点指针 题目 给定一个 完美二叉树 &#xff0c;其所有叶子节点都在同一层&#xff0c;每个父节点都有两个子节点。二叉树定义如下&#xff1a; struct Node { int val; Node *left; Node *right; Node *next; } 填充它的每个 next …...

[C++]优先级队列

1 .了解优先级队列 优先级队列是一种容器适配器&#xff0c;根据一些严格的弱排序标准&#xff0c;专门设计使其第一个元素始终是它所包含的元素中最大的元素。 此上下文类似于堆&#xff0c;其中可以随时插入元素&#xff0c;并且只能检索最大堆元素&#xff08;优先级队列中顶…...

学习大数据DAY22 Linux 基 本 指 令 3与 在 Linux 系 统 中 配 置MySQL 和 Oracle

目录 网络配置类 ps 显示系统执行的进程 kill systemctl 服务管理 配置静态 ip 常见错误---虚拟机重启网卡失败或者网卡丢失 mysql 操作 上机练习 6---安装 mysql---参考《mysql 安装》文档 解锁 scott 重启后的步骤 上机练习 7---安装 oracle---参考《oracle 安装》…...

scp 服务器复制命令

步骤如下&#xff1a; 终端执行如下命令 #ssh-keygen -t rsa 2. 密钥生成后会在 /root/.ssh/ 文件夹下产生两个文件 id_rsa id_rsa.pub 将 id_rsa.pub 文件复制到 152.136.121.24 执行如下命令 scp /root/.ssh/id_rsa.pub root152.136.121.24:/root/.ssh/authorized_keys…...

PyQt5学习路线

后续会根据该文章的路线逐步发布对应的教程&#xff0c;订阅专栏不迷路&#x1f970; 本专栏纯干货&#x1f929; 学习Python的PyQt5库&#xff0c;可以遵循以下的学习路线&#xff1a; 1. Python基础 掌握Python语法&#xff1a;确保你熟悉Python的基本语法&#xff0c;包括…...

2024论文精读:利用大语言模型(GPT)增强上下文学习去做关系抽取任务

文章目录 1. 前置知识2. 文章通过什么来引出他要解决的问题3. 作者通过什么提出RE任务存在上面所提出的那几个问题3.1 问题一&#xff1a;ICL检索到的**示范**中实体个关系的相关性很低。3.2 问题二&#xff1a;示范中缺乏解释输入-标签映射导致ICL效果不佳。 4. 作者为了解决上…...

WEB 手柄 http通信,mcu端解析代码 2024/7/23 日志

WEB 手柄 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>WEB遥控器</title> </head> &l…...

cmake中的正则表达式

以下字符或者字符组合在cmake的正则表达式中的特殊含义&#xff1a; ^ 匹配输入的开始 $ 匹配输入的结束 . 匹配任意一个字符 \<char> 匹配一个字符&#xff0c;如.匹配字符.&#xff0c;\匹配字符\&#xff0c;\a匹配字符a [ ] 匹配在括号里面的任意字符&#xff0…...

05. Java 三大范式

1. 前言 在面向对象语言中涉及到诸多的设计模式&#xff0c;例如单例模式、适配器模式&#xff0c;设计模式的存在是为了让系统中的代码逻辑更加清晰&#xff0c;帮助开发者建立更加健壮的系统&#xff0c;同时满足易修改特性和易扩展特性。数据库设计时也存在类似设计模式的通…...

opencv 按键开启连续截图,并加载提示图片

背景图小图 键盘监听使用的是pynput 库 保存图片时使用了年月日时分秒命名 原图&#xff1a; from pynput import keyboard import cv2 import time# 键盘监听 def on_press(key):global jieglobal guanif key.char a:jie Trueelif key.char d:jie Falseelif key.char…...

Android-- 集成谷歌地图

引言 项目需求需要在谷歌地图&#xff1a; 地图展示&#xff0c;设备点聚合&#xff0c;设备站点&#xff0c;绘制点和区域等功能。 我只针对我涉及到的技术做一下总结&#xff0c;希望能帮到开始接触谷歌地图的伙伴们。 集成步骤 1、在项目的modle的build.gradle中添加依赖如…...

Jvm是如何处理异常的

异常抛出 当Java程序运行时遇到无法处理的情况时,会抛出一个异常(比如在一个方法中如果发生异常),这时会创建一个异常对象,并转交给JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给JVM的过程称为抛出异常。 异常捕捉 当JVM检测…...

recursion depth exceeded” error

有些时候不可以用jax.jit装饰器 参考资料&#xff1a;使用 JAX 后端在 Keras 3 中训练 GAN |由 Khawaja Abaid |中等 (medium.com)...

虚拟现实和增强现实技术系列—Expressive Talking Avatars

文章目录 1. 概述2. 背景介绍3. 数据集3.1 设计标准3.2 数据采集 4. 方法4.1 概述4.2 架构4.3 目标函数 5. 实验评测5.1 用户研究5.2 我们方法的结果5.3 比较与消融研究 1. 概述 支持远程协作者之间的交互和沟通。然而&#xff0c;明确的表达是出了名的难以创建&#xff0c;主…...

网站验证:确保网络安全与信任的重要步骤

网站验证&#xff1a;确保网络安全与信任的重要步骤 引言 在数字时代&#xff0c;网站验证是确保网络安全和建立用户信任的关键措施。随着网络诈骗和恶意软件的日益增多&#xff0c;验证网站的真实性和安全性变得尤为重要。本文将探讨网站验证的重要性、常见的验证方法以及如…...

C语言——字符串比较函数strcmp和strncmp

目录 strcmp 函数原型如下&#xff1a; 示例 注意事项 strcmp自实现代码&#xff1a; strncmp 函数 函数原型&#xff1a; 参数&#xff1a; 返回值&#xff1a; 特点&#xff1a; 两者之间的区别和联系 strcmp strcmp 是 C 语言标准库中的一个函数&#xff0c;用于…...

redis的集群模式

目录 1. 为什么使用redis集群 2. 主从模式 2.1修改配置文件 2.2 开启三台redis服务 2.3配置主从关系 3. 哨兵模式 3.1 监控功能 3.2 选举的机制 3.3 准备条件 4. 去中心化模式 4.1 准备三主三从 4.2 启动redis 4.3 分配槽以及主从关系 4.4 命令行的客户端 redis提供…...

基于微信小程序+SpringBoot+Vue的青少年科普教学系统平台(带1w+文档)

基于微信小程序SpringBootVue的青少年科普教学系统平台(带1w文档) 基于微信小程序SpringBootVue的青少年科普教学系统平台(带1w文档) 这个工具就是解决上述问题的最好的解决方案。它不仅可以实时完成信息处理&#xff0c;还缩短高校教师成果信息管理流程&#xff0c;使其系统化…...

智能听觉:从任务特定的机器学习到基础模型

关键词&#xff1a;计算机听觉、音频基础模型、多模态学习、声音事件检测 声音无处不在&#xff0c;弥漫于我们生活的每一个角落。鸟儿向伴侣倾诉心意的歌声&#xff0c;浓缩咖啡机中蒸汽的嘶嘶作响&#xff0c;午后阳光下昆虫振翅的嗡嗡声&#xff0c;金属屋顶上雨滴跳跃的滴答…...

14、如何⽤DDD设计微服务代码模型

在完成领域模型设计后&#xff0c;接下来我们就可以开始微服务的设计和 落地了。在微服务落地前&#xff0c;⾸先要确定微服务的代码结构&#xff0c;也就是我 下⾯要讲的微服务代码模型。 只有建⽴了标准的微服务代码模型和代码规范后&#xff0c;我们才可以将 领域对象映射到…...

ArcGIS Pro SDK (九)几何 12 多面体

ArcGIS Pro SDK &#xff08;九&#xff09;几何 12 多面体 文章目录 ArcGIS Pro SDK &#xff08;九&#xff09;几何 12 多面体1 通过拉伸多边形或折线构建多面体2 多面体属性3 构建多面体4 通过MultipatchBuilderEx构建多面体5 从另一个多面体构建多面体6 从 3D 模型文件构建…...

二次元手游《交错战线》游戏拆解

交错战线游戏拆解案 游戏亮点即核心趣味 一、关键词&#xff1a; 回合制游戏、二次元、机甲、横板、剧情、养成、异星探索。 二、游戏亮点&#xff1a; 符合目标群体审美的原画。 三、核心趣味&#xff1a; 抽卡、肝或者氪金解锁新皮肤。 核心玩法及系统规则 核心玩法&…...

【BUG】已解决:Downgrade the protobuf package to 3.20.x or lower.

Downgrade the protobuf package to 3.20.x or lower. 目录 Downgrade the protobuf package to 3.20.x or lower. 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身…...

Java开发之Redis

1、非关系型数据库、快、高并发、功能强大 2、为什么快&#xff1f;内存单线程 非阻塞的IO多路复用有效的数据类型/结构 3、应用&#xff1a;支持缓存、支持事务、持久化、发布订阅模型、Lua脚本 4、数据类型&#xff1a; 5 种基础数据类型&#xff1a;String&#xff08;字…...

Java面试八股之 Spring Bean的生命周期

Spring Bean的生命周期 实例化&#xff08;Instantiation&#xff09;&#xff1a;Spring容器根据Bean定义信息创建Bean的实例&#xff0c;通常通过无参构造函数进行。 依赖注入&#xff08;Dependency Injection&#xff0c;DI&#xff09;&#xff1a;Spring容器按照Bean定…...

SQL中的函数

目录 前言 一、系统内置函数 1、数学函数 2、日期和时间函数 3、聚合函数 4、字符串函数 二、自定义函数 1、标量函数的创建与调用 2、内嵌表值函数的创建与调用 3、多语句表值函数的创建与调用 前言 函数是由一个或多个 T-SQL 语句组成的子程序&#xff0c;可用于封…...

VSCode | 修改编辑器注释的颜色

1 打开VsCode的设置进入settings.json 2 添加如下代码 "editor.tokenColorCustomizations": {"comments": "#17e917"},3 保存即可生效...

媒体邀约专访与群访的区别?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体邀约中的专访与群访在多个方面存在显著差异&#xff0c;以下是对这两种采访方式的详细比较&#xff1a; 一、定义与形式 专访&#xff1a; 定义&#xff1a;专访是指由媒体记者对单…...

Pycharm2024最新版community社区版下载安装配置,快速上手

第一步&#xff1a;下载 方法1&#xff1a;官网链接 https://www.jetbrains.com/pycharm/download/?sectionwindows .方法2&#xff1a;百度网盘 链接&#xff1a;https://pan.baidu.com/s/1ic2N5hUQ2m1Kmyr5nK9Jxw?pwd76dt 提取码&#xff1a;76dt --来自百度网盘超级…...

服务器选择租用还是托管?托管和租用哪个比较划算

在构建或扩展IT基础设施时&#xff0c;服务器作为关键组件&#xff0c;其选择方式——租用或托管&#xff0c;直接关系到企业的运营成本、灵活性、安全性及长期发展战略。本文将从技术、经济、安全等多个维度&#xff0c;深入解析这两种方案的优缺点&#xff0c;并探讨在何种情…...

智能制造·数字化工厂建设规划方案(65P)

获取完整PPT见下图 更多有关华为研发管理/IPD、MBSE、PLM、ERP、MES、数据治理、数字样机等方面免费解决方案、资料获取&#xff0c;请见下图...

ACM中国图灵大会专题 | 图灵奖得主Manuel Blum教授与仓颉团队交流 | 华为论坛:面向全场景应用编程语言精彩回顾

ACM 中国图灵大会&#xff08;ACM Turing Award Celebration Conference TURC 2024&#xff09;于2024年7月5日至7日在长沙举行。本届大会由ACM主办&#xff0c;in cooperation with CCF&#xff0c;互联网之父Vinton Cerf、中国计算机学会前理事长梅宏院士和廖湘科院士担任学术…...

k8s 公共服务

修改named.conf。修改第13行和第21行 下面是 named.rfc1912 修改位置&#xff0c;在最后 所以用cp -p 复制文件&#xff0c;保留权限 nslookup 回车&#xff0c;server是看哪个dns 在起作用 dns服务器要配置给所有公共服务节点和 k8s 节点 就在网络文件加个DNS2就行了&…...

【数据分析详细教学】全球气温变迁:一个多世纪的数据分析

全球气温变迁&#xff1a;一个多世纪的数据分析 1. 数据集选择与获取 数据可以从NASA的GISTEMP数据集获取&#xff0c;通常提供的格式有TXT和CSV。我们假设数据是以CSV格式提供。 2. 数据预处理 使用Python的pandas库读取数据并进行预处理。 import pandas as pd# 加载数…...

AV1技术学习:Reference Frame System

一、Reference Frames AV1 Codec 允许在其解码的帧缓冲区中最多允许保存 8 帧。对于一个编码帧&#xff0c;可以从解码的帧缓冲区中选择任意 7 个帧作为它的参考帧。编码端可以通过比特流显式地传输参考帧索引&#xff0c;范围从 1到 7。原则上&#xff0c;参考帧索引 1-4 为当…...

数学建模(7)——Logistic模型

一、马尔萨斯人口模型 import numpy as np import matplotlib.pyplot as plt# 初始人口 N0 100 # 人口增长率 r 0.02 # 时间段&#xff08;年&#xff09; t np.linspace(0, 200, 200)# 马尔萨斯人口模型 N N0 * np.exp(r * t)# 绘图 plt.plot(t, N, labelPopulation) plt.…...

“微软蓝屏”事件,给IT行业带来的宝贵经验和教训

“微软蓝屏”事件是指2024年7月19日发生的一次全球性技术故障&#xff0c;主要涉及微软视窗&#xff08;Windows&#xff09;操作系统及其相关应用和服务。 以下是对该事件的详细解析&#xff1a; 一、事件概述 发生时间&#xff1a;2024年7月19日事件影响&#xff1a;全球多个…...

QT总结——图标显示坑

最近写代码遇到一个神仙大坑&#xff0c;我都怀疑我软件是不是坏了&#xff0c;这里记录一下。 写qt工程的时候我们一般会设置图标&#xff0c;这个图标是窗体的图标同时也是任务栏的图标&#xff0c;但是我发现生成的exe没有图标&#xff0c;这个时候就想着给他加一个图标&…...

SQL 注入漏洞详解 - Union 注入

1)漏洞简介 SQL 注入简介 SQL 注入 即是指 Web 应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在 Web 应用程序中事先定义好的查询语句的结尾上添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,…...

Qt创建自定义组件并且promote to之后导致编译错误(CMake)

创建自定组件并且加入到全局(勾选"Global include"选项)后&#xff0c;重新编译&#xff0c;元对象编译器生成的ui_xxxx.h文件中会新加入自定义组件的头文件&#xff1a; 如图所示&#xff0c;编译器提示找不到自定义组件的头文件&#xff1a; Solution: 在CMakeL…...

告别写作瓶颈,4款AI协作工具助你迸发灵感

想要一个可以理解你思路&#xff0c;捕捉你灵感&#xff0c;并且帮你将这些内容转化为高质量文本的工具吗&#xff1f;现下大火的ai智能写作就能做到。 1 宙.语AI 传送门&#xff1a;https://ailjyk.com/pc 这个工具也是一种在线的AI工具。他可以写的文章种类非常多&#…...

java30-Shiro

概述 解决认证和授权 基本使用 package com.xpc.simple;import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.SimpleAccountRealm; import o…...