Spring架构篇--2.7.4 远程通信基础--Netty原理--bind实现客户端acceptread事件处理

news2024/11/26 13:45:00

前言:本文在Netty 服务端已经实现NioServerSocketChannel 管道的初始化并且绑定了端口后,继续对客户端accept&read事件如何处理进行探究;

1 对客户端accept&read事件的触发:

从之前的ServerBootstrap 的bind 方法中似乎并没有发现类似于Nio 中 selector.select(); 去轮询事件的代码;显然事件轮询肯定存在,如果没有在main 线程中去轮询事件,它也只能交由其他线程去处理;在netty 中看到最多的就是NioEventLoop 去执行任务,而在之前NioServerSocketChannel 初始化的时候也确实有NioEventLoop 去execute执行任务,那么execute 方法都做了什么呢;

2 NioEventLoop 的execute 任务执行:
2.1 NioEventLoop task 任务的执行:
当NioEventLoopGroup 中使用execute 提交任务,实际是向NioEventLoop 获取到一个NioEventLoop,然后封装为一个task 任务进行:SingleThreadEventExecutor 类中execute 方法:

private void execute(Runnable task, boolean immediate) {
    boolean inEventLoop = this.inEventLoop();
    // 封装task 任务
    this.addTask(task);
    if (!inEventLoop) {
// 如果非nio 线程则,启动一个新的的线程  执行任务
        this.startThread();
        if (this.isShutdown()) {
            boolean reject = false;

            try {
                if (this.removeTask(task)) {
                    reject = true;
                }
            } catch (UnsupportedOperationException var6) {
            }

            if (reject) {
                reject();
            }
        }
    }

    if (!this.addTaskWakesUp && immediate) {
        this.wakeup(inEventLoop);
    }

}

关键点2.2 最终进入到NioEventLoop 类中的run 方法:run 方法中会来处理nio 事件,普通任务

protected void run() {
    int selectCnt = 0;

    while(true) {
        while(true) {
            while(true) {
                try {
                    int strategy;
                    try {
                        strategy = this.selectStrategy.calculateStrategy(this.selectNowSupplier, this.hasTasks());
                        switch (strategy) {
                            case -3:
                            case -1:
                                long curDeadlineNanos = this.nextScheduledTaskDeadlineNanos();
                                if (curDeadlineNanos == -1L) {
                                    curDeadlineNanos = Long.MAX_VALUE;
                                }

                                this.nextWakeupNanos.set(curDeadlineNanos);

                                try {
                                    if (!this.hasTasks()) {
                                        strategy = this.select(curDeadlineNanos);
                                    }
                                    break;
                                } finally {
                                    this.nextWakeupNanos.lazySet(-1L);
                                }
                            case -2:
                                continue;
                        }
                    } catch (IOException var38) {
                        this.rebuildSelector0();
                        selectCnt = 0;
                        handleLoopException(var38);
                        continue;
                    }

                    ++selectCnt;
                    this.cancelledKeys = 0;
                    this.needsToSelectAgain = false;
                    int ioRatio = this.ioRatio;
                    boolean ranTasks;
                    if (ioRatio == 100) {
                        try {
                            if (strategy > 0) {
                                this.processSelectedKeys();
                            }
                        } finally {
                            ranTasks = this.runAllTasks();
                        }
                    } else if (strategy > 0) {
                        long ioStartTime = System.nanoTime();
                        boolean var26 = false;

                        try {
                            var26 = true;
                            this.processSelectedKeys();
                            var26 = false;
                        } finally {
                            if (var26) {
                                long ioTime = System.nanoTime() - ioStartTime;
                                this.runAllTasks(ioTime * (long)(100 - ioRatio) / (long)ioRatio);
                            }
                        }

                        long ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = this.runAllTasks(ioTime * (long)(100 - ioRatio) / (long)ioRatio);
                    } else {
                        ranTasks = this.runAllTasks(0L);
                    }

                    if (!ranTasks && strategy <= 0) {
                        if (this.unexpectedSelectorWakeup(selectCnt)) {
                            selectCnt = 0;
                        }
                        break;
                    }

                    if (selectCnt > 3 && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.", selectCnt - 1, this.selector);
                    }

                    selectCnt = 0;
                } catch (CancelledKeyException var39) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?", this.selector, var39);
                    }
                } catch (Throwable var40) {
                    handleLoopException(var40);
                }
                break;
            }

            try {
                if (this.isShuttingDown()) {
                    this.closeAll();
                    if (this.confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable var34) {
                handleLoopException(var34);
            }
        }
    }
}

