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

【Netty】ChannelPipeline源码分析(五)

文章目录

  • 前言
  • 一、ChannelPipeline 接口
    • 1.1 创建 ChannelPipeline
    • 1.2 ChannelPipeline 事件传输机制
      • 1.2.1 处理出站事件
      • 1.2.2 处理入站事件
  • 二、ChannelPipeline 中的 ChannelHandler
  • 三、ChannelHandlerContext 接口
    • 3.1 ChannelHandlerContext 与其他组件的关系
    • 3.2 跳过某些 ChannelHandler
  • 总结

前言

我们在前面的文章中也对ChannelPipeline接口做了初步的介绍。

  • Netty 概述(一)
  • Netty 架构设计(二)
  • Netty Channel 概述(三)
  • Netty ChannelHandler(四)

一、ChannelPipeline 接口

ChannelPipeline接口采用了责任链设计模式,底层采用双向链表的数据结构,将链上的各个处理器串联起来。客户端每一个请求的到来,ChannelPipeline中所有的处理器都有机会处理它。

每一个新创建的Channel都将会被分配一个新的ChannelPipeline。这项关联是永久性的;Channel既不能附加另一个ChannelPipeline,也不能分离其当前的。

1.1 创建 ChannelPipeline

ChannelPipeline数据管道是与Channel管道绑定的,一个Channel通道对应一个ChannelPipeline,ChannelPipeline是在Channel初始化时被创建。

观察下面这个实例:

public void run() throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap(); // (2)b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3).childHandler(new ChannelInitializer<SocketChannel>() { // (4)@Overridepublic void initChannel(SocketChannel ch) throws Exception {// 添加ChannelHandler到ChannelPipelinech.pipeline().addLast(new DiscardServerHandler());}}).option(ChannelOption.SO_BACKLOG, 128)          // (5).childOption(ChannelOption.SO_KEEPALIVE, true); // (6)// 绑定端口,开始接收进来的连接ChannelFuture f = b.bind(port).sync(); // (7)System.out.println("DiscardServer已启动,端口:" + port);// 等待服务器  socket 关闭 。// 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}
}

从上述代码中可以看到,当ServerBootstrap初始化后,直接就可以获取到SocketChannel上的ChannelPipeline,而无需手动实例化,因为 Netty 会为每个Channel连接创建一个ChannelPipeline。

Channel的大部分子类都继承了AbstractChannel,在创建实例时也会调用AbstractChannel构造器。在AbstractChannel构造器中会创建ChannelPipeline管道实例,核心代码如下:

protected AbstractChannel(Channel parent) {this.parent = parent;this.id = this.newId();this.unsafe = this.newUnsafe();this.pipeline = this.newChannelPipeline();
}protected DefaultChannelPipeline newChannelPipeline() {return new DefaultChannelPipeline(this);
}

从上述代码中可以看出,在创建Channel时,会由Channel创建DefaultChannelPipeline类的实例。DefaultChannelPipeline是ChannelPipeline的默认实现。

pipeline是AbstractChannel的属性,内部维护着一个以AbstractChannelHandlerContext为节点的双向链表,创建的head和tail节点分别指向链表头尾,源码如下:

public class DefaultChannelPipeline implements ChannelPipeline {   	protected DefaultChannelPipeline(Channel channel) {this.channel = (Channel)ObjectUtil.checkNotNull(channel, "channel");this.succeededFuture = new SucceededChannelFuture(channel, (EventExecutor)null);this.voidPromise = new VoidChannelPromise(channel, true);this.tail = new DefaultChannelPipeline.TailContext(this);this.head = new DefaultChannelPipeline.HeadContext(this);this.head.next = this.tail;this.tail.prev = this.head;}...final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {TailContext(DefaultChannelPipeline pipeline) {super(pipeline, (EventExecutor)null, DefaultChannelPipeline.TAIL_NAME, DefaultChannelPipeline.TailContext.class);this.setAddComplete();}...}final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler {private final Unsafe unsafe;HeadContext(DefaultChannelPipeline pipeline) {super(pipeline, (EventExecutor)null, DefaultChannelPipeline.HEAD_NAME, DefaultChannelPipeline.HeadContext.class);this.unsafe = pipeline.channel().unsafe();this.setAddComplete();}...}...
}

从上述源码可以看到,TailContext和HeadContext都继承了AbstractChannelHandlerContext,并实现了ChannelHandler接口。AbstractChannelHandlerContext内部维护着next、prev链表指针和入站、出站节点方向等。其中TailContext实现了ChannelInboundHandler,HeadContext实现了ChannelOutboundHandler和ChannelInboundHandler。

