【手写实现一个简单版的Dubbo,深刻理解RPC框架的底层实现原理】

手写实现一个简单版的Dubbo,深刻理解RPC框架的底层实现原理

  • RPC框架简介
  • 了解Dubbo的实现原理
    • 服务暴露
    • 服务引入
    • 服务调用
  • 手写实现一个简单版的Dubbo
    • 服务暴露
      • ServiceBean
      • ProxyFactory#getInvoker
      • Protocol#export
      • RegistryProtocol#export
    • 服务引入
      • RegistryProto#refer
      • DubboProtocol#refer
    • 服务调用
      • 服务消费者
      • 服务提供者
    • 与Spring对接
  • 代码

我越来越觉得学习一个开源框架或者中间件的底层原理,最好的方法不是看源码,而是自己尝试去写一个。之前看过许多开源框架和中间件的源码,但是随着时间的推移,多多少少都有点遗忘了,遗忘之后又花时间去把它捡一捡,于是就在这捡起又遗忘,遗忘又捡起这样来来回回的折腾中浪费了许多时间。

Dubbo的源码我很早以前就看过了,而且看了不止一遍,现在对于Dubbo的底层原理只记得个大概,至于源码中的细节,已经忘得差不多了。最近想起之前学习过Dubbo源码,为了加深印象,于是乎自己动手写了一个。这自己动手写跟只用眼睛看,效果还真不一样,因此写成文章分享出来。

RPC框架简介

在以前都是单体应用的年代,是不需要RPC框架的,那时候还不知道RPC是什么,所有的数据都是来自于本地的数据库,然后全都是本地的方法调用。

在这里插入图片描述

但是随着业务的发展,访问量的不断增加,原先的单体应用已经不能满足需求,于是要做应用拆分,原先的单体应用被拆成了多个服务,然后就出现了服务间调用的问题。

在这里插入图片描述

这样复杂度就一下子上来了,如何解决服务间调用的问题呢?我们可以自己写代码,通过http的方式去访问对方,或者通过Socket连上对方的系统进行通信,发送调用的方法名和调用参数等信息。但是这种方式有点笨,因为这种网络通信的代码一般都是重复的,而且也容易出错。

那么有没有什么办法可以让远程调用变成像之前单体应用那样就调一个本地方法,就能获取到返回结果呢?那就是引入一个RPC框架,RPC框架封装了远程调用的底层逻辑,屏蔽掉了网络通信的过程,让API调用工程师们简单调一下方法,就可以获取到远程系统返回的方法调用结果,于是这些API调用工程师们就觉得自己很牛B了。

在这里插入图片描述

我们当然不能当这种API调用工程师,要不然35岁之后就要强势加入骑士军团了。

在这里插入图片描述

其实RPC框架的原理并没有多复杂。

比如现在有一个 UserService接口,在以前还是单体应用的时候,Spring给我们注入的是一个实现类,我们调用这个接口的方法时就可以调用UserServiceImpl实现类。

在这里插入图片描述

而现在这个实现类在别的系统上,我们拿不到,于是RPC框架给我们注入的是一个代理对象,这个代理对象也实现了这个UserService接口,通过这个代理对象可以访问到对方系统的UserServiceImpl。然后RPC框架通过一个stub存根封装了网络通信的逻辑,当我们调用UserService接口的某个方法时,实际上是通过代理对象调用到了底层的存根类,stub存根再把我们调用的方法和参数封装为网络协议包,发送到对端系统。对端系统也有一个stub存根,接收到发送过来的协议包后,解析出要调用的方法和参数,交给UserServiceImpl去处理,然后再把处理结果通过stub存根返回给我们本地的存根,我们本地的存根再把结果返回给我们。

在这里插入图片描述

