源码篇--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<>();// 资源是sdklabels.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_MAPreturn 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;}// 标记客户端为 UNHEALTHYboolean 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 服务端进行注册。

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

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

相关文章

栈和队列-介绍与实现(超级!!!详解-C语言)

目录 栈 栈的介绍 栈的概念 栈的结构 栈的实现 初始化栈 StackInit 销毁栈 StackDestroy 入栈 StackPush 出栈 StackPop 获取栈顶元素 StackTop 检查栈是否为空 StackEmpty 获取栈中有效元素个数 StackSize 队列 队列的介绍 队列的概念 队列的结构 队列的应用 队列的实现 …

申请泛域名证书步骤

泛域名证书的广泛应用范围&#xff1a; 泛域名证书不同于普通的单域名数字证书和多域名数字证书&#xff0c;可以一次以一张证书对应无限多的域名&#xff0c;在功能性和方便性上远优于一般证书。 单域名证书顾名思义&#xff0c;一张证书只对应一个独立域名&#xff0c;多域…

数据结构——双端队列

数据结构——双端队列 什么是双端队列双端队列的实现双端队列的使用场景 我们今天来看队列的变形——双端队列&#xff1a; 什么是双端队列 双端队列&#xff08;Double-Ended Queue, 简称deque&#xff09;是一种特殊的数据结构&#xff0c;它结合了队列&#xff08;Queue&a…

算法刷题day46

目录 引言一、树的重心二、毕业旅行问题三、高精度乘法 引言 今天复习了一下高精度的所有模板&#xff0c;包括加法、减法、乘法、除法&#xff0c;因为自己当时在蓝桥杯的时候没有看出来那个题使用高精度&#xff0c;因为对于一个数的大小和一个数的长度&#xff0c;自己有时…

flutter笔记-万物皆是widget

文章目录 helloFlluter自定义Widget优化 这篇文章后就不见写了&#xff0c;学flutter主要是为了更好的使用 flutter-webrtc&#xff0c;所以到这里基本就了解了大部分的知识&#xff0c;后续边用边查&#xff1b; 在flutter中所有的view都叫widget&#xff0c;类似文本组件Tex…

女生学习PLC专业,好就业吗?

好就业&#xff0c;plc找工作容易 但不建议女生做PLC相关工作&#xff0c; plc的工作会涉及现场安装调试&#xff0c;难免体力或者登高爬梯&#xff0c;对女生来说有点辛苦。还都会长期出差&#xff0c;身体辛苦之外&#xff0c;心理是煎熬&#xff0c;初入行时出差或许是乐事…

qt5-入门-自定义委托-可编辑的TableModel与信号接收

参考&#xff1a; C GUI Programming with Qt 4, Second Edition 本地环境&#xff1a; win10专业版&#xff0c;64位&#xff0c;Qt5.12 上一篇&#xff1a; qt5-入门-自定义委托-简单例子_qt 委托-CSDN博客 https://blog.csdn.net/pxy7896/article/details/137234839 本篇重…

编写你的第一个java 程序

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

通义千问(Qwen)AI大模型-系列_2

一、通义千问系列模型 1、CodeQwen1.5-7B-Chat CodeQwen1.5是Qwen1.5的代码特定版本。它是一种基于变换器的纯解码器语言模型&#xff0c;在大量代码数据上进行预训练。 强大的代码生成能力和在一系列基准测试中具有竞争力的性能;支持长上下文理解和生成&#xff0c;上下文长度…

【FX110网】股市、汇市一年有多少个交易日?

事实上&#xff0c;作为交易者&#xff0c;重要的是要了解并非每天都是交易日。虽然金融市场在大多数工作日开放交易&#xff0c;但在某些特定情况下无法进行交易。这些非交易日可能因各种原因而发生&#xff0c;包括节假日、周末和市场休市。 通过随时了解假期、交易时间表和市…

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

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

混沌工程理论建设和项目实践

混沌工程理论建设和项目实践 1. 背景说明2. 为什么要做混沌工程2.1 混沌目标2.2 演习对象2.3 影响可用性的主要因素及应对2.4 可行性论证和控制爆炸半径 3. 如何落地3.1 安全、有效的实验3.2 安全&#xff1a;不影响线上业务3.2.1 爆炸半径3.2.2 特殊限制与审批 3.3 有效&#…

使用BibTeX导入参考文献到Overleaf项目(常用方法)

使用bib为overleaf导入参考文献的好处 整洁的管理&#xff1a; 使用 .bib 文件可以使你的参考文献管理更加整洁和有条理。你可以将所有的参考文献集中存储在一个文件中&#xff0c;而不是在文档中直接引用或复制粘贴。 易于维护和更新&#xff1a; 当你需要添加、删除或修改参考…

WEB攻防-ASP中间件IIS文件上传解析安全漏洞

漏洞原理&#xff1a; 基于文件 IIS6.0默认不解析;号后面的内容&#xff0c;例如1.asp;.jpg会当成1.asp解析&#xff0c;相当于分号截断。 基于文件夹 IIS6.0会将/*.asp/文件夹下的文件当成asp解析。 案例&#xff1a; 写一个木马文件&#xff0c;并改为jpg后缀 GIF89agif8…

报名 | Qt汽车及工业行业解决方案及实战训练 深圳站(5月15日 星期三)

加入我们的Qt技术培训&#xff0c;探索跨平台应用开发的无限可能&#xff01;本次培训将深入Qt框架&#xff0c;涵盖从基础概念到高级功能的全方位知识&#xff0c;无论您是刚入门的新手还是希望提升技能的资深开发者&#xff0c;都能在此找到适合自己的学习路径。通过实践案例…

python学习笔记(集合)

知识点思维导图 # 直接使用{}进行创建 s{10,20,30,40} print(s)# 使用内置函数set()创建 sset() print(s)# 创建一个空的{}默认是字典类型 s{} print(s,type(s))sset(helloworld) print(s) sset([10,20,30]) print(s) s1set(range(1,10)) print(s1)print(max:,max(s1)) print(m…

OceanBase 开发者大会 - 见闻与洞察

文章目录 前言主论坛见闻技术专场见闻产品技术专场技术生态专场 同行论道启发互动展区写在最后 前言 4 月 20 日&#xff0c;我有幸受邀参加了第二届 OceanBase 开发者大会。 50 余位业界知名数据库大咖和数据库爱好者&#xff0c;与来自全国近 600 名开发者相聚。共同探讨一体…

【01-机器学习入门:理解Scikit-learn与Python的关系】

文章目录 前言Python与机器学习Scikit-learn简介Scikit-learn与Python的关系使用Scikit-learn进行机器学习结语前言 在当今的数据科学和人工智能领域,机器学习已经成为了一个不可或缺的组成部分。而对于那些刚刚踏入这一领域的新手来说,理解机器学习的基本概念和找到合适的工…

Security初探(二)

SpringSecurity初探(一)-CSDN博客 上面介绍了用了在SpringBoot里配置UserDetailsService和PasswordEncoder两个Bean 下面介绍一种替换掉上面两个Bean的方式 看下效果实际是和创建UserDetailsService和PassswordEncoder两个Bean的效果是一样的 还有一种方式混合搭配 当然不推…

【C语言__指针01__复习篇11】

目录 前言 一、什么是指针 二、计算机中常见的单位 三、CPU是怎样找到一块内存空间的 四、如何得到变量的地址 五、指针变量 六、解引用指针变量的作用 七、指针变量的大小 八、指针变量类型的意义 8.1 指针的解引用 8.2 指针-整数 九、void*指针 十、const修饰变…