【Netty4核心原理④】【简单实现 Tomcat 和 RPC框架功能】

news2025/4/17 18:09:13

文章目录

  • 一、前言
  • 二、 基于 Netty 实现 Tomcat
    • 1. 基于传统 IO 重构 Tomcat
      • 1.1 创建 MyRequest 和 MyReponse 对象
      • 1.2 构建一个基础的 Servlet
      • 1.3 创建用户业务代码
      • 1.4 完成web.properties 配置
      • 1.5 创建 Tomcat 启动类
    • 2. 基于 Netty 重构 Tomcat
      • 2.1 创建 NettyRequest和 NettyResponse 对象
      • 2.2 构建一个基础的 Servlet
      • 2.3 创建业务底代码
      • 2.4 完成web.properties 配置
      • 2.5 创建业务逻辑处理类
      • 2.6 创建 Tomcat 启动类
  • 三、基于 Netty 重构 RPC 框架
    • 1. API 模块
      • 1.1 定义 RPC API 接口
      • 1.2 自定义传输协议
    • 2. Provider 模块
      • 2.1 实现 HelloService
      • 2.2 自定义 Netty 消息处理器
      • 2.3 服务端启动
    • 3. Consumer 模块
      • 3.1 自定义 Netty 消息处理器
      • 3.2 实现消费者端的代理调用
      • 3.3 消费者调用
  • 四、参考内容

一、前言

本系列内容为阅读《Netty4 核心原理》一书内容总结,内容存在个人删改,仅做个人笔记使用。

本篇涉及内容 :第四章 基于 Netty 手写 Tomcat第五章 基于 Netty 重构 RPC 框架


本系列内容基于 Netty 4.1.73.Final 版本,如下:

 <dependency>
    <groupId>io.netty</groupId>
     <artifactId>netty-all</artifactId>
     <version>4.1.73.Final</version>
 </dependency>

系列文章目录:
TODO


二、 基于 Netty 实现 Tomcat

Netty 作为底层通信框架,也可以用于实现 Web 容器。

Tomcat 时基于 J2EE规范的 Web 容器,主要入口是 web.xml 文件。web.xml 文件中主要配置 Servlet、Filter、Listener 等,而 Servlet、Filter、Listener 在 J2EE 中只是抽象的实现,具体业务逻辑由开发者实现。

下面用传统 IO 和 Netty 的方式分别简单实现 Tomcat 的功能



至此为止准备工作就已经就绪,下面我们按照传统 IO 和 Netty 的方式分别实现 Tomcat 的功能。

1. 基于传统 IO 重构 Tomcat

1.1 创建 MyRequest 和 MyReponse 对象

@Slf4j
@Getter
public class MyRequest {
    private String uri;
    private String method;

    public MyRequest(InputStream inputStream) throws IOException {
        BufferedReader bufferedReader =
                new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String line = bufferedReader.readLine();
        if (StringUtils.isNotBlank(line)) {
            String[] split = line.split("\\s");
            this.method = split[0];
            this.uri = split[1].split("\\?")[0];
        }
    }
}

public class MyResponse {
    private OutputStream outputStream;

    public MyResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public void write(String content) throws IOException {
        //按照HTTP响应报文的格式写入
        String httpResponse = "HTTP/1.1 200 OK\n" +
                "Content-Type:text/html\n" +
                "\r\n" +
                content;
        outputStream.write(httpResponse.getBytes());
    }
}

1.2 构建一个基础的 Servlet

public abstract class MyServlet {

    public void service(MyRequest request, MyResponse response) throws Exception {
       if(request.getMethod().equals("GET")){
            doGet(request, response);
        }else if(request.getMethod().equals("POST")){
            doPost(request, response);
        }
    }

    public abstract void doGet(MyRequest request, MyResponse response) throws Exception;

    public abstract void doPost(MyRequest request, MyResponse response) throws Exception;
}

1.3 创建用户业务代码

public class FirstServlet extends MyServlet {
    @Override
    public void doGet(MyRequest request, MyResponse response) throws Exception {
        this.doPost(request, response);
    }

