SpringCloud OpenFeign 源码分析

news2024/9/24 6:22:06

前言

        由于公司正在高sky迁移,我们部门的老应用SpringBoot 1.x升级2.x,其中老的Neflix Feign也升级成了Spring Cloud OpenFeign,由于业务条线的各种高低版本以及服务之间调用等存在一些兼容性问题,于是看了下OpenFeign的源码,本文采用UML时序图+文字描述+代码描述+代码注释的形式来对OpenFeign的完整生命周期做剖析,希望能对大家老应用升级中遇到的类似服务调用问题提供一些帮助。

流程说明

  1. 服务启动类上添加@EnableFeignClients注解;

  2. 当程序启动时,会进行包扫描,扫描所有@FeignClients的注解的类,并且将这些信息注入Spring IOC容器中;

  3. 当定义的的Feign接口中的方法被实例化时,通过JDK动态代理方式从容器中获取到bean;

  4. 当服务被调用时,FeignClient 接口类的实例生成具体的RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate对象,该对象封装可HTTP请求需要的全部信息,如请求参数名,请求方法等信息都是在这个过程中确定的,然后RequestTemplate生成Request,然后把Request交给Client去处理,完成服务的调用。

源码分析

一、动态注册Bean

a75b0d3ff0444abdaaae387e9094ebd8.png

1、入口从启动类开始,引入了@EnableFeignClients注解,如下:

@Slf4j
@EnableFeignClients({"com.xxxx.xxxx.feign"})
@SpringBootApplication
@ImportResource(locations={"classpath:beanRefContext.xml"})
public class xxxxApplication {
	
	
    /**
     * 项目启动类
     *
     * @param args 启动参数
     */
    public static void main(String[] args) {
        BootApplication.run(log, false, xxxxApplication.class, args);
        String deployEnv = DeployEnvEnum.getDeployEnv();
        log.info("xxxxApplication start completed...  ");
    }
    
}

2、点进去,发现通过import注解导入了FeignClientsRegistrar配置类,代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] defaultConfiguration() default {};

    Class<?>[] clients() default {};
}

3、FeignClientsRegistrar 点进去,这个类实现了ImportBeanDefinitionRegistrar接口,重写registerBeanDefinitions方法,代码如下:

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

	// patterned after Spring Integration IntegrationComponentScanRegistrar
	// and RibbonClientsConfigurationRegistgrar

	private ResourceLoader resourceLoader;

	private Environment environment;

    //省略代码...

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //注册全局配置,解析EnableFeignClients注解上配置的defaultConfiguration属性
		registerDefaultConfiguration(metadata, registry);

        //扫描指定的所有包名下的被@FeignClient注解注释的接口,将扫描出来的接口调用registerFeignClient方法注册到spring容器
		registerFeignClients(metadata, registry);
	}

    //省略代码...

4、先执行registerDefaultConfiguration方法,如下:

private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //解析EnableFeignClients属性
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
            //注册客户端配置
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}

5、然后再执行registerFeignClients方法,如下:

	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {

		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();

        // 扫描带有FeignClient注解的类
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
        
        //获取@EnableFeignClients 中clients的值
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            // 如果没有设置,则扫描的包路径为 @EnableFeignClients 注解所在的包
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
            //设置了则使用注解属性来进行扫描注册
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

        //循环扫描注册
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
				Assert.isTrue(annotationMetadata.isInterface(),
						"@FeignClient can only be specified on an interface");

				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());

				String name = getClientName(attributes);
                //注册被调用客户端配置
				registerClientConfiguration(registry, name,
						attributes.get("configuration"));
                //注册 FeignClient
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

6、registerFeignClients方法到最后调用了registerFeignClient方法,registerFeignClient方法代码如下:

//注册 FeignClient,组装BeanDefinition,实质是一个FeignClientFactoryBean,然后注册到Spring IOC容器	
private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
        构建FeignClientFactoryBean类型的BeanDefinitionBuilder
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
        //将属性设置到 FeignClientFactoryBean中
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		definition.addPropertyValue("type", className);
		definition.addPropertyValue("decode404", attributes.get("decode404"));
		definition.addPropertyValue("fallback", attributes.get("fallback"));
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");

		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}
        //将BeanDefinition包装成BeanDefinitionHolder,用于注册
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
        //注册BeanDefinition
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

可以看到,这个方法就是将FeignClient注解上的属性信息,封装到BeanDefinition中,并注册到Spring容器中。但是在这个方法中,有一个关键信息,就是真实注册的FeignClientFactoryBean,它实现了FactoryBean接口,表明这是一个工厂bean,用于创建代理Bean,真正执行的逻辑是FactoryBeangetObject方法。至此,FeignClient的动态注册Bean就完成了。

