Netty-RPC

news2024/10/6 10:26:22

RPC:(Remote Procedure Call)-- 远程过程调用

(1)一个计算机通信协议。该协议允许运行与A计算机的程序调用运行于另一台计算机的子程序,而程序员无需额外滴为这个交互作用编程。
(2)两个或多个应用程序都分布在不同的服务器上,他们之间的调用都像是本地方法调用一样。
(3)常见的RPC框架有:比较知名的如阿里的Dubbo,google的gRPC,Go语言的rpcx,Apache的thrift,Spring的Spring Cloud。
在这里插入图片描述

实现Dubbo RPC(基于Netty)

在这里插入图片描述
基本实现逻辑:
在这里插入图片描述
我们首先来实现HelloService,这是一个共有的接口

package com.sgg.Netty.DubboRpc.publicinterface;

//这个是接口,是服务提供方和服务消费方都需要的
public interface HelloService {
    String hello(String mes);
}

然后我们先来实现服务端的部分,分成两个部分:netty部分和provider部分

服务端(服务提供者)

provider部分:

HelloServiceImpl :

package com.sgg.Netty.DubboRpc.provider;

import com.sgg.Netty.DubboRpc.publicinterface.HelloService;

public class HelloServiceImpl implements HelloService {
    //当有消费方调用该方法时,就返回一个结果
    @Override
    public String hello(String mes) {
        System.out.println("收到客户端消息="+mes);
        if(mes!=null){
            return "你好客户端,我已经收到你的消息【"+mes+"】";
        }else {
            return "你好客户端,我已经收到你的消息";
        }
    }
}

启动类:ServerBootstrap

package com.sgg.Netty.DubboRpc.provider;

import com.sgg.Netty.DubboRpc.Netty.NettyServer;

public class ServerBootstrap {
    public static void main(String[] args) {
        //代码代填
        NettyServer.startServer("127.0.0.1",8849);
    }
}

netty部分

NettyServer

package com.sgg.Netty.DubboRpc.Netty;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyServer {

    //对外只暴露这一个方法,在这个方法内部可以选择不同的启动方法
    public static void startServer(String hostname,int port){
        startServer0(hostname,port);
    }

    //编写一个方法,完成对NettyServer的初始化和启动
    private static void startServer0(String hostname,int port){
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        try{
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,128)       //设置线程队列的连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE,true)    //设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast("decoder",new StringDecoder());
                            pipeline.addLast("encoder", new StringEncoder());
                            pipeline.addLast(new NettyServerhandler());   //自定义的handler
                        }
                    });

            System.out.println("服务器提供者启动成功...");
            ChannelFuture cf = b.bind(port).sync();

            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(cf.isSuccess()){
                        System.out.println("监听端口成功");
                    }else{
                        System.out.println("监听端口失败");
                    }
                }
            });

            cf.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

NettyServerhandler

package com.sgg.Netty.DubboRpc.Netty;