可以看到方法是比较长的,这个方法里不仅轮询处理了普通任务,而且轮询处理io 事件,并且还处理Select 的空轮训问题;
关键点2.2.1 事件或者/任务的获取:

  strategy = this.selectStrategy.calculateStrategy(this.selectNowSupplier, this.hasTasks());

calculateStrategy 方法的调用:

public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
        return hasTasks ? selectSupplier.get() : -1;
    }

代码比较简单 如果没有任务处理 this.hasTasks() 返回false ,则次判断会返回-1 ,如果有任务处理则返回 selectSupplier.get() 值;所以只需要看selectSupplier.get() 的方法做了什么工作:
在NioEventLoop 类中对get 方法进行了实现,其中this.selectNow() 会立即去寻找channel 管道中的注册事件,并返回事件的个数:

private final IntSupplier selectNowSupplier = new IntSupplier() {
    public int get() throws Exception {
        return NioEventLoop.this.selectNow();
    }
};

从代码中可以看出,显然run 中死循环的方法,优先关注的是channel 管道中的io 事件;如果没有任务则直接返回-1 ,如果有任务也要先去拿到channel 中事件发生的数量,如果没有事件发生则返回0,否则返回发生事件的个数;

2.2.2 switch 分支的判断,可以看到只有当返回-1 ,-3的时候,有逻辑处理:

 switch (strategy) {
 	 case -3:
     case -1:
         long curDeadlineNanos = this.nextScheduledTaskDeadlineNanos();
         if (curDeadlineNanos == -1L) {
             curDeadlineNanos = Long.MAX_VALUE;
         }

         this.nextWakeupNanos.set(curDeadlineNanos);

         try {
             if (!this.hasTasks()) {
                 strategy = this.select(curDeadlineNanos);
             }
             break;
         } finally {
             this.nextWakeupNanos.lazySet(-1L);
         }
     case -2:
         continue;
 }

关键点在于 strategy = this.select(curDeadlineNanos) 如果此时没有认为,则会阻塞curDeadlineNanos 时间尝试去取的任务;

关键点2.2.3 普通任务和io 任务的执行:

if (ioRatio == 100) {
  try {
        if (strategy > 0) {
            this.processSelectedKeys();
        }
    } finally {
        ranTasks = this.runAllTasks();
    }
} else if (strategy > 0) {
    long ioStartTime = System.nanoTime();
    boolean var26 = false;

    try {
        var26 = true;
        this.processSelectedKeys();
        var26 = false;
    } finally {
        if (var26) {
            long ioTime = System.nanoTime() - ioStartTime;
            this.runAllTasks(ioTime * (long)(100 - ioRatio) / (long)ioRatio);
        }
    }

    long ioTime = System.nanoTime() - ioStartTime;
    ranTasks = this.runAllTasks(ioTime * (long)(100 - ioRatio) / (long)ioRatio);
} else {
    ranTasks = this.runAllTasks(0L);
}

这里有几个点简单做下解释:

  • ioRatio: io 时间处理的占比,默认值 50,也就意味着一半时间处理io 时间,一半时间处理普通任务;可以在 new NioEventLoopGroup 对其进行设置:
    在这里插入图片描述
  • this.processSelectedKeys(); 用来处理io 任务,this.runAllTasks(); 用来处理普通任务;
  • 处理普通任务时间占比计算方式如下:10s(io 执行的时间) *(100- 50(ioRatio))/ 50 = 10s
    在这里插入图片描述
  • ioRatio 设定为100 时 会先去执行io 任务,然后在去执行全部的普通任务,所以100 反而会降低 io 任务的处理;