    @Override
    public void doPost(MyRequest request, MyResponse response) throws Exception {
        response.write("FirstServlet");
    }
}

public class SecondServlet extends MyServlet {

    @Override
    public void doGet(MyRequest request, MyResponse response) throws Exception {
        this.doPost(request, response);
    }

    @Override
    public void doPost(MyRequest request, MyResponse response) throws Exception {
        response.write("SecondServlet");
    }
}

1.4 完成web.properties 配置

这里为了简化操作,使用 web.properties 来替代 web.xml 文件,如下:

servlet.one.url=/firstServlet.do
servlet.one.className=com.kingfish.netty.unit4.tomcat.FirstServlet

servlet.two.url=/secondServlet.do
servlet.two.className=com.kingfish.netty.unit4.tomcat.SecondServlet

1.5 创建 Tomcat 启动类

@Slf4j
public class MyTomcat {
    private int port = 8080;
    private ServerSocket server;
    private Map<String, MyServlet> servletMap = Maps.newHashMap();

    private Properties webxml = new Properties();

    @SneakyThrows
    private void init() {
        String webInf = Objects.requireNonNull(this.getClass().getResource("/")).getPath();
        webxml.load(this.getClass().getResourceAsStream("/web.properties"));
        for (Object k : webxml.keySet()) {
            final String key = k.toString();
            if (key.endsWith(".url")) {
                String serverName = key.replaceAll("\\.url", "");
                String url = webxml.getProperty(key);
                String className = webxml.getProperty(serverName + ".className");
                MyServlet servlet = (MyServlet) Class.forName(className).newInstance();
                servletMap.put(url, servlet);
            }
        }
    }

    @SneakyThrows
    public void start() {
        // 1. 初始化。加载配置,初始化 servletMap
        init();
        // 初始化服务
        server = new ServerSocket(port);
        System.out.println("服务启动成功, 端口 : " + port);
        // 等待客户端连接
        while (!Thread.interrupted()) {
            // TODO : 实际要改为多线程
            process(server.accept());
        }
    }

    /**
     * 请求处理
     *
     * @param client
     * @throws Exception
     */
    private void process(Socket client) throws Exception {
        try (InputStream inputStream = client.getInputStream();
             OutputStream outputStream = client.getOutputStream();) {
            MyRequest request = new MyRequest(inputStream);
            MyResponse response = new MyResponse(outputStream);
            String uri = request.getUri();

            if (servletMap.containsKey(uri)) {
                servletMap.get(uri).service(request, response);
            } else {
                response.write("404 - Not Found");
            }
            outputStream.flush();
        } catch (Exception e) {
            log.error("[请求异常]", e);
        } finally {
            client.close();
        }
    }

    public static void main(String[] args) {
        new MyTomcat().start();
    }
}

通过请求 http://localhost:8080/firstServlet.dohttp://localhost:8080/sencondServlet.do 可以得到相应结果。如下:

在这里插入图片描述

2. 基于 Netty 重构 Tomcat

2.1 创建 NettyRequest和 NettyResponse 对象

public class NettyRequest {
    private ChannelHandlerContext ctx;

    private HttpRequest request;

    public NettyRequest(ChannelHandlerContext ctx, HttpRequest request) {
        this.ctx = ctx;
        this.request = request;
    }

    public String getUri() {
        return request.uri();
    }

    public String getMethod() {
        return request.method().name();
    }

    public Map<String, List<String>> getParameters() {
        QueryStringDecoder decoder = new QueryStringDecoder(getUri());
        return decoder.parameters();
    }

    public String getParameter(String name) {
        final List<String> params = getParameters().get(name);
        return params == null ? null : params.get(0);
    }
}

public class NettyResponse {
    private ChannelHandlerContext ctx;

    private HttpRequest request;

    public NettyResponse(ChannelHandlerContext ctx, HttpRequest request) {
        this.ctx = ctx;
        this.request = request;
    }