二、实例初始化

243af59d2fda4615a1a21568459d236e.png

FeignClientFactoryBean 是工厂类, Spring 容器通过调用它的getObject 方法来获取对应的Bean 实例。被@FeignClient 修饰的接口类都是通过FeignClientFactoryBean 的getObject方法来进行实例化的。

1、先看方法getObject,如下:

	@Override
	public Object getObject() throws Exception {
		return getTarget();
	}

2、然后调用getTarget方法,代码如下:

    	<T> T getTarget() {
        //实例化Feign上下文对象FeignContext
		FeignContext context = applicationContext.getBean(FeignContext.class);
        //生成Builder对象,用来生成Feign
		Feign.Builder builder = feign(context);

        //如果url为空,则走负载均衡,生成有负载均衡功能的代理类
		if (!StringUtils.hasText(url)) {
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
            //@FeignClient没有配置url属性,返回有负载均衡功能的代理对象
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(type, name, url));
		}
        //如果指定了url,则生成默认的代理类
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
        //生成默认代理类
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(type, name, url));
	}

3、getTarget方法最后调用targeter.target方法,Targeter.target方法代码如下:

interface Targeter {

	<T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target);

}

其中target接口有两个实现类,如下截图:

d3c31d97995d418b81efc3b4dc940e15.png

4、我们没有使用Hystrix,走DefaultTargeter方法,代码如下:

class DefaultTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

}

5、然后feign.target点进去,代码如下:

        public <T> T target(Target<T> target) {
            return this.build().newInstance(target);
        }

6、然后newInstance方法点进去,如下:

public abstract <T> T newInstance(Target<T> var1);

7、然后调用ReflectiveFeign.newInstance,代码如下:

    public <T> T newInstance(Target<T> target) {
        // 解析接口注解信息,根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
        Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
        Method[] var5 = target.type().getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method method = var5[var7];
            if (method.getDeclaringClass() != Object.class) {
                if (Util.isDefault(method)) {
                    DefaultMethodHandler handler = new DefaultMethodHandler(method);
                    defaultMethodHandlers.add(handler);
                    methodToHandler.put(method, handler);
                } else {
                    methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
                }
            }
        }

        InvocationHandler handler = this.factory.create(target, methodToHandler);
        // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
        Iterator var12 = defaultMethodHandlers.iterator();

        while(var12.hasNext()) {
            DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
            defaultMethodHandler.bindTo(proxy);
        }

        return proxy;
    }

最后到这里生成了动态代理,把接口中的方法和默认实现放到Map<Method, MethodHandler>中,然后使用InvocationHandlerFactory.Default()创建InvocationHandler,最后使用jdk动态代理生成接口的代理并返回;

到这里二方服务定义的接口就被@EnableFeignClient通过动态代理的方式注入到了服务中;

三、服务调用

946b15e4f9c34a16b8a0fe8b3b1979e5.png

1、前面通过@Autowired或者@Resource注入的时候,注入的是被封装之后的代理类实现,jdk动态代理持有的是ReflectiveFeign.FeignInvocationHandler类型的InvocationHandler,那么具体调用的时候,会调用FeignInvocationHandler#invoke,代码如下:

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (!"equals".equals(method.getName())) {
                if ("hashCode".equals(method.getName())) {
                    return this.hashCode();
                } else {
                    // 利用分发器筛选方法,找到对应的handler 进行处理
                    return "toString".equals(method.getName()) ? this.toString() : ((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(args);
                }
            } else {
                try {
                    Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                    return this.equals(otherHandler);
                } catch (IllegalArgumentException var5) {
                    return false;
                }
            }
        }

2、然后在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) ;this.dispatch.get(method)会返回一个SynchronousMethodHandler,进行拦截处理。这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下。 看SynchronousMethodHandler中的invoke

    public Object invoke(Object[] argv) throws Throwable {
        //根据参数生成RequestTemplate对象
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
        Request.Options options = this.findOptions(argv);
        Retryer retryer = this.retryer.clone();

        while(true) {
            try {
                //通过RequestTemplate生成Request请求对象
                return this.executeAndDecode(template, options);
            } catch (RetryableException var9) {
                RetryableException e = var9;

                try {
                    retryer.continueOrPropagate(e);
                } catch (RetryableException var8) {
                    Throwable cause = var8.getCause();
                    if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                        throw cause;
                    }

                    throw var8;
                }

                if (this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }
    }

3、然后再看executeAndDecode()方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client(默认)获取response,来获取响应信息,代码如下:

    Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {
        //转化为Http请求报文
        Request request = this.targetRequest(template);
        if (this.logLevel != Level.NONE) {
            this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
        }

        long start = System.nanoTime();

        Response response;
        try {
            //发起远程通信
            response = this.client.execute(request, options);
            //获取返回结果
            response = response.toBuilder().request(request).requestTemplate(template).build();
        } catch (IOException var13) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var13, this.elapsedTime(start));
            }

            throw FeignException.errorExecuting(request, var13);
        }

        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        if (this.decoder != null) {
            return this.decoder.decode(response, this.metadata.returnType());
        } else {
            CompletableFuture<Object> resultFuture = new CompletableFuture();
            this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);

            try {
                if (!resultFuture.isDone()) {
                    throw new IllegalStateException("Response handling not done");
                } else {
                    return resultFuture.join();
                }
            } catch (CompletionException var12) {
                Throwable cause = var12.getCause();
                if (cause != null) {
                    throw cause;
                } else {
                    throw var12;
                }
            }
        }
    }