但是A系统怎么知道B系统的ip地址和端口好呢?如果是在A系统上配死那就太low了,一旦B系统改了ip地址或端口,A系统就要改配置重启。因此,还需要一个注册中心存储B系统发布的ip地址和端口,A系统从注册中心拉取B系统的ip地址和端口。此时B系统就是服务提供者,把ip地址和端口注册到注册中心这个动作就是服务暴露,而A系统就是服务消费者,从注册中心拉取服务提供者的ip地址端口等信息这个动作就是服务引入,A系统调用B系统这个动作就叫服务调用

在这里插入图片描述
这样,一个RPC调用框架就形成了。

了解Dubbo的实现原理

但是,理解到这一层,还远远不够,毕竟别人可以面试前把这些背一背,面试的时候忽悠面试官。于是,我们要阅读源码,对开源框架有更深一层的理解,这样才能让面试官从一众的渣渣中把我们区分出来。

Dubbo是众多的RPC框架中用的比较多的一个,我们来参考参考Dubbo的原理,也是分为服务暴露、服务引入、服务调用三块。

服务暴露

首先我们写的类是这样子的:
在这里插入图片描述

UserServiceImpl上声明了@Service注解(这个是Dubbo自己定义的@Service注解,不是Spring的那个),就代表我们要把这个UserServiceImpl暴露出来供服务消费者调用。

Dubbo要扫描它,然后把它封装成ServiceBean,ServiceBean是Dubbo封装的一个用来做服务暴露的实现类,它实现了Spring的ApplicationListener<ContextRefreshedEvent>接口,会监听ContextRefreshedEvent容器刷新完成事件触发服务暴露,而ServiceBean的export方法就是服务暴露的入口。

在这里插入图片描述

Dubbo在服务暴露中,一共做了三件事:

  1. 把我们的UserServiceImpl对象包装成Invoker,再把Invoker包装成Exporter,再把Exporter放入一个map中,key是根据实现类计算出来的一个唯一的serviceKey。这一步相当于是本地注册,方便后面接收到远程调用请求时,根据请求信息计算出serviceKey,找到目标实现类。
  2. 开启Netty服务端,用于接收远程调用请求。
  3. ip地址、端口、协议等服务信息注册到注册中心,这些信息注册到注册中心后,消费方就可以从注册中心中获取到服务提供者的信息了。

在这里插入图片描述

服务引入

至于服务引入,我们写的消费者类一般是这样:
在这里插入图片描述

属性上声明了这个@Reference注解,表示这个属性需要通过Dubbo的服务引入来进行属性注入。

那么,Dubbo就是扫描所有被@Reference注解修饰的属性,进行属性注入。Dubbo这里处理属性注入的逻辑与Spring处理@Autowired的逻辑大体是相同的,这里先不用管(后面会讲解)。扫描到的属性,通过ReferenceBean的get()方法触发服务引入,返回一个代理对象,注入到该属性中。

在这里插入图片描述

而服务引入里面做了些啥呢?其实就是从注册中心中获取服务提供者的ip端口等信息,开启NettyClient连接上服务提供者,然后封装到一个Invoker中,这个Invoker就等同于上面说的存根类。然后通过动态代理返回一个代理对象,这个代理对象会调用Invoker进行远程方法调用。

在这里插入图片描述

服务调用

服务调用的逻辑自然是被隐藏在了Invoker里面。服务间通过Netty进行网络通信,服务消费者把调用的方法名,接口的类全限定名,方法参数等封装成一个Invocation对象,然后序列化成二进制,通过Netty发送给服务提供者,服务提供者的Netty接收到服务消费者发来的数据时,会反序列化成Invocation对象,根据服务消费者提供的信息,找到具体的实现类进行处理,处理结果再由Netty返回给服务消费者。

在这里插入图片描述

最终整体流程就是这样:
在这里插入图片描述

没有看过源码的,也可以记一下这张图,然后面试的时候喷给面试官,也能拿个60分。

按照这个思路,我们就可以动手写代码了。

手写实现一个简单版的Dubbo

