手写RPC框架--1.介绍与网络传输

介绍与网络传输

  • 0.介绍
    • a.什么是rpc
    • b.rpc的通信流程
  • 1.网络传输
    • a.零拷贝
      • 1) 零拷贝的概念
      • 2) Netty的零拷贝
    • b.IO多路复用
    • c.Netty入门
      • 1) netty中的helloworld
    • d.封装报文
      • 1) 协议结构
      • 2) 模拟封装报文
    • e.序列化
    • f.压缩和解压缩

0.介绍

a.什么是rpc

rpc 的全称是 Remote Procedure Call,即远程过程调用。从字面上的来看,rpc就是通过网络通信访问另一台机器的应用程序接口。但随着近几年的技术在不断发展,rpc也有了一些新的含义。

目前,我们的rpc组件的基本能力就是屏蔽网络编程细节实现调用远程方法就跟调用本地(同一个项目中的方法)一样**。事实上一个合格的可用于生产的rpc框架还应该具备**负载均衡、优雅启停、链路追踪、灰度发布等等功能。

b.rpc的通信流程

rpc能实现调用远程方法就跟调用本地(同一个项目中的方法)一样,发起调用请求的那一方叫做调用方,被调用的一方叫做服务提供方

在这里插入图片描述

1.网络传输

a.零拷贝

1) 零拷贝的概念

几个buffer缓冲区:

  • 当某个程序或已存在的进程需要某段数据时,它只能在用户空间中属于它自己的内存中访问、修改,这段内

    存暂且称之为user buffer

  • 正常情况下,数据只能从磁盘(或其他外部设备)加载到内核的缓冲区,且称之为 kernel buffer

  • TCP/IP协议栈维护着两个缓冲区: send buffer 和 recv buffer ,它们合称为 socket buffer

(1) DMZ操作

DMA 的全称叫直接内存存取(Direct Memory Access),是一种允许外围设备(硬件子系统)直接访问系统主内存的机制。

DMA下读取磁盘数据流程如下:

  • 1.用户进程向 CPU 发起 read 系统调用读取数据,由用户态切换为内核态,然后一直阻塞等待数据的返回。
  • 2.CPU 在接收到指令以后对 DMA 磁盘控制器发起调度指令
  • 3.DMA 磁盘控制器对磁盘发起 I/O 请求,将磁盘数据先放入磁盘控制器缓冲区,CPU 全程不参与此过程
  • 4.DMA 磁盘控制器对磁盘发起 I/O 请求,将磁盘数据先放入磁盘控制器缓冲区,CPU 全程不参与此过程。
  • 5.DMA 磁盘控制器向 CPU 发出数据读完的信号,由 CPU 负责将数据从内核缓冲区拷贝到用户缓冲区
  • 6.用户进程由内核态切换回用户态,解除阻塞状态

DMA可以脱离CPU,将一些数据从外设读取到内核缓冲区当中

(2) 传统读取数据和发送数据

程序传统IO实际上是调用系统的 read() 和 write() 实现,通过 read() 把数据从硬盘读取到内核缓冲区,再复制到用户缓冲区;然后再通过 write() 写入到socket缓冲区,最后写入网卡设备

在这里插入图片描述

整个过程发生了四次用户态和内核态的切换还有四次IO拷贝, 具体流程是:

  1. 用户进程通过 read() 方法向操作系统发起调用,此时上下文从用户态转向内核态
  2. DMA控制器把数据从硬盘中拷贝到读缓冲区
  3. CPU把读缓冲区数据拷贝到应用缓冲区,上下文从内核态转为用户态, read() 返回
  4. 用户进程通过 write() 方法发起调用,上下文从用户态转为内核态
  5. CPU将应用缓冲区中数据拷贝到socket缓冲区
  6. DMA控制器把数据从socket缓冲区拷贝到网卡,上下文从内核态切换回用户态, write() 返回

(3) 零拷贝实现技术

方案一、内存映射(mmap+write)

mmap 是 Linux 提供的一种内存映射文件方法,即将一个进程的地址空间中的一段虚拟地址映射到磁盘文件地

址。

mmap 主要实现方式是将**读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,**从而减少了从读缓冲区到用户缓冲区的一次CPU拷贝,然而内核读缓冲区(read buffer)仍需将数据拷贝到内核写缓冲区(socket buffer)。

在这里插入图片描述

方案二、sendfile

