【吃透Java手写】5-RPC-简易版

news2024/10/6 20:33:30

【吃透Java手写】RPC-简易版-源码解析

  • 1 RPC
    • 1.1 RPC概念
    • 1.2 常用RPC技术或框架
    • 1.3 初始工程
      • 1.3.1 Productor-common:HelloService
      • 1.3.2 Productor:HelloServiceImpl
      • 1.3.3 Consumer
  • 2 模拟RPC
    • 2.1 Productor
    • 2.2 模拟一个RPC框架
      • 2.2.1 HttpServer
      • 2.2.2 HttpClient
      • 2.2.2 用rpc启动tomcat
      • 2.2.3 启动Productor
    • 2.3 DispatcherServlet
      • 2.3.1 Handler
      • 2.3.2 Invocation
      • 2.3.3 完善Handler
    • 2.4 注册中心LocalRegister
      • 2.4.1 Productor
    • 2.5 Handler
    • 2.6 Consumer测试
  • 3 优化
    • 3.1 ProxyFactory
    • 3.2 Consumer
    • 3.3 测试
  • 4 rpc服务注册和服务发现
    • 4.1 URL
    • 4.2 MapRemoteRegister
    • 4.3 注册中心注册
    • 4.4 负载均衡
    • 4.5 测试
      • 4.5.1 解决
    • 4.6 BootStrap
  • 5 服务重试


1 RPC

1.1 RPC概念

  1. RPC(Remote Procedure Call Protocol) 远程过程调用协议。
  2. RPC是一种通过网络从远程计算机程序上请求服务,不需要了解底层网络技术的协议。
  3. RPC主要作用就是不同的服务间方法调用就像本地调用一样便捷。

1.2 常用RPC技术或框架

  1. 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
  2. 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
  3. 通信框架:MINA 和 Netty

1.3 初始工程

在这里插入图片描述

1.3.1 Productor-common:HelloService

在Productor-common中创建com.sjb.HelloService

public interface HelloService {
    String sayHello(String name);
}

1.3.2 Productor:HelloServiceImpl

在Productor中创建com.sjb.HelloServiceImpl

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

pom.xml依赖

<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>Productor-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

1.3.3 Consumer

在Consumer中创建com.sjb.Consumer

public class Consumer {
    public static void main(String[] args) {
        HelloService helloService = ?;
        System.out.println(helloService.sayHello("world"));
    }
}

pom.xml依赖

<dependencies>
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>Productor-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

2 模拟RPC

2.1 Productor

我们需要在springboot启动时完成一部分功能。启动时要能接收一部分功能的调用。只能通过网络来接收一定的请求,比如netty或者tomcat、socket。

在Productor中创建com.sjb.Productor

public class Productor {
    public static void main(String[] args) {
        //netty、tomcat
    }
}

2.2 模拟一个RPC框架

创捷sjbRPC模块,并且使Consumer模块和Productor模块依赖于sjbRPC模块

<dependency>
    <groupId>org.example</groupId>
    <artifactId>sjbRPC</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

2.2.1 HttpServer

在sjbRPC模块中创建com.sjb.Productorcom.sjb.register.HttpServer,负责网络服务启动

public class HttpServer {
    public void start(String hostname, int port) {
        System.out.println("HttpServer start at " + hostname + ":" + port);
    }   
}

然后Productor就可以创建HttpServer对象调用里面的start方法

public class Productor {
    public static void main(String[] args) {
        //netty、tomcat
        HttpServer httpServer = new HttpServer();
        httpServer.start("localhost", 8080);
    }
}

2.2.2 HttpClient

创建com.sjb.protocol.HttpClient