关键点2.2.4 selector 的重建,解决空轮训 :
空轮训问题:在linux 底层当执行 selector.select(); 即时没有事件发生也会返回,而不会进行阻塞,由于轮询都放在死循环中,所以就会一直空轮训,当发生空轮训时 ,短时间内selectCnt 就会变得很大;

this.unexpectedSelectorWakeup(selectCnt)

空轮训的处理:SELECTOR_AUTO_REBUILD_THRESHOLD 的默认值是512,也即以为这当没有任务可以去执行,并且此时改值达到512 ,就会进入 this.rebuildSelector() 重新构建selector:

private boolean unexpectedSelectorWakeup(int selectCnt) {
    if (Thread.interrupted()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Selector.select() returned prematurely because Thread.currentThread().interrupt() was called. Use NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
        }

        return true;
    } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
        logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.", selectCnt, this.selector);
        this.rebuildSelector();
        return true;
    } else {
        return false;
    }
}

重新构建selector 不在进行展开,会new 出新的Selector 并把原有selector 事件注册到新的selector 上;

到这里为止,已经看到事件的轮询,任务的执行,已netty 对于Selector 空轮训的处理;

3 netty 中io 事件的处理:his.processSelectedKeys(); 处理nio 事件:

3.1 事件的处理:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    if (!k.isValid()) {
        NioEventLoop eventLoop;
        try {
            eventLoop = ch.eventLoop();
        } catch (Throwable var6) {
            return;
        }

        if (eventLoop == this) {
            unsafe.close(unsafe.voidPromise());
        }

    } else {
        try {
            int readyOps = k.readyOps();
            if ((readyOps & 8) != 0) {
                int ops = k.interestOps();
                ops &= -9;
                k.interestOps(ops);
                unsafe.finishConnect();
            }

            if ((readyOps & 4) != 0) {
                ch.unsafe().forceFlush();
            }

            if ((readyOps & 17) != 0 || readyOps == 0) {
				// 处理accept 和read 事件
                unsafe.read();
            }
        } catch (CancelledKeyException var7) {
            unsafe.close(unsafe.voidPromise());
        }

    }
}

关键点在与下面代码:处理accept 和read 事件

if ((readyOps & 17) != 0 || readyOps == 0) {
   // 处理accept 和read 事件
    unsafe.read();
}

3.2 unsafe.read(); 方法处理 accept 和read 事件:

private final class NioMessageUnsafe extends AbstractNioChannel.AbstractNioUnsafe {
    private final List<Object> readBuf;

    private NioMessageUnsafe() {
        super(AbstractNioMessageChannel.this);
        this.readBuf = new ArrayList();
    }

    public void read() {
        assert AbstractNioMessageChannel.this.eventLoop().inEventLoop();

        ChannelConfig config = AbstractNioMessageChannel.this.config();
        ChannelPipeline pipeline = AbstractNioMessageChannel.this.pipeline();
        RecvByteBufAllocator.Handle allocHandle = AbstractNioMessageChannel.this.unsafe().recvBufAllocHandle();
        allocHandle.reset(config);
        boolean closed = false;
        Throwable exception = null;

        try {
            int localRead;
            try {
                do {
                    localRead = AbstractNioMessageChannel.this.doReadMessages(this.readBuf);
                    if (localRead == 0) {
                        break;
                    }

                    if (localRead < 0) {
                        closed = true;
                        break;
                    }

                    allocHandle.incMessagesRead(localRead);
                } while(allocHandle.continueReading());
            } catch (Throwable var11) {
                exception = var11;
            }

            localRead = this.readBuf.size();

            for(int i = 0; i < localRead; ++i) {
                AbstractNioMessageChannel.this.readPending = false;
                pipeline.fireChannelRead(this.readBuf.get(i));
            }

            this.readBuf.clear();
            allocHandle.readComplete();
            pipeline.fireChannelReadComplete();
            if (exception != null) {
                closed = AbstractNioMessageChannel.this.closeOnReadError(exception);
                pipeline.fireExceptionCaught(exception);
            }

            if (closed) {
                AbstractNioMessageChannel.this.inputShutdown = true;
                if (AbstractNioMessageChannel.this.isOpen()) {
                    this.close(this.voidPromise());
                }
            }
        } finally {
            if (!AbstractNioMessageChannel.this.readPending && !config.isAutoRead()) {
                this.removeReadOp();
            }

        }

    }
}