通过使用 sendfile 函数,数据可以直接在内核空间进行传输,因此避免了用户空间和内核空间的拷贝,同时由于

使用sendfile替代了read+write从而节省了一次系统调用,也就是2次上下文切换

在这里插入图片描述

方案三、sendfile+DMA scatter/gather

将读缓冲区中的数据描述信息–内存地址和偏移量记录到socket缓冲区,由 DMA 根据这些将数据从读缓冲区拷贝到网卡,相比之前版本减少了一次CPU拷贝的过程。

在这里插入图片描述

总结:

  • 由于CPU和IO速度的差异问题,产生了DMA技术,通过DMA搬运来减少CPU的等待时间。

  • 传统的 IO read/write 方式会产生2次DMA拷贝+2次CPU拷贝,同时有4次上下文切换。

  • 而通过 mmap+write 方式则产生2次DMA拷贝+1次CPU拷贝,4次上下文切换,通过内存映射减少了一次CPU拷贝,可以减少内存使用,适合大文件的传输。

  • sendfile 方式是新增的一个系统调用函数,产生2次DMA拷贝+1次CPU拷贝,但是只有2次上下文切换。因为只有一次调用,减少了上下文的切换,但是用户空间对IO数据不可见,适用于静态文件服务器。

  • sendfile+DMA gather 方式产生2次DMA拷贝,没有CPU拷贝,而且也只有2次上下文切换。虽然极大地提升了性能,但是需要依赖新的硬件设备支持。

2) Netty的零拷贝

操作系统层面的零拷贝主要避免在用户态(User-space)内核态(Kernel-space)之间来回拷贝数据。

Netty中的zero-copy不同于操作系统,它完全是在用户态(java 层面),更多的偏向于优化数据操作这样的概念,体现在:

(1) ByteBuf

ByteBuf是Netty进行数据读写交互的单位,结构如下:

在这里插入图片描述

  • 1.ByteBuf 是一个字节容器,容器里面的的数据分为三个部分,第一个部分是已经丢弃的字节,这部分数据是无效的;第二部分是可读字节,这部分数据是 ByteBuf 的主体数据, 从 ByteBuf 里面读取的数据都来自这一部分;最后一部分的数据是可写字节,所有写到 ByteBuf 的数据都会写到这一段。最后一部分虚线表示的是该ByteBuf 最多还能扩容多少容量
  • 2.以上三段内容是被两个指针给划分出来的,从左到右,依次是读指针(readerIndex)、写指针(writerIndex),然后还有一个变量 capacity,表示 ByteBuf 底层内存的总容量
  • 3.从 ByteBuf 中每读取一个字节,readerIndex 自增1,ByteBuf 里面总共有 writerIndex-readerIndex 个字节可读,当 readerIndex 与 writerIndex 相等的时候,ByteBuf 不可读
  • 4.写数据是从 writerIndex 指向的部分开始写,每写一个字节,writerIndex 自增1,直到增到 capacity,这个时候,表示 ByteBuf 已经不可写了
  • 5.ByteBuf 里面其实还有一个参数 maxCapacity,当向 ByteBuf 写数据的时候,如果容量不足,那么这个时候可以进行扩容,直到 capacity 扩容到 maxCapacity,超过 maxCapacity 就会报错
@Test
public void testByteBuf() {ByteBuf header = Unpooled.buffer();ByteBuf body = Unpooled.buffer();// 通过逻辑组装而不是物理拷贝,实现jvm中零拷贝CompositeByteBuf byteBuf = Unpooled.compositeBuffer();byteBuf.addComponents(header, body);
}

(2) CompositeByteBuf 零拷贝

Composite buffer 实现了透明的零拷贝, 将物理上的多个 Buffer 组合成了一个逻辑上完整 CompositeByteBuf

比如在网络编程中, 一个完整的 http 请求常常会被分散到多个 Buffer 中。用 CompositeByteBuf 很容易将多个分散的Buffer组装到一起,而无需额外的复制

@Test
public void testWrapper() {byte[] buf = new byte[1024];byte[] buf2 = new byte[1024];// 共享byte数组的内容而不是拷贝,也是零拷贝ByteBuf byteBuf = Unpooled.wrappedBuffer(buf, buf2);
}

b.IO多路复用

常见的网络 IO 模型分为四种:

  • 同步阻塞 IO(BIO)

  • 同步非阻塞 IO(NIO)

  • IO 多路复用

  • 异步非阻塞 IO(AIO)