public class HttpClient {
    public String send(String hostName, int port, Invocation invocation) {
        //读取用户的发送方式
        //http、netty、tcp
        try{
            URL url = new URL("http", hostName, port, "/");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            connection.setRequestMethod("POST");
            connection.setDoOutput(true);

            //配置
            OutputStream outputStream = connection.getOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(outputStream);

            //发送
            oos.writeObject(invocation);
            oos.flush();
            oos.close();

            //接收
            InputStream inputStream = connection.getInputStream();
            String result = IOUtils.toString(inputStream);
            return result;
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.2.2 用rpc启动tomcat

为rpc添加tomcat依赖

<dependencies>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>8.5.31</version>
    </dependency>
</dependencies>

rpc第一步应当扫描当前模块的配置,获取需要启动的网络服务,这里写死直接启动tomcat

在com.sjb.register.HttpServer#start中

public void start(String hostname, int port) {
    //1.读取用户的配置(application.yaml或者Nacos配置)
    //2.这里启动一个Tomcat
    Tomcat tomcat = new Tomcat();

    Server server = tomcat.getServer();
    Service service = server.findService("Tomcat");

    Connector connector = new Connector();
    connector.setPort(port);

    Engine engine = new StandardEngine();
    engine.setDefaultHost(hostname);

    Host host = tomcat.getHost();
    host.setName(hostname);

    String contextPath = "";
    Context context = new StandardContext();
    context.setPath(contextPath);
    context.addLifecycleListener(new Tomcat.FixContextListener());

    host.addChild(context);
    engine.addChild(host);

    service.setContainer(engine);
    service.addConnector(connector);

    try{
        tomcat.start();
        tomcat.getServer().await();
    }
    catch (LifecycleException e){
        e.printStackTrace();
    }
}

2.2.3 启动Productor

public class Productor {
    public static void main(String[] args) {
        //netty、tomcat
        HttpServer httpServer = new HttpServer();
        httpServer.start("localhost", 8080);
    }
}
D:\Software\software_with_code\idea\jdk\jdk-17\bin\java.exe "-javaagent:D:\Software\software_with_code\idea\software\IntelliJ IDEA 2023.2\lib\idea_rt.jar=13802:D:\Software\software_with_code\idea\software\IntelliJ IDEA 2023.2\bin" -Dfile.encoding=UTF-8 -classpath D:\Code\JavaCode\handwith-Spring\handwith-Spring\RPC\Productor\target\classes;D:\Code\JavaCode\handwith-Spring\handwith-Spring\RPC\Productor-common\target\classes;D:\Code\JavaCode\handwith-Spring\handwith-Spring\RPC\sjbRPC\target\classes;D:\Software\software_with_code\apache-maven-3.9.5-bin\apache-maven-3.9.5\mvn_repo\org\apache\tomcat\embed\tomcat-embed-core\8.5.31\tomcat-embed-core-8.5.31.jar;D:\Software\software_with_code\apache-maven-3.9.5-bin\apache-maven-3.9.5\mvn_repo\org\apache\tomcat\tomcat-annotations-api\8.5.31\tomcat-annotations-api-8.5.31.jar com.sjb.Productor
5月 13, 2024 1:26:00 下午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-nio-8080"]
5月 13, 2024 1:26:00 下午 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
信息: Using a shared selector for servlet write/read
5月 13, 2024 1:26:00 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service [Tomcat]
5月 13, 2024 1:26:00 下午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet Engine: Apache Tomcat/8.5.31
5月 13, 2024 1:26:01 下午 org.apache.catalina.util.SessionIdGeneratorBase createSecureRandom
警告: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [117] milliseconds.
5月 13, 2024 1:26:01 下午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-nio-8080"]

2.3 DispatcherServlet

大家如果学过Spring MVC的底层原理就会知道,在SpringMVC中有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法,所有的服务都会放入DispatchServlet中。我们rpc框架启动的服务也要放入DispatcherServlet

在com.sjb.protocol.HttpServer#start中

tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet());
context.addServletMappingDecoded("/*", "dispatcher");

try{
    tomcat.start();
    tomcat.getServer().await();
}
catch (LifecycleException e){
    e.printStackTrace();

context.addServletMappingDecoded("/*", "dispatcher");接收到的请求都会交由dispatcher处理

创建com.sjb.register.DispatcherServlet

public class DispatcherServlet extends HttpServlet {
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        new HttpServerHandler().handle(req, res);
    }
}

2.3.1 Handler

创建com.sjb.register.HttpServerHandler,因为有可能有很多请求请求dispatcher,相当于一个过滤器的作用,相当可以用每一个请求都可以用一个独立的handler类处理,也就是new一个新handler来处理。

public class HttpServerHandler {
    public void handle(ServletRequest req, ServletResponse res) {
        //处理请求

    }
}

2.3.2 Invocation

创建com.sjb.common.Invocation,记录传入的接口名、方法名、参数列表、参数值

implements Serializable序列化是方便解析request

public class Invocation implements Serializable {
    private String interfaceName;
    private String methodName;
    private Class[] paramTypes;
    private Object[] params;

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }

    public Invocation(String interfaceName, String methodName, Class[] paramTypes, Object[] params) {
        this.interfaceName = interfaceName;
        this.methodName = methodName;
        this.paramTypes = paramTypes;
        this.params = params;
    }
}

2.3.3 完善Handler

public class HttpServerHandler {
    public void handle(ServletRequest req, ServletResponse res) {
        //处理请求-->接口,方法,参数
        try {
            Invocation invocation=(Invocation)new ObjectInputStream(req.getInputStream()).readObject();
            String interfaceName=invocation.getInterfaceName();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }


    }
}

这样就获取到调用请求的类的接口,那么怎么找到接口的实现类呢?如果是扫描全包查看谁实现了HelloService这样性能就非常的低。所以我们需要一个注册中心。

2.4 注册中心LocalRegister

创建com.sjb.register.LocalRegister

public class LocalRegister {
    private static Map<String, Class> map = new HashMap<>();