到此,整个流程就结束了...

总结

  1. FeignClientsRegistrar:扫描@FeignClient注解的接口并注册成BeanDefinition到应用容器
  2. FeignClientFactoryBean:注入远程服务接口代理实现
  3. FeignContext:维护服务维度上下文,存储服务调用相关工具
  4. Feign.Builder:用于构建服务调用的Client,维护服务调用相关组件(编解码器、重试组件、代理创建工厂等)
  5. Targeter:用于组装服务接口代理
  6. Feign:用于创建服务接口代理
  7. ReflectiveFeign:接口代理实现
  8. Client:封装网络调用工具,并提供请求调用能力(Default、ApacheHttpClient和OkHttpClient等)

引用

一文看懂Openfeign服务调用原理_叔牙的博客-CSDN博客_openfeign调用原理

Spring Cloud OpenFeign源码解析 - 知乎

Spring Cloud——OpenFeign源码解析 - 简书

Spring Cloud OpenFeign源码分析_51CTO博客_spring-cloud-starter-openfeign

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

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

相关文章

【Web逆向】某津市公共资源交易平台链接加密分析

【Web逆向】某津市公共资源交易平台链接加密分析声明一、起因二、开始分析三、源码下载地址声明 本文章中所有内容仅供学习交流&#xff0c;相关链接做了脱敏处理&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; 一、起因 好奇抓取 天津市公共资源交易平台 的…

这个WPF DataGrid组件,可以让业务数据管理更轻松!(Part 1)

DevExpress WPF的DataGrid组件是专用于WPF平台的高性能XAML网格控件&#xff0c;该组件附带了几十个具有高影响力的功能&#xff0c;使开发者可以轻松地管理数据并根据也无需要在屏幕上显示数据。PS&#xff1a;DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚…

Telerik JustMock 2023 R1 Crack

Telerik JustMock 2023 R1 Crack 制作单元测试的最快、最灵活和模拟选项。 Telerik JustLock也很简单&#xff0c;可以使用一个模拟工具来帮助您更快地生成更好的单元测试。JustLock使您更容易创建对象并建立对依赖关系的期望&#xff0c;例如&#xff0c;互联网服务需求、数据…

常用的xpath

一、xpath 语法 简单看一下菜鸟教程即可 1、基本语法 XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。 下面列出了最有用的路径表达式&#xff1a; 表达式描述nodename选取此节点的所有子节点。/从根节点选取&#xff08;取子节点&#…

数据挖掘(1)--基础知识学习

前言 自20世纪90年代以来&#xff0c;随着数据库技术应用的普及&#xff0c;数据挖掘&#xff08; Data Mining &#xff09;技术已经引起了学术界、产业界的极大关注&#xff0c;其主要原因是当前各个单位已经存储了超大规模&#xff0c;即海量规模的数据&#xff0c;未来能够…

【设计模式】5.原型模式

概述 用一个已经创建的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型对象相同的新对象。 结构 原型模式包含如下角色&#xff1a; 抽象原型类&#xff1a;规定了具体原型对象必须实现的的 clone() 方法。具体原型类&#xff1a;实现抽象原型类的 clone() 方…

【IP技术】ipv4和ipv6是什么?

IPv4和IPv6是两种互联网协议&#xff0c;用于在互联网上标识和寻址设备。IPv4&#xff08;Internet Protocol version 4&#xff09;是互联网协议的第四个版本&#xff0c;是当前广泛使用的互联网协议。IPv4地址由32位二进制数构成&#xff0c;通常表示为4个十进制数&#xff0…

使用python测试框架完成自动化测试并生成报告-实例练习