关键点3.2.1 AbstractNioMessageChannel.this.doReadMessages(this.readBuf); SocketChannel 对象 的创建:

protected int doReadMessages(List<Object> buf) throws Exception {
    SocketChannel ch = SocketUtils.accept(this.javaChannel());

    try {
        if (ch != null) {
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable var6) {
        logger.warn("Failed to create a new channel from an accepted socket.", var6);

        try {
            ch.close();
        } catch (Throwable var5) {
            logger.warn("Failed to close a socket.", var5);
        }
    }

    return 0;
}
  • 改方法获取原生的ServerSocketChannel并创建SocketChannel 对象;
  • 通过new NioSocketChannel(this, ch)对SocketChannel 对象 设置io 流的非阻塞,对fNioServerSocketChannel读写属性的配置,默认Pipeline设置;
  • 将新建的 SocketChannel 的对象作为消息放入到List readBuf 中;

关键点3.2.2 对于新建SocketChannel 的占位事件注册:
for 循环通过调用链调用每个pipeline的fireChannelRead 方法并将消息当做参数进行传递;
在这里插入图片描述
pipeline.fireChannelRead(this.readBuf.get(i));进入ServerBootstrap 中ServerBootstrapAcceptor 的channelRead 方法:对传递过来的msg(NioServerSocketChannel) 进行handler 以及其他属性的设置后,通过childGroup 进行register:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel)msg;
    child.pipeline().addLast(new ChannelHandler[]{this.childHandler});
    AbstractBootstrap.setChannelOptions(child, this.childOptions, ServerBootstrap.logger);
    AbstractBootstrap.setAttributes(child, this.childAttrs);

    try {
        this.childGroup.register(child).addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    ServerBootstrap.ServerBootstrapAcceptor.forceClose(child, future.cause());
                }

            }
        });
    } catch (Throwable var5) {
        forceClose(child, var5);
    }

}

3.2.3 进入 MultithreadEventLoopGroup 中的register 方法将NioServerSocketChannel 完成注册:

public ChannelFuture register(Channel channel) {
    return this.next().register(channel);
}

进入到AbstractChannel register方法:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ObjectUtil.checkNotNull(eventLoop, "eventLoop");
    if (AbstractChannel.this.isRegistered()) {
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
    } else if (!AbstractChannel.this.isCompatible(eventLoop)) {
        promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
    } else {
        AbstractChannel.this.eventLoop = eventLoop;
        if (eventLoop.inEventLoop()) {
            this.register0(promise);
        } else {
            try {
                eventLoop.execute(new Runnable() {
                    public void run() {
                        AbstractUnsafe.this.register0(promise);
                    }
                });
            } catch (Throwable var4) {
                AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
                this.closeForcibly();
                AbstractChannel.this.closeFuture.setClosed();
                this.safeSetFailure(promise, var4);
            }
        }

    }
}

因为此时的线程 是:
在这里插入图片描述
所以进入else 通过workGroup 中的NioEventLoop 进行任务的提交:关键点 在AbstractChannel 类中通过AbstractChannel.this.doRegister(); 方法完成对SocketChannel 的注册并且进行感兴趣事件的占位;
在这里插入图片描述