在这四种 IO 模型中,只有 AIO 为异步 IO,其他都是同步 IO。

下图是应用程序发起一次网络IO的流程:

在这里插入图片描述

那么什么是 IO 多路复用呢?通过字面上的理解,多路就是指多个通道,也就是多个网络连接的 IO,而复用就是指多个通道复用在一个selector上。

多个网络连接的 IO 可以注册到一个selector上,当用户进程调用了 select,那么整个进程会被阻塞。同时,内核会“监视”所有 selector 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从内核中拷贝到用户进程。

当用户进程发起了 select 调用,进程会被阻塞,当发现该 select 负责的 socket 有准备好的数据时才返回,之后才发起一次 read,整个流程要比阻塞 IO 要复杂,似乎也更浪费性能。但它最大的优势在于,用户可以在一个线程内同时处理多个 socket 的 IO 请求。用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 IO 请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

同样好比我们去餐厅吃饭,这次我们是几个人一起去的,我们专门留了一个人在餐厅排号等位,其他人就去逛街了,等排号的朋友通知我们可以吃饭了,我们就直接去享用了。

c.Netty入门

netty的基本工作流程

在netty中存在以下的核心组件:

  • ServerBootstrap:服务器端启动辅助对象;

  • Bootstrap:客户端启动辅助对象;

  • Channel:通道,代表一个连接,每个Client请对会对应到具体的一个Channel;

  • ChannelPipeline:责任链,每个Channel都有且仅有一个ChannelPipeline与之对应,里面是各种各样的Handler;

  • handler:用于处理出入站消息及相应的事件,实现我们自己要的业务逻辑;

  • EventLoopGroup:I/O线程池,负责处理Channel对应的I/O事件;

  • ChannelInitializer:Channel初始化器;

  • ChannelFuture:代表I/O操作的执行结果,通过事件机制,获取执行结果,通过添加监听器,执行我们想要的操作;

  • ByteBuf:字节序列,通过ByteBuf操作基础的字节数组和缓冲区。

在这里插入图片描述

1) netty中的helloworld

创建客户端Client

客户端启动类根据服务器端的IP和端口,建立连接,连接建立后,实现消息的双向传输

public class AppClient {public void run() {// 1.定义线程池 EventLoopGroupNioEventLoopGroup group = new NioEventLoopGroup();try {// 2.启动一个客户端需要一个辅助类 bootstrapBootstrap bootstrap = new Bootstrap();bootstrap = bootstrap.group(group).remoteAddress(new InetSocketAddress(8080))// 选择初始化一个什么样的channel.channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new ClientChannelHandler());}});// 3.连接到远程节点;等待连接完成ChannelFuture channelFuture = bootstrap.connect().sync();// 4.获取channel并且写数据,发送消息到服务器端channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("hello netty".getBytes(StandardCharsets.UTF_8)));// 5.阻塞程序,等待接收消息channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {throw new RuntimeException(e);} finally {try {group.shutdownGracefully().sync();} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {new AppClient().run();}}

定义客户端Client的处理器

public class ClientChannelHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf byteBuf = (ByteBuf) msg;System.out.println("客户端已经收到了消息:--> " + byteBuf.toString(StandardCharsets.UTF_8));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {}
}

创建服务器Server

public class AppServer {private int port;private AppServer(int port) {this.port = port;}public void start() {// 1.创建EventLoopGroup,老板只负责处理请求,之后会将请求分发给worker,1比2的比例NioEventLoopGroup boss = new NioEventLoopGroup(2);NioEventLoopGroup worker = new NioEventLoopGroup(10);try{// 2.服务器端启动辅助对象ServerBootstrap serverBootstrap = new ServerBootstrap();// 3.配置服务器serverBootstrap = serverBootstrap.group(boss, worker).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new ServerChannelHandler());}});// 4.绑定端口ChannelFuture channelFuture = serverBootstrap.bind(port).sync();// 5.阻塞操作channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();} finally {try {boss.shutdownGracefully().sync();worker.shutdownGracefully().sync();} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {new AppServer(8080).start();}
}

创建服务器Server处理器

public class ServerChannelHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf byteBuf = (ByteBuf) msg;System.out.println("服务端已经收到了消息:--> " + byteBuf.toString(StandardCharsets.UTF_8));// 可以通过ctx获取channelctx.channel().writeAndFlush(Unpooled.copiedBuffer("hello client".getBytes(StandardCharsets.UTF_8)));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {}
}

d.封装报文

在设计一个 rpc远程调用框架时,需要考虑如何对请求和响应数据进行封装、以及编码、解码,以及如何表示调用的方法和参数。我们必须要设计一个私有且通用的私有协议,协议是一种公平对话的模式,有了标准协议调用方和服务提供方就可以互相按照标准进行协商。

1) 协议结构