    public void write(String out) {
        try {
            if (out == null || out.length() == 0){
                return;
            }

            // 设置 HTTP 以及请求头信息
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(
                    // 设置版本为 HTTP 1.1
                    HttpVersion.HTTP_1_1,
                    // 设置响应状态码 200
                    HttpResponseStatus.OK,
                    // 设置输出内容编码格式 UTF-8
                    Unpooled.wrappedBuffer(out.getBytes(StandardCharsets.UTF_8)));
            response.headers().set("Content-Type", "text/html;");
            ctx.write(response);
        } finally {
            ctx.flush();
            ctx.close();
        }
    }
}

2.2 构建一个基础的 Servlet

public abstract class NettyServlet {

    public void service(NettyRequest request, NettyResponse response) throws Exception {
       if(request.getMethod().equals("GET")){
            doGet(request, response);
        }else if(request.getMethod().equals("POST")){
            doPost(request, response);
        }
    }

    public abstract void doGet(NettyRequest request, NettyResponse response) throws Exception;

    public abstract void doPost(NettyRequest request, NettyResponse response) throws Exception;

}

2.3 创建业务底代码

public class FirstServlet extends NettyServlet {

    @Override
    public void doGet(NettyRequest request, NettyResponse response) throws Exception {
        doPost(request, response);
    }

    @Override
    public void doPost(NettyRequest request, NettyResponse response) throws Exception {
        response.write("FirstServlet");
    }
}

2.4 完成web.properties 配置

这里为了简化操作,使用 web.properties 来替代 web.xml 文件,如下:

servlet.one.url=/firstServlet.do
servlet.one.className=com.kingfish.netty.unit4.netty.FirstServlet

2.5 创建业务逻辑处理类

public class TomcatHandler extends ChannelInboundHandlerAdapter {

    private Map<String, NettyServlet> servletMap;

