Dubbo源码解析之@DubboService、@DubboReference(Dubbo源码一)

server/2024/9/23 9:25:55/

更好的阅读体验:Dubbo源码解析之@DubboService、@DubboReference(Dubbo源码一)
视频讲解:https://www.bilibili.com/video/BV1nBsMejEbL



对于Dubbo用的最多的就是@DubboService、@DubboReference,与之对应的就是服务的提供方、调用方。

之所以加上注解就可以运行,定是生成了代理对象,这篇文章就来讲讲如何基于这两个注解生成代理对象。


不管是服务端还是客户端,在使用Dubbo的时候都会先使用@EnableDubbo,比如下面的demo

@SpringBootApplication
@EnableDubbo
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}
}

@EnableDubbo 是一个组合注解,它头上还有@DubboComponentScan和@EnableDubboConfig,它们就是分别来解析@DubboService、@DubboReference


EnableDubbo

@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {}

DubboComponentScan

@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {}

EnableDubboConfig

@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {}

一、@DubboReference 注解解析


DubboConfigConfigurationRegistrar 里面注入了一个bean处理器 ReferenceAnnotationBeanPostProcessor,这个处理器会把带有下面三个注解的bean代理成一个 ReferenceBean

  • DubboReference
  • Reference
  • com.alibaba.dubbo.config.annotation.Reference

1、入口


入口跳的很多,全部把代码复制过来阅读体验不好,也全无必要,这里给出每个方法的关键代码

EnableDubbo

@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {}

EnableDubboConfig

@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {}

DubboConfigConfigurationRegistrar

public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// initialize dubbo beansDubboSpringInitializer.initialize(registry);}
}

DubboSpringInitializer

public static void initialize(BeanDefinitionRegistry registry) {// ...// init dubbo contextinitContext(context, registry, beanFactory);
}private static void initContext(DubboSpringInitContext context, BeanDefinitionRegistry registry,ConfigurableListableBeanFactory beanFactory) {// ...// register common beansDubboBeanUtils.registerCommonBeans(registry);
}

DubboBeanUtils

static void registerCommonBeans(BeanDefinitionRegistry registry) {// ...// Since 2.5.7 Register @Reference Annotation Bean Processor as an infrastructure BeanregisterInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME,ReferenceAnnotationBeanPostProcessor.class);// ...
}

2、bean处理器


所谓bean的后置处理器,其实就是在bean完成初始化之后,就会调用这个方法,给你所有的bean,然后你就可以对这个bean为所欲为了

bean处理器有很多,有前置处理器、后置处理器、bean工厂处理器


2-1、前、后置处理器

只需要实现BeanPostProcessor 接口,重写里面的前置、后置处理器就好了,系统会把每个bean作为参数去调用这个方法

public interface BeanPostProcessor {@Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}
}

2-2、bean工厂处理器

上面的前后置处理器,是系统拿到bean去调用这个方法,bean工厂处理器直接把bean工厂给你

public interface BeanFactoryPostProcessor {void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;}

3、循环所有的bean,找到使用注解的bean生成代理对象


ReferenceAnnotationBeanPostProcessor实现了BeanFactoryPostProcessor,重写了postProcessBeanFactory,在这个方法里面对使用了注解的bean进行代理

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// 遍历所有的beanString[] beanNames = beanFactory.getBeanDefinitionNames();for (String beanName : beanNames) {Class<?> beanType;// 判断是不是工厂bean,显然我们不是if (beanFactory.isFactoryBean(beanName)) {// ...} else {// 走这个、拿到classbeanType = beanFactory.getType(beanName);}if (beanType != null) {// 找到需要被代理的 metadataAnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);try {// 进行代理生成prepareInjection(metadata);} catch (BeansException e) {throw e;} catch (Exception e) {throw new IllegalStateException("Prepare dubbo reference injection element failed", e);}}}
}

4、找到合适的bean


匹配逻辑,其实就是拿每一个Class 上面的注解,看看是否和DubboReference相关注解匹配,下面代码给出大概步骤,忽略一些判断逻辑

