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

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

文章目录

  • 一、前言
  • 二、 基于 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 {@Overridepublic void doGet(MyRequest request, MyResponse response) throws Exception {this.doPost(request, response);}@Overridepublic void doPost(MyRequest request, MyResponse response) throws Exception {response.write("FirstServlet");}
}public class SecondServlet extends MyServlet {@Overridepublic void doGet(MyRequest request, MyResponse response) throws Exception {this.doPost(request, response);}@Overridepublic 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.FirstServletservlet.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();@SneakyThrowsprivate 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);}}}@SneakyThrowspublic void start() {// 1. 初始化。加载配置,初始化 servletMapinit();// 初始化服务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.1HttpVersion.HTTP_1_1,// 设置响应状态码 200HttpResponseStatus.OK,// 设置输出内容编码格式 UTF-8Unpooled.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 {@Overridepublic void doGet(NettyRequest request, NettyResponse response) throws Exception {doPost(request, response);}@Overridepublic 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;}@Overridepublic 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");}}}@Overridepublic 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();@SneakyThrowsprivate 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);}}}@SneakyThrowspublic void start() {// 1. 初始化。加载配置,初始化 servletMapinit();// 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>() {// 客户端初始化@Overrideprotected 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 {@Overridepublic 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();}@Overridepublic 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();}@Overridepublic 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", ""));}}@SneakyThrowsprivate 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;@SneakyThrowspublic 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>() {// 客户端初始化@Overrideprotected 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 {@Getterprivate Object response;@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {response = msg;}@Overridepublic 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;}@Overridepublic 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<>() {@Overrideprotected 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

相关文章:

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

文章目录 一、前言二、 基于 Netty 实现 Tomcat1. 基于传统 IO 重构 Tomcat1.1 创建 MyRequest 和 MyReponse 对象1.2 构建一个基础的 Servlet1.3 创建用户业务代码1.4 完成web.properties 配置1.5 创建 Tomcat 启动类 2. 基于 Netty 重构 Tomcat2.1 创建 NettyRequest和 Netty…...

4.6学习总结

包装类 包装类&#xff1a;基本数据类型对应的引用数据类型 JDK5以后新增了自动装箱&#xff0c;自动拆箱 以后获取包装类方法&#xff0c;不需要new&#xff0c;直接调用方法&#xff0c;直接赋值即可 //1.把整数转成二进制&#xff0c;十六进制 String str1 Integer.toBin…...

MySQL学习笔记五

第七章数据过滤 7.1组合WHERE子句 7.1.1AND操作符 输入&#xff1a; SELECT first_name, last_name, salary FROM employees WHERE salary < 4800 AND department_id 60; 输出&#xff1a; 说明&#xff1a;MySQL允许使用多个WHERE子句&#xff0c;可以以AND子句或OR…...

成为社交场的导演而非演员

一、情绪的本质&#xff1a;社交信号而非自我牢笼 进化功能&#xff1a;情绪是人类进化出的原始社交工具。愤怒触发群体保护机制&#xff0c;悲伤唤起同情支持&#xff0c;喜悦巩固联盟关系。它们如同可见光谱&#xff0c;快速传递生存需求信号。双刃剑效应&#xff1a;情绪的…...

怎么使用vue3实现一个优雅的不定高虚拟列表

前言 很多同学将虚拟列表当做亮点写在简历上面&#xff0c;但是却不知道如何手写&#xff0c;那么这个就不是加分项而是减分项了。实际项目中更多的是不定高虚拟列表&#xff0c;这篇文章来教你不定高如何实现。 什么是不定高虚拟列表 不定高的意思很简单&#xff0c;就是不…...

LemonSqueezy: 1靶场渗透

LemonSqueezy: 1 来自 <LemonSqueezy: 1 ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182&#xff0c;靶场IP192.168.23.225 3&#xff0c;对靶机进…...

2025 年山东保安员职业资格考试要点梳理​

山东作为人口大省&#xff0c;保安市场规模庞大。2025 年考试报考条件常规。报名通过山东省各市公安机关指定的培训机构或政务服务窗口&#xff0c;提交资料与其他地区类似。​ 理论考试注重对山东地域文化特色相关安保知识的考查&#xff0c;如在孔庙等文化圣地安保中&#x…...

ARM处理器内核全解析:从Cortex到Neoverse的架构与区别

ARM处理器内核全解析&#xff1a;从Cortex到Neoverse的架构与区别 ARM作为全球领先的处理器架构设计公司&#xff0c;其内核产品线覆盖了从高性能计算到低功耗嵌入式应用的广泛领域。本文将全面解析ARM处理器的内核分类、架构特点、性能差异以及应用场景&#xff0c;帮助读者深…...

网络缓冲区

网络缓冲区分为内核缓冲区和用户态网络缓冲区 我们重点要实现用户态网络缓冲区 1.设计用户态网络缓冲区的原因 ①.生产者和消费者的速度不匹配问题&#xff0c; 需要缓存数据。 ②.粘包处理问题&#xff0c; 不能确保一次系统调用读取或写入完整数据包。 2.代码实现(cha…...

数据仓库的核心架构与关键技术(数据仓库系列二)

目录 一、引言 二、数据仓库的核心架构 三、数据仓库的关键技术 1 数据集成与治理 2 查询优化与性能提升 3 数据共享服务 BI&#xff1a;以Tableau为例 SQL2API&#xff1a;以麦聪QuickAPI为例 4 实时数据处理 四、技术的协同作用 五、总结与展望 六、预告 一、引言…...

基于PyQt5与OpenCV的图像处理系统设计与实现

1. 系统概述 本系统是一个集成了多种经典图像处理算法的图形用户界面(GUI)应用程序,采用Python语言开发,基于PyQt5框架构建用户界面,利用OpenCV库实现核心图像处理功能。 系统支持11种图像处理操作,每种操作都提供参数实时调节功能,并具备原始图像与处理后图像的双视图对…...

如何根据设计稿进行移动端适配:全面详解

如何根据设计稿进行移动端适配&#xff1a;全面详解 文章目录 如何根据设计稿进行移动端适配&#xff1a;全面详解1. **理解设计稿**1.1 设计稿的尺寸1.2 设计稿的单位 2. **移动端适配的核心技术**2.1 使用 viewport 元标签2.1.1 代码示例2.1.2 参数说明 2.2 使用相对单位2.2.…...

什么是大型语言模型(LLM)?哪个大模型更好用?

什么是 LLM&#xff1f; ChatGPT 是一种大型语言模型 (LLM)&#xff0c;您可能对此并不陌生。它以非凡的能力而闻名&#xff0c;已证明能够出色地完成各种任务&#xff0c;例如通过考试、生成产品内容、解决问题&#xff0c;甚至在最少的输入提示下编写程序。 他们的实力现已…...

集合学习内容总结

集合简介 1、Scala 的集合有三大类&#xff1a;序列 Seq、集Set、映射 Map&#xff0c;所有的集合都扩展自 Iterable 特质。 2、对于几乎所有的集合类&#xff0c;Scala 都同时提供了可变和不可变的版本&#xff0c;分别位于以下两个包 不可变集合&#xff1a;scala.collect…...

使用typedef和不使用的区别

使用 typedef 定义的函数指针类型 typedef sensor_drv_params_t* (*load_sensor_drv_func)(); 不使用 typedef 的函数指针声明 sensor_drv_params_t* (*load_sensor_drv_func)(); 这两者看似相似&#xff0c;但在语义和用途上有显著区别。下面将详细解释这两种声明的区别、各…...

基于线性回归模型的汽车燃油效率预测

基于线性回归模型的汽车燃油效率预测 1.作者介绍2.线性回归介绍2.1 线性回归简介2.2 线性回归应用场景 3.基于线性回归模型的汽车燃油效率预测实验3.1 Auto MPG Data Set数据集3.2代码调试3.3完整代码3.4结果展示 4.问题分析 基于线性回归模型的汽车燃油效率预测 1.作者介绍 郝…...

Playwright之自定义浏览器目录访问出错:BrowserType.launch: Executable doesn‘t exist

Playwright之自定义浏览器目录访问出错&#xff1a;BrowserType.launch: Executable doesn’t exist 问题描述&#xff1a; 在使用playwright进行浏览器自动化的时候&#xff0c;配置了自定义的浏览器目录&#xff0c;当按照自定义的浏览器目录启动浏览器进行操作时&#xff0c…...

如何拿到iframe中嵌入的游戏数据

在 iframe 中嵌入的游戏数据是否能被获取&#xff0c;取决于以下几个关键因素&#xff1a; 1. 同源策略 浏览器的同源策略是核心限制。如果父页面和 iframe 中的内容同源&#xff08;即协议、域名和端口号完全相同&#xff09;&#xff0c;那么可以直接通过 JavaScript 访问 …...

优选算法第七讲:分治

优选算法第七讲&#xff1a;分治 1.分治_快排1.1颜色分类1.2排序数组1.3数组中第k个最大元素1.4库存管理II 2.分治_归并2.1排序数组2.2交易逆序对的总数2.3计算右侧小于当前元素的个数2.4翻转对 1.分治_快排 1.1颜色分类 1.2排序数组 1.3数组中第k个最大元素 1.4库存管理II 2.…...

OpenBMC:BmcWeb 处理http请求4 处理路由对象

OpenBMC:BmcWeb 处理http请求2 查找路由对象-CSDN博客 Router::handle通过findRoute获取了FindRouteResponse对象foundRoute void handle(const std::shared_ptr<Request>& req,const std::shared_ptr<bmcweb::AsyncResp>& asyncResp){FindRouteResponse …...

直流电能表计量解决方案适用于光伏储能充电桩基站等场景

多场景解决方案&#xff0c;准确测量 01 市场规模与增长动力 全球直流表市场预测&#xff1a; 2025年市场规模14亿美元&#xff0c;CAGR超15%。 驱动因素&#xff1a;充电桩、光伏/储能、基站、直流配电 市场增长引擎分析&#xff1a; 充电桩随新能源车迅猛增长&#xff…...

x-cmd install | Slumber - 告别繁琐,拥抱高效的终端 HTTP 客户端

目录 核心优势&#xff0c;一览无遗安装应用场景&#xff0c;无限可能示例告别 GUI&#xff0c;拥抱终端 还在为调试 API 接口&#xff0c;发送 HTTP 请求而苦恼吗&#xff1f;还在各种 GUI 工具之间切换&#xff0c;只为了发送一个简单的请求吗&#xff1f;现在&#xff0c;有…...

git修改已经push的commit的message

1.修改信息 2.修改message 3.强推...

STM32 基础2

STM32中断响应过程 1、中断源发出中断请求。 2、判断处理器是否允许中断&#xff0c;以及该中断源是否被屏蔽。 3、中断优先级排队。 4、处理器暂停当前程序&#xff0c;保护断点地址和处理器的当前状态&#xff0c;根据中断类型号&#xff0c;查找中断向量表&#xff0c;转到…...

【STL 之速通pair vector list stack queue set map 】

考list 的比较少 --双端的啦 pair 想下&#xff0c;程序是什么样的. 我是我们要带着自己的思考去学习DevangLic.. #include <iostream> #include <utility> #include <string>using namespace std;int main() {// 第一部分&#xff1a;创建并输出两个 pair …...

深度学习篇---LSTM+Attention模型

文章目录 前言1. LSTM深入原理剖析1.1 LSTM 架构的进化理解遗忘门简介数学表达式实际作用 输入门简介数学表达式后选候选值实际作用 输出门简介数学表达式最终输出实际作用 1.2 Attention 机制的动态特性内容感知位置无关可解释性数学本质 1.3 LSTM与Attention的协同效应组合优…...

React 多个 HOC 嵌套太深,会带来哪些隐患?

在 React 中&#xff0c;使用多个 高阶组件&#xff08;HOC&#xff0c;Higher-Order Component&#xff09; 可能会导致组件层级变深&#xff0c;这可能会带来以下几个影响&#xff1a; 一、带来的影响 1、调试困难 由于组件被多个 HOC 包裹&#xff0c;React 开发者工具&am…...

企业工厂生产线马达保护装置 功能参数介绍

安科瑞刘鸿鹏 摘要 工业生产中&#xff0c;电压暂降&#xff08;晃电&#xff09;是导致电动机停机、生产中断的主要原因之一&#xff0c;给企业带来巨大的经济损失。本文以安科瑞晃电再起动控制器为例&#xff0c;探讨抗晃电保护器在生产型企业工厂中的应用&#xff0c;分析…...

Redis 的五种数据类型面试回答

这里简单介绍一下面试回答、我之前有详细的去学习、但是一直都觉得太多内容了、太深入了 然后面试的时候不知道从哪里讲起、于是我写了这篇CSDN帮助大家面试回答、具体的深入解析下次再说 面试官你好 我来介绍一下Redis的五种基本数据类型 有String List Set ZSet Map 五种基…...

多线程代码案例(定时器) - 3

定时器&#xff0c;是我们日常开发所常用的组件工具&#xff0c;类似于闹钟&#xff0c;设定一个时间&#xff0c;当时间到了之后&#xff0c;定时器可以自动的去执行某个逻辑 目录 Timer 的基本使用 实现一个 Timer 通过这个类&#xff0c;来描述一个任务 通过这个类&…...

基于大模型的GCSE预测与治疗优化系统技术方案

目录 技术方案文档:基于大模型的GCSE预测与治疗优化系统1. 数据预处理模块功能:整合多模态数据(EEG、MRI、临床指标等),标准化并生成训练集。伪代码流程图2. 大模型架构(Transformer-GNN混合模型)功能:联合建模时序信号(EEG)与空间结构(脑网络)。伪代码流程图3. 术…...

IntelliJ IDEA 中 Continue 插件使用 DeepSeek-R1 模型指南

IntelliJ IDEA 中 Continue 插件使用 DeepSeek-R1 模型指南 Continue 是一款开源的 AI 编码助手插件&#xff0c;支持 IntelliJ IDEA 等 JetBrains 系列 IDE。它可以通过连接多种语言模型&#xff08;如 DeepSeek-R1&#xff09;提供实时代码生成、问题解答和单元测试生成等功…...

Valgrind——内存调试和性能分析工具

文章目录 一、Valgrind 介绍二、Valgrind 功能和使用1. 主要功能2. 基本用法2.1 常用选项2.2 内存泄漏检测2.3 详细报告2.4 性能分析2.5 多线程错误检测 三、在 Ubuntu 上安装 Valgrind四、示例1. 检测内存泄漏2. 使用未初始化的内存3. 内存读写越界4. 综合错误 五、工具集1. M…...

京东API智能风控引擎:基于行为分析识别恶意爬虫与异常调用

京东 API 智能风控引擎基于行为分析识别恶意爬虫与异常调用&#xff0c;主要通过以下几种方式实现&#xff1a; 行为特征分析 请求频率&#xff1a;正常用户对 API 的调用频率相对稳定&#xff0c;受到网络延迟、操作速度等因素限制。若发现某个 IP 地址或用户在短时间内对同一…...

Swift 解 LeetCode 250:搞懂同值子树,用递归写出权限系统检查器

文章目录 前言问题描述简单说&#xff1a;痛点分析&#xff1a;到底难在哪&#xff1f;1. 子树的概念搞不清楚2. 要不要“递归”&#xff1f;递归从哪开始&#xff1f;3. 怎么“边遍历边判断”&#xff1f;这套路不熟 后序遍历 全局计数器遍历过程解释一下&#xff1a;和实际场…...

Nginx搭建API网关服务教程-系统架构优化 API统一管理

超实用&#xff01;用Nginx搭建API网关服务&#xff0c;让你的系统架构更稳更强大&#xff01;&#x1f680; 亲们&#xff0c;今天来给大家种草一个超级实用的API网关搭建方案啦&#xff01;&#x1f440; 在如今的Web系统架构中&#xff0c;一个稳定、高性能、可扩展的API网…...

SQL2API是什么?SQL2API与BI为何对数据仓库至关重要?

目录 一、SQL2API是什么&#xff1f; 二、SQL2API的历史演变&#xff1a;从数据共享到服务化革命 1990年代&#xff1a;萌芽于数据仓库的数据共享需求 2010年代初&#xff1a;数据中台推动服务化浪潮 2022年左右&#xff1a;DaaS平台的兴起 2025年代&#xff1a;麦聪定义…...

CentOS 7无法上网问题解决

CentOS 7无法上网问题解决 问题 配置了桥接模式以后&#xff0c;能够ping通本地IP但是无法ping通www.baidu.com 这里的前提是VWare上已经对虚拟机桥接模式网卡做了正确的选择&#xff0c;比如我现在选择的就是当前能够上外网的网卡&#xff1a; 问题根因 DNS未正确配置。…...

优化 Web 性能:使用 WebP 图片(Uses WebP Images)

在 Web 开发中&#xff0c;图片资源的优化是提升页面加载速度和用户体验的关键。Google 的 Lighthouse 工具在性能审计中特别推荐“使用 WebP 图片”&#xff08;Uses WebP Images&#xff09;&#xff0c;因为 WebP 格式在保持视觉质量的同时显著减少文件大小。本文将基于 Chr…...

SQL121 创建索引

-- 普通索引 CREATE INDEX idx_duration ON examination_info(duration);-- 唯一索引 CREATE UNIQUE INDEX uniq_idx_exam_id ON examination_info(exam_id);-- 全文索引 CREATE FULLTEXT INDEX full_idx_tag ON examination_info(tag);描述 现有一张试卷信息表examination_in…...

Leetcode - 周赛443

目录 一、3502. 到达每个位置的最小费用二、3503. 子字符串连接后的最长回文串 I三、3504. 子字符串连接后的最长回文串 II四、3505. 使 K 个子数组内元素相等的最少操作数 一、3502. 到达每个位置的最小费用 题目链接 本题是一道脑筋急转弯&#xff0c;实际就是计算前缀最小…...

dolphinscheduler单机部署链接oracle

部署成功请给小编一个赞或者收藏激励小编 1、安装准备 JDK版本:1.8或者1.8oracle版本&#xff1a;19Coracle驱动版本&#xff1a;8 2、安装jdk 下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/#java8 下载后上传到/tmp目录下。 然后执行下面命…...

Three.js 系列专题 5:加载外部模型

内容概述 Three.js 支持加载多种 3D 文件格式(如 GLTF、OBJ、FBX),这让开发者可以直接使用专业建模软件(如 Blender、Maya)创建的复杂模型。本专题将重点介绍 GLTF 格式的加载,并调整模型的位置和材质。 学习目标 理解常见 3D 文件格式及其特点。掌握使用 GLTFLoader 加…...

【C++算法】50.分治_归并_翻转对

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 493. 翻转对 题目描述&#xff1a; 解法 分治 策略一&#xff1a;计算当前元素cur1后面&#xff0c;有多少元素的两倍比我cur1小&#xff08;降序&#xff09; 利用单…...

观察者模式详解实战

观察者模式深度解析与实战案例 一、传统观察者模式痛点分析 强制接收所有通知&#xff1a;观察者被迫处理无关事件 事件信息不透明&#xff1a;状态变更缺乏上下文信息 类型安全缺失&#xff1a;需要手动进行类型判断和转换 扩展成本高&#xff1a;新增事件类型需要修改接口 …...

Light RPC:一款轻量高效的Java RPC框架实践指南

Light RPC&#xff1a;一款轻量高效的Java RPC框架实践指南 一、框架简介二、快速入门1. 环境准备2. 服务端配置2.1 添加依赖2.2 YAML配置2.3 接口与实现 3. 客户端配置3.1 添加依赖3.2 YAML配置3.3 客户端调用 三、核心设计解析四、适用场景与优势对比五、总结 一、框架简介 …...

国内MCP资源网站有哪些?MCP工具上哪找?

在人工智能领域&#xff0c;MCP&#xff08;模型上下文协议&#xff09;正逐渐成为连接 AI 模型与外部世界的重要桥梁。而AIbase &#xff08;https://www.aibase.com/zh/repos/topic/mcp&#xff09;正是探索 MCP 生态的绝佳平台&#xff0c;它为开发者和研究者提供了一个集中…...

在PowerBI中通过比较日期实现累加计算

#表格数据# 日期数量2025/4/712025/4/822025/4/932025/4/1042025/4/1152025/4/1262025/4/1372025/4/1482025/4/1592025/4/16102025/4/1711 #新建计算列# 列 SUMX(FILTER(表格数据,[日期]<EARLIER([日期])),表格数据[数量])...

十四届蓝桥杯Java省赛 B组(持续更新..)

十四届蓝桥杯Java省赛 B组 第一题&#xff1a;阶乘求和 &#x1f4d6; &#x1f4da;阶乘求和 第二题&#xff1a;幸运数字 &#x1f4d6; &#x1f4da;幸运数字 第三题&#xff1a;数组分割 &#x1f4d6; &#x1f4da;数组分割 说是考动态规划&#xff0c;但没有用…...

NO.73十六届蓝桥杯备战|搜索算法-剪枝与优化-记忆化搜索|数的划分|小猫爬山|斐波那契数|Function|天下第一|滑雪(C++)

剪枝与优化 剪枝&#xff0c;形象得看&#xff0c;就是剪掉搜索树的分⽀&#xff0c;从⽽减⼩搜索树的规模&#xff0c;排除掉搜索树中没有必要的分⽀&#xff0c;优化时间复杂度。 在深度优先遍历中&#xff0c;有⼏种常⻅的剪枝⽅法 排除等效冗余 如果在搜索过程中&#xf…...