    public static void register(String interfaceName, Class implClass) {
        map.put(interfaceName, implClass);
    }
    public static Class get(String interfaceName) {
        return map.get(interfaceName);
    }
}

2.4.1 Productor

这样就可以在Productor中将接口和实现类放入,在com.sjb.Productor中

public class Productor {
    public static void main(String[] args) {
        //注册服务
        LocalRegister.register(HelloService.class.getName(), HelloServiceImpl.class);
        //netty、tomcat
        HttpServer httpServer = new HttpServer();
        httpServer.start("localhost", 8080);
    }
}

这样在Handler中就可以从LocalRegister的map中拿到对应的接口和实现类

2.5 Handler

添加commons-io依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
</dependency>
public class HttpServerHandler {
    public void handle(ServletRequest req, ServletResponse res) {
        //处理请求-->接口,方法,参数
        try {
            Invocation invocation=(Invocation)new ObjectInputStream(req.getInputStream()).readObject();
            String interfaceName=invocation.getInterfaceName();
            Class implClass= LocalRegister.get(interfaceName);
            Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamTypes());
            Object result = method.invoke(implClass.newInstance(), invocation.getParams());

            //res.getOutputStream().write(invoke.toString().getBytes());
            IOUtils.write(result.toString(), res.getOutputStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }


    }
}
  • Invocation invocation=(Invocation)new ObjectInputStream(req.getInputStream()).readObject();反序列化获取invocation
  • String interfaceName=invocation.getInterfaceName();获取接口名
  • Class implClass= LocalRegister.get(interfaceName);通过注册中心获取接口实现类
  • Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamTypes());获取实现类中的方法
  • Object result = method.invoke(implClass.newInstance(), invocation.getParams());执行方法返回返回值
  • IOUtils.write(result.toString(), res.getOutputStream());写入response中

2.6 Consumer测试

public class Consumer {
    public static void main(String[] args) {
//        HelloService helloService = ?;
//        System.out.println(helloService.sayHello("world"));
        Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello", new Class[]{String.class}, new Object[]{"world"});

        HttpClient httpClient = new HttpClient();
        String result = httpClient.send("localhost", 8080, invocation);
        System.out.println(result);
    }
}

输出

Hello, world

3 优化

我们想让网络调用像调用本地方法一样,创建一个HelloService对象,直接传参就好了

HelloService helloService = ?;
System.out.println(helloService.sayHello("world"));

所以我们需要在rpc框架中创建一个代理对象代理HelloService

3.1 ProxyFactory

创建com.sjb.proxy.ProxyFactory

public class ProxyFactory {
    public static <T> T getProxy(Class interfaceClass) {
        //读取用户配置
        Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Invocation invocation = new Invocation(
                        interfaceClass.getName(),
                        method.getName(),
                        method.getParameterTypes(),
                        args);
                HttpClient httpClient = new HttpClient();
                String result = httpClient.send("localhost", 8080, invocation);
                return result;
            }
        });
        return (T) proxyInstance;
    }

}

3.2 Consumer

public class Consumer {
    public static void main(String[] args) {
//        HelloService helloService = ?;
//        System.out.println(helloService.sayHello("world"));

        HelloService helloService = ProxyFactory.getProxy(HelloService.class);
        System.out.println(helloService.sayHello("world"));
    }
}

helloService.sayHello("world")调用invoke方法返回

3.3 测试

Hello, world

4 rpc服务注册和服务发现

我们希望String result = httpClient.send("localhost", 8080, invocation);在send的时候可以灵活的找到传入的接口对应的ip和端口是多少,也就是应用所对应的ip和端口是多少,所以就自然而然的想到注册中心,在Productor创建的时候,将对应服务的ip和端口保存到rpc中起来,以供其他服务使用。

在这里插入图片描述

4.1 URL

