源码篇--Nacos服务--中章(5):Nacos客户端启动-实例注册-grpc连接建立

文章目录

  • 前言
  • 一、 前奏:
  • 二、客户端连接的建立:
    • 2.1 NacosNamingService 创建:
    • 2.2 NacosNamingService 初始化:
    • 2.3 NamingClientProxyDelegate 长连接建立:
      • 2.3.1 grpc 代理对象创建:
      • 2.3.2 NamingGrpcClientProxy grpc:
        • 2.3.2.1 createClient 客户端的创建:
        • 2.3.2.2 start 长连接建立:
  • 三、客户端实例的注册:
    • 3.1 NamingGrpcClientProxy# registerService:
    • 3.2 客户端发送注册请求:
  • 总结


前言

本文对Nacos 客户端启动时,同服务端建立长连接的过程进行介绍。环境:客户端版本2.2.1,服务端版本 3.0.13;


一、 前奏:

实际客户端同服务端进行grpc 通道的建立,是在客户端实例注册过程中进行的,因为注册肯定要向服务端发送请求,所以要先通过grpc 完成通道的建立 ;一下对客户端实例的注册流程进行简单介绍。
流程图:
在这里插入图片描述
流程步骤解释:

  • 客户端所在web 应用启动完成,发布 WebServiceInitializedEvent 事件;
  • AbstractAutoServiceRegistration ,onApplicationEvent 方法接收事件 并调用start 方法;
public void onApplicationEvent(WebServerInitializedEvent event) {
  ApplicationContext context = event.getApplicationContext();
   if (!(context instanceof ConfigurableWebServerApplicationContext) || !"management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace())) {
   	  // 本机web 端口 
       this.port.compareAndSet(0, event.getWebServer().getPort());
       // 初始化方法调用
       this.start();
   }
}

start() 方法:

public void start() {
    if (!this.isEnabled()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Discovery Lifecycle disabled. Not starting");
        }

    } else {
        if (!this.running.get()) {
        	// 发布 实例注册 前事件
            this.context.publishEvent(new InstancePreRegisteredEvent(this, this.getRegistration()));
            this.registrationLifecycles.forEach((registrationLifecycle) -> {
                registrationLifecycle.postProcessBeforeStartRegister(this.getRegistration());
            });
            // 实例注册方法调用
            this.register();
            this.registrationLifecycles.forEach((registrationLifecycle) -> {
                registrationLifecycle.postProcessAfterStartRegister(this.getRegistration());
            });
            if (this.shouldRegisterManagement()) {
                this.registrationManagementLifecycles.forEach((registrationManagementLifecycle) -> {
                    registrationManagementLifecycle.postProcessBeforeStartRegisterManagement(this.getManagementRegistration());
                });
                this.registerManagement();
                this.registrationManagementLifecycles.forEach((registrationManagementLifecycle) -> {
                    registrationManagementLifecycle.postProcessAfterStartRegisterManagement(this.getManagementRegistration());
                });
            }
			 // 实例注册完成事件
            this.context.publishEvent(new InstanceRegisteredEvent(this, this.getConfiguration()));
            this.running.compareAndSet(false, true);
        }

    }
}
  • AbstractAutoServiceRegistration ,register() 方法 调用到 NacosServiceRegistry 的 register 方法;
  • namingService.registerInstance 进行客户端的注册;
 public void register(Registration registration) {
    if (StringUtils.isEmpty(registration.getServiceId())) {
        log.warn("No service to register for nacos client...");
    } else {
    	// 客户端长连接的建立
        NamingService namingService = this.namingService();
        String serviceId = registration.getServiceId();
        String group = this.nacosDiscoveryProperties.getGroup();
        // 构建客户端实例对象
        Instance instance = this.getNacosInstanceFromRegistration(registration);

        try {
             // grpc 发送 客户端实例注册请求
            namingService.registerInstance(serviceId, group, instance);
            log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});
        } catch (Exception var7) {
            if (this.nacosDiscoveryProperties.isFailFast()) {
                log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var7});
                ReflectionUtils.rethrowRuntimeException(var7);
            } else {
                log.warn("Failfast is false. {} register failed...{},", new Object[]{serviceId, registration.toString(), var7});
            }
        }

    }
}

二、客户端连接的建立:

NamingService namingService = this.namingService(); 这行代码做了很多事情,其中需要重点关注的时 客户端与服务端连接的建立,以及客户端的故障转移机制,下文先对连接的建立进行介绍;