setp 1
protected AnnotatedInjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {// ...try {// 走这里构建metadata = buildAnnotatedMetadata(clazz);this.injectionMetadataCache.put(cacheKey, metadata);} catch (NoClassDefFoundError err) {throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() +"] for annotation metadata: could not find class that it depends on", err);} // ...return metadata;
}
setp 2
private AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {// 这个方法就会找到合适匹配的beanCollection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);return new AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}
setp 3
private List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {final List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> elements = new LinkedList<>();ReflectionUtils.doWithFields(beanClass, field -> {for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {AnnotationAttributes attributes = AnnotationUtils.getAnnotationAttributes(field, annotationType, getEnvironment(), true, true);if (attributes != null) {if (Modifier.isStatic(field.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn(CONFIG_DUBBO_BEAN_INITIALIZER, "", "", "@" + annotationType.getName() + " is not supported on static fields: " + field);}return;}elements.add(new AnnotatedFieldElement(field, attributes));}}});return elements;
}

getAnnotationTypes 就是在初始化的时候Set进去的

public ReferenceAnnotationBeanPostProcessor() {super(DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class);
}public AbstractAnnotationBeanPostProcessor(Class<? extends Annotation>... annotationTypes) {Assert.notEmpty(annotationTypes, "The argument of annotations' types must not empty");this.annotationTypes = annotationTypes;
}

5、注册 ReferenceBean类型的 beanDefinition


让我们再回到 ReferenceAnnotationBeanPostProcessor#postProcessBeanFactory 在这个方法里面循环每一个bean看它是否用到了 DubboReference 相关注解,如果有就进行代理对象

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {String[] beanNames = beanFactory.getBeanDefinitionNames();for (String beanName : beanNames) {// ...if (beanType != null) {// 找到满足代理的bean,上一节已经讲了AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);try {// 进行代理prepareInjection(metadata);}}// ... }
}

protected void prepareInjection(AnnotatedInjectionMetadata metadata) throws BeansException {try {//find and register bean definition for @DubboReference/@Referencefor (AnnotatedFieldElement fieldElement : metadata.getFieldElements()) {if (fieldElement.injectedObject != null) {continue;}Class<?> injectedType = fieldElement.field.getType();AnnotationAttributes attributes = fieldElement.attributes;// 注册代理beanString referenceBeanName = registerReferenceBean(fieldElement.getPropertyName(), injectedType, attributes, fieldElement.field);//associate fieldElement and reference beanfieldElement.injectedObject = referenceBeanName;injectedFieldReferenceBeanCache.put(fieldElement, referenceBeanName);}// ...} catch (ClassNotFoundException e) {throw new BeanCreationException("prepare reference annotation failed", e);}
}

registerReferenceBean 方法很长,前面有一系列的校验和缓存处理,这里为了简便,只给出后面注册 beanDefinition 部分

public String registerReferenceBean(String propertyName, Class<?> injectedType, Map<String, Object> attributes, Member member) throws BeansException {// ... 省略一大段校验缓存逻辑// Register the reference bean definition to the beanFactoryRootBeanDefinition beanDefinition = new RootBeanDefinition();// 看看看看,找到了,注册的这个 beanDefinition ,是 ReferenceBean.classbeanDefinition.setBeanClassName(ReferenceBean.class.getName());// ... 省略赋值的逻辑// 注册BeanDefinition, 被注册的 BeanDefinition,会生成一个对象beanDefinitionRegistry.registerBeanDefinition(referenceBeanName, beanDefinition);logger.info("Register dubbo reference bean: " + referenceBeanName + " = " + referenceKey + " at " + member);return referenceBeanName;
}

6、生成代理对象 ReferenceBean


ReferenceBean 是一个FactoryBean,所以在需要这个bean的时候会调用getObject 获取bean

ReferenceBean<T> implements FactoryBean<T>,ApplicationContextAware, BeanClassLoaderAware, BeanNameAware, InitializingBean, DisposableBean 
@Override
public T getObject() {if (lazyProxy == null) {createLazyProxy();}return (T) lazyProxy;
}

private void createLazyProxy() {// ....// 这里默认走的就是Javassist代理if (StringUtils.isEmpty(this.proxy) || CommonConstants.DEFAULT_PROXY.equalsIgnoreCase(this.proxy)) {generateFromJavassistFirst(interfaces);}if (this.lazyProxy == null) {generateFromJdk(interfaces);}
}

好啦,到这里代理对象就生成完毕了,这里看到它在代理对象里面注册了一个 LazyTargetInvocationHandler,它本质上是一个 InvocationHandler,所以当执行这个代理对象的时候就会执行org.apache.LazyTargetInvocationHandler#invoke 这就行下一期要讲的,运行原理

private void generateFromJavassistFirst(List<Class<?>> interfaces) {try {this.lazyProxy = Proxy.getProxy(interfaces.toArray(new Class[0])).newInstance(new LazyTargetInvocationHandler(new DubboReferenceLazyInitTargetSource()));} catch (Throwable fromJavassist) {// ...}
}

二、@DubboService 注解解析


相较于@DubboReference,@DubboService的解析要复杂不少,先是扫描到带有注解的实体,生成 ServiceBean,再基于ServiceBean生成CallbackRegistrationInvoker对象


1、入口


@EnableDubbo > @DubboComponentScan > DubboComponentScanRegistrar.class

DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar,重写了registerBeanDefinitions,这个方法里面就注入了Service处理器,通过扫描包下面使用相关注解的bean

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// initialize dubbo beansDubboSpringInitializer.initialize(registry);Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);// 注册Service处理器registerServiceAnnotationPostProcessor(packagesToScan, registry);
}

2、bean 处理器


通过扫描指定包下的bean来筛选是否满足生成 ServiceBean

org.apache.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationPostProcessor#postProcessBeanDefinitionRegistry

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {this.registry = registry;scanServiceBeans(resolvedPackagesToScan, registry);
}

scanServiceBeans

看看这里把 serviceAnnotationTypes 装进去了,下面判断的时候会用到

private void scanServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {// 循环每个包for (String packageToScan : packagesToScan) {DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);// 🌈🌈🌈🌈构建需要包含哪些bean, serviceAnnotationTypes 就是上面定义的注解for (Class<? extends Annotation> annotationType : serviceAnnotationTypes) {scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));}// 扫描每个包scanner.scan(packageToScan);// 找到合适的 BeanDefinitionSet<BeanDefinitionHolder> beanDefinitionHolders =findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);// 循环每一个合适的 BeanDefinitionfor (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {// 生成 ServiceBeanprocessScannedBeanDefinition(beanDefinitionHolder);servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName());}servicePackagesHolder.addScannedPackage(packageToScan);}
}

serviceAnnotationTypes

private final static List<Class<? extends Annotation>> serviceAnnotationTypes = asList(// @since 2.7.7 Add the @DubboService , the issue : https://github.com/apache/dubbo/issues/6007DubboService.class,// @since 2.7.0 the substitute @com.alibaba.dubbo.config.annotation.ServiceService.class,// @since 2.7.3 Add the compatibility for legacy Dubbo's @Service , the issue : https://github.com/apache/dubbo/issues/4330com.alibaba.dubbo.config.annotation.Service.class
);

3、找到合适的 BeanDefinition


这里寻找的过程有一点绕,还是和之前一起,给出每个关键节点的关键代码

本质上就是扫描包下面的BeanDefinition看看是否有包含 @DubboService相关注解


findServiceBeanDefinitionHolders

org.apache.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationPostProcessor#findServiceBeanDefinitionHolders

private Set<BeanDefinitionHolder> findServiceBeanDefinitionHolders(ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,BeanNameGenerator beanNameGenerator) {Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageToScan);Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<>(beanDefinitions.size());for (BeanDefinition beanDefinition : beanDefinitions) {String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);beanDefinitionHolders.add(beanDefinitionHolder);}return beanDefinitionHolders;}

findCandidateComponents

org.apache.dubbo.config.spring.context.annotation.DubboClassPathBeanDefinitionScanner#findCandidateComponents

@Override
public Set<BeanDefinition> findCandidateComponents(String basePackage) {Set<BeanDefinition> beanDefinitions = beanDefinitionMap.get(basePackage);// if beanDefinitions size is null => scanif (Objects.isNull(beanDefinitions)) {beanDefinitions = super.findCandidateComponents(basePackage);beanDefinitionMap.put(basePackage, beanDefinitions);}return beanDefinitions;
}

scanCandidateComponents

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet();// ...MetadataReader metadataReader = this.getMetadataReaderFactory().getMetadataReader(resource);if (this.isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);// 判断当前bean是不是满足if (this.isCandidateComponent((AnnotatedBeanDefinition)sbd)) {if (debugEnabled) {this.logger.debug("Identified candidate component class: " + resource);}candidates.add(sbd);} else if (debugEnabled) {this.logger.debug("Ignored because not a concrete top-level class: " + resource);}} // ...return candidates;
}