练习一: 使用unittest 完成自动化测试并使用HttpTestRunner生成报告 1、写个简单的计算器功能&#xff0c;大小写转换功能&#xff0c;随机生成字符串功能 2、编写测试用例&#xff0c;不同的数据&#xff08;你能想到的所有测试用例&#xff09;&#xff0c;并进行断言。除0的…

GitHub 标星 15w,如何用 Python 实现所有算法?

学会了 Python 基础知识&#xff0c;想进阶一下&#xff0c;那就来点算法吧&#xff01;毕竟编程语言只是工具&#xff0c;结构算法才是灵魂。 新手如何入门 Python 算法&#xff1f; 几位印度小哥在 GitHub 上建了一个各种 Python 算法的新手入门大全。从原理到代码&#xf…

蒸汽波风格图不会画?AI作画帮你秒级出图

在最近几年&#xff0c;AI图像生成技术在视觉艺术领域得到了广泛应用&#xff0c;可以通过数据和机器学习技术来创作出非常有趣的艺术品。今天我就带来的图片风格是&#xff1a;蒸汽波混搭二次元。 蒸汽波混搭二次元图像的创作&#xff0c;大都是以现有的二次元图像作为基础&a…

配置CentOS 7

1&#xff1a;配置CentOS71.1&#xff1a;安装需注意如果出现类似提示&#xff0c;勾选复选框&#xff0c;并点击确定。1.2&#xff1a;选择安装项选择开始安装后&#xff0c;需等待….1.3&#xff1a;选择语言1.4&#xff1a;安装信息摘要大部分采用默认项设置自动分区网络设置…

筑基八层 —— 问题思考分析并解决

目录 零&#xff1a;移步 一.修炼必备 二.问题思考&#xff08;先思考&#xff09; 三.问题解答 零&#xff1a;移步 CSDN由于我的排版不怎么好看&#xff0c;我的有道云笔记相当的美观&#xff0c;请移步有道云笔记 一.修炼必备 1.入门必备&#xff1a;VS2019社区版&#x…

华为OD机试模拟题 用 C++ 实现 - 双十一(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明双十一题目输入输出示例一输入输出示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 …

Linux系统基础命令(一)

一、图形界面和终端界面 图形界面&#xff1a;是指采用图形方式显示的计算机操作用户界面。 终端界面&#xff1a;是指黑底白字的命令行界面。 什么是tty呢&#xff1f; tty&#xff1a;终端设备的统称。 tty一词源于Teletypes&#xff0c;或者teletypewriters&#xff0c;…

高精度真空度程序控制在真空解冻过程中的应用

摘要&#xff1a;为了解决目前各种真空冷冻过程中存在的真空压力还是人工手动调节&#xff0c;无法进行可编程准确自动控制的问题&#xff0c;本文提出了具体解决方案。解决方案的基本原理是根据动态平衡法&#xff0c;具体实现是依据不同的真空压力设定值分别采用电动针阀调节…

WebRTC入门与提高-WebRTC原理(STUN/TURN/SDP)

1 WebRTC入门本章目的&#xff1a;&#xff08;1&#xff09;了解什么WebRTC&#xff08;2&#xff09;掌握WebRTC通话原理&#xff08;3&#xff09;学完该课程的收获1.1 什么是WebRTCWebRTC&#xff08;Web Real-Time Communication&#xff09;是 Google于2010以6829万美元从…

华为OD机试模拟题 用 C++ 实现 - 旋转骰子(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明旋转骰子题目输入输出示例一输入输出示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。…

C++ Primer Plus 第6版 读书笔记(3) 第3章 处理数据

目录 3.1 简单变量 3.1.1 变量名 *位与字节 3.1.4 无符号类型 3.1.7 C如何确定常量的类型 C是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言&#xff0c;是C语言的超集。本书是根据2003年的ISO/ANSI C标准编写的&#xff0c;通过大量短…

linux基本功系列之free命令实战

文章目录前言一. free命令介绍二. 语法格式及常用选项三. 参考案例3.1 查看free相关的信息3.2 以MB的形式显示内存的使用情况3.3 以总和的形式显示内存的使用情况3.4 周期性的查询内存的使用情况3.5 以更人性化的形式来查看内存的结果输出总结前言 大家好&#xff0c;又见面了…

音视频开发 RTMP协议发送H.264编码及AAC编码的音视频(C++实现)

RTMP&#xff08;Real Time Messaging Protocol&#xff09;是专门用来传输音视频数据的流媒体协议&#xff0c;最初由Macromedia 公司创建&#xff0c;后来归Adobe公司所有&#xff0c;是一种私有协议&#xff0c;主要用来联系Flash Player和RtmpServer&#xff0c;如FMS,Red5…