我们写代码的顺序还是按照服务暴露、服务引入、服务调用的顺序来,但是我们实现的迷你版Dubbo是要与Spring对接的,所以最后还要实现与Spring对接的逻辑。

这里要注意,下面还是以一边讲解一边画图的方式进行,不会贴代码。因为如果贴代码的话,这文章没个几万字是结束不了的。想看代码的可以到代码仓去下载,文末会附上代码仓的地址。

服务暴露

ServiceBean

服务暴露的逻辑从ServiceBean开始,我们就先从ServiceBean开始实现我们的迷你版Dubbo。

我们的ServiceBean也是实现ApplicationListener<ContextRefreshedEvent>接口,在onApplicationEvent(ContextRefreshedEvent event)方法中调用export()方法触发服务暴露。

在这里插入图片描述

export()方法首先要读取注册中心的配置,Dubbo是通过RegistryConfig类型封装注册中心的配置的,我们也定义一个RegistryConfig类封装注册中心的配置,这里通过RegistryConfig获取到注册中心的ip地址和端口等信息。

Dubbo是通过URL对象去封装服务暴露的信息,比如协议、ip地址、端口、访问路径(调哪个类的哪个方法)、方法参数等信息,然后注册到注册中心的也是这个URL对象转成的url格式的字符串。我们这里也定义一个URL对象去存这些信息,我们组装我们的URL对象。

URL中包含的协议、端口等信息,可以通过ProtocolConfig进行配置,然后从ProtocolConfig中取出放入到URL对象中。

封装好URL对象后,接下来就是通过ProxyFactory(代理工厂)生成服务提供方的Invoker,这个Invoker保存了真正的服务实现类。

最后就是通过Protocol对象的export方法进行服务暴露,这里也是参考Dubbo的,Dubbo真正的服务暴露逻辑是封装在Protocol的export方法里面的,本地注册、开启Netty、注册相关信息到注册中心等动作都是在这里面进行。我们也定义一个Protocol接口。

这样看来,ServiceBean需要RegistryConfig和ProtocolConfig这两个对象,我们可以通过ApplicationContext去获取,因此ServiceBean需要实现ApplicationContextAware这个接口,通过这个接口获取到ApplicationContext,然后ServiceBean还要实现InitializingBean接口,在InitializingBean接口的afterPropertiesSet()方法里面通过ApplicationContext获取RegistryConfig和ProtocolConfig这两个对象。

这里的ApplicationContextAware、InitializingBean、ApplicationListener等接口都是Spring的接口,不熟悉的可以去补一下Spring的知识。

在这里插入图片描述

ProxyFactory#getInvoker

ProxyFactory.getInvoker(ref, this.interfaceClass, url) 这一步是封装具体实现类ref为Invoker的。这个Invoker的invoke方法会从Invocation中获取方法名、方法参数类型和方法参数,然后通过反射调用具体实现类ref。

在这里插入图片描述

Protocol#export

protocol.export(registryURL, invoker);这一步是真正进行服务暴露的,但是这里的protocol其实是一个代理类,Dubbo通过他自己实现的SPI机制,会调用到具体的Protocol实现类。我们这里也是一个代理类,使用的时JDK的动态代理,但是我们就不实现自己的SPI机制了,我们就使用Java的SPI机制。在InvocationHandler的invoke方法中通过Java的SPI机制加载所有的实现类,循环遍历进行匹配,匹配逻辑是看URL中的protocol属性,也就是url中的协议,然后反射调用匹配到的具体实现类。

在这里插入图片描述

现在的整体流程就走到这里:
在这里插入图片描述

RegistryProtocol#export

这里的URL的protocol属性是registry,因此这里匹配到的是RegistryProtocol类型的Protocol,于是会调用到RegistryProtocol的export方法。

RegistryProtocol的export方法又会再次调用protocol的export方法,这里的protocol依然是代理对象,但是这次的URL的protocol属性是dubbo,因此会调用到DubboProtocol的export方法。