    public TomcatHandler(Map<String, NettyServlet> servletMap) {
        this.servletMap = servletMap;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof HttpRequest) {
            HttpRequest httpRequest = (HttpRequest) msg;
            NettyRequest request = new NettyRequest(ctx, httpRequest);
            NettyResponse response = new NettyResponse(ctx, httpRequest);

            String uri = request.getUri();
            if (servletMap.containsKey(uri)) {
                servletMap.get(uri).service(request, response);
            } else {
                response.write("404 - Not Found");
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

2.6 创建 Tomcat 启动类

@Slf4j
public class NettyTomcat {
    private int port = 8080;
    private Map<String, NettyServlet> servletMap = Maps.newHashMap();

    private Properties webxml = new Properties();

    @SneakyThrows
    private void init() {
        webxml.load(this.getClass().getResourceAsStream("/web.properties"));
        for (Object k : webxml.keySet()) {
            final String key = k.toString();
            if (key.endsWith(".url")) {
                String serverName = key.replaceAll("\\.url", "");
                String url = webxml.getProperty(key);
                String className = webxml.getProperty(serverName + ".className");
                NettyServlet servlet = (NettyServlet) Class.forName(className).newInstance();
                servletMap.put(url, servlet);
            }
        }
    }

    @SneakyThrows
    public void start() {
        // 1. 初始化。加载配置,初始化 servletMap
        init();
        // 2. 创建 boss 和 worker 线程
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    // 主线程处理类
                    .channel(NioServerSocketChannel.class)
                    // 子线程处理类
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        // 客户端初始化
                        @Override
                        protected void initChannel(SocketChannel client) throws Exception {
                            // Netty 对 Http 的封装,对顺序有要求
                            // HttpResponseEncoder 解码器
                            client.pipeline().addLast(new HttpResponseEncoder());
                            // HttpRequestDecoder 编码器
                            client.pipeline().addLast(new HttpRequestDecoder());
                            // 业务逻辑处理
                            client.pipeline().addLast(new TomcatHandler(servletMap));
                        }
                    })
                    // 针对主线程配置 : 分配线程数量最大 128
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 针对子线程配置 保持长连接
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 启动服务
            ChannelFuture channelFuture = bootstrap.bind(port).sync();
            System.out.println("服务启动成功, 端口 : " + port);
            // 阻塞主线程,防止直接执行 finally 中语句导致服务关闭,当有关闭事件到来时才会放行
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 关闭线程池
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

    public static void main(String[] args) {
        new NettyTomcat().start();
    }

通过请求 http://localhost:8080/firstServlet.do 可以得到相应结果。如下:

在这里插入图片描述


三、基于 Netty 重构 RPC 框架

Netty 基本上是作为架构的底层存在,主要是完成高性能的网络通信。下面通过 Netty 简单实现 RPC框架的通信功能,整个项目结构如下图:
在这里插入图片描述

1. API 模块

1.1 定义 RPC API 接口

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

1.2 自定义传输协议

@Data
public class InvokerProtocol implements Serializable {

    /**
     * 类名
     */
    private String className;
    /**
     * 方法名
     */
    private String methodName;

    /**
     * 参数列表
     */
    private Class<?>[] params;

    /**
     * 参数列表
     */
    private Object[] values;
}

2. Provider 模块

2.1 实现 HelloService

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

2.2 自定义 Netty 消息处理器

public class RegistryHandler extends ChannelInboundHandlerAdapter {

    private static Map<String, Object> registerMap = Maps.newConcurrentMap();

    private List<String> classNames = Lists.newArrayList();

    public RegistryHandler() {
        // 扫描指定目录下的提供者实例
        scannerClass("provider.provider");
        // 将提供者注册
        doRegister();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        InvokerProtocol invokerProtocol = (InvokerProtocol) msg;
        Object result = new Object();
        // 通过协议参数获取到具体提提供者,并通过反射调用
        if (registerMap.containsKey(invokerProtocol.getClassName())) {
            Object clazz = registerMap.get(invokerProtocol.getClassName());
            Method method = clazz.getClass().getMethod(invokerProtocol.getMethodName(), invokerProtocol.getParams());
            result = method.invoke(clazz, invokerProtocol.getValues());
        }

        ctx.write(result);
        ctx.flush();
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 递归扫描
     *
     * @param packageName
     */
    private void scannerClass(String packageName) {
        URL url = this.getClass().getClassLoader().getResource(packageName.replaceAll("\\.", "/"));
        List<File> files = FileUtil.loopFiles(url.getFile(), pathname -> pathname.getName().endsWith(".class"));
        for (File file : files) {
            classNames.add(packageName + "." + file.getName().replace(".class", ""));
        }
    }

    @SneakyThrows
    private void doRegister() {
        for (String className : classNames) {
            Class<?> clazz = Class.forName(className);
            Class<?> i = clazz.getInterfaces()[0];
            registerMap.put(i.getName(), clazz.getDeclaredConstructor().newInstance());
        }
    }
}

2.3 服务端启动

public class RpcRegister {
    private int port = 8090;

    @SneakyThrows
    public void start() {
        // 2. 创建 boss 和 worker 线程
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    // 主线程处理类
                    .channel(NioServerSocketChannel.class)
                    // 子线程处理类
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        // 客户端初始化
                        @Override
                        protected void initChannel(SocketChannel client) throws Exception {
                            // 自定义协议解码器
                            // LengthFieldBasedFrameDecoder 五个入参分别如下:
                            // maxFrameLength : 框架的最大长度。如果帧长度大于此值,将抛出 TooLongFrameException
                            // lengthFieldOffset : 长度属性的偏移量。即对应的长度属性在整个消息数据中的位置
                            // lengthFieldLength : 长度属性的长度。如果长度属性是 int,那么这个值就是 4 (long 类型就是 8)
                            // lengthAdjustment : 要添加到长度属性值的补偿值
                            // initialBytesToStrip : 从解码帧中取出的第一个字节数。
                            client.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            // 自定义协议编码器
                            client.pipeline().addLast(new LengthFieldPrepender(4));
                            // 对象参数类型编码器
                            client.pipeline().addLast("encoder", new ObjectEncoder());
                            // 对象参数类型解码器
                            client.pipeline().addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            client.pipeline().addLast(new RegistryHandler());
                        }
                    })
                    // 针对主线程配置 : 分配线程数量最大 128
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 针对子线程配置 保持长连接
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 启动服务
            ChannelFuture channelFuture = bootstrap.bind(port).sync();
            System.out.println("服务启动成功, 端口 : " + port);
            // 阻塞主线程,防止直接执行 finally 中语句导致服务关闭,当有关闭事件到来时才会放行
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 关闭线程池
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new RpcRegister().start();
    }
}

3. Consumer 模块

3.1 自定义 Netty 消息处理器

public class RpcProxyHandler extends ChannelInboundHandlerAdapter {

    @Getter
    private Object response;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        response = msg;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
}

3.2 实现消费者端的代理调用

public class RpcProxy {
    /**
     * 创建代理对象
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T create(Class<?> clazz) {
        Class<?>[] interfaces = clazz.isInterface() ? new Class<?>[]{clazz} : clazz.getInterfaces();
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, new MethodProxy(clazz));
    }


    /**
     * 方法代理
     */
    public static class MethodProxy implements InvocationHandler {
        private Class<?> clazz;

        public MethodProxy(Class<?> clazz) {
            this.clazz = clazz;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 传进来的一个已实现的具体类,本次实现暂不处理该逻辑
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                // 如果传进来的是一个接口,则说明要进行 RPC 调用
                return rpcInvoke(proxy, method, args);
            }
        }

        /**
         * rpc 调用
         *
         * @param proxy
         * @param method
         * @param args
         * @return
         * @throws InterruptedException
         */
        private Object rpcInvoke(Object proxy, Method method, Object[] args) throws InterruptedException {
            NioEventLoopGroup group = new NioEventLoopGroup();
            RpcProxyHandler rpcProxyHandler = new RpcProxyHandler();

            try {
                // 传输协议封装
                InvokerProtocol invokerProtocol = new InvokerProtocol();
                invokerProtocol.setClassName(this.clazz.getName());
                invokerProtocol.setMethodName(method.getName());
                invokerProtocol.setValues(args);
                invokerProtocol.setParams(method.getParameterTypes());

                // 通过 netty 连接 服务提供者
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<>() {
                            @Override
                            protected void initChannel(Channel channel) throws Exception {
                                // 自定义协议解码器
                                // LengthFieldBasedFrameDecoder 五个入参分别如下:
                                // maxFrameLength : 框架的最大长度。如果帧长度大于此值,将抛出 TooLongFrameException
                                // lengthFieldOffset : 长度属性的偏移量。即对应的长度属性在整个消息数据中的位置
                                // lengthFieldLength : 长度属性的长度。如果长度属性是 int,那么这个值就是 4 (long 类型就是 8)
                                // lengthAdjustment : 要添加到长度属性值的补偿值
                                // initialBytesToStrip : 从解码帧中取出的第一个字节数。
                                channel.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                                // 自定义协议编码器
                                channel.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4));
                                // 对象参数类型编码器
                                channel.pipeline().addLast("encoder", new ObjectEncoder());
                                // 对象参数类型解码器
                                channel.pipeline().addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                                // 业务处理器
                                channel.pipeline().addLast("handler", rpcProxyHandler);
                            }
                        });
                // 连接提供者服务并发送消息
                ChannelFuture future = bootstrap.connect(new InetSocketAddress("127.0.0.1", 8090)).sync();
                future.channel().writeAndFlush(invokerProtocol);
                // 阻塞主线程,防止直接执行 finally 中语句导致服务关闭,当有关闭事件到来时才会放行
                future.channel().closeFuture().sync();
            } finally {
                group.shutdownGracefully();
            }