import com.sgg.Netty.DubboRpc.provider.HelloServiceImpl;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class NettyServerhandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //获取客户端发送的消息,并调用服务
        System.out.println("msg="+msg);
        //客户端在调用服务提供者的API时,我们需要定义一个协议
        if(msg.toString().startsWith("HelloService#hello#")){
            String result = new HelloServiceImpl().hello(msg.toString().substring(msg.toString().lastIndexOf("#")+1));
            ctx.writeAndFlush(result);
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

客户端部分(服务消费者)

customer部分

ClientBootstrap

package com.sgg.Netty.DubboRpc.customer;

import com.sgg.Netty.DubboRpc.Netty.NettyClient;
import com.sgg.Netty.DubboRpc.publicinterface.HelloService;

public class ClientBootstrap {

    //这里定义协议头
    public static final String providerName = "HelloService#hello#";

    public static void main(String[] args) {
        NettyClient nettyClient = new NettyClient();

        //创建代理对象
        HelloService helloService = (HelloService) nettyClient.getBean(HelloService.class,providerName);

        //通过代理对象调用服务提供者提供的方法
        String res = helloService.hello("你好!Dubbo");
        System.out.println("调用的结果res="+res);
    }
}

netty部分

NettyClient

package com.sgg.Netty.DubboRpc.Netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NettyClient {
    //创建线程池
    private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private static NettyClienthandler client;


    //编写方法使用代理模式,获取一个代理对象
    public Object getBean(final Class<?> serviceclass,final String providerName){
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{serviceclass},(proxy,method,args)->{
            if(client==null){
                initClient();
            }
            //设置要发给服务端的信息
            //providerName协议头,args[0]就是客户端调用api
            client.setpara(providerName+args[0]);
            return executorService.submit(client).get();
        });
    }



    //初始化客户端
    private static void initClient(){
//        System.out.println("进入initClient");
        client = new NettyClienthandler();
        //创建EventLoopGroup
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY,true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(client);
                    }
                });

        try {
//            System.out.println("连接8849");
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",8849).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(channelFuture.isSuccess()){
                        System.out.println("连接端口成功");

                    }else{
                        System.out.println("连接端口失败");
                    }
                }
            });
        }catch (Exception e){
            System.out.println("连接发生错误");
            e.printStackTrace();
        }
    }

}

NettyClienthandler

package com.sgg.Netty.DubboRpc.Netty;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.concurrent.Callable;

public class NettyClienthandler extends ChannelInboundHandlerAdapter implements Callable {
    private ChannelHandlerContext context;//上下文
    private String result;//返回的结果
    private String para; //客户调用方法时传入的参数


    //改程序首先被调用,与服务器的连接创建后,就会被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("连接服务器");
        context = ctx;    //因为我们在其他方法中会用到ctx,所以我们将其拿出
    }

    /**
     * call()和 channelRead()两个方法通过配合,完成远程方法的调用
     * 1、首先channelActive()方法执行,获取到context
     * 2、执行call()方法,将参数通过buffer进行传递
     * 3、然后该线程进入wait(),
     * 4、等待远程过程调用返回结果
     * 5、返回结果,channelRead()被执行,返回结果取出,然后唤醒call线程
     * 6、call()线程唤醒,返回结果给代理对象
     */
    //被代理对象调用,发送数据给服务提供者-->  wait()  -->  等待被唤醒(channelRead)-->  返回结果
    @Override
    public synchronized Object call() throws Exception {
//        System.out.println("进入call");
        context.writeAndFlush(para);
        wait();         //等待channeRead的唤醒
//        System.out.println("返回call");
        return result;  //服务提供者返回的结果
    }


    //收到服务提供者传回的数据后,调用方法
    @Override
    public synchronized void channelRead(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        System.out.println("返回参数"+o.toString());
        result = o.toString();
        notify(); //唤醒call,执行后续步骤
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    void setpara(String param){
        System.out.println("参数:"+param);
        this.para = param;
    }
}

文件结构如下:

在这里插入图片描述
代码说明:
1、公共接口HelloService接口只是定义了一些需要实现的方法,具体的实现是在HelloServiceImpl中。这些方法就是服务端提供的方法,供客户端远程调用的。
2、客户端想要调用服务端提供的方法,需要定义一个特殊的协议,也就是服务端和客户端之间自定义的特有通信协议,例如,客户端传来的消息必须要以“HelloService#hello#”为头才会接收,并调用方法。

  • 服务端的这部分功能由下面的方法实现,触发时间点就是当接收到消息时,所以写在自定义handler中的channelRead方法中。
  • 客户端的这部分功能通过定义一个协议头加到发送的消息前:
@Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //获取客户端发送的消息,并调用服务
        System.out.println("msg="+msg);
        //客户端在调用服务提供者的API时,我们需要定义一个协议
        if(msg.toString().startsWith("HelloService#hello#")){
            String result = new HelloServiceImpl().hello(msg.toString().substring(msg.toString().lastIndexOf("#")+1));
            ctx.writeAndFlush(result);
        }
    }