项目设计的协议分为 Header(头部)和 Body(主体)两部分。Header 包含协议的元数据,例如消息类型、序列化类型、请求ID 等。Body 包含实际的 rpc 请求或响应数据。

+-----------------------------------------------+
| 					Header					    |
+-----------------------------------------------+
| 					 Body 						|
+-----------------------------------------------+

Header 结构

Header 可以包含以下字段:

  • Magic Number(4 字节):魔数,用于识别该协议,例如:0xCAFEBABE。
  • Version(1 字节):协议版本号。
  • MessageType(1 字节):消息类型,例如:0x01 表示请求,0x02 表示响应。
  • Serialization Type(1 字节):序列化类型,例如:0x01 表示 JSON,0x02 表示 Protobuf 等。
  • Request ID(8 字节):请求ID,用于标识请求和响应的匹配。
  • Body Length(4 字节):Body 部分的长度。
  • head length (4 字节) :请求长度

Body 结构

Body 的结构取决于具体的 yrpc 请求或响应数据

对于 yrpc 请求,Body 可以包含以下字段:

  • Service Name:被调用的服务名称。
  • Method Name:被调用的方法名称。
  • Method Arguments:被调用方法的参数列表。
  • Method Argument Types:被调用方法参数的类型列表。

对于 yrpc 响应,Body 可以包含以下字段:

  • Status Code:响应状态码,例如:0x00 表示成功,0x01 表示失败。
  • Error Message:错误信息,当 Status Code 为失败时,包含具体的错误信息。
  • Return Value:方法返回值,当 Status Code 为成功时,包含方法调用的返回值。

在这里插入图片描述

2) 模拟封装报文

/*** 模拟封装报文*/
@Test
public void testMessage() throws IOException {ByteBuf message = Unpooled.buffer();message.writeBytes("rpc".getBytes(StandardCharsets.UTF_8)); // magic numbermessage.writeByte(1); // versionmessage.writeShort(125); // head lengthmessage.writeInt(256); //full lengthmessage.writeByte(1); // Message Typemessage.writeByte(0); // Serialization Typemessage.writeByte(2); // compmessage.writeLong(251455L); // Request ID// 对象流转换为字节数组AppClient appClient = new AppClient();ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(outputStream);oos.writeObject(appClient);byte[] bytes = outputStream.toByteArray();message.writeBytes(bytes);printAsBinary(message);
}

e.序列化

在这里插入图片描述

网络传输中,我们不能直接将堆内存的对象实例直接进行传输,而是需要将其序列化成一组二进制数据,这样的二进制数据可以是字符序列,最简单的莫过于我们熟悉的json字符序列

  • jdk的ObjectInputStream
  • Hession
  • Json
  • protobuf等

f.压缩和解压缩

如果我们觉得序列化后的二进制内容体积任然比较大,任然不能支持当前的业务容量,我们可以选择对序列化的结果进行压缩,但是开启压缩一定要注意,这个操作本是就是一个cpu资源换取存储和带宽资源的操作,要判断当前的业务是更需要cpu资源还是内存资源。

模拟压缩与解压代码:

/*** 模拟压缩*/
@Test
public void testCompress() throws IOException {byte[] buf = new byte[]{12, 26, 26, 26, 25, 12, 26, 26, 26, 25, 12, 26, 26, 26, 25, 12, 26, 26, 26, 25, 23, 25, 14, 25, 23, 25, 14, 25, 23, 25, 14, 25, 23, 25, 14, 26, 25, 23, 25, 14, 26, 25, 23, 25, 14};// 将buf作为输入,将结果输出到另一个字节数组当中ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);gzipOutputStream.write(buf);gzipOutputStream.finish();byte[] bytes = byteArrayOutputStream.toByteArray();System.out.println(Arrays.toString(bytes));System.out.println("压缩前:" + buf.length);System.out.println("压缩后:" + bytes.length);}/*** 模拟解压缩*/
@Test
public void testDeCompress() throws IOException {byte[] buf = new byte[]{31, -117, 8, 0, 0, 0, 0, 0, 0, -1, -29, -111, -110, -110, -110, -28, 65, 37, -60, 37, -7, -112, -79, 20, -100, 0, 0, -90, -21, -43, 46, 45, 0, 0, 0};ByteArrayOutputStream out = new ByteArrayOutputStream();GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(buf));byte[] bytes = gzipInputStream.readAllBytes();System.out.println(Arrays.toString(bytes));System.out.println("解压前:" + buf.length);System.out.println("解压后:" + bytes.length);
}

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

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