DubboProtocol的export方法会把Invoker包装成Exporter,用接口类全限定名作为key,Exporter作为value,放入到一个map,这样当接收到远程调用请求时,就可以通过接口名找到对应的Invoker,进而调用里面的实现类。

DubboProtocol的export方法接下来会开启Netty服务端,用于接收远程调用请求。

DubboProtocol的export方法结束后返回到RegistryProtocol的export方法,接下来会调用注册中心的客户端把服务暴露信息注册到注册中心,比如注册中心是Zookeeper,则通过Zookeeper的客户端把服务暴露信息发布到Zookeeper注册中心。这里注册中心的具体类型还是通过动态代理加上Java的SPI机制来动态进行匹配的,而注册到注册中心的信息也是url格式的。

在这里插入图片描述

到这里,服务暴露的流程就结束了:
在这里插入图片描述

服务引入

服务引入的入口是在ReferenceBean的get()方法,我们ReferenceBean的get()的会返回一个代理对象,注入到被@Reference注解修饰的属性上。这里先不用管ReferenceBean的get()方法如何被调用到,我们先完成ReferenceBean的get()方法的服务引入逻辑。

ReferenceBean的get()方法第一步也是通过RegistryConfig获取到注册中心的ip和端口等信息。

然后调用Protocol的refer方法进行服务引入,这个方法会返回一个Invoker对象,这个Invoker对象的invoke方法会通过Netty向远端发起远程调用。

最后通过ProxyFactory的getProxy方法把Invoker封装到一个代理对象中,这里我们还是使用JDK的动态代理,InvocationHandler的invoke方法会调用Invoker的invoke方法。

在这里插入图片描述

RegistryProto#refer

Protocol的refer方法是真正进行服务引入的方法,这里还是通过动态代理,首先走到RegistryProto的refer方法

RegistryProto的refer方法返回的Invoker对象是比较复杂的,有个几层的嵌套。

考虑到服务提供者有可能是以集群的形式部署的,因此我们这里定义了一个ClusterInvoker类型的Invoker与之对应。ClusterInvoker包装了一个RegistryDirectory对象,它是一个服务目录,里面保存了一个List<Invoker>,这里的一个Invoker对应一个服务提供者。这样我们的服务就具备一定的容错能力,可以通过负载均衡选出一个Invoker,一个调不通,可以换下一个。

在这里插入图片描述

那List<Invoker>里面的Invoker是怎么来的呢?

首先,我们会监听注册中心,一旦服务提供者上下线导致注册中心中的信息有变动,我们会收到通知,收到通知后我们重新从注册中心中查询服务提供者的url。同时在创建RegistryDirectory对象时,RegistryDirectory的构造方法也会主动去注册中心查一次。

然后,我们定义了一个NotifyListener接口,我们的RegistryDirectory实现了NotifyListener接口,NotifyListener的notify方法接收从注册中心查询回来的url,循环调用protocol.refer(url, serviceType)方法。protocol.refer(url, serviceType)会调用到DubboProtocol的refer方法,DubboProtocol的refer方法就会返回与服务提供者对应的一个Invoker。

在这里插入图片描述

DubboProtocol#refer

DubboProtocol的refer方法会从参数url中解析出服务提供者的ip地址和端口号,开启Netty客户端,连接到服务提供者,然后包装成一个Invoker返回。

在这里插入图片描述

那么服务引入的整体逻辑就是这样:

在这里插入图片描述

服务调用

服务消费者

服务消费者的代理对象会通过InvocationHandler的invoke方法,调用到ClusterInvoker的invoke方法。

ClusterInvoker的invoke方法调用RegistryDirectory的list方法获取List<Invoker>,然后通过负载均衡算法选出一个进行调用,调用失败则切换下一个。

负载均衡选出一个Invoker后,调用Invoker的invoke方法就会进入到DubboInvoker的invoke方法。DubboInvoker的invoke方法把接口类全限定名、方法名、方法参数类型、方法参数等信息包装成Invocation对象,调用Netty客户端发送请求。