2.1 NacosNamingService 创建:

public static NamingService createNamingService(Properties properties) throws NacosException {
    try {
        Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
        // 调用 NacosNamingService 的构造方法,传入配置参数
        Constructor constructor = driverImplClass.getConstructor(Properties.class);
        return (NamingService) constructor.newInstance(properties);
    } catch (Throwable e) {
        throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
    }
}

2.2 NacosNamingService 初始化:

public NacosNamingService(Properties properties) throws NacosException {
init(properties);
}

private void init(Properties properties) throws NacosException {
    PreInitUtils.asyncPreLoadCostComponent();
    // 自定义属性
    final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(properties);
    ValidatorUtils.checkInitParam(nacosClientProperties);
    // 命名空间
    this.namespace = InitUtils.initNamespaceForNaming(nacosClientProperties);
    InitUtils.initSerialization();
    InitUtils.initWebRootContext(nacosClientProperties);
    // 日志名称属性设置
    initLogName(nacosClientProperties);

    this.notifierEventScope = UUID.randomUUID().toString();
    //  InstancesChangeNotifier extends Subscriber<InstancesChangeEvent>
    // 订阅者,订阅InstancesChangeEvent 实例变更事件 ,出现变更调用InstancesChangeNotifier onchange 方法
    this.changeNotifier = new InstancesChangeNotifier(this.notifierEventScope);
    // 注册事件发布器
    NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
    // 注册订阅者
    NotifyCenter.registerSubscriber(changeNotifier);
    // 服务信息获取(故障转移)
    this.serviceInfoHolder = new ServiceInfoHolder(namespace, this.notifierEventScope, nacosClientProperties);
    // 客户端代理
    this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, nacosClientProperties,
            changeNotifier);
}

2.3 NamingClientProxyDelegate 长连接建立:

2.3.1 grpc 代理对象创建:

 public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfoHolder,
            NacosClientProperties properties, InstancesChangeNotifier changeNotifier) throws NacosException {
 // 服务更新
    this.serviceInfoUpdateService = new ServiceInfoUpdateService(properties, serviceInfoHolder, this,
            changeNotifier);
    // 服务端地址管理器
    this.serverListManager = new ServerListManager(properties, namespace);
    this.serviceInfoHolder = serviceInfoHolder;
    this.securityProxy = new SecurityProxy(this.serverListManager.getServerList(),
            NamingHttpClientManager.getInstance().getNacosRestTemplate());
    initSecurityProxy(properties);
    // http 代理
    this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties);
    // grpc 代理
    this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties,
            serviceInfoHolder);
}

2.3.2 NamingGrpcClientProxy grpc:

public NamingGrpcClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListFactory serverListFactory,
        NacosClientProperties properties, ServiceInfoHolder serviceInfoHolder) throws NacosException {
    super(securityProxy);
    this.namespaceId = namespaceId;
    this.uuid = UUID.randomUUID().toString();
    // 请求超时时间
    this.requestTimeout = Long.parseLong(properties.getProperty(CommonParams.NAMING_REQUEST_TIMEOUT, "-1"));
    Map<String, String> labels = new HashMap<>();
    // 资源是sdk
    labels.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK);
    // 模式是注册
    labels.put(RemoteConstants.LABEL_MODULE, RemoteConstants.LABEL_MODULE_NAMING);
    labels.put(Constants.APPNAME, AppNameUtils.getAppName());
    // rpc  客户端的创建
    this.rpcClient = RpcClientFactory.createClient(uuid, ConnectionType.GRPC, labels,
            RpcClientTlsConfig.properties(properties.asProperties()));
    this.redoService = new NamingGrpcRedoService(this, properties);
    NAMING_LOGGER.info("Create naming rpc client for uuid->{}", uuid);
    start(serverListFactory, serviceInfoHolder);
}
2.3.2.1 createClient 客户端的创建:
public static RpcClient createClient(String clientName, ConnectionType connectionType, Integer threadPoolCoreSize,
            Integer threadPoolMaxSize, Map<String, String> labels, RpcClientTlsConfig tlsConfig) {
        // 不是grpc 抛出异常
   if (!ConnectionType.GRPC.equals(connectionType)) {
        throw new UnsupportedOperationException("unsupported connection type :" + connectionType.getType());
    }
    // 客户端创建 Map<String, RpcClient> CLIENT_MAP
    return CLIENT_MAP.computeIfAbsent(clientName, clientNameInner -> {
        LOGGER.info("[RpcClientFactory] create a new rpc client of " + clientName);

        return new GrpcSdkClient(clientNameInner, threadPoolCoreSize, threadPoolMaxSize, labels, tlsConfig);
    });
}
2.3.2.2 start 长连接建立:
 private void start(ServerListFactory serverListFactory, ServiceInfoHolder serviceInfoHolder) throws NacosException {
  // 服务地址的工厂
   rpcClient.serverListFactory(serverListFactory);
   // 监听器放入
   rpcClient.registerConnectionListener(redoService);
   // 请求处理器
   rpcClient.registerServerRequestHandler(new NamingPushRequestHandler(serviceInfoHolder));
   // 客户端启动
   rpcClient.start();
   NotifyCenter.registerSubscriber(this);
}