1.2 ChannelPipeline 事件传输机制

通过ChannelPipeline的addFirst()方法来添加ChannelHandler,并为这个ChannelHandler创建一个对应的DefaultChannelHandlerContext实例。

public class DefaultChannelPipeline implements ChannelPipeline {  //...public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {AbstractChannelHandlerContext newCtx;synchronized(this) {checkMultiplicity(handler);name = this.filterName(name, handler);newCtx = this.newContext(group, name, handler);this.addFirst0(newCtx);if (!this.registered) {newCtx.setAddPending();this.callHandlerCallbackLater(newCtx, true);return this;}EventExecutor executor = newCtx.executor();if (!executor.inEventLoop()) {this.callHandlerAddedInEventLoop(newCtx, executor);return this;}}this.callHandlerAdded0(newCtx);return this;}//...private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {return new DefaultChannelHandlerContext(this, this.childExecutor(group), name, handler);}//...}

1.2.1 处理出站事件

当处理出站事件时,channelRead()方法的示例如下:

public class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println(ctx.channel().remoteAddress() + " -> Server :" + msg);// 写消息到管道ctx.write(msg);// 写消息}//...
}

上述代码中的write()方法会触发一个出站事件,该方法会调用DefaultChannelPipeline上的write()方法。

public final ChannelFuture write(Object msg) {return this.tail.write(msg);
}

从上述源码可以看到,调用的是DefaultChannelPipeline上尾部节点(tail)的write方法。
上述方法最终会调用到DefaultChannelHandlerContext的write()方法。

private void write(Object msg, boolean flush, ChannelPromise promise) {ObjectUtil.checkNotNull(msg, "msg");try {if (this.isNotValidPromise(promise, true)) {ReferenceCountUtil.release(msg);return;}} catch (RuntimeException var8) {ReferenceCountUtil.release(msg);throw var8;}AbstractChannelHandlerContext next = this.findContextOutbound(flush ? 98304 : '耀');Object m = this.pipeline.touch(msg, next);EventExecutor executor = next.executor();if (executor.inEventLoop()) {if (flush) {next.invokeWriteAndFlush(m, promise);} else {next.invokeWrite(m, promise);}} else {AbstractChannelHandlerContext.WriteTask task = AbstractChannelHandlerContext.WriteTask.newInstance(next, m, promise, flush);if (!safeExecute(executor, task, promise, m, !flush)) {task.cancel();}}}

上述的write()方法会查找下一个出站的节点,也就是当前ChannelHandler后的一个出站类型的ChannelHandler,并调用下一个节点的invokeWrite()方法。

void invokeWrite(Object msg, ChannelPromise promise) {if (this.invokeHandler()) {this.invokeWrite0(msg, promise);} else {this.write(msg, promise);}}

接着调用invokeWrite0()方法,该方法最终调用ChannelOutboundHandler的write方法。

private void invokeWrite0(Object msg, ChannelPromise promise) {try {((ChannelOutboundHandler)this.handler()).write(this, msg, promise);} catch (Throwable var4) {notifyOutboundHandlerException(var4, promise);}}

至此,处理完成了第一个节点的处理,开始执行下一个节点并不断循环。
所以,处理出站事件时,数据传输的方向是从尾部节点tail到头部节点head。

1.2.2 处理入站事件

入站事件处理的起点是触发ChannelPipeline fire方法,例如fireChannelActive()方法的示例如下:

public class DefaultChannelPipeline implements ChannelPipeline {   	  //...public final ChannelPipeline fireChannelActive() {AbstractChannelHandlerContext.invokeChannelActive(this.head);return this;}//...
}

从上述源码可以看到,处理的节点是头部节点head。AbstractChannelHandlerContext.invokeChannelActive方法定义如下:

static void invokeChannelActive(final AbstractChannelHandlerContext next) {EventExecutor executor = next.executor();if (executor.inEventLoop()) {next.invokeChannelActive();} else {executor.execute(new Runnable() {public void run() {next.invokeChannelActive();}});}}

该方法最终调用ChannelInboundHandler的channelActive方法。

private void invokeChannelActive() {if (this.invokeHandler()) {try {((ChannelInboundHandler)this.handler()).channelActive(this);} catch (Throwable var2) {this.invokeExceptionCaught(var2);}} else {this.fireChannelActive();}}

至此完成了第一个节点的处理,开始执行下一个节点的不断循环。
所以,处理入站事件时,数据传输的方向是从头部节点head到尾部节点tail。

二、ChannelPipeline 中的 ChannelHandler