            return rpcProxyHandler.getResponse();
        }


    }
}

3.3 消费者调用

public class RpcMain {
    public static void main(String[] args) {
        HelloService helloService = RpcProxy.create(HelloService.class);
        String hello = helloService.sayHello("张三");
        // 输出 hello = 张三, Hello!
        System.out.println("hello = " + hello);
    }
}

四、参考内容

  1. https://www.cnblogs.com/kendoziyu/articles/why-selector-always-invoke-new-event.html
  2. https://blog.csdn.net/wyaoyao93/article/details/114938670
  3. https://blog.csdn.net/woaiwojialanxi/article/details/123602000

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

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

相关文章

【AI学习】初步了解TRL

TRL&#xff08;Transformer Reinforcement Learning&#xff09; 是由 Hugging Face 开发的一套基于强化学习&#xff08;Reinforcement Learning, RL&#xff09;的训练工具&#xff0c;专门用于优化和微调大规模语言模型&#xff08;如 GPT、LLaMA 等&#xff09;。它结合了…

阿里云oss视频苹果端无法播放问题记录

记录一下苹果端视频不可以播放的原因. 看了一下其他视频可以正常播放,但是今天客户发来的视频无法正常播放.咨询过阿里云售后给出的原因是编码格式过高. 需要调整编码格式为:baseline, 下面记录如何使用ffmpeg修改视频的编码格式. 下载文件(可从官方下载) 配置环境变量(系统变…

Ubuntu离线安装mysql

在 Ubuntu 24.04 上离线安装 MySQL 的步骤如下(支持 MySQL 8.0 或 8.4): 一.安装方法 此次安装是按照方法一安装,其它方法供参考: 安装成功截图: 安全配置截图: sudo mysql_secure_installation 登录测试: 方法一:使用 apt-rdepends 下载依赖包(推荐) 1. 在联网…

IntelliJ IDEA下开发FPGA——FPGA开发体验提升__上

前言 由于Quartus写代码比较费劲&#xff0c;虽然新版已经有了代码补全&#xff0c;但体验上还有所欠缺。于是使用VS Code开发&#xff0c;效果如下所示&#xff0c;代码样式和基本的代码补全已经可以满足开发&#xff0c;其余工作则交由Quartus完成 但VS Code的自带的git功能&…

SpringBoot底层-数据源自动配置类

SpringBoot默认使用Hikari连接池&#xff0c;当我们想要切换成Druid连接池&#xff0c;底层原理是怎样呢 SpringBoot默认连接池——Hikari 在spring-boot-autoconfiguration包内有一个DataSourceConfiguraion配置类 abstract class DataSourceConfiguration {Configuration(p…

数字内容个性化推荐引擎构建

实时数据驱动推荐优化 现代数字内容体验的核心竞争力在于系统对用户需求的即时捕捉与响应。通过实时数据流处理技术&#xff0c;推荐引擎能够同步采集用户点击、停留时长、交互轨迹等多维度行为数据&#xff0c;并借助分布式计算框架在毫秒级完成特征提取与模式识别。例如&…

【工具】Redis管理工具推荐

【运维】Redis管理工具推荐 Another Redis Desktop Manager &#x1f680;&#x1f680;&#x1f680; 更快、更好、更稳定的Redis桌面(GUI)管理客户端&#xff0c;兼容Windows、Mac、Linux&#xff0c;性能出众&#xff0c;轻松加载海量键值 AnotherRedisDesktopManager 发行版…

【高校主办】2025年第四届信息与通信工程国际会议(JCICE 2025)

重要信息 会议网址&#xff1a;www.jcice.org 会议时间&#xff1a;2025年7月25-27日 召开地点&#xff1a;哈尔滨 截稿时间&#xff1a;2025年6月15日 录用通知&#xff1a;投稿后2周内 收录检索&#xff1a;EI,Scopus 会议简介 JCICE 2022、JCICE 2023、JCICE 2…

【区块链安全 | 第三十一篇】合约(五)

文章目录 合约库库中的函数签名和选择器库的调用保护合约 库 库与合约类似,但它们的目的是仅在特定地址上部署一次,并通过 EVM 的 DELEGATECALL(在 Homestead 之前是 CALLCODE)功能重复使用其代码。这意味着如果调用库函数,它们的代码将在调用合约的上下文中执行,即 th…

系统与网络安全------Windows系统安全(8)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 DNS DNS概述 为什么需要DNS系统 www.baidu.com与119.75.217.56&#xff0c;哪个更好记&#xff1f; 互联网中的114查号台/导航员 DNS&#xff08;Domian Name System&#xff0c;域名系统&#xff09;的功…

ROS云课三分钟-差动移动机器人巡逻报告如何撰写-中等报告

评语&#xff1a; 成绩中等&#xff08;70/100&#xff09;&#xff0c;具体如下&#xff1a; 1. 摘要部分 问题描述&#xff1a; 内容空洞&#xff1a;摘要过于简短&#xff0c;仅简要概述了研究内容和实现方法&#xff0c;未突出研究的创新点或重要性。缺乏细节&#xff1…

Java8+Spring Boot + Vue + Langchain4j 实现阿里云百炼平台 AI 流式对话对接

1. 引言 在本文中&#xff0c;我们将介绍如何使用 Spring Boot、Vue.js 和 Langchain4j&#xff0c;实现与 阿里云百炼平台 的 AI 流式对话对接。通过结合这些技术&#xff0c;我们将创建一个能够实时互动的 AI 聊天应用。 这是一个基于 Spring Boot Vue.js Langchain4j 的智…

Dify接口api对接,流式接收流式返回(.net)

试了好多种方法除了Console.WriteLine()能打印出来&#xff0c;试了好些方法都不行&#xff0c;不是报错就是打印只有一行&#xff0c;要么就是接收完才返回...下面代码实现调用api接收流式数据&#xff0c;并进行流式返回给前端&#xff1a; using Furion.HttpRemote; using …

代码随想录算法训练营第五十二天|图论专题: 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿

101. 孤岛的总面积 本题要求找到不靠边的陆地面积&#xff0c;那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋&#xff0c;然后再去重新遍历地图 统计此时还剩下的陆地就可以了。 1、从左边和后边向中间遍历 2、从上边和下边向中间遍历…

仿modou库one thread one loop式并发服务器

源码&#xff1a;田某super/moduo 目录 SERVER模块&#xff1a; Buffer模块&#xff1a; Socket模块&#xff1a; Channel模块&#xff1a; Connection模块&#xff1a; Acceptor模块&#xff1a; TimerQueue模块&#xff1a; Poller模块&#xff1a; EventLoop模块&a…

SpringSecurity6.0 通过JWTtoken进行认证授权

之前写过一个文章&#xff0c;从SpringSecurity 5.x升级到6.0&#xff0c;当时是为了配合公司的大版本升级做的&#xff0c;里面的各项配置都是前人留下来的&#xff0c;其实没有花时间进行研究SpringSecurity的工作机制。现在新东家有一个简单的系统要搭建&#xff0c;用户的认…

【Java】Maven

一、概念 是一个项目管理和构建工具&#xff0c;它基于项目对象模型&#xff08;POM&#xff09;的概念&#xff0c;通过一小段描述信息来管理项目的构建。 二、Maven坐标 <groupId>com.itheima</groupId><artifactId>maven-project01</artifactId>&…

MATLAB中plot函数的详细参数表

LineSpec - 线型、标记和颜色 线型说明-实线--虚线:点线-.点划线 标记说明o圆圈加号*星号.点x叉号_水平线条|垂直线条s方形d菱形^上三角v下三角>右三角<左三角p五角形h六角形 颜色说明 y 黄色 m 品红色 c 青蓝色 r 红色 g 绿色 b 蓝色 w 白色 k 黑色 MarkerFaceColor…

R语言赋能气象水文科研:从多维数据处理到学术级可视化

全球气候变化加剧了极端天气与水文事件的复杂性&#xff0c;气象卫星、雷达、地面观测站及水文传感器每天产生TB级‌时空异质数据‌。传统研究常面临四大瓶颈&#xff1a; ‌数据清洗低效‌&#xff1a;缺失值、异常值处理耗时&#xff1b;‌时空分析模型构建复杂‌&#xff1…

BGP路由协议之属性2

Orgin 起源 公认必遵属性 起源名称标记描述IGPi如果路由是由始发的 BGP 路由器使用 network 命令注入到 BGP 的&#xff0c;那么该 BGP 路由的 origin 属性为 IGPEGPe如果路由是通过 EGP 学习到的&#xff0c;那么该 BGP 路由的 Origin 属性为 EGPIncomplete?如果路由是通过…