rpcClient.start():注意做了3件事 (具体的实现细节在后续文章进行介绍)

  • 客户端与服务端的通道建立:
    1)和nacos 服务端建立通信的channel 管道;建立双向流的grpc 通信存根;
    2)发送服务检查请求,从nacos 服务端获取到连接的connectId ;
    3) 发送给服务端客户端和服务端完成连接建立的请求;

  • 客户端与服务端的心跳监测:
    1) 在while(true) 循环中,发送healthCheck() 请求,得到true 则保持心跳(继续下一次循环),false 则失去心跳;
    2)如果失去心跳,则将客户端从健康状态标记为不健康状态;
    3)通过reconnect 方法尝试与nacos 服务端重新建立通信连接;

  • 客户端与服务端的断线重连:

    1. 通过 connectToServer 尝试与nacos 服务端重新建立通信连接;
      2)建立成功,则将原有的连接置为不可用,并关闭原有连接,释放资源;发布新的连接建立事件到 eventLinkedBlockingQueue 队列中;
      3)如果建立不成功则进行增大通nacos 服务端建立连接请求的时间间隔;
/**
 * Start this client.
 */
public final void start() throws NacosException {
    // cas 状态转换: 乐观锁实现
    boolean success = rpcClientStatus.compareAndSet(RpcClientStatus.INITIALIZED, RpcClientStatus.STARTING);
    if (!success) {
        return;
    }
    // 创建 clientEventExecutor  线程池,池子中设置了2个线程
    clientEventExecutor = new ScheduledThreadPoolExecutor(2, r -> {
        Thread t = new Thread(r);
        t.setName("com.alibaba.nacos.client.remote.worker");
        t.setDaemon(true);
        return t;
    });
    
    // connection event consumer.
    // 线程池提交任务: 客户端和服务端 连接重置;当nacos 服务端重启,客户端在心跳监测
    clientEventExecutor.submit(() -> {
        while (!clientEventExecutor.isTerminated() && !clientEventExecutor.isShutdown()) {
            ConnectionEvent take;
            try {
                take = eventLinkedBlockingQueue.take();
                if (take.isConnected()) {
                    notifyConnected();
                } else if (take.isDisConnected()) {
                    notifyDisConnected();
                }
            } catch (Throwable e) {
                // Do nothing
            }
        }
    });
    
    clientEventExecutor.submit(() -> {
        while (true) {
            try {
                if (isShutdown()) {
                    break;
                }
                // reconnectionSignal 重连接队列
                ReconnectContext reconnectContext = reconnectionSignal
                        .poll(rpcClientConfig.connectionKeepAlive(), TimeUnit.MILLISECONDS);
                if (reconnectContext == null) {
                	// 重连接队列是null 则表示 客户端与服务端没有发生断线重连的情况
                    // check alive time. 超过心跳的间隔时间,则重新发送healthCheck 监控检查请求
                    if (System.currentTimeMillis() - lastActiveTimeStamp >= rpcClientConfig.connectionKeepAlive()) {
                        boolean isHealthy = healthCheck();
                        if (!isHealthy) {
                        	// 如果 健康检测失败
                            if (currentConnection == null) {
                                continue;
                            }
                            LoggerUtils.printIfInfoEnabled(LOGGER,
                                    "[{}] Server healthy check fail, currentConnection = {}",
                                    rpcClientConfig.name(), currentConnection.getConnectionId());
                            
                            RpcClientStatus rpcClientStatus = RpcClient.this.rpcClientStatus.get();
                            if (RpcClientStatus.SHUTDOWN.equals(rpcClientStatus)) {
                                break;
                            }
                            // 标记客户端为 UNHEALTHY
                            boolean statusFLowSuccess = RpcClient.this.rpcClientStatus
                                    .compareAndSet(rpcClientStatus, RpcClientStatus.UNHEALTHY);
                            if (statusFLowSuccess) {
                            	// 服务端有可能发生了故障,则将服务端信息 ServerInfo 置为null 
                                reconnectContext = new ReconnectContext(null, false);
                            } else {
                                continue;
                            }
                            
                        } else {
                            lastActiveTimeStamp = System.currentTimeMillis();
                            continue;
                        }
                    } else {
                        continue;
                    }
                    
                }
                
                if (reconnectContext.serverInfo != null) {
                	// 发送连接重置时,检查 nacos 服务端的ip 和端口
                    // clear recommend server if server is not in server list.
                    boolean serverExist = false;
                    for (String server : getServerListFactory().getServerList()) {
                        ServerInfo serverInfo = resolveServerInfo(server);
                        if (serverInfo.getServerIp().equals(reconnectContext.serverInfo.getServerIp())) {
                            serverExist = true;
                            reconnectContext.serverInfo.serverPort = serverInfo.serverPort;
                            break;
                        }
                    }
                    if (!serverExist) {
                        LoggerUtils.printIfInfoEnabled(LOGGER,
                                "[{}] Recommend server is not in server list, ignore recommend server {}",
                                rpcClientConfig.name(), reconnectContext.serverInfo.getAddress());
                        
                        reconnectContext.serverInfo = null;
                        
                    }
                }
                // 发送重新连接服务端的请求
                reconnect(reconnectContext.serverInfo, reconnectContext.onRequestFail);
            } catch (Throwable throwable) {
                // Do nothing
            }
        }
    });
    
    // connect to server, try to connect to server sync retryTimes times, async starting if failed.
    // 客户端启动时 第一次进行同nacos 服务端的连接建立
    Connection connectToServer = null;
    rpcClientStatus.set(RpcClientStatus.STARTING);
    
    int startUpRetryTimes = rpcClientConfig.retryTimes();
    // 重试次数判断
    while (startUpRetryTimes > 0 && connectToServer == null) {
        try {
            startUpRetryTimes--;
            // 随机获取一个nacos 服务端的地址
            ServerInfo serverInfo = nextRpcServer();
            
            LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Try to connect to server on start up, server: {}",
                    rpcClientConfig.name(), serverInfo);
            // 服务端的连接
            connectToServer = connectToServer(serverInfo);
        } catch (Throwable e) {
            LoggerUtils.printIfWarnEnabled(LOGGER,
                    "[{}] Fail to connect to server on start up, error message = {}, start up retry times left: {}",
                    rpcClientConfig.name(), e.getMessage(), startUpRetryTimes, e);
        }
        
    }
    
    if (connectToServer != null) {
        LoggerUtils
                .printIfInfoEnabled(LOGGER, "[{}] Success to connect to server [{}] on start up, connectionId = {}",
                        rpcClientConfig.name(), connectToServer.serverInfo.getAddress(),
                        connectToServer.getConnectionId());
         // 连接建立成功,则将连接成功时间放入到 eventLinkedBlockingQueue 队列中进行消费
        this.currentConnection = connectToServer;
        rpcClientStatus.set(RpcClientStatus.RUNNING);
        eventLinkedBlockingQueue.offer(new ConnectionEvent(ConnectionEvent.CONNECTED));
    } else {
    	// 连接失败则 将失败时间放入到reconnectionSignal 队列中,消费改队列时 进入重连的逻辑
        switchServerAsync();
    }
    // 注册连接重置 处理器
    registerServerRequestHandler(new ConnectResetRequestHandler());
    
    // register client detection request.
    registerServerRequestHandler(request -> {
        if (request instanceof ClientDetectionRequest) {
            return new ClientDetectionResponse();
        }
        
        return null;
    });
    
}