Netty客户端的编解码器会把Invocation对象序列化成二进制,然后发送到网络。

因为Netty是异步的,因此这里要创建一个Future对象然后绑定一个id,发送请求时把这id带上,服务提供者处理完后返回处理结果时同时返回这个id,服务消费者就能通过id拿到Future,设置返回结果到这个Future上,那么当时发送请求的线程就能通过这个Future拿到返回结果。

在这里插入图片描述

服务提供者

服务提供者的Netty接收到请求后,反序列成Invocation对象,根据Invocation中的接口类全限定名,从map中取出Exporter。然后从Exporter中取出Invoker,调用Invoker的invoke方法,Invoker的invoke方法从Invocation取出方法名,方法参数类型、方法参数,反射调用具体实现类,具体实现类处理完成后返回的结果,带上服务消费者传过来的id,一起返回给服务提供者。

在这里插入图片描述

与Spring对接

这些都写好以后,一个RPC框架大体就形成了,但是我们还差最后一步,就是与Spring对接。

我们参考Dubbo,定义了一个@EnableDubbo注解,@EnableDubbo注解通过@Import注解Spring容器导入一个DubboComponentScanRegistrar类,DubboComponentScanRegistrar实现了Spring的ImportBeanDefinitionRegistrar接口,重写了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,DubboComponentScanRegistrar的registerBeanDefinitions方法会被Spring回调到。DubboComponentScanRegistrar的registerBeanDefinitions方法往Spring容器中注入两个bean,一个是ServiceAnnotationBeanPostProcessor类型,实现了Spring的BeanDefinitionRegistryPostProcessor接口,用于扫描@Service注解然后注册BeanDefinition到Spring容器中;另一个是ReferenceAnnotationBeanPostProcessor类型,是一个Bean后置处理器,用于扫描@Reference注解进行服务引入的。

在这里插入图片描述

ServiceAnnotationBeanPostProcessor通过Spring的ClassPathBeanDefinitionScanner类扫描出@Service注解修饰的类的BeanDefinition,然后创建一个ServiceBean类型的BeanDefinition,配置相应的interfaceClass和ref等属性,然后注册到容器中。

ClassPathBeanDefinitionScanner是Spring提供的一个用来扫描指定包路径获取BeanDefinition的工具类。

在这里插入图片描述

ReferenceAnnotationBeanPostProcessor参考Spring处理@Autowired的逻辑,ReferenceAnnotationBeanPostProcessor继承了InstantiationAwareBeanPostProcessorAdapter,并重写了InstantiationAwareBeanPostProcessorAdapter的postProcessPropertyValues方法,ReferenceAnnotationBeanPostProcessor的postProcessPropertyValues方法会被Spring回调到,给当前bean进行属性注入,然后postProcessPropertyValues方法中收集当前bean中被@Reference注解修饰的属性,创建ReferenceBean,对ReferenceBean进行相应配置,调用ReferenceBean的get()方法返回一个代理对象,注入到该属性中。

InstantiationAwareBeanPostProcessorAdapter是Spring提供的一个Bean后置处理器,可以通过继承InstantiationAwareBeanPostProcessorAdapter并重写postProcessPropertyValues方法定制我们的属性注入逻辑。

在这里插入图片描述

代码

到这里,我们的RPC框架就大功告成了。基本上是参考Dubbo来写的,保留了Dubbo的核心逻辑,又去掉了一些细枝末节,比起原来的Dubbo代码简化了许多,但是确实能够帮助我们比较深刻的理解Dubbo的原理和逻辑。

本文章涉及的所有代码,都上传到git代码仓库了,由于篇幅有限,这些代码就不贴到本篇文章中了,有兴趣的可以到代码仓库下载下来看一下。

git代码仓库地址:https://gitcode.net/weixin_43889578/mini-dubbo

在这里插入图片描述

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

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

相关文章

matlab画双坐标图的样式