从上述的ChannelPipeline 接口源码可以看出,ChannelPipeline 是通过addXxx或者removeXxx方法来将ChannelHandler动态的添加到ChannelPipeline中,或者从ChannelPipeline移除ChannelHandler的。那么ChannelPipeline是如何保障并发访问时的安全呢?

以addLast方法为例,DefaultChannelPipeline的源码如下:

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {AbstractChannelHandlerContext newCtx;//synchronized 保障线程安全synchronized(this) {checkMultiplicity(handler);newCtx = this.newContext(group, this.filterName(name, handler), handler);this.addLast0(newCtx);if (!this.registered) {newCtx.setAddPending();this.callHandlerCallbackLater(newCtx, true);return this;}EventExecutor executor = newCtx.executor();if (!executor.inEventLoop()) {this.callHandlerAddedInEventLoop(newCtx, executor);return this;}}this.callHandlerAdded0(newCtx);return this;
}

从上述源码可以看到,使用synchronized关键字保障了线程的安全访问。其他方法的实现方式也是类似。

三、ChannelHandlerContext 接口

ChannelHandlerContext 接口是联系ChannelHandler和ChannelPipeline 之间的纽带。

每当有ChannelHandler添加到ChannelPipeline 中时,都会创建ChannelHandlerContext 。
ChannelHandlerContext 的主要功能是管理它所关联的ChannelHandler和在同一个ChannelPipeline 中的其他ChannelHandler之间的交互。

例如,ChannelHandlerContext 可以通知ChannelPipeline 中的下一个ChannelHandler开始执行及动态修改其所属的ChannelPipeline 。

ChannelHandlerContext 中包含了许多方法,其中一些方法也出现在Channel和ChannelPipeline 中。如果通过Channel或ChannelPipeline 的实例来调用这些方法,它们就会在整个ChannelPipeline 中传播。相比之下,一样的方法在ChannelHandlerContext 的实例上调用,就只会从当前ChannelHandler开始并传播到相关管道中的下一个有处理事件能力的ChannelHandler中。因此ChannelHandlerContext 所包含的事件流比其他类中同样的方法都要短,利用这一点可以尽可能提高性能。

3.1 ChannelHandlerContext 与其他组件的关系

下图展示了ChannelPipeline 、Channel、ChannelHandler和ChannelHandlerContext 之间的关系做了如下说明:
在这里插入图片描述

  1. Channel被绑定到ChannelPipeline 上。
  2. 和Channel绑定的ChannelPipeline 包含了所有的ChannelHandler。
  3. ChannelHandler。
  4. 当添加ChannelHandler到ChannelPipeline 时,ChannelHandlerContext 被创建。

3.2 跳过某些 ChannelHandler

下面的代码,展示了从ChannelHandlerContext 获取到Channel的引用,并通过调用Channel上的write()方法来触发一个写事件到流中。

ChannelHandlerContext ctx = context;
Channel channel = ctx.channel(); //获取ChannelHandlerContext上的Channel
channel.write(msg);

以下代码展示了从ChannelHandlerContext 获取到ChannelPipeline 。

ChannelHandlerContext ctx = context;
ChannelPipeline pipeline = ctx.pipeline(); //获取ChannelHandlerContext上的ChannelPipeline 
pipeline.write(msg);

上述的两个示例,事件流是一样的。虽然被调用的Channel和ChannelPipeline 上的write()方法将一直传播事件通过整个ChannelPipeline ,但是在ChannelHandler的级别上,事件从一个ChannelHandler到下一个ChannelHandler的移动是由ChannelHandlerContext 上的调用完成的。

下图展示了Channel或者ChannelPipeline 进行的事件传播机制。
在这里插入图片描述

在上图中可以看出:

  1. 事件传递给ChannelPipeline 的第一个ChannelHandler;
  2. ChannelHandler通过关联的ChannelHandlerContext 传递事件给ChannelPipeline 中的下一个ChannelHandler。
  3. ChannelHandler通过关联的ChannelHandlerContext 传递事件给ChannelPipeline 中的下一个ChannelHandler。

从上面的流程可以看出,如果通过Channel或ChannelPipeline 的实例来调用这些方法,它们肯定会在整个ChannelPipeline 中传播。

那么是否可以跳过某些处理器呢?答案是肯定的。

通过减少ChannelHandler不感兴趣的事件的传递减少开销,并排除掉特定的对此事件感兴趣的处理器的处理以提升性能。想要实现从一个特定的ChannelHandler开始处理,必须引用与此ChannelHandler的前一个ChannelHandler关联的ChannelHandlerContext 。这个ChannelHandlerContext 将会调用与自身关联的ChannelHandler的下一个ChannelHandler,代码如下:

ChannelHandlerContext ctx = context;
ctx.write(msg);

直接调用ChannelHandlerContext 的write()方法,将会把缓冲区发送到下一个ChannelHandler。

如下图,消息会将从下一个ChannelHandler开始流过ChannelPipeline ,绕过所有在它之前的ChannelHandler。
在这里插入图片描述

  1. 执行ChannelHandlerContext 方法调用。
  2. 事件发送到了下一个ChannelHandler。
  3. 经过最后一个ChannelHandler后,事件从ChannelPipeline 中移除。

当调用某个特定的ChannelHandler操作时,它尤为有用。
例如:

public class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println(ctx.channel().remoteAddress() + " -> Server :" + msg);// 写消息到管道ctx.write(msg);// 写消息ctx.flush(); // 冲刷消息// 上面两个方法等同于 ctx.writeAndFlush(msg);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 当出现异常就关闭连接cause.printStackTrace();ctx.close();}
}

总结

以上就是关于ChannelPipeline 的源码分析,相信认真看完了,你就明白ChannelPipeline 、Channel、ChannelHandler和ChannelHandlerContext 之间的关系。下节我们继续来剖析 Netty 的源码。

相关文章:

【Netty】ChannelPipeline源码分析(五)

文章目录 前言一、ChannelPipeline 接口1.1 创建 ChannelPipeline1.2 ChannelPipeline 事件传输机制1.2.1 处理出站事件1.2.2 处理入站事件 二、ChannelPipeline 中的 ChannelHandler三、ChannelHandlerContext 接口3.1 ChannelHandlerContext 与其他组件的关系3.2 跳过某些 Ch…...

并行计算技术解密:MPI和OpenMP的学习和应用指南

欢迎来到并行计算技术的奇妙世界&#xff01;本指南将带您深入了解MPI&#xff08;Message Passing Interface&#xff09;和OpenMP&#xff08;Open Multi-Processing&#xff09;两种重要的并行计算技术&#xff0c;并为您提供学习和应用的指南。无论您是一个科研工作者、开发…...

什么是自动化测试框架?我们该如何搭建自动化测试框架?

无论是在自动化测试实践&#xff0c;还是日常交流中&#xff0c;经常听到一个词&#xff1a;框架。之前学习自动化测试的过程中&#xff0c;一直对“框架”这个词知其然不知其所以然。 最近看了很多自动化相关的资料&#xff0c;加上自己的一些实践&#xff0c;算是对“框架”…...

Debezium报错处理系列之六十七:TopicAuthorizationException: Not authorized to access topics

Debezium报错处理系列之六十七:TopicAuthorizationException: Not authorized to access topics 一、完整报错二、错误原因三、解决方法Debezium报错处理系列一:The db history topic is missing. Debezium报错处理系列二:Make sure that the same history topic isn‘t sha…...

javaWebssh中小学课件资源系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh中小学课件资源系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用 B/S模式开发。开发环境为TOMCAT…...

MySQL高级查询操作

文章目录 前言聚集函数分组查询&#xff1a;GROUP BY过滤&#xff1a;HAVING嵌套子查询比较运算中使用子查询带有IN的子查询SOME(子查询)ALL(子查询)EXISTS子查询 前言 查询语句书写顺序&#xff1a; 1、select 2、from 3、where 4、group by 5、having 6、order by 7、limit …...

Day53【动态规划】1143.最长公共子序列、1035.不相交的线、53.最大子序和

1143.最长公共子序列 力扣题目链接/文章讲解 视频讲解 本题最大的难点还是定义 dp 数组 本题和718.最长重复子数组区别在于这里不要求是连续的了&#xff0c;但要有相对顺序 直接动态规划五部曲&#xff01; 1、确定 dp 数组下标及值含义 dp[i][j]&#xff1a;取 text1…...

Three.js--》实现3d地球模型展示

目录 项目搭建 实现网页简单布局 初始化three.js基础代码 创建环境背景 加载地球模型 实现光柱效果 添加月球模型 今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多…...

<SQL>《SQL命令(含例句)精心整理版(6)》

《SQL命令&#xff08;含例句&#xff09;精心整理版&#xff08;6&#xff09;》 18 DB2查询语句18.1 查询数据库大小18.2 查看表占表空间大小18.3 查看正在执行的语句18.4 db2expln 查看执行计划18.5 db2advis 查看优化建议 19 空值19.1 NULL19.2 TRIM 18 DB2查询语句 18.1 …...