三、客户端实例的注册:

在完成与服务端的通信channel 建立之后,就可以通过 namingService.registerInstance(serviceId, group, instance) 进行nacos 客户端实例的注册;

3.1 NamingGrpcClientProxy# registerService:

@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
            instance);
     //  ConcurrentMap<String, InstanceRedoData> registeredInstance map 中放入实例信息 key:分组名@@服务名
    redoService.cacheInstanceForRedo(serviceName, groupName, instance);
    // 向nacos 服务端发送注册请求,然后修改 InstanceRedoData 的实例信息为注册成功状态
    doRegisterService(serviceName, groupName, instance);
}
public void cacheInstanceForRedo(String serviceName, String groupName, Instance instance) {
//  key:分组名@@服务名
 String key = NamingUtils.getGroupedName(serviceName, groupName);
 	// 客户端服务对象创建,然后放入到  registeredInstance map 缓存(注册状态是未注册)
    InstanceRedoData redoData = InstanceRedoData.build(serviceName, groupName, instance);
    synchronized (registeredInstances) {
        registeredInstances.put(key, redoData);
    }
}

3.2 客户端发送注册请求:

public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {
    InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
            NamingRemoteConstants.REGISTER_INSTANCE, instance);
     // 发送注册请求到服务端
    requestToServer(request, Response.class);
    // 请求发送成功 ,将当前服务实例的注册状态改为已注册
    redoService.instanceRegistered(serviceName, groupName);
}