isCandidateComponent

includeFilters 数据就是在scanServiceBeans 里面设置进去的

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.core.type.classreading.MetadataReader)

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {Iterator var2 = this.excludeFilters.iterator();TypeFilter tf;do {if (!var2.hasNext()) {var2 = this.includeFilters.iterator();do {if (!var2.hasNext()) {return false;}tf = (TypeFilter)var2.next();} while(!tf.match(metadataReader, this.getMetadataReaderFactory()));return this.isConditionMatch(metadataReader);}tf = (TypeFilter)var2.next();} while(!tf.match(metadataReader, this.getMetadataReaderFactory()));return false;
}

4、生成ServiceBean


org.apache.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationPostProcessor#processScannedBeanDefinition


private void processScannedBeanDefinition(BeanDefinitionHolder beanDefinitionHolder) {Class<?> beanClass = resolveClass(beanDefinitionHolder);Annotation service = findServiceAnnotation(beanClass);// The attributes of @Service annotationMap<String, Object> serviceAnnotationAttributes = AnnotationUtils.getAttributes(service, true);String serviceInterface = resolveInterfaceName(serviceAnnotationAttributes, beanClass);String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();// ServiceBean Bean nameString beanName = generateServiceBeanName(serviceAnnotationAttributes, serviceInterface);AbstractBeanDefinition serviceBeanDefinition =buildServiceBeanDefinition(serviceAnnotationAttributes, serviceInterface, annotatedServiceBeanName);registerServiceBeanDefinition(beanName, serviceBeanDefinition, serviceInterface);}

生成beanName

private String generateServiceBeanName(Map<String, Object> serviceAnnotationAttributes, String serviceInterface) {ServiceBeanNameBuilder builder = create(serviceInterface, environment).group((String) serviceAnnotationAttributes.get("group")).version((String) serviceAnnotationAttributes.get("version"));return builder.build();
}

构建bean参数

定义 BeanDefinition 的class,并且解析注解上的参数,放入BeanDefinition

private AbstractBeanDefinition buildServiceBeanDefinition(Map<String, Object> serviceAnnotationAttributes,String serviceInterface,String refServiceBeanName) {// 设置class                                                            BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();String[] ignoreAttributeNames = ObjectUtils.of("provider", "monitor", "application", "module", "registry", "protocol","methods", "interfaceName", "parameters", "executor");propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotationAttributes, environment, ignoreAttributeNames));addPropertyReference(builder, "ref", refServiceBeanName);// Set interfacebuilder.addPropertyValue("interface", serviceInterface);// ... 设置各种参数return builder.getBeanDefinition();
}

注册到 BeanDefinitionRegistry

private void registerServiceBeanDefinition(String serviceBeanName, AbstractBeanDefinition serviceBeanDefinition, String serviceInterface) {// check service bean// ...registry.registerBeanDefinition(serviceBeanName, serviceBeanDefinition);// ...
}

5、ServiceBean


一般生成代理对象,都会有一个 invoker方法,但ServiceBean没有,这是因为ServiceBean 只是存储DubboService的相关信息,后面还会生成一个真正invoker。


先来看一下ServiceBean的继承关系

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware {}

真正的invoker是在 ServiceConfig中生成的


ServiceBean 重写了 afterPropertiesSet ,把自己注册到了moduleConfigManager(这个后面会看到)
@Override
public void afterPropertiesSet() throws Exception {if (StringUtils.isEmpty(getPath())) {if (StringUtils.isNotEmpty(getInterface())) {setPath(getInterface());}}//register service beanModuleModel moduleModel = DubboBeanUtils.getModuleModel(applicationContext);moduleModel.getConfigManager().addService(this);moduleModel.getDeployer().setPending();
}

6、invoker生成


Dubbo注册了一个事件监听器,这个事件监听器会做很多初始化的工作,就会到生成Invoker的地方,这里的调用链路太长了,给一个Debug的图

在这里插入图片描述


最终会到当前类的org.apache.dubbo.config.ServiceConfig#doExportUrl方法

private void doExportUrl(URL url, boolean withMetaData, RegisterTypeEnum registerType) {if (!url.getParameter(REGISTER_KEY, true)) {registerType = RegisterTypeEnum.MANUAL_REGISTER;}if (registerType == RegisterTypeEnum.NEVER_REGISTER ||registerType == RegisterTypeEnum.MANUAL_REGISTER ||registerType == RegisterTypeEnum.AUTO_REGISTER_BY_DEPLOYER) {url = url.addParameter(REGISTER_KEY, false);}Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);if (withMetaData) {invoker = new DelegateProviderMetaDataInvoker(invoker, this);}Exporter<?> exporter = protocolSPI.export(invoker);exporters.computeIfAbsent(registerType, k -> new CopyOnWriteArrayList<>()).add(exporter);
}