相关文章

计算机视觉 – Computer Vision | CV

计算机视觉为什么重要&#xff1f; 人的大脑皮层&#xff0c; 有差不多 70% 都是在处理视觉信息。 是人类获取信息最主要的渠道&#xff0c;没有之一。 在网络世界&#xff0c;照片和视频&#xff08;图像的集合&#xff09;也正在发生爆炸式的增长&#xff01; 下图是网络上…

数据结构】二叉树篇|超清晰图解和详解:后序篇

博主简介&#xff1a;努力学习的22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a; 是瑶瑶子啦每日一言&#x1f33c;: 你不能要求一片海洋&#xff0c;没有风暴&#xff0c;那不是海洋&#xff0c;是泥塘——毕淑敏 目录 一、核心二、题目 一、核心 我们清楚…

wireshark过滤器的使用

目录 wiresharkwireshark的基本使用wireshark过滤器的区别 抓包案例 wireshark wireshark的基本使用 抓包采用 wireshark&#xff0c;提取特征时&#xff0c;要对 session 进行过滤&#xff0c;找到关键的stream&#xff0c;这里总结了 wireshark 过滤的基本语法&#xff0c;…

帝国CMS仿管理资源吧资料下载网站模板源码/下载会员+积分付费下载功能自动采集资源网站源码

帝国CMS仿管理资源吧资料下载网站模板源码&#xff0c;带下载会员积分付费下载功能自动采集资源网站源码&#xff0c;管理资源吧——为中小企业管理者提供全方位的管理资料下载服务&#xff1b;是一个所有资料免费下载&#xff0c;免注册、免登陆、免积分的公益性的管理知识共享…

PDF校对工具正式上线,为用户提供卓越的文档校对解决方案

为满足当下对数字化文档校对的精准需求&#xff0c;我们今日正式发布全新的PDF校对工具。经过深入的技术研发与细致的测试&#xff0c;该工具旨在为企业和个人用户带来一个高效且准确的PDF文档校对平台。 PDF校对工具的主要特性&#xff1a; 1.全面性校对&#xff1a;工具支持…

前端vue3+typescript架构

1、vue creat 项目名称 选择自定义 选择需要的依赖 选择vue3 一路enter&#xff0c;选择eslistprettier 继续enter&#xff0c;等待安装 按步骤操作&#xff0c;项目启动成功 2、vscode安装5款插件 2、代码保存自动格式化&#xff0c;保证每个开发人员代码一致&#xff0c;根目…

【报错记录】疯狂踩坑之RockyLinux创建Raid1镜像分区,Raid分区在重启后消失了!外加华硕主板使用Raid模式后,硬盘在系统中无法找到问题

前言 为了摆脱对于专业NAS的依赖&#xff0c;我决定专门使用一台Linux服务器安装NAS程序的方式实现NAS功能&#xff0c;这里就需要用到Raid功能&#xff0c;由于目前我只有3块SSD&#xff08;256G500G500G&#xff09;&#xff0c;在ChatGPT的推荐下还是使用一个256G系统盘2块…

TCP 和 UDP 的区别、TCP 是如何保证可靠传输的?

先来介绍一些osi七层模型 分为应用层、表示层、会话层、运输层、网络层、链路层、物理层。 应用层(数据)&#xff1a;确定进程之间通信的性质以及满足用户需要以及提供网络和用户应用&#xff0c;为应用程序提供服务&#xff0c;DNS&#xff0c;HTTP&#xff0c;HTTPS&#xf…

1. 深度学习介绍

1.1 AI地图 ① 如下图所示&#xff0c;X轴是不同的模式&#xff0c;最早的是符号学&#xff0c;然后概率模型、机器学习。Y轴是我们想做什么东西&#xff0c;感知是我了解这是什么东西&#xff0c;推理形成自己的知识&#xff0c;然后做规划。 ② 感知类似我能看到前面有个屏…