3、服务端的其他代码实现比较简单,和正常的服务端没有区别
4、客户端在启动类中需要定义代理对象(也就是公共接口HelloService),然后远程调用其hello方法,得到返回值。实际的具体操作都在创建代理对象类的方法中实现。如下所示:

public static void main(String[] args) {
        NettyClient nettyClient = new NettyClient();

        //创建代理对象
        HelloService helloService = (HelloService) nettyClient.getBean(HelloService.class,providerName);

        //通过代理对象调用服务提供者提供的方法
        String res = helloService.hello("你好!Dubbo");
        System.out.println("调用的结果res="+res);
    }

5、在nettyClient.getBean中,我们返回了一个代理对象,在创建这个代理对象的过程中,初始化netty中客户端对象,设置传递参数

//编写方法使用代理模式,获取一个代理对象
    public Object getBean(final Class<?> serviceclass,final String providerName){
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{serviceclass},(proxy,method,args)->{
            if(client==null){
                initClient();
            }
            //设置要发给服务端的信息
            //providerName协议头,args[0]就是客户端调用api
            client.setpara(providerName+args[0]);
            return executorService.submit(client).get();
        });
    }

6、在initClient()中,我们构建EventLoopGroup,并添加handler,连接服务端等。

private static void initClient(){
//        System.out.println("进入initClient");
        client = new NettyClienthandler();
        //创建EventLoopGroup
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY,true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(client);
                    }
                });

        try {
//            System.out.println("连接8849");
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",8849).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(channelFuture.isSuccess()){
                        System.out.println("连接端口成功");

                    }else{
                        System.out.println("连接端口失败");
                    }
                }
            });
        }catch (Exception e){
            System.out.println("连接发生错误");
            e.printStackTrace();
        }
    }

7、具体和服务端交互获取消息的步骤在自定义handler中实现,主要难点在于理解其执行过程,在哪一部分与服务端进行了远程交互。具体如下:
(1)、首先在handler中有三个参数值,分别是:context;//上下文、result;//返回的结果、para; //客户调用方法时传入的参数,因为在整个handler中不同函数间需要用到这三个值,所以我们需要在得到这三个值时,将其赋值到该类的参数,实现类内的共享。
(2)、该类有5个函数,分别是:channelActive()、call() 、channelRead()、exceptionCaught()、setpara()

  • channelActive():在与服务端建立连接时触发
  • channelRead():在服务端有消息返回时触发
  • call():在构建代理对象的最后一步调用,return executorService.submit(client).get();
  • exceptionCaught():捕捉异常
  • setpara():设置具体发送消息的参数
    (3)、这5个函数执行顺序时怎样的,如何配合完成远程方法调用?
    执行逻辑:获取上下文 —> 设置参数 —> 执行远程调用(发送消息给服务端)—> 接收返回消息 —> 消息返回代理对象

获取上下文: channelActive()
设置参数:setpara()
执行远程调用(发送消息给服务端):call()
接收返回消息:channelRead()
消息返回代理对象:call()

可以看出:执行远程调用以及接收消息并发回代理对象这个过程是需要同步执行,所以在call()和channelRead()前面加了synchronized 关键字,并且,在call()中将消息发送至服务端后,需要等待其返回(通过wait()使线程休眠),而在消息返回时触发的是channelRead()函数,所以需要唤醒call然后继续将消息返回代理对象。

public class NettyClienthandler extends ChannelInboundHandlerAdapter implements Callable {
    private ChannelHandlerContext context;//上下文
    private String result;//返回的结果
    private String para; //客户调用方法时传入的参数