关键点3.2.4 自己业务类中handler 的添加:
在注册完成之后通过AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded(); 完成对新的SocketChannel 进行初始化方法的调用,进入自己业务中的ChannelInitializer 的initChannel 方法:进行handler 的添加;
在这里插入图片描述
关键点3.2.5 对SocketChannel 读事件的注册:
AbstractChannel.this.pipeline.fireChannelActive();进入到AbstractNioChannel 中doBeginRead 方法完成读事件的注册;这样就将新建的SocketChannel 在处理任务的worker 事件处理组中完成了读事件的注册;
在这里插入图片描述

到现在为止,netty 中已经在boss NioEventLoopGroup 中完成了对accept 事件的处理;并创建出了 新的SocketChannel 并注册了读事件,并且注册到 worker NioEventLoopGroup 中的一个NioEventLoop的selector 上;这样 worker NioEventLoopGroup 终于可以处理来自客户端的读事件了;

3.2.6 worker NioEventLoopGroup 对于客户端写入数据的处理:
在客户端进行写数据后,进入到服务端的:NioEventLoop 中的processSelectedKey 方法然后读取事件:随后进入到AbstractNioByteChannel 中的read 方法得到客户端的数据并调用pipeline.fireChannelRead(byteBuf)方法依次调用服务端的hadler 处理器;

public final void read() {
    ChannelConfig config = AbstractNioByteChannel.this.config();
    if (AbstractNioByteChannel.this.shouldBreakReadReady(config)) {
        AbstractNioByteChannel.this.clearReadPending();
    } else {
        ChannelPipeline pipeline = AbstractNioByteChannel.this.pipeline();
        ByteBufAllocator allocator = config.getAllocator();
        RecvByteBufAllocator.Handle allocHandle = this.recvBufAllocHandle();
        allocHandle.reset(config);
        ByteBuf byteBuf = null;
        boolean close = false;

        try {
            do {
                byteBuf = allocHandle.allocate(allocator);
                allocHandle.lastBytesRead(AbstractNioByteChannel.this.doReadBytes(byteBuf));
                if (allocHandle.lastBytesRead() <= 0) {
                    byteBuf.release();
                    byteBuf = null;
                    close = allocHandle.lastBytesRead() < 0;
                    if (close) {
                        AbstractNioByteChannel.this.readPending = false;
                    }
                    break;
                }

                allocHandle.incMessagesRead(1);
                AbstractNioByteChannel.this.readPending = false;
                pipeline.fireChannelRead(byteBuf);
                byteBuf = null;
            } while(allocHandle.continueReading());

            allocHandle.readComplete();
            pipeline.fireChannelReadComplete();
            if (close) {
                this.closeOnRead(pipeline);
            }
        } catch (Throwable var11) {
            this.handleReadException(pipeline, byteBuf, var11, close, allocHandle);
        } finally {
            if (!AbstractNioByteChannel.this.readPending && !config.isAutoRead()) {
                this.removeReadOp();
            }

        }

    }
}

关键代码在于 pipeline.fireChannelRead(byteBuf);会依次调用服务端inbound的hadler 处理器
在这里插入图片描述

至此netty 中 实现客户端accept&read事件处理;

4 总结:

  • boos 的NioEventLoopGroup 对来自于客户端的accept 的事件进行了处理;
  • 并且创建了SocketChannel 对象完成初始化之后,在其Pipeline 增加了本身业务的handler;
  • 然后在worker 的NioEventLoopGroup 选择一个NioEventLoop 进行占位事件的注册;
  • 当SocketChannel channel 初始化完成之后 ,完成对客户端读事件的注册,这样在worker 的NioEventLoopGroup 就实现了对于客户端 读事件的处理,而boos 的NioEventLoopGroup 只专注于对客户端accept 事件的处理;

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

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

相关文章

JMeter接口测试新思路——灵活使用BeanShell