interfaceClass 取的是刚刚解析ServiceBean来的,也就是使用DubboService注解的类,invoker 返回的是Java生成的代理对象,Dubbo会再把它包装一层,所谓的包装就是用 Filter去包它,一层层的。最终生成 CallbackRegistrationInvoker。


在这里插入图片描述


withMetaData: 每一个使用DubboService注解的对象都会生成两个代理对象,一个正常的代理对象,用来执行服务,一个 MetaData 类型的代理对象,用来维护基础数据


后续Netty收到请求,就会转而找到对应的 Invoker。


http://www.ppmy.cn/server/107392.html

相关文章

高效的时间序列可视化:减少认知负荷获得更清晰的洞察

可视化时间序列数据是具有挑战性,尤其是涉及多个数据集时。精心设计的可视化不仅能清晰地传达信息,还能减少观察者的认知负荷,使其更容易提取有意义的洞察。 在本文中,我们将探讨使真实世界的疫苗接种数据来可视化单个时间序列和多个时间序列。 数据可视化中认知负荷的重要性 …

从etcd学习raft

在etcd的项目下有一个使用raft的示例&#xff0c;在之前读etcd代码的时候会比较难理解raft相关的代码。因此通过这个示例会更容易的了解raft相关的实现细节。 我将这部分代码推送到了我的git仓库&#xff1a;https://github.com/yugu2day/raftexample 在示例中&#xff0c;主…