axios 介绍

axios 介绍 axios 是一款基于 javascript xhr 进行封装的插件&#xff0c;自己通过 xhr 进行编写 ajax 请求&#xff0c;实现起来逻辑比较复杂&#xff0c;axios 封装后将复杂的逻辑写在插件的内部&#xff0c;我们用户只需要关心如何调用即可。对我们的开发带来了很大的便捷。…

C语言-内存分布(STM32内存分析)

C/C内存分布 一、内存组成二、静态区域文本段 &#xff08;Text / 只读区域 RO&#xff09;已初始化读写数据段&#xff08;RW data -- Initialized Data Segment&#xff09;未初始化数据段&#xff08;BSS -- Block Started by Symbol&#xff09; 三、动态区域堆&#xff08…

C++笔记之rolling counter(滚动计数器)

C笔记之rolling counter&#xff08;滚动计数器&#xff09; 一个 rolling counter&#xff08;滚动计数器&#xff09;是一个计数器&#xff0c;可以在给定的范围内不断增加&#xff0c;当达到最大值时会从最小值重新开始。 code review! 文章目录 C笔记之rolling counter&…

计算机视觉:深层卷积神经网络的构建

本文重点 上一节课程中我们学习了单卷积层的前向传播,本次课程我们构建一个具有三个卷积层的卷积神经网络,然后从输入(39*39*3)开始进行三次卷积操作,我们来看一下每次卷积的输入和输出维度的变化。 第一层 第一层使用3*3*3的过滤器来提取特征,那么f[1]=3,然后步长s[…

基于springboot学生社团管理系统/基于Java的高校社团管理系统的设计与实现

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&…

Stable Diffusion WebUI 整合包

现在网络上出现的各种整合包只是整合了运行 Stable Diffusion WebUI&#xff08;以下简称为 SD-WebUI&#xff09;必需的 Python 和 Git 环境&#xff0c;并且预置好模型&#xff0c;有些整合包还添加了一些常用的插件&#xff0c;其实际与手动进行本地部署并没有区别。 不过&a…

QT学习笔记-开发环境编译Qt MySql数据库驱动与交叉编译Qt MySql数据库驱动

QT学习笔记-开发环境编译Qt MySql数据库驱动与交叉编译Qt MySql数据库驱动 0、背景1、基本环境2、开发环境编译Qt MySql数据库驱动2.1 依赖说明2.2 MySQL驱动编译过程 3、交叉编译Qt MySql数据库驱动3.1 依赖说明3.3.1 如何在交叉编译服务器上找到mysql.h及相关头文件3.3.2 如果…

时序预测 | MATLAB实现SSA-XGBoost(麻雀算法优化极限梯度提升树)时间序列预测

时序预测 | MATLAB实现SSA-XGBoost(麻雀算法优化极限梯度提升树)时间序列预测 目录 时序预测 | MATLAB实现SSA-XGBoost(麻雀算法优化极限梯度提升树)时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 Matlab实现SSA-XGBoost时间序列预测&#xff0c;麻…

【rust/egui】(六)看看template的app.rs:TextEdit

说在前面 rust新手&#xff0c;egui没啥找到啥教程&#xff0c;这里自己记录下学习过程环境&#xff1a;windows11 22H2rust版本&#xff1a;rustc 1.71.1egui版本&#xff1a;0.22.0eframe版本&#xff1a;0.22.0上一篇&#xff1a;这里 TextEdit 文本编辑框 其定义为&#…

Flink CDC数据同步

背景 随着信息化程度的不断提高&#xff0c;企业内部系统的数量和复杂度不断增加&#xff0c;因此&#xff0c;数据库系统的同步问题已成为越来越重要的问题。 缓存失效 在缓存中缓存的条目(entry)在源头被更改或者被删除的时候立即让缓存中的条目失效。如果缓存在一个独立的…

微信小程序canvas type=2d生成海报保存到相册、文字换行溢出显示...、文字删除线、分享面板

一、简介 做个简单的生成二维码海报分享&#xff0c;我做的时候也找简单的方法看能不能实现页面直接截图那种生成图片&#xff0c;原生小程序不支持&#xff0c;不多介绍下面有全部代码有注释、参数自行替换运行看看&#xff0c;还有需要优化的地方&#xff0c;有问题可以咨询…