手撸RPC【gw-rpc】
文章目录
- 基于 Netty 的简易版 RPC需求分析
- 简易RPC框架的整体实现
- 协议模块 📖
- 自定义协议 🆕
- 序列化方式 🔢
- 服务工厂 🏭
- 服务调用方 ❓
- 前置知识——动态代理🕳️
- Proxy类
- InvocationHandler 接口
- RPC服务代理类
- 内嵌Netty客户端
- 核心handler:RpcResponseMessageHandler
- 服务提供方🤔
- 核心handler:RpcRequestMessageHandler
- 撸了这么多,验收一下吧!
在之前的博文中,我们学习了Netty的基础知识,了解了其原理和组件。在本篇博文中,我们将结合实际案例,分享一个基于Netty的简化版RPC(远程过程调用)实现。通过这个案例,我们不仅可以学习Netty的使用和原理,还能够对RPC的设计有一个整体的学习。
项目托管与gitee: gw-rpc
基于 Netty 的简易版 RPC需求分析
随着分布式和微服务的盛行,给我们的项目带来的收益是不同模块间的解耦,从而使整个软件开发流程更加的灵活。同时,模块间的调用是稀松平常的事情。这就会出现一系列的新的需求:
- 不同的模块有可能是分布在不同的机器上,要想相互调用一定会涉及到网络传输,所以要有相应的
通信模块
。 - 其次,网络传输的数据是二进制流。而在面向对象的程序中,业务处理的是对象,这就需要发送方在网络发送之前把对象
序列化
成二进制流,同时网络接收方收到二进制流后需要把二进制流反序列化
成对象。 - 同时,为了让调用方调用远程服务像调用本地方法一样简单,需要对网络请求、序列化做封装,Java 中一般采用
动态代理
去实现。 - 还有,要有
注册中心
提供服务方的地址列表,同时出现了新的服务节点需要注册中心及时发现,这样调用方才能找到合适的服务方。 - 最后,还需要负载均衡、熔断、降级、心跳探活等功能。
本篇博文中只讲解一个简化版的 RPC 设计,注册中心和负载均衡,及心跳探活等功能就不讲了……
捋了捋,大致流程如下
- 首先,客户端通过
动态代理模块
获取代理实例。 - 接下来,客户端通过
动态代理模块
来调用动态代理方法,用来实现封装 RpcRequestMessage 对象,把要调用的服务和方法,以及方法参数准备通过网络请求发送出去。 - 在发送之前通过编码模块转换
对象序列化
为字节数组。 动态代理
随后会通过网络通信把序列化成字节数组的请求发送给服务端,同时客户端同步或异步等待服务端的响应。这些工作都由动态代理完成,对于调用方来说是无感的
。- 服务端收到客户端的请求后,把字节数组
反序列化
成业务对象。 - 服务端根据请求中要调用的类和方法,通过反射实例化类并找到对应的方法。
- 服务端用收到的参数调用本地方法后封装响应对象。
- 把响应对象序列化为字符数组。
- 服务端把序列化的响应对象通过网络返回给客户端。
- 客户端收到序列化成字节数组的响应后
反序列化成响应对象
。
简易RPC框架的整体实现
我们的 RPC 项目主要分为下面几个模块,结构非常清晰:
配套的源码源代码地址:gw-rpc 。 主要分为以下几个基本模块。
协议模块
:设计了通信请求体、响应体,序列化模式。
服务工厂
:通过map维护接口类和实现类的映射关系。
服务调用方模块
:实现了服务调用方的基本功能,同时包含了动态代理的功能实现。
服务提供方模块
:实现了服务提供方的基本功能。
协议模块 📖
自定义协议 🆕
关于协议模块拆分至另外一篇博文进行讲解,使用Netty进行协议开发:多协议支持与自定义协议的实现。
序列化方式 🔢
对于RPC来说,序列化是一个必不可少的过程,它将业务对象以字节数组的形式在网络中进行传输。为了实现序列化功能,通常定义一个序列化接口,其中包含序列化方法和反序列化方法。在实际应用中,提供几种常见的序列化方式可供选择,包括以下几种:
- Java JDK 自带的序列化:Java 提供了默认的序列化机制,通过ObjectInputStream和ObjectOutputStream实现。对象可以以二进制形式进行序列化和反序列化。这种方式简单易用,但存在一些性能和版本兼容性的问题。
- Json算法使用Gson库将对象转换为JSON字符串,并通过JSON字符串进行序列化和反序列化操作。
- Hessian算法使用Hessian库将对象序列化为字节数组,提供了更高的性能和较小的序列化结果。
/*** Description: 序列化** @author LinHuiBa-YanAn* @date 2023/8/7 20:32*/public interface Serializer {/*** 反序列化方法** @param clazz 类型* @param bytes 字节码* @param <T> 类型* @return 对象*/<T> T deserialize(Class<T> clazz, byte[] bytes);/*** 序列化方法** @param object 对象* @param <T> 类型* @return byte[]*/<T> byte[] serialize(T object);enum Algorithm implements Serializer {Java {@Overridepublic <T> T deserialize(Class<T> clazz, byte[] bytes) {try {ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));return (T) ois.readObject();} catch (IOException | ClassNotFoundException e) {throw new RuntimeException("反序列化失败", e);}}@Overridepublic <T> byte[] serialize(T object) {try {ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(object);return bos.toByteArray();} catch (IOException e) {throw new RuntimeException("序列化失败", e);}}},Json {@Overridepublic <T> T deserialize(Class<T> clazz, byte[] bytes) {Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create();String json = new String(bytes, StandardCharsets.UTF_8);return gson.fromJson(json, clazz);}@Overridepublic <T> byte[] serialize(T object) {Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new ClassCodec()).create();String json = gson.toJson(object);return json.getBytes(StandardCharsets.UTF_8);}},Hessian {@Overridepublic <T> T deserialize(Class<T> clazz, byte[] bytes) {ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);HessianInput hessianInput = new HessianInput((byteArrayInputStream));// 反序列化成对象Object object = null;try {object = hessianInput.readObject(clazz);} catch (IOException e) {e.printStackTrace();} finally {hessianInput.close();}return (T) object;}@Overridepublic <T> byte[] serialize(T object) {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();byte[] bytes = new byte[0];try {HessianOutput ho = new HessianOutput(byteArrayOutputStream);ho.writeObject(object);bytes = byteArrayOutputStream.toByteArray();} catch (IOException e) {e.printStackTrace();} finally {try {byteArrayOutputStream.close();} catch (IOException e) {e.printStackTrace();}return bytes;}}}}/*** 适配器*/class ClassCodec implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {@Overridepublic Class<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {try {String str = json.getAsString();return Class.forName(str);} catch (ClassNotFoundException e) {throw new JsonParseException(e);}}@Overridepublic JsonElement serialize(Class<?> src, Type typeOfSrc, JsonSerializationContext context) {// class -> jsonreturn new JsonPrimitive(src.getName());}}}
对象的序列化和反序列化的需求在于:当我们收到数据的时候需要把二进制的 byte 数组转换为业务对象,这里就需要在 Netty 的 pipeline 中添加 inbound Handler,而对于发送数据则需要把业务对象转换为二进制的 byte 数据,也就是需要在 Netty 的 pipeline 中添加 outbound Handler。
服务工厂 🏭
ServicesFactory
是一个用于创建服务类实例的Java类。它根据在application.properties
文件中定义的配置属性,将接口类与实现类进行映射,并提供了一个getService
方法用于获取接口类对应的实例。在类加载时,它读取application.properties
文件并将属性加载到Properties
对象中。然后,它遍历属性名称,检查是否以"Service"结尾,并获取相应的接口类和实现类。通过使用反射创建实现类的实例,并将接口类和实例对象存储在ConcurrentHashMap
中。通过调用getService
方法并传入接口类,可以获取对应的实现类实例。该类的设计允许根据配置文件动态创建服务类实例,提供了一种灵活的方式来管理和获取服务实例。
/*** Description: 服务工厂** @author YanAn* @date 2023/8/7 20:54*/
public class ServicesFactory {static Properties properties;static Map<Class<?>, Object> map = new ConcurrentHashMap<>();static {try (InputStream in = Config.class.getResourceAsStream("/application.properties")) {properties = new Properties();properties.load(in);Set<String> names = properties.stringPropertyNames();for (String name : names) {if (name.endsWith("Service")) {Class<?> interfaceClass = Class.forName(name);Class<?> instanceClass = Class.forName(properties.getProperty(name));map.put(interfaceClass, instanceClass.newInstance());}}} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new ExceptionInInitializerError(e);}}public static <T> T getService(Class<T> interfaceClass) {return (T) map.get(interfaceClass);}
}
配置文件举例:
com.gw.core.service.HelloService=com.gw.core.service.impl.HelloServiceImpl
服务调用方 ❓
前置知识——动态代理🕳️
在前几年,代购家喻户晓。何为代购,简单来说就是找人帮忙购买所需要的商品,当然可能需要向实施代购的人支付一定的费用。在软件开发中也有一种设计模式可以提供与代购类似的功能:由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为“代理”的第三者来实现间接访问,该访问对应的设计模式被称为代理模式。即 给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
那什么是动态代理呢?动态代理,Dynamic Proxy。可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法。动态代理是一种较为高级的代理模式,它在事务管理、AOP等领域都发挥了重要的作用。
从jdk1.3开始,java就提供了对动态代理的支持。下面简要说明一下~
Proxy类
Proxy类提供了用于创建动态代理类和实例对象的方法,它是所创建的动态代理类的父类。我们直接去看它的核心方法:
getProxyClass方法 用于返回一个 Class 类型的代理类,在参数中需要提供类加载器并需要指定代理的接口数组。
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)
loader
:类加载器interfaces
:代理的接口数组
newProxyInstance方法 用于返回一个动态创建的代理类的实例。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
loader
:类加载器interfaces
:代理类所实现的接口列表h
:所指派的调用处理程序类,我们可以在这个类中添加公共逻辑,比如网络逻辑
InvocationHandler 接口
InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用出阿里着(InvocationHandler 接口的实现类)。在实现该接口的同时必须得实现InvocationHandler接口中声明的invoke
方法~
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
该方法用于处理对代理实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。
proxy
:代理类的实例method
:需要代理的方法args
:代理方法的参数数组
动态代理类需要在运行时指定所代理真实主题类的接口,客户端在调用动态代理对象的方法时调用请求会将请求自动转发给 InvocationHandler 对象的 invoke()
方法,由 invoke()
方法来实现对请求的统一处理。
RPC服务代理类
我们在Proxy的基础上封装了一个代理模块。在 invoke()
方法中,我们将需要调用的接口方法和其他相关信息封装成一个业务对象,并使用 RpcClient.getChannel()
方法获取通道。然后,我们将封装好的消息 msg
写入并刷新通道,发送给远程服务器。
同时,我们创建了一个与通道关联的 DefaultPromise
对象,用于处理异步操作的结果。我们将生成的序列号和 promise 对象存放在 RpcResponseMessageHandler.PROMISES
集合中(后面会讲解该核心handler),以便在接收到响应时进行对应处理。
接下来,我们等待 promise 对象的完成。一旦 promise 对象完成,我们根据其结果进行判断。如果操作成功,我们返回相应的结果;如果操作失败,我们抛出一个异常来表示错误情况。
@Slf4j
public class RpcServiceProxy {/*** 获取代理实例** @param serviceClass 服务类.class* @param <T> 服务类.class* @return 执行结果*/public static <T> T getProxyService(Class<T> serviceClass) {ClassLoader loader = serviceClass.getClassLoader();Class<?>[] interfaces = new Class[]{serviceClass};Object obj = Proxy.newProxyInstance(loader, interfaces, new RpcServiceProxyInvocationHandler(serviceClass));return (T) obj;}/*** The class that actually implements the proxy logic*/static class RpcServiceProxyInvocationHandler implements InvocationHandler {private final Class referenceConfig;public RpcServiceProxyInvocationHandler(Class referenceConfig) {this.referenceConfig = referenceConfig;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {int sequenceId = SequenceIdGenerator.nextId();RpcRequestMessage msg = new RpcRequestMessage(sequenceId,referenceConfig.getName(),method.getName(),method.getReturnType(),method.getParameterTypes(),args);RpcClient.getChannel().writeAndFlush(msg);DefaultPromise<Object> promise = new DefaultPromise<>(RpcClient.getChannel().eventLoop());RpcResponseMessageHandler.PROMISES.put(sequenceId, promise);promise.await();if (promise.isSuccess()) {return promise.getNow();} else {throw new RuntimeException(promise.cause());}}}
}
内嵌Netty客户端
在RpcServiceProxy类中内嵌Netty客户端类,用于与服务提供方建立连接并进行通信。
/*** 内嵌Netty客户端*/
static class RpcClient {/*** channel*/private static Channel channel = null;/*** lock*/private static final Object LOCK = new Object();/*** get channel** @return Channel*/public static Channel getChannel() {if (channel != null) {return channel;}synchronized (LOCK) {if (channel != null) {return channel;}initChannel();return channel;}}/*** init channel*/private static void initChannel() {NioEventLoopGroup group = new NioEventLoopGroup();LoggingHandler loggingHandler = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable messageCodec = new MessageCodecSharable();RpcResponseMessageHandler rpcHandler = new RpcResponseMessageHandler();Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(group);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new ProtocolFrameDecoder());ch.pipeline().addLast(loggingHandler);ch.pipeline().addLast(messageCodec);ch.pipeline().addLast(rpcHandler);}});try {channel = bootstrap.connect(Config.getServerIp(), Config.getProjectPort()).sync().channel();channel.closeFuture().addListener(future -> group.shutdownGracefully());} catch (Exception e) {log.error("client error", e);}}
}
主要有四个Handler,分别是:
- ProtocolFrameDecoder:协议帧解码器
- LoggingHandler:日志处理
- MessageCodecSharable:消息的解编码器
- RpcResponseMessageHandler:Rpc响应消息处理程序
核心handler:RpcResponseMessageHandler
核心方法 channelRead0
在代理模块中负责处理服务器的响应。在方法中,首先从从维护的 PROMISES
集合中删除与当前响应相关的映射关系。随后,响应消息中获取结果,并根据结果设置相应的 promise 对象,以完成异步操作。
当 invoke()
方法中的 promise 对象等待结果时,通过 channelRead0
方法的处理,promise 对象将结束阻塞,并获取到封装的执行结果。这样,便完成了对远程方法调用的响应处理和结果返回过程。
/*** Description: Rpc响应消息处理程序** @author LinHuiBa-YanAn* @date 2023/8/8 10:29*/
@Slf4j
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {/*** The promise object used to receive the result*/public static final Map<Integer, Promise<Object>> PROMISES = new ConcurrentHashMap<>();@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {log.info("Netty rpc client receives the response:{}", msg);Promise<Object> promise = PROMISES.remove(msg.getSequenceId());if (promise != null) {Object returnValue = msg.getReturnValue();Exception exceptionValue = msg.getExceptionValue();if (exceptionValue == null) {promise.setSuccess(returnValue);} else {promise.setFailure(exceptionValue);}}}
}
服务提供方🤔
相对于服务端调用方模块而言,服务提供方模块相对简单。通过前面几篇博文对 Netty 的学习,我们已经具备了足够的知识来处理服务提供方的实现,这里简直是小菜一碟啦!
/*** Description: RPC服务端** @author LinHuiBa-YanAn* @date 2023/8/7 20:45*/
@Slf4j
public class RpcServer {public static void main(String[] args) {log.info("netty rpc server starting......");NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();RpcRequestMessageHandler RPC_HANDLER = new RpcRequestMessageHandler();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProtocolFrameDecoder());ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);ch.pipeline().addLast(RPC_HANDLER);}});Channel channel = serverBootstrap.bind(Config.getProjectPort()).sync().channel();channel.closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();}}
}
服务端demo已经写的手烂了,还是老样子介绍一下pipeline上的handler:
- ProtocolFrameDecoder:协议帧解码器
- LoggingHandler:日志处理
- MessageCodecSharable:消息的解编码器
- RpcRequestMessageHandler:Rpc请求消息处理程序
核心handler:RpcRequestMessageHandler
/*** Description: Rpc请求消息处理程序** @author LinHuiBa-YanAn* @date 2023/8/7 20:52*/
@Slf4j
@ChannelHandler.Sharable
public class RpcRequestMessageHandler extends SimpleChannelInboundHandler<RpcRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage rpcRequest) {RpcResponseMessage rpcResponse = new RpcResponseMessage();log.info("Netty rpc server receives the request:{}", rpcRequest);rpcResponse.setSequenceId(rpcRequest.getSequenceId());rpcResponse.setMessageType(rpcRequest.getMessageType());try {Object service = ServicesFactory.getService(Class.forName(rpcRequest.getInterfaceName()));Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());Object invoke = method.invoke(service, rpcRequest.getParameterValue());rpcResponse.setReturnValue(invoke);} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {log.error("RPC processing failed. An exception occurred. Procedure. exception:{}", e.getMessage());rpcResponse.setExceptionValue(e);}ctx.writeAndFlush(rpcResponse);}
}
核心方法 channelRead0
在代理模块中负责处理服务器的请求。在该方法中,首先从请求消息体中获取需要调用的接口方法以及其他相关信息。这些信息通常包括接口名称、方法名称、参数类型和参数值等。
接下来,通过服务工厂获取到需要调用接口的对象实例。服务工厂负责管理和创建服务实例,以便在接收到请求时能够正确地调用相应的方法。
接着,通过反射的方式执行需要调用的方法。根据接口名称、方法名称以及参数类型和参数值,使用反射机制调用相应的方法,并获取执行结果。
最后,将执行的结果封装成响应消息,并写入通道,以便返回给服务调用方。响应消息通常包括执行结果、状态码和其他相关信息,用于服务调用方处理和解析。
撸了这么多,验收一下吧!
首先启动服务提供方
以com.gw.core.service.HelloService#sayHello
方法为例
非常感谢您的阅读!项目中还有其他小设计,鼓励您深入研究和体验这些设计,以便更好地理解和掌握项目的细节。
如果您在项目中遇到任何问题或需要进一步的帮助,随时向我提问。我将尽力为您提供支持和解答。祝您在项目中取得成功,并愉快地品尝这些小设计!
项目托管与gitee:gw-rpc
相关文章:
![](https://img-blog.csdnimg.cn/34e399cba836427f8d3cb2b711306b59.png)
手撸RPC【gw-rpc】
文章目录 基于 Netty 的简易版 RPC需求分析简易RPC框架的整体实现协议模块 📖自定义协议 🆕序列化方式 🔢 服务工厂 🏭服务调用方 ❓前置知识——动态代理🕳️Proxy类InvocationHandler 接口 RPC服务代理类内嵌Netty客…...
![](https://img-blog.csdnimg.cn/723f2b1e49714db396b97c8915b331ee.png)
【Linux】:Kafka组件介绍
目录 环境简介 一、消息 二、主题 三、分区 四、副本 五、生产者 六、消费者 七、消费者组 八、offsets【偏移量】 环境简介 Linux内核:Centos7 Kafka版本:3.5.1 执行命令的目录位置:Kafka安装目录的bin目录下:/usr/loca…...
![](https://www.ngui.cc/images/no-images.jpg)
Redis〔篇〕
redis怎么做到双写一致性呢? 这个是要分情况的 业务要是对一致性要求不是很高的话可以使用延时双删,要强一致的话需要双写一致性。 Redis数据持久化? redis是有两种数据持久化方式的,一种RDB一种AOF rdb是redis数据快照&#x…...
![](https://img-blog.csdnimg.cn/img_convert/7fa49a80002f2376d0b29a8433d911bf.jpeg)
龙芯2K1000核心板在智能座舱行业产品方案-迅为电子
迅为2K1000核心板是一款高性能的处理器,适用于智能座舱行业。它具备多核CPU、高级图像处理和丰富的接口选项,可用于开发先进的智能座舱解决方案,提高乘坐体验、安全性和便捷性。以下是2K1000处理器在智能座舱行业中的产品方案。 高清晰度显…...
![](https://img-blog.csdnimg.cn/24f5f3fcbfb74d3ea1fb53142358f02b.png)
2023/9/20 -- C++/QT
时钟: widget.h: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPainter> #include <QPaintEvent> #include <QDateTime> #include <QLabel> #include <QTimer> #include <QDebug>QT_BEGIN_NAMESPACE namespac…...
![](https://img-blog.csdnimg.cn/img_convert/9c0211f54bd3d52a27db484363a78786.jpeg)
WordPress主题DUX v8.2源码下载
新增产品分类左侧多级分类折叠显示 新增网站默认字体对 MiSans 和 HarmonyOS Sans 的支持 新增顶部左上角显示登录注册的模块开关,且支持原生登录方式 新增手机端导航菜单的关闭按钮 新增文章内容中标题二的强化展示 新增全站禁止复制、右键和选择的操作 新增文章内…...
![](https://www.ngui.cc/images/no-images.jpg)
c++图像的边缘检测
图像的边缘检测 cv::Canny 是 OpenCV 中用于进行边缘检测的函数,特别是用于检测图像中的边缘。Canny 边缘检测是一种广泛使用的技术,它能够识别图像中的边缘,这些边缘通常表示对象之间的边界或图像中的显著特征 void cv::Canny(const cv::M…...
![](https://img-blog.csdnimg.cn/7219828407584631872d49f13d7381da.png)
C++ Primer 类和对象(3)
类和结构体是比较相似,而传统的C的结构体中都是一些数据的类型,类除了有数据之外还有函数。所以可以把类想象成一个具有既有数据又有函数的复合数据类型。 类是一种将抽象转换为用户定义类型的C工具,它将数据表示和操纵数据的方法组合成一个整…...
![](https://img-blog.csdnimg.cn/img_convert/379bf0da4cd0e9d9afe640a6a31afcef.png)
IntelliJ IDEA 介绍、安装、配置优化与快捷键大全
一、简介 IDEA全称 IntelliJ IDEA,是Java编程语言的集成开发环境。IntelliJ在业界被公认为最好的Java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能…...
![](https://img-blog.csdnimg.cn/9d4f478907454f5d802043b3933e8ee5.png)
css 语法笔记
.abc {margin-left:20px; } .xyz {margin-left:20px; } 等同于 .abc, .xyz {margin-left: 20px; } 参考 CSS - 选择器_css最后一个元素选择器_伏城之外的博客-CSDN博客 CSS Selectors Reference...
![](https://img-blog.csdnimg.cn/d832bd25ebe34cfe8eceac21df009995.png#pic_center)
【初阶数据结构】二叉树全面知识总结
二叉树详解 树的概念及其结构树的概念树的相关概念树的表示方法孩纸兄弟表示法双亲表示法(并查集) 树的实际应用 二叉树二叉树的概念二叉树的种类二叉树的性质二叉树的存储结构 二叉树顺序结构的实现堆的概念及结构堆向上、向下调整法堆的插入堆的删除堆…...
![](https://img-blog.csdnimg.cn/72f0426bf4ca468c9f9b6c471a0b2100.png)
CMD命令终端快捷键学习
很多环境需要安装并且指定环境变量才可用终端访问 比如一些数据库、一些环境、例如:nodejs Oracle、mysql 在一个文件夹按住shift鼠标右键可以快速在当前目录运行终端!免去cd 目录的烦恼 快捷键 当你学习和使用命令终端(如 Windows 的 CMD&…...
![](https://img-blog.csdnimg.cn/dd0acc06b71d46799c496b57317ddfaa.png)
Leetcode198. 打家劫舍
https://leetcode.cn/problems/house-robber/description/ 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入&…...
![](https://img-blog.csdnimg.cn/8b054859a09541ec9daeb643b260b2af.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAR0lT5LmL5a625a626ZW_,size_15,color_FFFFFF,t_70,g_se,x_16#pic_center)
前端技术社区总目录
前端技术社区欢迎您的订阅。订阅后,您将可以查看以下所有博客内容。 注:专栏内容主要面向新手 注:每个示例都有相对应的完整代码 注:该专栏博客内容将会逐步迁移至https://blog.csdn.net/m0_60387551/article/details/128017725 …...
![](https://www.ngui.cc/images/no-images.jpg)
极客时间:左耳听风【文章笔记 思考总结】
本篇博客是学习过程中的笔记、思考和总结。原文链接:https://time.geekbang.org/column/intro/100002201 开篇词 | 洞悉技术的本质,享受科技的乐趣01 | 程序员如何用技术变现(上)02 | 程序员如何用技术变现(下…...
![](https://img-blog.csdnimg.cn/3a7dd828e688466785970e46351f9baa.png)
《论文阅读27》SuperGlue: Learning Feature Matching with Graph Neural Networks
一、论文 研究领域: 图像特征点匹配论文:SuperGlue: Learning Feature Matching with Graph Neural NetworksCVPR 2020veido论文code 二、论文简述 [参考] [参考] [参考] 三、论文详述 SuperGlue:使用图神经网络学习特征匹配 本文介绍了…...
![](https://img-blog.csdnimg.cn/f77d1913a92745cb9f1473e41ecad07b.png)
远程计算机或设备不接受连接解决方法
远程计算机或设备不接受连接解决方法 点击左下角开始,点击运行,输入inetcpl.cpl,点击确定,打开Internet选项。 将三个框的勾勾去掉,即为不选中状态,点击确定。 当你的电脑浏览器不能正常上网时ÿ…...
![](https://img-blog.csdnimg.cn/1ec960dc1b8548749f5be5952a377629.png)
基于Python实现的快递管理系统源码+数据库,采用PyQt6实现GUI界面
快递管理系统 完整代码下载地址:快递管理系统 介绍 通过对传统的快递收发流程进行分析,完成网上快递管理系统的分析设计与开发,使客户能方便在网站上查询自己的快件信息以及网上寄件,同时管理员又能对每天的收到快件进行登记和…...
![](https://img-blog.csdnimg.cn/c7cfb07f67da44fc8234706743a3064f.png)
如何使用docker快速部署MinDoc文档系统
MinDoc是非常优秀的知识分享系统,但是很多刚接触的人会一脸懵逼,而且官方文档写的也并不清晰,所以和大家分享一下快速部署MinDoc的方法。 首先docker环境先自行安装好,这里不再赘述。 拉取docker镜像: docker pull …...
![](https://www.ngui.cc/images/no-images.jpg)
9月25日,每日信息差
今天是2023年9月27日,以下是为您准备的18条信息差 第一、苹果向法国监管机构提交iPhone 12软件更新,解决辐射超标问题 第二、“双节”期间,北京全市预计接待游客1283万人次,中秋国庆“双节”长假将至,北京市民和游客…...
![](https://www.ngui.cc/images/no-images.jpg)
【网络协议】Https
HTTP 协议内容都是按照⽂本的⽅式明⽂传输的,这就可能导致在传输过程中出现⼀些被篡改的情况。所以在HTTP协议的基础上,引入加密层,形成了HTTPS协议。 HTTPS 协议是由 HTTP 加上 TLS/SSL 协议构建的可进行加密传输、身份认证的网络协议,主要…...
![](https://www.ngui.cc/images/no-images.jpg)
Lostash同步Mysql数据到Elasticsearch(三)Elasticsearch模板与索引设置
logstash数据同步ES相关 同步数据时,Elasticsearch配合脚本的相关处理设置 1.模板创建更新 在kibana中执行,或者直接给ES发送请求,你懂得,不懂得百度下ES创建template PUT /_template/test-xxx {"template": "…...
![](https://img-blog.csdnimg.cn/d65da85d04744bea959d762bf6984158.png)
python——ptp()函数
函数功能:求最大值和最小值的差值 def ptp(a, axisNone, outNone):"""沿轴的值范围(最大值-最小值)。该函数的名称来自“peak-to-peak”的首字母缩写。参数----------a:array_like输入值。axis:int,可选找到山峰的…...
![](https://img-blog.csdnimg.cn/bb1455f9769f4a9eac32ae0a080f9ca5.png)
SpringBoot之异常处理
文章目录 前言一、默认规则二、定制异常处理处理自定义错误页面ControllerAdviceExceptionHandler处理全局异常ResponseStatus自定义异常自定义实现 HandlerExceptionResolver 处理异常 三、异常处理自动配置原理四、异常处理流程总结 前言 包含SpringBoot默认处理规则、如何定…...
![](https://www.ngui.cc/images/no-images.jpg)
Flask-[实现websocket]-(2): flask-socketio文档学习
一、简单项目的构建 flask_websocket |---static |---js |---jquery-3.7.0.min.js |---socket.io_4.3.1.js |---templates |---home |---group_chat.html |---index.html |---app.py 1.1、python环境 python3.9.0 1.2、依赖包 Flask2.1.0 eventlet0.33.3 #使用这个性能会…...
![](https://www.ngui.cc/images/no-images.jpg)
网页中使用的图片格式
网页中使用的图片格式有以下几种,各有优缺点: JPEG:适用于照片或彩色图像,支持16.7万种颜色,有损压缩,可以调节压缩比和质量,文件较小,加载较快PNG:适用于图标或透明图像…...
![](https://www.ngui.cc/images/no-images.jpg)
【android】如何设置LD_LIBRARY_PATH?
目录 一 配置方法 1 进入Android shell 2 使用export命令 3 使用echo命令查看变量是否设置成功 二 扩展 1 LD_LIBRARY_PATH设置多个路径 2 push文件 一 配置方法 android中配置LD_LIBRARY_PATH的方法具体为: 1 进入Android shell adb shell 2 使用export…...
![](https://img-blog.csdnimg.cn/024de7247ee24b18a26fa91e1cd7c99c.png)
【hadoop3.x】一 搭建集群调优
一、基础环境安装 https://blog.csdn.net/fen_dou_shao_nian/article/details/120945221 二、hadoop运行环境搭建 2.1 模板虚拟机环境准备 0)安装模板虚拟机,IP 地址 192.168.10.100、主机名称 hadoop100、内存 4G、硬盘 50G 1)hadoop100…...
![](https://img-blog.csdnimg.cn/e1babd9512df4c81be6d6f74796fb591.png)
linux使用操作[2]
文章目录 版权声明网络传输ping命令wget命令curl命令端口linux端口端口命令和工具 进程管理查看进程关闭进程 主机状态top命令内容详解磁盘信息监控 版权声明 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明,所有版权属于黑马程序员或相…...
![](https://img-blog.csdnimg.cn/824098599e3245879f1d86c28b9d2a65.png)
华南理工大学电子与信息学院23年预推免复试面试经验贴
运气较好,复试分数90.24,电科学硕分数线84、信通83、专硕电子与信息74. 面试流程: 1:5min ppt的介绍。其中前2min用英语简要介绍基本信息,后3min可用英语也可用中文 介绍具体项目信息如大创、科研、竞赛等(…...
![](/images/no-images.jpg)
备案后修改网站名称/一点优化
堆和栈是两种内存分配的统称。 一.栈 栈会存放函数的局部变量,函数的返回地址等。栈有"LIFO"(后进先出)的特点。栈由操作系统分配,自动回收.栈的大小受到限制。在x86体系下,栈一般通过esp 指向栈帧顶部,ebp指向底部不…...
![](/images/no-images.jpg)
广州市专业做网站/外贸获客软件
css 选择器 1- 基础选择器 1- ID选择器 // #id 2- 类名选择器 // .class 3- 元素选择器 // element 4- 全局选择器 // * 5- 并集选择器selector1,selector2 //选择满足选择器1条件的元素,也选择满足选择器2条件的元素,一起选择到统一…...
![](/images/no-images.jpg)
h5网站建设哪家好/六种常见的网络广告类型
众所周知,windows 2003远程终端服务基于默认端口3389。入侵者一般先扫描主机开放端口,一旦发现其开放了3389端口,就会进行下一步的入侵,所以我们只需要修改该务默认端口就可以避开大多数入侵者的耳目。 步骤:打开“开始…...
![](https://www.oschina.net/img/hot3.png)
企业网站用户群/百度seo搜索营销新视角
为什么80%的码农都做不了架构师?>>> 有时候不想放弃jpa持久化的便利性,又不想因此牺牲了查询的灵活性,所以列举两种jpa下的灵活查询方式。 单表查询指定Entity中的部分字段 这块比较简单,只需要定义一个存放部分属性的…...
![](/images/no-images.jpg)
wordpress首页跳转/seo平台
构建编译项目不需要安装CMake,MinGW等额外工具。只需要Android NDK和OpenCV源码。在WIN10上使用cmd窗口命令编译OpenCV步骤,操作版本为4.0.1:在OpenCV根目录下面新建一个文件夹,比如叫做build_cmd。因为OpenCV要求CMAKE_BINARY_DI…...
![](/images/no-images.jpg)
有网站做淘宝天猫优惠券代理/个人博客网站
Apache配置WSGI 什么是WSGI WSGI被称作web服务器网关接口,在笔者看来其实是基于CGI标准针对Python语言做了一些改进,其主要功能是规范了web 服务器与Pythonj应用程序之间的交互方式,为Python在web开发方面提供了便利而已。关于WSGI原生开发可…...