    //改程序首先被调用,与服务器的连接创建后,就会被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("连接服务器");
        context = ctx;    //因为我们在其他方法中会用到ctx,所以我们将其拿出
    }

    /**
     * call()和 channelRead()两个方法通过配合,完成远程方法的调用
     * 1、首先channelActive()方法执行,获取到context
     * 2、执行call()方法,将参数通过buffer进行传递
     * 3、然后该线程进入wait(),
     * 4、等待远程过程调用返回结果
     * 5、返回结果,channelRead()被执行,返回结果取出,然后唤醒call线程
     * 6、call()线程唤醒,返回结果给代理对象
     */
    //被代理对象调用,发送数据给服务提供者-->  wait()  -->  等待被唤醒(channelRead)-->  返回结果
    @Override
    public synchronized Object call() throws Exception {
//        System.out.println("进入call");
        context.writeAndFlush(para);
        wait();         //等待channeRead的唤醒
//        System.out.println("返回call");
        return result;  //服务提供者返回的结果
    }


    //收到服务提供者传回的数据后,调用方法
    @Override
    public synchronized void channelRead(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        System.out.println("返回参数"+o.toString());
        result = o.toString();
        notify(); //唤醒call,执行后续步骤
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    void setpara(String param){
        System.out.println("参数:"+param);
        this.para = param;
    }
}

如此我们就实现了远程方法的调用,实验结果如下:
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

华为机试 - 考古学家

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 有一个考古学家发现一个石碑&#xff0c;但是很可惜&#xff0c;发现时其已经断成多段&#xff0c;原地发现n个断口整齐的石碑碎片。为了破解石碑内容&#xff0c;考古学家希望有程序能帮忙计算复原后…

[附源码]java毕业设计旅游管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[数据结构]二叉树之堆的实现

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【数据结构】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站…

各种信息收集

谷歌hack语法 site: baidu.com 搜索关于baidu.com的相关子域名网站 黑客 site:baidu.com 搜索关于baidu.com的子域名网站中有关字符“黑客”的网页 inurl: admin/login.php 搜索含有"admin/login.php"的url inurl: login site:baidu.com …

ATF SMC处理

文章目录ATF SMC上下文结构体SMC handler实现ATF SMC上下文结构体 ATF在处理SMC的时候会把CPU的寄存器保存起来&#xff0c;退出SMC的时候恢复现场。使用qemu_v8.mk编译出来的ATF没有定义CTX_INCLUDE_EL2_REGS&#xff0c;CTX_INCLUDE_FPREGS和CTX_INCLUDE_PAUTH_REGS。 lib/c…

Pytorch的grid_sample是如何实现对grid求导的?(源码解读)

Pytorch的grid_sample是如何实现对grid求导的&#xff1f;&#xff08;源码解读&#xff09; 这里本人的参考源码是grid_sample的CPU内核的CPP实现&#xff1a;https://github.com/pytorch/pytorch/blob/b039a715ce4e9cca82ae3bf72cb84652957b2844/aten/src/ATen/native/cpu/G…

【Detectron2】代码库学习-4. LazyConfig 配置文件

目录1. 配置文件2. LazyConfig 导入导出3. 递归实例化4. 基于LazyConfig的训练步骤4.1 导入依赖库4.2 日志初始化4.3 训练4.4 评估4.5 训练流程4.6 主函数入口5. TipsDetectron2是Facebook AI Research(FAIR)推出的基于Pytorch的视觉算法开源框架&#xff0c;主要聚焦于目标检测…

力扣160 - 相交链表【双指针妙解】

链表也能相交~一、题目描述二、思路分析与罗列三、整体代码展示四、总结与提炼一、题目描述 原题传送门 示例 1&#xff1a; 输入&#xff1a;intersectVal 8, listA [4,1,8,4,5], listB [5,6,1,8,4,5], skipA 2, skipB 3 输出&#xff1a;Intersected at ‘8’ 解释&…

MySQL索引

索引索引的相关概念索引分类索引的底层数据结构及其原理主键索引&二级索引聚集和非聚集索引哈西索引&&自适应哈西索引索引和慢查询日志索引优化索引的相关概念 什么是索引&#xff1f;索引其实就是一个数据结构。当表中的数据量到达几十万甚至上百万的时候&#x…

每个 Flutter 开发者都应该知道的一些原则