matlab画双坐标图的样式 %% clc,clear,close all; t0:0.1:9*pi; figure; [AX,Ha,Hb]plotyy(t,sin(t),t,exp(t)); % 绘图并创建句柄 % ----------------- 设置刻度 set(AX(1),yTick,[-1.250:0.25:1.25]) % 设置左边Y轴的刻度 set(AX(2),yTick,[0:50:350]) …

服务器连接github

https://zhuanlan.zhihu.com/p/543490354 比着这个一步步做就行。 https://blog.l0v0.com/posts/94ffdbdf.html 上传文件可以看这个 注意&#xff1a; 密钥ssh-keygen设置好之后&#xff0c;以后就不用每次输入账号密码才能访问了。 otherwise&#xff0c;每次要输入账号密码。…

文件批量改名方法:文件自动批量重命名,提升文件管理效率

在日常工作中随着工作时间的推移&#xff0c;在文件数量日益增长的情况下&#xff0c;会在电脑中积累大量的文件。如果文件名混乱无序&#xff0c;查找和识别重要文件将变得非常困难。这不仅会浪费大量的时间和精力&#xff0c;还可能导致重要文件的丢失或混乱。文件批量改名可…

高级驾驶辅助系统 (ADAS)介绍

随着汽车技术持续快速发展,推动更安全、更智能、更高效的驾驶体验一直是汽车创新的前沿。高级驾驶辅助系统( ADAS ) 是这场技术革命的关键参与者,是 指集成到现代车辆中的一组技术和功能,用于增强驾驶员安全、改善驾驶体验并协助完成各种驾驶任务。它使用传感器、摄像头、雷…

【数据结构实验】排序(三)快速排序算法的改进(三者取中法)

文章目录 1. 引言2. 快速排序算法2.1 传统快速排序2.2 三者取中法 3. 实验内容3.1 实验题目&#xff08;一&#xff09;输入要求&#xff08;二&#xff09;输出要求 3.2 算法实现 4. 实验结果 1. 引言 快速排序是一种经典的排序算法&#xff0c;其核心思想是通过选择一个基准元…

armbian折腾之docker搭建chatgptweb指导(无需魔法)

文章目录 前言面板/docker的安装获取中转Key创建docker容器chatgpt-next-web部署[推荐]chatgpt-Web部署 推荐学习openai-hk官方的部署指导 前言 好久都没有折腾armbian&#xff0c;导致吃了很长时间的灰&#xff0c;今天偶然看到B站UP主JeeJK007的搭建视频&#xff0c;便想着能…

小程序如何禁止指定用户访问?如何设置指定用户才能访问?

​有些商家为了价格保密或者实行严格的会员制等原因&#xff0c;希望小程序能够限制某些人的访问或者设置指定人员才能访问。这种功能在小程序中&#xff0c;怎么支持这些功能呢&#xff1f;下面具体介绍。 一、禁止指定用户访问 禁止指定用户访问&#xff0c;可以通过小程序…

智能汽车十大网络安全攻击场景-《智能汽车网络安全权威指南》

引言 大家都很熟悉OWASP Top 10风险报告&#xff0c;这个报告不但总结了Web应用程序最可能、最常见、最危险的10大安全隐患&#xff0c;还包括了如何消除这些隐患的建议&#xff0c;这个“OWASP Top 10“差不多每隔三年更新一次。目前汽车网络安全攻击威胁隐患繁多&#xff0c…

杰发科技AC7801——keil工程移植到IAR

0、简介 发现AC7801的代码只有keil工程的&#xff0c;IAR和Eclipse的代码只有一个例程&#xff0c;于是在从Keil移植到IAR时候遇到的问题记录下。 正常情况下&#xff0c;直接把keil的usr用户代码移植到iar的文件夹下面&#xff0c;删除原本的文件再添加新加进来的文件即可。…

链表?细!详细知识点总结!

