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

news2024/11/24 19:54:27

介绍与网络传输

  • 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.定义线程池 EventLoopGroup
        NioEventLoopGroup group = new NioEventLoopGroup();

        try {
            // 2.启动一个客户端需要一个辅助类 bootstrap
            Bootstrap bootstrap = new Bootstrap();
            bootstrap = bootstrap.group(group)
                    .remoteAddress(new InetSocketAddress(8080))
                    // 选择初始化一个什么样的channel
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected 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 {

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

    @Override
    public 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>() {
                        @Override
                        protected 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 {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("服务端已经收到了消息:--> " + byteBuf.toString(StandardCharsets.UTF_8));
        // 可以通过ctx获取channel
        ctx.channel().writeAndFlush(Unpooled.copiedBuffer("hello client".getBytes(StandardCharsets.UTF_8)));
    }

    @Override
    public 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 number
    message.writeByte(1); // version
    message.writeShort(125); // head length
    message.writeInt(256); //full length
    message.writeByte(1); // Message Type
    message.writeByte(0); // Serialization Type
    message.writeByte(2); // comp
    message.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.coloradmin.cn/o/937529.html

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

相关文章

S905L3A(M401A)拆解, 运行EmuELEC和Armbian

关于S905L3A / S905L3AB S905Lx系列没有公开资料, 猜测是Amlogic用于2B的芯片型号, 最早的 S905LB 是 S905X 的马甲, 而这个 S905L3A/S905L3AB 则是 S905X2 的马甲, 因为在性能评测里这两个U的得分几乎一样. S905L3A/S905L3AB 和 S905X2, S905X3 一样 GPU 是 G31, 相比前一代的…

【Linux】深入理解文件操作

文章目录 初次谈论文件重温C语言文件操作系统文件操作接口openwriteread 再次谈论文件文件描述符文件描述符的分配规则 重定向什么是重定向重定向的本质系统调用接口实现重定向<、>、>> 初次谈论文件 开始之前先谈论一下关于文件的一些共识性问题。 一个文件可以…

(笔记一)利用open_cv在图像上进行点标记,文字注记,画圆、多边形、椭圆

&#xff08;1&#xff09;CV2中的绘图函数&#xff1a; cv2.line() 绘制线条cv2.circle() 绘制圆cv2.rectangle() 绘制矩形cv2.ellipse() 绘制椭圆cv2.putText() 添加注记 &#xff08;2&#xff09;注释 img表示需要绘制的图像color表示线条的颜色&#xff0c;采用颜色矩阵…

联想电脑装系统无法按F9后无法从系统盘启动的解决方案

开机时按F9发现没有加载系统盘. 打开BIOS设置界面&#xff0c;调整设置如下: BOOT MODE: Legacy Support.允许legacy方式boot. BOOT PRIORITY: Legacy First. Legacy方式作为首选的boot方式. USB BOOT: ENABLED. 允许以usb方式boot. Legacy: 这里设置legacy boot的优先级,…

保姆级教程:从0到1使用Stable Diffusion XL训练LoRA模型 |【人人都是算法专家】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【人人都是算法专家】栏目专注于分享Rocky在AI行业中对业务/竞赛/研究/产品维度的思考与感悟。欢迎大家一起交流学习&#x1f4aa; 大家好&#xff0c;我是Rocky。 Rocky在知乎上持续撰写Stable Diffusion XL全方位的解析文章…

HTML之VSCode简单配置与创建

目录 插件下载 然后输入源码&#xff1a; 使用 效果 插件下载 下载这个插件后可以直接运行&#xff1a; 然后创建一个文件&#xff1a; 然后输入源码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"…

渗透测试工具ZAP入门教程(1)-安装和快速开始

介绍 ZAP Zed Attack Proxy&#xff08;ZAP&#xff09;是一个免费的开源渗透测试工具&#xff0c;在 软件安全项目 &#xff08;SSP&#xff09;。ZAP 专为测试 Web 应用程序而设计&#xff0c;既灵活又可扩展。 ZAP的核心是所谓的“中间人代理”。它位于测试人员的浏览器和…

【算法训练-双指针】最长无重复子串(数组)

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是最长无重复子串或最长无重复子数组&#xff0c;这类题目出现频率还是很高的。 最长无重复子数组 先来看看数组数据结构的题目 题干 输入&#…

Shiro认证框架

目录 概述 认证授权及鉴权 Shiro框架的核心组件 基本流程 spring bootshiromybatisPlus...实现用户登录 step1:准备工作 (1)坐标 (2)连接数据库 (3)JavaBean (4)dao数据访问层 (5)密码工具类 DigestsUtil (6)配置类 step2&#xff1a;认证功能 step3:授权鉴权 概述…

11. 网络模型保存与读取

11.1 网络模型保存(方式一) import torchvision import torch vgg16 torchvision.models.vgg16(pretrainedFalse) torch.save(vgg16,"./model/vgg16_method1.pth") # 保存方式一&#xff1a;模型结构 模型参数 print(vgg16) 结果&#xff1a; VGG((feature…

飞腾FT-2000/4、D2000 log报错指导(2)

在爱好者群中遇见了很多的固件问题,这里总结记录了大家的交流内容和调试心得。主要是飞腾桌面CPU FT-2000/4 D2000相关的,包含uboot和UEFI。希望对大家调试有所帮助。 这个专题会持续更新,凑够一些就发。 12 UEFI现象:主板启动时串口信息停在 s3 flag form ec 处,如下图所…

局域网中电脑共享文件给手机

学习资源&#xff1a; 局域网共享&#xff1a;这样设置&#xff0c;你可以轻松拷贝任何电脑的文件。_哔哩哔哩_bilibili 可以实现什么效果&#xff1f; 连接同一个WIFI&#xff0c;电脑端为服务端&#xff0c;提供共享文件&#xff0c;手机是客户端&#xff0c;可以读取服务端…

Java-泛型

文章目录 Java泛型什么是泛型&#xff1f;在哪里使用泛型&#xff1f;设计出泛型的好处是什么&#xff1f;动手设计一个泛型泛型的限定符泛型擦除泛型的通配符 结论 Java泛型 什么是泛型&#xff1f; Java泛型是一种编程技术&#xff0c;它允许在编译期间指定使用的数据类型。…

Leetcode:238. 除自身以外数组的乘积【题解超详细】

纯C语言实现&#xff08;小白也能看明白&#xff09; 题目 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数…

8. 损失函数与反向传播

8.1 损失函数 ① Loss损失函数一方面计算实际输出和目标之间的差距。 ② Loss损失函数另一方面为我们更新输出提供一定的依据。 8.2 L1loss损失函数 ① L1loss数学公式如下图所示&#xff0c;例子如下下图所示。 import torch from torch.nn import L1Loss inputs torch.tens…

冷启问题目前常见解决方案

1、冷启动的基本方式 随机冷启 个性化冷启 2、冷启动解决问题 冷启动保量 冷启动保量&#xff0c;保证每个item曝光的次数一样&#xff0c;实时统计已曝光的次数和要曝光的次数 冷启动结束过滤&#xff0c;如果需曝光的量越大&#xff0c;且越接近冷启结束时间&#xff0…

3.2 数据的表现形式及其运算

3.2.1 常量和变量 在计算机高级语言中&#xff0c;数据有两种表现形式&#xff1a;常量和变量。 1.常量 在程序运行过程中&#xff0c;其值不能被改变的量称为常量。如例3.1程序中的5,9,32和例3.2程序中的1000,0.0036,0.0225,0.0198是常量。数值常量就是数学中的常数。 常用的…

三、前端监控之Sentry的介绍

Sentry介绍 Sentry是一个开源的实时事件日志记录和聚合平台。它专门用于监视错误和提取执行适当的事后操作所需的所有信息, 而无需使用标准用户反馈循环的任何麻烦。支持 web 前后端、移动应用以及游戏&#xff0c;支持 Python、OC、Java、Go、Node、Django、RoR 等主流编程语…

【Docker】网络

文章目录 Docker 网络基础Docker网络管理Docker网络架构CNMLibnetwork驱动 常见的网络类型 Docker 网络管理命令docker network createdocker network inspectdocker network connectdocker network disconnectdocker network prunedocker network rmdocker network ls docker …

跳跃游戏 II【贪心算法】

跳跃游戏 II class Solution {public int jump(int[] nums) {int cur 0;//当前最大覆盖路径int next 0;//下一步的最大覆盖路径int res 0;//存放结果&#xff0c;到达终点时最少的跳跃步数for (int i 0; i < nums.length; i) {//遍历数组&#xff0c;以给出数组以一个…