目录 前言&#xff1a; BeanShell的简介 调用Java方法 执行Class文件 结合实际案例 总结 前言&#xff1a; 在JMeter进行接口测试时&#xff0c;我们可能会遇到需要调用Java方法或者执行Java代码的情况&#xff0c;这时候我们可以使用BeanShell来实现。BeanShell是一个类…

QuintoAndar 如何提高转化率

QuintoAndar 如何提高转化率 ——求关注、求点赞、求分享&#xff0c;二毛拜谢。 QuintoAndar 如何通过提高页面性能来提高每次会话的转化率和页面数 一个专注于优化 Core Web Vitals 并迁移到 Next.js 的项目使转换率提高了 5%&#xff0c;每个会话的页面增加了 87%。 Quint…

07.JavaWeb-Vue+elementUI

1.Vue 功能替代JavaScript和jQuery&#xff0c;基于JavaScript实现的前端框架 1.1配置Vue 1.1.1引入vue库 方法一&#xff1a;通过cdn链接引入最新版本的vue&#xff08;可能会慢些&#xff09; <head><script src"https://cdn.jsdelivr.net/npm/vue">…

基于yolov5开发构建道路路面病害检测识别系统——以捷克、印度、日本三国城市道路实况场景数据为例,开发对比分析模型并分析对应性能

城市道路病害检测是最近比较热门的一个任务领域&#xff0c;核心就是迁移深度学习目前已有的研究成果来实现实时城市道路路面病害的检测识别分析&#xff0c;在我之前的很多博文中都有做过类似桥梁、大坝、基建、隧道等水泥设施裂缝裂痕等目标检测相关的项目&#xff0c;除此之…

利用powershell脚本进行内网渗透

powershell知识点 ps1是powershell脚本的拓展名&#xff0c;就相当于cmd的.bat脚本&#xff0c;但是他更加强大。 获取版本信息 get-host #查看powershell的版本信息$psversiontable #查看powershell的版本信息执行策略 PowerShell 执行策略是一项安全功能&#xff0c;用于控…

softmax之温度系数

1.数学表示 这是传统的softmax&#xff1a; q i e x p ( z i ) ∑ j e x p ( z j ) q_i \frac{exp(z_i)}{\sum_jexp(z_j)} qi​∑j​exp(zj​)exp(zi​)​ 或者写&#xff1a; q i e x p ( z i ) / 1.0 ∑ j e x p ( z j / 1.0 ) q_i \frac{exp(z_i)/1.0}{\sum_jexp(z_j/…

《LCHub低代码指南》:ChatGPT会取代低代码开发平台吗?

目录 一、低代码开发平台的优势 1. 提高开发效率 2. 降低开发成本 3. 提高应用程序的质量 二、ChatGPT的优势 三、ChatGPT是否会取代低代码开发平台 四、结论 随着数字化时代的到来,低代码开发平台已经成为了企业数字化转型的重要工具之一。然而,随着人工智能技术的不…

提升教学质量,监督教室课堂秩序?这招小白也能轻松搞定

在当今快速发展的教育领域&#xff0c;提高教学质量和监督教师的工作表现是学校和教育机构的重要任务之一。 传统的巡课方式存在许多限制&#xff0c;如耗时、人力成本高以及数据收集和分析的困难等。为了应对这些挑战&#xff0c;越来越多的学校和教育机构转向在线巡课系统&am…

微信小程序怎么直播?

我们目前使用的小程序都是支持直播功能的&#xff0c;小程序直播功能是通过小程序直播组件实现的&#xff0c;这是微信为商家提供的实时视频直播工具&#xff0c;可以帮助商家快速通过小程序向用户提供优质的直播内容。同时&#xff0c;借助小程序丰富的营销功能&#xff0c;使…

一、Drools 规则引擎

一、问题引出 现有一个在线申请信用卡的业务场景&#xff0c;用户需要录入个人信息&#xff0c;如下图所示&#xff1a; 通过上图可以看到&#xff0c;用户录入的个人信息包括 姓名、性别、年龄、学历、电话、所在公司、职位、月收入、是否有房、是否有车、是否有信用卡等。录入…