NVIDIA将在Hot Chips 2024会议上展示Blackwell服务器装置

NVIDIA 将在 Hot Chips 2024 上展示其 Blackwell 技术堆栈&#xff0c;并在本周末和下周的主要活动中进行会前演示。对于 NVIDIA 发烧友来说&#xff0c;这是一个激动人心的时刻&#xff0c;他们将深入了解NVIDIA的一些最新技术。然而&#xff0c;Blackwell GPU 的潜在延迟可能…

字母的大小写转换(tolower、toupper、transform)

字母的大小写转换&#xff08;tolower、toupper、transform&#xff09; 1. tolower&#xff08;&#xff09;、toupper&#xff08;&#xff09;函数 &#xff08;这个在之前的一篇文章 “字符串中需要掌握的函数总结&#xff08;1&#xff09;”中有较为详细的介绍。&#…

解决 JS WebSocket 心跳检测 重连

解决 JS WebSocket 心跳检测 重连 文章目录 解决 JS WebSocket 心跳检测 重连一、WebSocket 心跳检测的作用二、心跳检测的处理方案1. 创建 WebSocket 连接2. 心跳参数设置3. 心跳检测逻辑4. 心跳包响应处理5. 断线重连机制 三、总结 一、WebSocket 心跳检测的作用 WebSocket 是…

【网络】网络层协议——IP协议

目录 1.TCP和IP的关系 2.IP协议报文 2.1. 4位首部长度&#xff0c;16位总长度&#xff0c;8位协议 2.2. 8位生存时间 &#xff0c;32位源IP地址和32位目的IP地址 3.IP地址的划分 3.1.IP地址的表现形式 3.2.旧版IP地址的划分 3.2.1.旧版IP地址的划分思路 3.2.2.分类划…

【虚拟化】KVM常用命令操作(virsh虚拟机常用操作之开关|连接|自启|克隆|快照)

目录 ​编辑一、KVM概述 1.1 KVM工具栈 1.2 libvirt架构概述 二、使用virsh管理虚拟机 三、kvm基本功能管理 1.帮助命令 2.KVM的配置文件存放目录 3.查看虚拟机状态 4.虚拟机关机与开机 5.强制虚拟机系统关闭电源 6.通过配置文件启动虚拟机系统 7.修改虚拟机配置文…

C++操作excel,即使函数设置了不备份,但保存后,excel依然会自动生成备份文件的原因分析,及如何来禁止自动备份

开发环境 操作系统&#xff1a;windows 10 编译器&#xff1a;Visual Studio 2010、2015、2017、2022 office 2016、2019、2021 wps 2019、2024 问题描述 通过C操作excel&#xff0c;保存后&#xff0c;excel会自动生成备份文件。 void CExcelDemoDlg::OnBnClickedButton1() …