requestToServer(request, Response.class);

private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass)
            throws NacosException {
try {
       request.putAllHeader(
               getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName()));
       // 通过 rpcClient 获取通道 发送 InstanceRequest 类型的 request 请求
       Response response =
               requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
       if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) {
           throw new NacosException(response.getErrorCode(), response.getMessage());
       }
       if (responseClass.isAssignableFrom(response.getClass())) {
           return (T) response;
       }
       NAMING_LOGGER.error("Server return unexpected response '{}', expected response should be '{}'",
               response.getClass().getName(), responseClass.getName());
   } catch (NacosException e) {
       throw e;
   } catch (Exception e) {
       throw new NacosException(NacosException.SERVER_ERROR, "Request nacos server failed: ", e);
   }
   throw new NacosException(NacosException.SERVER_ERROR, "Server return invalid response");
}

总结

客户端在启动成功之后发布 WebServiceInitializedEvent 事件,nacos 客户端同服务端创建通信通道,发送nacos 服务端的服务检查请求,正常返回后得到通道的id,创建双向流 grpc 的通信存根,发送连接确定建立的请求后;发起客户端实例的注册请求到nacos 服务端进行注册。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Meta Llama 3本地部署

感谢阅读 环境安装收尾 环境安装 项目文件 下载完后在根目录进入命令终端&#xff08;windows下cmd、linux下终端、conda的话activate&#xff09; 运行 pip install -e .不要控制台&#xff0c;因为还要下载模型。这里挂着是节省时间 模型申请链接 复制如图所示的链接 然后…

每周一算法:多起点最短路

题目描述 有一天&#xff0c;琪琪想乘坐公交车去拜访她的一位朋友。由于琪琪非常容易晕车&#xff0c;所以她想尽快到达朋友家。 现在给定你一张城市交通路线图&#xff0c;上面包含城市的公交站台以及公交线路的具体分布。 已知城市中共包含 n n n个车站&#xff08;编号 …

Adobe Firefly Image 3:创新步伐与挑战并存的AI图像生成技术升级

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

编写你的第一个java 程序

1.安装 jdk 网址&#xff1a; Java Downloads | Oracle 一般我们安装jdk 17 就行了 自己练习 自己学习 真正的开发中我们使用jdk 8 这个是最适合开发java 应用程序的 当然你也可以选择你的 系统 来安装这个java 在文件资源管理器打开JDK的安装目录的bin目录&#xff0c;会发…

VSCode通过跳板机免密连接远程服务器的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Android Monkey工具介绍与使用

过于爽快的承认失败&#xff0c;就可能发觉不了曾经与正确非常接近。大家好&#xff0c;依旧是在翻看旧文档的时候&#xff0c;发现一篇关于Monkey的介绍和使用&#xff0c;Monkey这款工具在软件测试中主要用于进行压力测试和稳定性测试。它可以模拟大量随机的用户操作&#xf…

618买什么最划算?618买什么东西便宜?必备数码好物清单分享

​只不&#xff0c;马上又到了618购物节咯&#xff0c;数码产品的优惠力度尤为显著&#xff0c;是购买数码产品的绝佳时机。接下来&#xff0c;我将为大家分享几款性价比超高的数码产品&#xff0c;相信总有一款能吸引你的目光。 一、南卡OE MIX开放式蓝牙耳机 在618购物狂欢节…