Netty中ServerBootstrap类介绍

一、Netty基本介绍 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具&#xff0c;用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty 在保证易于开发的同时还保证了其应用的性能&#xff0c;稳定性和伸缩性。 Netty 是一…

UE5 PCG模块学习1

这次来学习一下UE5.2中正式加入的PCG功能。网上较多的案例是在Landscape地形上创建贴合地面的物体&#xff0c;博主研究了一下&#xff0c;这个案例将创建贴合Mesh的物体&#xff1a; 1.基础生成 1.首先在插件中检查Procedural Content Generation Framework是否已经被开启&…

自学黑客的12个步骤

黑客攻防是一个极具魅力的技术领域&#xff0c;但成为一名黑客毫无疑问也并不容易。你必须拥有对新技术的好奇心和积极的学习态度&#xff0c;具备很深的计算机系统、编程语言和操作系统知识&#xff0c;并乐意不断地去学习和进步。 如果你想成为一名优秀的黑客&#xff0c;下…

Java 获取七牛云存储空间中的所有图片列表

文章目录 获取七牛云密钥导入依赖编辑 YAML 配置文件添加七牛云配置类编写 QiNiuImgUrls 方法测试结果 七牛云官方文档&#xff1a;https://developer.qiniu.com/kodo/sdk/java 如果有还不会使用SpringBoot整合七牛云存储的小伙伴们&#xff0c;可以跳转查看这篇文章&#xff1…

Revit中如何画弯曲的轴网和显示实时轴号?

一、Revit中如何画弯曲的轴网 生活中&#xff0c;有很多圆筒样式的建筑&#xff0c;比如说鸟巢和土楼&#xff0c;他们的外壁是弯曲的。所以&#xff0c;当我们用Revit创建这类模型时&#xff0c;轴网就要画弯曲的&#xff0c;那么&#xff0c;Revit中如何画弯曲的轴网呢&#…

JMeter接口压测和性能监测

引言 今天我来和大家分享一篇关于JMeter接口压测和性能监测的文章。在现代互联网时代&#xff0c;应用程序的性能已经成为了一个非常重要的问题&#xff0c;并且对于许多公司的生存和发展都起着至关重要的作用。 而在这其中&#xff0c;JMeter是一个非常实用的工具&#xff0…

CSAPP - AttackLab实验(阶段1-5)

AttackLab实验 实验内容 官网&#xff1a;http://csapp.cs.cmu.edu/3e/labs.html “AttackLab”是一个Linux下的可执行C程序&#xff0c;包含了5个阶段&#xff08;phase1~phase5&#xff09;的不同内容。程序运行过程中&#xff0c;要求学生能够根据缓冲区的工作方式和程序…

【Flutter】如何移除 Flutter 右上角的 DEBUG 标识

文章目录 一、前言二、什么是 DEBUG 标识三、为什么我们需要移除 DEBUG 标识四、如何移除 DEBUG 标识五、完整代码六、总结 一、前言 欢迎来到 Flutter 的世界&#xff01;在这篇文章中&#xff0c;我们将探索 Flutter 的一些基础知识。但是&#xff0c;你知道吗&#xff1f;这…

Science:“消除噪音”量子比特实现了纠错的重大突破

光子盒研究院 在《科学》杂志的一篇新论文中&#xff0c;芝加哥大学普利兹克分子工程学院Hannes Bernien助教实验室的研究人员描述了一种不断监测量子系统周围噪音并实时调整量子比特以减少误差的方法——他们引入了“旁观者量子比特(spectator qubit)”。 尽管他们有解决新型问…

数字图像处理实验报告

目录 实验二、图像在空间域上的处理方法 实验三、图像在频率域上的处理方法 实验二、图像在空间域上的处理方法 一、实验目的 了解图像亮&#xff08;灰&#xff09;度变换与空间滤波的意义和手段&#xff1b;熟悉图像亮&#xff08;灰&#xff09;度变换与空间滤波的MATLA…