public class URL {
    private String hostname;
    private Integer port;

这样我们Productor启动的时候,不仅需要注册服务,还要注册注册中心

4.2 MapRemoteRegister

创建com.sjb.register.MapRemoteRegister

public class MapRemoteRegister {
    private static Map<String, List<URL>> mapRemoteRegister = new HashMap<>();
    public static void register(String interfaceName,URL url) {
        List<URL> list = mapRemoteRegister.get(interfaceName);
        if (list == null) {
            list = new java.util.ArrayList<>();
        }
        list.add(url);
        mapRemoteRegister.put(interfaceName, list);
    }
    public static List<URL> get(String interfaceName) {
        return mapRemoteRegister.get(interfaceName);
    }
}

4.3 注册中心注册

public class Productor {
    public static void main(String[] args) {
        //注册服务
        LocalRegister.register(HelloService.class.getName(), HelloServiceImpl.class);
        //注册中心注册
        URL url = new URL("localhost", 8080);
        MapRemoteRegister.register(HelloService.class.getName(), url);

那么在创建HelloService的代理对象时,就要读取注册中心

public class ProxyFactory {
    public static <T> T getProxy(Class interfaceClass) {
        //读取用户配置
        Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Invocation invocation = new Invocation(
                        interfaceClass.getName(),
                        method.getName(),
                        method.getParameterTypes(),
                        args);
                HttpClient httpClient = new HttpClient();
                //服务发现
                List<URL> urls = MapRemoteRegister.get(interfaceClass.getName());
                //负载均衡
                URL url = LoadBalance.random(urls);
                //服务调用
                String result = httpClient.send(url.getHostname(), url.getPort(), invocation);
                return result;
            }
        });
        return (T) proxyInstance;
    }

}

4.4 负载均衡

创建com.sjb.loadbalance.LoadBalance

public class LoadBalance {
    public static URL random(List<URL> list) {
        int i = new Random().nextInt(list.size());
        return list.get(i);
    }
}

4.5 测试

感觉没问题,测试一下

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "list" is null
	at com.sjb.loadbalance.LoadBalance.random(LoadBalance.java:10)
	at com.sjb.proxy.ProxyFactory$1.invoke(ProxyFactory.java:29)
	at jdk.proxy1/jdk.proxy1.$Proxy0.sayHello(Unknown Source)
	at com.sjb.Consumer.main(Consumer.java:11)

报错,发现在Product中

//注册服务
LocalRegister.register(HelloService.class.getName(), HelloServiceImpl.class);
//注册中心注册
URL url = new URL("localhost", 8080);
MapRemoteRegister.register(HelloService.class.getName(), url);

LocalRegister.register的调用是在Product启动的HttpServer的handler处理中,等于LocalRegister这个map还是在Product这个进程中。而MapRemoteRegister.register的存放是在Product进程中,而调用却是在Consumer中的代理方法的invoke中,自然调用不到。

4.5.1 解决

要么使用redis等统一管理,但是又涉及心跳检测等等。我们这里使用一个简单的存入一个文件,再从文件里读取

public class MapRemoteRegister {
    private static Map<String, List<URL>> mapRemoteRegister = new HashMap<>();
    public static void register(String interfaceName,URL url) {
        List<URL> list = mapRemoteRegister.get(interfaceName);
        if (list == null) {
            list = new java.util.ArrayList<>();
        }
        list.add(url);
        mapRemoteRegister.put(interfaceName, list);
        saveFile();
    }
    public static List<URL> get(String interfaceName) {
        mapRemoteRegister = getFile();
        return mapRemoteRegister.get(interfaceName);
    }