“仅仅让代码起作用是不够的。有效的代码经常被严重破坏。仅满足于工作代码的程序员表现得不专业。他们可能担心没有时间改进代码的结构和设计,但我不同意。没有什么比糟糕的代码对开发项目产生更深远、更长期的影响了。” ― Robert C. Martin,Clean Code:敏捷软件工艺手册…

fpga nvme 寄存器

图1所示的NVMe多队列&#xff0c;每个队列支持64K命令&#xff0c;最多支持64K队列。这些队列的设计使得IO命令和对命令的处理不仅可以在同一处理器内核上运行&#xff0c;也可以充分利用多核处理器的并行处理能力。每个应用程序或线程可以有自己的独立队列&#xff0c;因此不需…

基于Nacos的注册中心与配置中心

基于Nacos的注册中心与配置中心 Nacos简介 概述 Nacos全称是动态命名和配置服务&#xff0c;Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos主要用于发现、配置和管理微服务。 什么是Nacos Nacos支持几乎所有主流类型的服务的发现、配置和…

同花顺_代码解析_技术指标_A

本文通过对同花顺中现成代码进行解析&#xff0c;用以了解同花顺相关策略设计的思想 目录 ABI AD ADL ADR ADTM ADVOL AMV ARBR ARMS ASI ATR ABI 绝对幅度指标 算法&#xff1a;上涨家数减去下跌家数所得的差的绝对值。 该指标只适用于大盘日线。 行号 1 aa…

题目7飞机票订票系统

题目7飞机票订票系统问题描述:某公司每天有10航班(航班号、价格)&#xff0c;每个航班的飞机&#xff0c;共有80个座位&#xff0c; 20排&#xff0c;每排4个位子。编号为A&#xff0c;BCD。如座位号:10D表示10排D座。 运行界面如下&#xff1a; 1)能从键盘录入订票信息:乘客的…

[Games 101] Lecture 13-16 Ray Tracing

Ray Tracing Why Ray Tracing 光栅化不能得到很好的全局光照效果 软阴影光线弹射超过一次&#xff08;间接光照&#xff09; 光栅化是一个快速的近似&#xff0c;但是质量较低 光线追踪是准确的&#xff0c;但是较慢 Rasterization: real-time, ray tracing: offline生成一帧…

狗屎一样的面试官,你遇到过几个?

做了几年软件开发&#xff0c;我们都或多或少面试过别人&#xff0c;或者被别人面试过。大家最常吐槽的就是面试造火箭&#xff0c;进厂拧螺丝。今天就来吐槽一下那些奇葩&#xff08;gou&#xff09;一样的面试官 A 那是在我刚工作1年的时候&#xff0c;出去面试前端开发。 那…

分布式开源存储架构Ceph概述

概述 k8s的后端存储中ceph应用较为广泛&#xff0c;当前的存储市场仍然是由一些行业巨头垄断&#xff0c;但在开源市场还是有一些不错的分布式存储&#xff0c;其中包括了Ceph、Swift、sheepdog、glusterfs等 什么是ceph&#xff1f; Ceph需要具有可靠性&#xff08;reliab…

C++11标准模板(STL)- 算法(std::partition_point)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 定位已划分范围的划分点 …

线上崩了?一招教你快速定位问题。

&#x1f44f; 背景 正浏览着下班后去哪家店撸串&#xff0c;结果隔壁组同事囧着脸过来问我&#xff1a;大哥&#xff0c;赶紧过去帮忙看个问题&#xff01;客户反馈很多次了&#xff0c;一直找不出问题出在哪里&#xff01;&#xff01;&#xff01; 我&#xff1a;能不能有…

利用WPS功能破解及本地恢复密码

利用WPS功能破解及本地恢复密码 认识WPS功能 ​ WPS&#xff08;Wi-Fi Protected Setup&#xff09;是Wi-Fi保护设置的英文缩写。WPS是由Wi-Fi联盟组织实施的认证项目&#xff0c;主要致力于简化无线局域网安装及安全性能的配置工作。WPS并不是一项新增的安全性能&#xff0c;它…