信息系统建设和服务能力评估证书CS

信息系统建设和服务能力评估体系CS简介 简介&#xff1a;本标准&#xff08;团标T/CITIF 001-2019&#xff09;是信息系统建设和服务能力评估体系系列标准的第一个&#xff0c;提出了对信息系统建设和服务提供者的综合能力要求。 发证单位&#xff1a;中国电子信息行业联合会。…...

vue3引入路由

1.首先在项目中安装路由 npm install vue-router -S 2.src文件夹下新建》views文件夹》新建home文件夹》新建Home.vue文件 在src文件夹下》新建router文件夹》新建index.js import { createRouter,createWebHashHistory } from vue-router const route s[ { path:/, compo…...

前后端联调跨域问题

文章目录 什么是同源策略如何判断是否同源&#xff1f;跨域资源共享(CORS)如何解决跨域问题 什么是同源策略 同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。 如何判断是否同源&#xff1f; 如果…...

day11 - 手写数字笔迹细化

手写数字笔迹细化 对于手写数字识别实验中&#xff0c;经常会遇到因为笔迹较粗导致误识别的情况&#xff0c;所以我们通常会先将笔迹进行细化&#xff0c;笔迹变细以后&#xff0c;数字的特征会更明显&#xff0c;后续进行识别的准确率就会更高。 例如数字7 和 1 &#xff0c…...

C++ QT QDBus基操

以下是使用QDBus进行跨进程通信的具体用法&#xff1a; 1. 创建DBus服务 在服务端进程中&#xff0c;需要创建一个DBus服务&#xff0c;并注册DBus对象。示例代码如下&#xff1a; #include <QDBusConnection> #include <QDBusMessage> #include <QDBusInterf…...

STM32的SPI外设

文章目录 1. STM32 的 SPI 外设简介2. STM32 的 SPI 架构剖析2.1 通讯引脚2.2 时钟控制逻辑2.3 数据控制逻辑2.4 整体控制逻辑 3. 通讯过程4. SPI 初始化结构体详解 1. STM32 的 SPI 外设简介 STM32 的 SPI 外设可用作通讯的主机及从机&#xff0c;支持最高的 SCK 时钟频率为 …...

VMWare ESXI6.7创建虚拟机

VMware ESXi&#xff1a;专门构建的裸机 管理程序 首先开启ESXI主机 登录ESXI 打开浏览器输入物理机ip&#xff0c;输入账号密码进行登录 创建虚拟机 选择创建类型 创建RedHat7.6 选择存储类型和数据存储 仅一个存储&#xff0c;直接点下一页即可 配置虚拟机硬件和虚拟机附…...

TensorFlow 1.x学习(系列二 :4):自实现线性回归

目录 线性回归基本介绍常用的op自实现线性回归预测tensorflow 变量作用域模型的保存和加载 线性回归基本介绍 线性回归&#xff1a; w 1 ∗ x 1 w 2 ∗ x 2 w 3 ∗ x 3 . . . w n ∗ x n b i a s w_1 * x_1 w_2 * x_2 w_3 * x_3 ... w_n * x_n bias w1​∗x1​w2​∗…...

Openwrt折腾记6-网络摄像头

前言&#xff1a; 前几天买了个电视机上的摄像头&#xff0c;但是估计是电视配置或软件不好&#xff0c;视频通话太卡顿。今天把它装的极路由4的usb上了。由于当初挑的是电视免驱的&#xff0c;所以我猜想是通用的芯片。 调查驱动 LINUX uvc支持型号的列表里 http://www.ide…...

C++判断大端小端

C判断大端小端 1. 基础知识 大端小端其实表示的是数据在存储器中的存放顺序。 大端模式&#xff1a;数据的高字节存放在内存的低地址中&#xff0c;而低字节则存放在高地址中。地址由小到大增加&#xff0c;数据则从高位向低位存放&#xff0c;这种存放方式符合人类的正常思维…...

K8S RBAC之Kubeconfig设置用户权限,不同的用户访问不同的namespace

1.CA签发客户端证书 检查证书是否存在 # ll /etc/kubernetes/pki/ 总用量 48K -rw-r----- 1 kube root 2.1K 3月 2 16:44 apiserver.crt -rw------- 1 kube root 1.7K 3月 2 16:44 apiserver.key -rw-r----- 1 kube root 1.2K 3月 2 16:44 apiserver-kubelet-client.cr…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

Windows安装Miniconda

一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟

2025年4月29日&#xff0c;在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上&#xff0c;可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞&#xff0c;强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...