链表 定义&#xff1a;链表是一种递归的数据结构&#xff0c;它或者为空&#xff08;null)&#xff0c;或者是指向一个结点&#xff08;node&#xff09;的引用&#xff0c;该结点含有一个泛型的元素和一个指向另一条链表的引用。 ​ 其实链表就是有序的列表&#xff0c;它在内…

03:2440--UART

目录 一:UART 1:概念 2:工作模式 3:逻辑电平 4:串口结构图 5:时间的计算 二:寄存器 1:简单的UART传输数据 A:GPHCON--配置引脚 B:GPHUP----使能内部上拉​编辑 C: UCON0---设置频率115200 D: ULCON0----数据格式8n1 E:发送数据 A:UTRSTAT0 B:UTXHO--发送数据输…

从0到0.01入门 Webpack| 004.精选 Webpack面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

ref详解(C#)

本质上来说 ref 的就是把 C/C 指针的那一套又拿回来了&#xff0c;而且还封装成一套自己的玩法。 我想设计者的初心把 ref 的功能限制得死死的&#xff0c;可能也考虑到 C# 是一门面向业务开发的语言&#xff0c;讲究的是做项目快狠准&#xff0c;性能反而不是第一要素&#x…

当你准备开始学习 Java 时,确保已完成以下准备工作,安装Java开发环境并验证通过。

当你准备开始学习 Java 时&#xff0c;确保已完成以下准备工作&#xff1a; a. 安装Java开发环境 下载Java Development Kit (JDK)&#xff1a; 访问Oracle官方网站&#xff0c;选择适用于你操作系统的JDK版本&#xff0c;点击下载。 安装JDK&#xff1a; 下载完成后&#xf…

3.1 CPU内部结构与时钟与指令

CPU内部结构 总线一些自定义部件总线图内存指令执行流程:取指令,译码,执行pc做的事内存地址寄存器内存缓存寄存器指令寄存器,译码第一步指令寄存器传递地址到内存地址寄存器指令MOV_A的过程(译码第二步)第一条指令执行完毕第三条指令的执行第四条指令第四条指令不同的执行流程…

基于python+TensorFlow+Django算法模型的车辆车型识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介简介技术栈主要模块1. 数据预处理2. 模型构建3. 模型训练4. 模型集成5. 用户界面 系统工作流程未来改进计划 二、功能三、系统四. 总结 一项目简介 # 车辆车…

深信服实验学习笔记——nmap常用命令

文章目录 1. 主机存活探测2. 常见端口扫描、服务版本探测、服务器版本识别3. 全端口&#xff08;TCP/UDP&#xff09;扫描4. 最详细的端口扫描5. 三种TCP扫描方式 1. 主机存活探测 nmap -sP <靶机IP>-sP代表 2. 常见端口扫描、服务版本探测、服务器版本识别 推荐加上-v参…

DNS/ICMP协议、NAT技术

目录 DNS协议DNS背景域名简介 ICMP协议ICMP功能ping命令traceroute命令 NAT技术NAT技术背景NAT IP转换过程NAPTNAT技术的缺陷NAT和代理服务器 网络协议总结应用层传输层网络层数据链路层 DNS协议 DNS&#xff08;Domain Name System&#xff0c;域名系统&#xff09;协议&…

基于51单片机的公交自动报站系统

**单片机设计介绍&#xff0c; 基于51单片机的公交自动报站系统 文章目录 一 概要公交自动报站系统概述工作原理应用与优势 二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 很高兴为您介绍基于51单片机的公交自动报站系统&#xff1a; 公交自动报…

吉他初学者学习网站搭建系列(1)——目录

文章目录 背景文章目录功能网站地址网站展示展望 背景 这个系列是对我最近周末搭建的吉他工具类平台YUERGS的总结。我个人业余爱好是自学吉他&#xff0c;我会在这个平台中动手集成我认为很有帮助的一些工具&#xff0c;来提升我的吉他水平和音乐素养&#xff0c;希望也可以帮…