javaScript中的闭包

什么是闭包 在理解 JavaScript 中的闭包前先了解以下两个知识点&#xff1a; JavaScript 中的作用域和作用域链JavaScript 中的垃圾回收 简单回顾一下这两个知识点&#xff1a; 1. JavaScript 中的作用域和作用域链 作用域就是一个独立的地盘&#xff0c;让变量不会外泄、…

tomcat 配置支持 ssl 附效果图

1、修改tomcat配置文件server.xml: vim ./conf/server.xml 把配置文件&#xff1a; <Connector port"8088" Server" " protocol"HTTP/1.1"connectionTimeout"20000"redirectPort"8443" URIEncoding"UTF-8" …

C++ | Leetcode C++题解之第46题全排列

题目&#xff1a; 题解&#xff1a; class Solution { public:void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){// 所有数都填完了if (first len) {res.emplace_back(output);return;}for (int i first; i &…

逆数对(树状数组的方法)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 5 4 5 1 3 2 输出 7 思路&#xff1a; 根据题意&#xff0c;求逆序对总数。 逆序对含义&#xff1a;如果数组中的两个不同位置&#xff0c;前面的数字比后面的数字严格大&…

投票刷礼物链接怎么弄?最新投票活动创建系统源码 轻松创建活动

投票刷礼物链接怎么弄&#xff1f;投票活动创建系统的作用和功能多种多样&#xff0c;为用户提供一个便捷、高效且功能强大的平台&#xff0c;用于创建、管理和执行各种投票活动。分享一个最新投票活动创建系统源码&#xff0c;源码开源可二开&#xff0c;含完整代码包和详细搭…

SCA-CNN-LSTM多输入回归预测|正余弦优化算法-卷积-长短期神经网络|Matlab

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&am…

[笔试训练](五)

013 游游的you__牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 组成一个you需要一个o且能得2分&#xff0c;而组成相邻字母oo需要两个o&#xff0c;只能得1分。优先考虑组成尽可能多的you&#xff0c;再考虑剩下的o&#xff0c;放一起。 #include <iostream…

【C++】C++的四种类型转换

一、C语言中的类型转换 在C语言中有两种类型转换&#xff0c;隐式类型转换和显示类型转换。 如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者返回值类型与接收返回值类型不一致时&#xff0c;就需要发生类型转化。 隐式类型转换&#…

汽车IVI中控开发入门及进阶(十六):carplay认证

现在有些中控采用高通的芯片如8155、8295等,实现多屏互动等,但是也有一些车型走低成本方案,比如能够实现HiCar、CarLife或者苹果Apple的Carplay等能进行手机投屏就好了。 能实现CarPlay功能通过Carplay认证,也就成了一些必须的过程,国产车规级中控芯片里,开阳有一款ARK1…

Android SDK Manager安装Google Play Intel x86 Atom_64 System Image依赖问题

Package Google Play Intel x86 Atom_64 System Image,Android API R, revision 2 depends on SDK Platform Android R Preview, revision 2 问题 一开始以为网络还有依赖包没有勾选&#xff0c;尝试了很多次&#xff0c;勾选这边报错对应的license即可。此时点击一下其他licen…

深入探索Go语言:io库的实战应用全解析

深入探索Go语言&#xff1a;io库的实战应用全解析 引言io库概览Reader接口Writer接口Closer接口Seeker接口 文件操作打开和关闭文件读取文件写入文件错误处理 数据读写技巧使用缓冲读写缓冲读取缓冲写入 复用缓冲区提高读写效率的技巧 处理I/O流网络I/O的处理创建简单的HTTP服务…

cdo 修改 calendar 为标准的格式

使用ncl脚本时出现警告&#xff1a;day_of_year: illegal calendar proleptic_gregorian 其原因是读取的降水nc文件是我手动合并生成&#xff0c;所以时间的calendar不是很标准&#xff0c;数据信息如下所示&#xff0c;可以发现Calendar是proleptic_gregorian&#xff0c;这…

前端补充17(JS)

一、JS组成成分 JS的组成成分&#xff0c;由三部分组成 第一、ECMAScript&#xff1a;语法规则&#xff0c;如何定义变量&#xff0c;数据类型有哪些&#xff0c;如何转换数据类型&#xff0c;if判断 if-else while for for-in forEach do-while switch 数组 函数 对…