    public static void saveFile(){
        try{
            FileOutputStream fos = new FileOutputStream("/temp.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(mapRemoteRegister);
            oos.close();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Map<String,List<URL>> getFile(){
        try{
            FileInputStream fis = new FileInputStream("/temp.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Map<String,List<URL>> map = (Map<String,List<URL>>)ois.readObject();
            ois.close();
            return map;
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

因为我们的URL也要存入文件,所以也要序列化

public class URL implements Serializable {

再次测试,成功输出

Hello, world

在实际的分布式系统中,通常会使用专门的分布式服务注册中心(例如ZooKeeper、Consul等)来管理服务的注册和发现。这样可以确保注册信息的一致性、可靠性和可扩展性。

4.6 BootStrap

我们注册服务和注册注册中心的操作可以作为一个方法一起使用

创建com.sjb.bootstrap.BootStrap

public class BootStrap {

    public static void bindAndStart(Class interfaceClass, Class implClass, String hostname, Integer port) {
        //注册服务
        LocalRegister.register(interfaceClass.getName(), implClass);
        //注册中心注册
        URL url = new URL("localhost", 8080);
        MapRemoteRegister.register(interfaceClass.getName(), url);

        //netty、tomcat
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostname(), url.getPort());
    }
}

product调用的时候

public class Productor {
    public static void main(String[] args) {
        BootStrap.bindAndStart(HelloService.class, HelloServiceImpl.class, "localhost", 8080);

    }
}

5 服务重试

可以设置默认的重试次数,直到全部失败

在com.sjb.proxy.ProxyFactory#getProxy中

//服务发现
List<URL> urls = MapRemoteRegister.get(interfaceClass.getName());
//负载均衡
URL url = LoadBalance.random(urls);
//服务调用
String result =null;
int defaltRetry = 3;
for (int i = 0; i < defaltRetry; i++) {
    try {
        result = httpClient.send(url.getHostname(), url.getPort(), invocation);
        if (result != null) {
            break;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
return result;

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

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

相关文章

Elasticsearch解决字段膨胀问题

文章目录 背景Flattened类型的产生Flattened类型的定义基于Flattened类型插入数据更新Flattened字段并添加数据Flattened类型检索 Flattened类型的不足 背景 Elasticsearch映射如果不进行特殊设置&#xff0c;则默认为dynamic:true。dynamic:true实际上支持不加约束地动态添加…

【AI大模型】自动生成红队攻击提示--GPTFUZZER

本篇参考论文为&#xff1a; Yu J, Lin X, Xing X. Gptfuzzer: Red teaming large language models with auto-generated jailbreak prompts[J]. arXiv preprint arXiv:2309.10253, 2023. https://arxiv.org/pdf/2309.10253 一 背景 虽然LLM在今天的各个领域得到了广泛的运用…

AI办公自动化-用kimi自动清理删除重复文件

在kimichat中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个编写Python脚本的任务&#xff0c;具体步骤如下&#xff1a; 1、打开文件夹D:\downloads&#xff1b; 2、哈希值比较比较里面所有的文件&#xff0c;如果文件相同&#xff0c;那么移动多余…

3D Gaussian Splatting for Real-Time Radiance Field Rendering 论文阅读

如此热门的项目&#xff0c;网络上有很多大牛分析了这篇文章的做法&#xff0c;在这里简单记录一下个人粗浅的理解。 关于各种数学表达式的推导&#xff0c;论文和参考资料中都提供了较为详细的解读&#xff0c;本人能力有限&#xff0c;这一部分理解不够深刻&#xff0c;先不做…

绝地求生:艾伦格回归活动来了,持续近1个月,新版本皮肤、G币等奖励白嫖

嗨&#xff0c;我是闲游盒~ 29.2版本更新在即&#xff0c;新活动来啦&#xff01;目前这个活动国内官方还没发&#xff0c;我就去台湾官方搬来了中文版方便大家观看&#xff0c;也分析一下这些奖励应该怎样才能获得。 新版本将在周二进行约9小时的停机维护&#xff0c;请注意安…

centos7中如何优雅的动态切换jdk版本?

在 CentOS 7 中动态切换 JDK 版本可以通过多种方法实现&#xff0c;其中最常见的方法是使用 alternatives 命令&#xff0c;这是 CentOS 和其他基于 Red Hat 的系统中用于管理多个软件版本的标准工具。下面我会详细介绍如何使用 alternatives 命令来切换 JDK 版本。 步骤 1: 安…

如何通过 AWS Managed Apache Flink 实现 Iceberg 的实时同步

AWS Managed Apache Flink &#xff08;以下以 MAF 代指&#xff09;是 AWS 提供的一款 Serverless 的 Flink 服务。 1. 问题 大家在使用 MAF 的时候&#xff0c;可能遇到最大的一个问题就是 MAF 的依赖管理&#xff0c;很多时候在 Flink 上运行的代码&#xff0c;托管到 MAF…

[Algorithm][回溯][找出所有子集的异或总和再求和][全排列 II][电话号码的字母组合][括号生成]详细讲解

目录 1.找出所有子集的异或总和再求和1.题目链接2.算法原理详解3.代码实现 2.全排列 II1.题目链接2.算法原理详解3.代码实现 3.电话号码的字母组合1.题目链接2.算法原理详解3.代码实现 4.括号生成1.题目链接2.算法原理详解3.代码实现 1.找出所有子集的异或总和再求和 1.题目链…

PCIE协议-2-事务层规范-TLP Prefix Rules

2.2.10 TLP前缀规则 以下规则适用于任何包含TLP前缀的TLP&#xff1a; 对于任何TLP&#xff0c;TLP中byte0的Fmt[2:0]字段中的值100b表示存在TLP前缀&#xff0c;并且Type[4]位指示TLP前缀的类型。 Type[4]位中的值0b表示存在本地TLP前缀。Type[4]位中的值1b表示存在端到端TL…

数据结构与算法-排序算法1-冒泡排序

本文先介绍排序算法&#xff0c;然后具体写冒泡排序。 目录 1.排序算法简介 2.常见的排序算法分类如下图&#xff1a; 3.冒泡排序&#xff1a; 1.介绍&#xff1a; 2.动态图解 3.举例 4.小结冒泡排序规则 5.冒泡排序代码 6.优化 7.优化后时间 代码&#xff1a; 运…

Java | Leetcode Java题解之第88题合并两个有序数组

题目&#xff1a; 题解&#xff1a; class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {int p1 m - 1, p2 n - 1;int tail m n - 1;int cur;while (p1 > 0 || p2 > 0) {if (p1 -1) {cur nums2[p2--];} else if (p2 -1) {cur nums1[p…

Vue的学习 —— <vue指令>

目录 前言 正文 内容渲染指令 内容渲染指令的使用方法 v-text v-html 属性绑定指令 双向数据绑定指令 事件绑定指令 条件渲染指令 循环列表渲染指令 侦听器 前言 在完成Vue开发环境的搭建后&#xff0c;若想将Vue应用于实际项目&#xff0c;首要任务是学习Vue的基…

黑马基于Web-socket的java聊天室基本解析

要是用Web-socket协议&#xff0c;我们要前端upgrade升级成web-socket协议 首先我们要引入springboot的websocket起步依赖&#xff0c;这样子方便使用&#xff0c;自己指定版本注意 <dependency><groupId>org.springframework.boot</groupId><artifactId&…

绘唐3启动器怎么启动一键追爆款3正式版

绘唐3启动器怎么启动一键追爆款3正式版 工具入口 一.文案助手&#xff1a; 【注意&#xff01;&#xff01;】如果图片无显示&#xff0c;一般情况下被杀毒拦截&#xff0c;需关闭杀毒软件或者信任文件路径。 win10设置排除文件&#xff1a; 1.【新建工程】使用前先新建工程…

std::ref和std::cref的使用和原理分析

目录 1.用法 2.std::reference_wrapper介绍 3.std::ref原理分析 4.std::cref原理分析 5.总结 1.用法 它的定义如下&#xff1a; std::ref&#xff1a;用于包装按引用传递的值。 std::cref&#xff1a;用户包装按const引用传递的值。 C本身就有引用&#xff08;&&#…

使用 Python 中的 TensorFlow 检测垃圾短信

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

【鸿蒙开发】第二十四章 IPC与RPC进程间通讯服务

1 IPC与RPC通信概述 IPC&#xff08;Inter-Process Communication&#xff09;与RPC&#xff08;Remote Procedure Call&#xff09;用于实现跨进程通信&#xff0c;不同的是前者使用Binder驱动&#xff0c;用于设备内的跨进程通信&#xff0c;后者使用软总线驱动&#xff0c;…

一个基于servlet的MVC项目-登录验证

一、MVC的概念 MVC是Model、View、Controller的缩写&#xff0c;分别代表 Web 应用程序中的3种职责1 模型:用于存储数据以及处理用户请求的业务逻辑。 2视图:向控制器提交数据&#xff0c;显示模型中的数据。 3控制器:根据视图提出的请求&#xff0c;判断将请求和数据交给哪个…

Linux下网络命令

目录 需求1-查看本机是否存在22端口解法1解法2解法3 需求2-查看其他主机是否存在22端口解法1解法2解法3 需求3-查看TCP连接解法1/2 需求4-统计80端口tcp连接次数解法 需求5-查看总体网络速度解法 需求6-查看进程流量解法 需求7-dns解法 需求8-traceroute到baidu解法 需求9-查看…

git仓库使用

git仓库是会限制空间大小限制的 git网络库的容量限制_github仓库大小限制-CSDN博客 git是用于管理github的工具 电脑左下角搜索git打开GitBash.exe 进入到要下载到本地的目录 下载到本地的文件不要更改&#xff01; 如果要使用请务必把文件复制到别的空间去再在这个别的空间…