网络编程-阻塞、非阻塞、多路复用、Selector对于accept、read、write事件监听实现详解

news2025/1/8 4:47:21

阻塞

理论

  • 阻塞模式下,相关方法(accept、read、write)都会导致线程暂停。
    • ServerSocketChannel.accept 会在没有连接建立时让线程暂停。
    • SocketChannel.read 会在没有数据可读时让线程暂停。
    • 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置。
  • 单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持。
  • 多线程下,有新的问题,体现在以下方面:
    • 32 位 jvm 一个线程 320k,64 位 jvm 一个线程 1024k,如果连接数过多,必然导致 OOM,并且线程太多,反而会因为频繁上下文切换导致性能降低。
    • 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间 inactive,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接。

Code

Server

package netty.netProgram.block;

import lombok.extern.slf4j.Slf4j;
import netty.utils.ByteBufferUtil;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;

/**
 * 阻塞
 */
@Slf4j
public class Server {
    public static void main(String[] args) {
        try {
            // 创建buffer用于接收从客户端获取的数据
            ByteBuffer buffer = ByteBuffer.allocate(10);
            // 创建服务端channel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 绑定连接的端口
            serverSocketChannel.bind(new InetSocketAddress(8080));
            // 连接集合
            List<SocketChannel> channels = new ArrayList<>();
            while (true){
                // 阻塞等待连接事件
                log.debug("connecting...");
                SocketChannel sc = serverSocketChannel.accept();
                log.debug("connected sc : {}", sc);
                channels.add(sc);
                for (SocketChannel channel : channels) {
                    // 接收客户端发送的数据
                    log.debug("receive data from client...");
                    int read = channel.read(buffer);
                    if(read == -1){
                        continue;
                    }
                    log.debug("receive data len is: {}", read);
                    // 切换为读模式
                    buffer.flip();
                    ByteBufferUtil.debugAll(buffer);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client

package netty.netProgram.block;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;

/**
 * 阻塞Client代码编写
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        try {
            // 创建client socketChannel
            SocketChannel sc = SocketChannel.open();
            // 连接到localhost 8080端口
            sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8080));
            log.debug("client connected");
            while (true){
                // 死循环,防止代码退出
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

非阻塞

理论

  • 非阻塞模式下,相关方法都会不会让线程暂停。
    • 在 ServerSocketChannel.accept 在没有连接建立时,会返回 null,继续运行。
    • SocketChannel.read 在没有数据可读时,会返回 0,但线程不必阻塞,可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept。
    • 写数据时,线程只是等待数据写入 Channel 即可,无需等 Channel 通过网络把数据发送出去。
  • 但非阻塞模式下,即使没有连接建立,和可读数据,线程仍然在不断运行,白白浪费了 cpu
  • 数据复制过程中,线程实际还是阻塞的(AIO 改进的地方)

Code

Server

package netty.netProgram.nonblock;

import lombok.extern.slf4j.Slf4j;
import netty.utils.ByteBufferUtil;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;

/**
 * nonBlock Server code
 */
@Slf4j
public class Server {
    public static void main(String[] args) {
        try {
            ByteBuffer buffer = ByteBuffer.allocate(16);
            // 创建服务端ServerSocketChannel
            ServerSocketChannel ssc = ServerSocketChannel.open();
            // 设置为非阻塞
            ssc.configureBlocking(false);
            // 绑定端口
            ssc.bind(new InetSocketAddress(8080));
            List<SocketChannel> channels = new ArrayList<>();
            while (true){
                // 建立连接
//                log.debug("server connecting...");
                SocketChannel sc = ssc.accept();
                if(sc != null){
                    // 设置sc为非阻塞
                    sc.configureBlocking(false);
                    log.debug("server connected, sc : {}", sc);
                    channels.add(sc);
                }
                for (SocketChannel channel : channels) {
                    int read = channel.read(buffer);
                    if(read <= 0){
                        continue;
                    }
                    buffer.flip();
                    ByteBufferUtil.debugAll(buffer);
                    buffer.clear();
                    log.debug("receive data from client end, sc : {}", channel);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client
client端代码同阻塞代码。

多路复用

单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控,这称之为多路复用

  • 多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用。普通文件IO不支持非阻塞模式,无法实现多路复用。
  • 如果不用 Selector 的非阻塞模式,线程大部分时间都在做无用功,而 Selector 能够保证:
    • 有可连接事件时才去连接。
    • 有可读事件才去读取。
    • 有可写事件才去写入。
    • 限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件
      在这里插入图片描述

Selector使用步骤

  • 创建Selector:Selector selector = Selector.open();
  • 绑定Channel事件,也称为注册事件,绑定的事件Selector才会关心。
    • channel 必须工作在非阻塞模式。
    • FileChannel 没有非阻塞模式,因此不能配合 selector 一起使用。
    • 绑定的事件类型可以有:connect、read、write、connect。
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, 绑定事件);
  • 监听channel事件:
    •           // 获取发生事件的channel数量
                int count = selector.select();
                if(count <= 0){
                    continue;
                }
                log.debug("current connected nums : {}", count);
                // 获取所有发生连接的事件
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iter = selectionKeys.iterator();
                
      

Select处理Accept事件

Code

Server:

package netty.netProgram.selector;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * selector处理accept事件Server
 */
@Slf4j
public class ServerAccept {
    public static void main(String[] args) {
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.bind(new InetSocketAddress(8080));
            // 创建selector
            Selector selector = Selector.open();
            // 绑定ssc到selector并指定当发生accept事件时触发
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            while (true){
                // 获取发生事件的channel数量
                int count = selector.select();
                if(count <= 0){
                    continue;
                }
                log.debug("current connected nums : {}", count);
                // 获取所有发生连接的事件
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iter = selectionKeys.iterator();
                while (iter.hasNext()){
                    SelectionKey key = iter.next();
                    // 判断事件类型
                    if(key.isAcceptable()){
                        ServerSocketChannel sSC = (ServerSocketChannel) key.channel();
                        SocketChannel sc = sSC.accept();
                        log.debug("sc is :{}", sc);
                    }
                    // 及时移除处理好的事件
                    iter.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client:

package netty.netProgram.selector;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * selector处理accept事件Server
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            log.debug("client is connecting...");
            socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8080));
            log.debug("client connected");
            ByteBuffer buffer = ByteBuffer.allocate(128);
            buffer.put("Hello Server i am client...".getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            while (true){}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Attention

事件发生后,要么处理,要么取消(cancel),不能什么都不做,否则下次该事件仍会触发,这是因为 nio 底层使用的是水平触发

Select处理Read事件

Code

Server:

package netty.netProgram.selector;

import lombok.extern.slf4j.Slf4j;
import netty.utils.ByteBufferUtil;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * selector Accept And Read Server
 */
@Slf4j
public class ServerAcceptRead {
    public static void main(String[] args) {
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.bind(new InetSocketAddress(8080));
            Selector selector = Selector.open();
            // 注册ssc到selector上,并且注明事件类型为ACCEPT事件
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            while (true){
                int count = selector.select();
                if(count <= 0){
                    continue;
                }
                log.debug("current connect num : {}", count);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iter = selectionKeys.iterator();
                while (iter.hasNext()){
                    SelectionKey key = iter.next();
                    // 判断事件类型是accept还是read
                    if(key.isAcceptable()){
                        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                        SocketChannel sc = serverSocketChannel.accept();
                        log.debug("ssc is: {}, sc is:{}", serverSocketChannel, sc);
                        sc.configureBlocking(false);
                        // 将sc注册到selector上,并设置其类型为Read
                        sc.register(selector, SelectionKey.OP_READ);
                    }else if(key.isReadable()){
                        SocketChannel sc = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(128);
                        int read = sc.read(buffer);
                        if(read == -1){
                            // 此时client已经关闭连接
                            // key.cancel()取消注册在selector上的channel,并从keys集合中删除key后续不在监听事件
                            key.cancel();
                            sc.close();
                        }else{
                            buffer.flip();
                            ByteBufferUtil.debugAll(buffer);
                        }
                    }
                    iter.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client:

package netty.netProgram.selector;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * selector处理accept事件Server
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            log.debug("client is connecting...");
            socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8080));
            log.debug("client connected");
            ByteBuffer buffer = ByteBuffer.allocate(128);
            buffer.put("Hello Server i am client...".getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            while (true){}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Attention

在这里插入图片描述

Select处理Connect事件

处理Write事件时需要考虑到buffer数据量过大时,不能一次性将其全部写入到channel中,需要注册write事件,并且需要关联buffer,以便下次继续写入。

  • 非阻塞模式下,无法保证把 buffer 中所有数据都写入 channel,因此需要追踪 write 方法的返回值(代表实际写入字节数)
  • 用 selector 监听所有 channel 的可写事件,每个 channel 都需要一个 key 来跟踪 buffer,但这样又会导致占用内存过多,就有两阶段策略
    • 当消息处理器第一次写入消息时,才将 channel 注册到 selector 上
    • selector 检查 channel 上的可写事件,如果所有的数据写完了,就取消 channel 的注册
    • 如果不取消,会每次可写均会触发 write 事件

Code

Server:

package netty.netProgram.selector;

import lombok.extern.slf4j.Slf4j;
import netty.utils.ByteBufferUtil;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

@Slf4j
public class ServerAcceptReadWrite {
    public static void main(String[] args) {
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.bind(new InetSocketAddress(8080));
            Selector selector = Selector.open();
            // 注册ssc到selector上,并且注明事件类型为ACCEPT事件
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            while (true){
                int count = selector.select();
                if(count <= 0){
                    continue;
                }
                log.debug("current connect num : {}", count);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iter = selectionKeys.iterator();
                while (iter.hasNext()){
                    SelectionKey key = iter.next();
                    // 判断事件类型是accept还是read
                    if(key.isAcceptable()){
                        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                        SocketChannel sc = serverSocketChannel.accept();
                        log.debug("ssc is: {}, sc is:{}", serverSocketChannel, sc);
                        sc.configureBlocking(false);
                        // 将sc注册到selector上,并设置其类型为Read
                        SelectionKey scKey = sc.register(selector, SelectionKey.OP_READ);

                        // 向client写入数据
                        StringBuilder sb = new StringBuilder();
                        for (int i = 0; i < 10000000; i++) {
                            sb.append("a");
                        }
                        ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
                        int write = sc.write(buffer);
                        log.debug("actual write bytes:{}", write);
                        if(buffer.hasRemaining()){
                            // 如果此时没有将buffer中的所有数据写入到channel,scKey在原有Read事件的基础上添加Write事件
                            scKey.interestOps(scKey.interestOps() + SelectionKey.OP_WRITE);
                            // 同时需要将buffer作为附件关联到scKey上
                            scKey.attach(buffer);
                        }
                    }else if(key.isReadable()){
                        SocketChannel sc = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(128);
                        int read = sc.read(buffer);
                        if(read == -1){
                            // 此时client已经关闭连接
                            // key.cancel()取消注册在selector上的channel,并从keys集合中删除key后续不在监听事件
                            key.cancel();
                            sc.close();
                        }else{
                            buffer.flip();
                            ByteBufferUtil.debugAll(buffer);
                        }
                    }else if(key.isWritable()){
                        ByteBuffer buffer  = (ByteBuffer) key.attachment();
                        SocketChannel channel = (SocketChannel) key.channel();
                        int write = channel.write(buffer);
                        log.debug("hit write, current write bytes:{}", write);
                        if(!buffer.hasRemaining()){
                            // 只要向 channel 发送数据时,socket 缓冲可写,这个事件会频繁触发,
                            // 因此应当只在 socket 缓冲区写不下时再关注可写事件,数据写完之后再取消关注
                            key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                            key.attach(null);
                        }
                    }
                    iter.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client:

package netty.netProgram.selector;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * selector处理accept事件Server
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            log.debug("client is connecting...");
            socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8080));
            log.debug("client connected");
            ByteBuffer buffer = ByteBuffer.allocate(128);
            buffer.put("Hello Server i am client...".getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            while (true){}
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

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

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

相关文章

Spring 解决bean的循环依赖

Spring循环依赖-博客园 1. 什么是循环依赖 2. 循环依赖能引发什么问题 循环依赖可能引发以下问题&#xff1a; 初始化顺序不确定&#xff1a;循环依赖导致无法确定哪个对象应该先被创建和初始化&#xff0c;从而造成初始化顺序的混乱。这可能导致错误的结果或意外的行为。死…

YouTube 创作者起诉 Nvidia 和 OpenAI

- **YouTube 创作者 David Millette**&#xff1a;一位 YouTube 创作者 David Millette 起诉了 AI 巨头 Nvidia&#xff0c;指控该公司未经许可使用他的视频来训练 AI 模型。此前不久&#xff0c;Millette 还起诉了 OpenAI&#xff0c;但并未指控这两家公司侵犯版权。 - **指控…

YOLOV8网络结构|搞懂Backbone-C2f

c2f没有改变图像的输入通道数和分辨率 传进去的和传出去的没变 n = 3*d d是模型缩放里面的depth split成两个分支:a和b 经过一个bottleneck就是加一个c 有n个bottleneck 一共是(n+2)个 学习资料:

加密创投周期进化论(下篇):失落的未来

“中心化主义收编”后时代&#xff0c;叙事枯竭怎么破&#xff1f; 作者&#xff1a;Wenser&#xff1b;编辑&#xff1a;郝方舟 出品 | Odaily星球日报&#xff08;ID&#xff1a;o-daily&#xff09; 在《加密创投周期进化论&#xff08;上篇&#xff09;&#xff1a;再造新世…

Win 10录屏也能如此专业?2024年还有3款免费工具,让你大开眼界

无论你是想捕捉游戏中的精彩瞬间&#xff0c;还是打算制作专业的教学视频&#xff0c;或者需要录制在线会议&#xff0c;Win 10都自带了很强的屏幕录制功能。今天我会告诉你怎么用Win 10自带的录屏工具&#xff0c;还会给你推荐三个好用的录屏软件&#xff0c;并且告诉你它们各…

【区块链+金融服务】农业大宗供应链线上融资平台 | FISCO BCOS应用案例

释放数据要素价值&#xff0c;FISCO BCOS 2024 应用案例征集 粮食贸易受季节性影响显著。每年的粮收季节&#xff0c;粮食收储企业会根据下游订单需求&#xff0c;从上游粮食贸易商或粮农手 里大量采购粮食&#xff0c;并分批销售给下游粮食加工企业&#xff08;面粉厂、饲料厂…

HTML—css

css概述 C S S 是 C a s c a d i n g S t y l e S h e e t s &#xff08; 级 联 样 式 表 &#xff09; 。 C S S 是 一 种 样 式 表 语 言 &#xff0c; 用 于 为 H T M L 文 档 控 制 外 观 &#xff0c; 定 义 布 局 。 例 如 &#xff0c; C S S 涉 及 字 体 、 颜 色 、…

社区帮扶对象管理系统pf

TOC springboot419社区帮扶对象管理系统pf 第1章 绪论 1.1 课题背景 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。所以各…

谷歌的高级指令有哪些

今天会分享一些组合用法&#xff0c;这样就能节省许多时间可以放在跟进客户上面&#xff08;本文只介绍谷歌的搜索指令&#xff0c;并无推广&#xff09; part one 谷歌常用的搜索引擎指令&#xff1a; 1、Inurl&#xff0c;在网址中 2、Intext&#xff0c;在网页内容中 3、…

Python使用BeautifulSoup进行网页爬虫技术详解

目录 一、BeautifulSoup简介 1.1 安装BeautifulSoup 1.2 引入BeautifulSoup 二、基本使用 2.1 实例化BeautifulSoup对象 2.2 解析HTML文档 2.2.1 查找标签 2.2.2 获取标签属性和内容 2.3 异常处理 三、进阶使用 3.1 复杂标签查找 3.1.1 CSS选择器 3.1.2 正则表达式…

【1-4】设计模式概述

目录 一.设计模式产生背景 二.软件设计模式的概念 三.学习设计模式的必要性 四.设计模式分类 一.设计模式产生背景 二.软件设计模式的概念 软件设计模式&#xff0c;又称设计模式&#xff0c;是一套被反复使用、多人知晓的、经过分类编目的、代码设计经验的总结。它描述了…

skynet的消息发送:send和call

skynet是一个轻量级的游戏服务器框架。 skynet的核心是服务&#xff0c;服务之间通过消息来通信&#xff0c;消息的来源主要有&#xff1a; 定时器网络服务之间的调用(skynet.send或skynet.call) skynet.send和skynet.call 假设我们有两个服务A和B&#xff0c;A发了两条消息…

AI+服装电商细分赛道的落地应用:图应AI模特的进化史干货篇

文章目录 AI绘制人物的效果进化史2022年2023年2024年 摄影师、设计师、模特三方在AI商拍领域的位置国家统计局的一些服装行业数据遇到的一些问题以及相应的解决方案图应AI这个产品未来可能怎么走统一回答某些投资人的一个问题 AI绘制人物的效果进化史 2022年 还记得我2022年从…

Understanding the Overheads of Launching CUDA Kernels (理解启动 CUDA Kernels 的开销)

Understanding the Overheads of Launching CUDA Kernels {理解启动 CUDA Kernels 的开销} Understanding the Overheads of Launching CUDA Kernels1. INTRODUCTION2. MICRO-BENCHMARKS USED IN OUR STUDY3. OVERHEAD OF LAUNCHING KERNELS3.1. Experimental Environment3.2. …

【Validation + i18n】✈️运行时参数校验+国际化上下文实现自定义参数校验规则

目录 &#x1f44b;前言 &#x1f440;一、环境准备 &#x1f4eb;二、代码实现 2.1 Validation 自定义验证类 2.2 自定义注解代码实现 &#x1f49e;️三、测试 &#x1f331;四、章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;最近在和一位读者讨论国际化上下文工具…

SpringBoot-01-全局异常处理器

在之前的项目中每一个异常的地方都要进行处理&#xff0c;十分的麻烦。 在springBoot项目中&#xff0c;提供了全局的异常处理器&#xff0c;可能出现异常的地方直接抛出即可。 RestControllerAdvice public class GlobalException {ExceptionHandlerpublic Result<String…

Golang | Leetcode Golang题解之第342题4的幂

题目&#xff1a; 题解&#xff1a; func isPowerOfFour(n int) bool {return n > 0 && n&(n-1) 0 && n%3 1 }

【电路笔记】-桥接 T 型衰减器

桥接 T 型衰减器 文章目录 桥接 T 型衰减器1、概述2、桥接 T 型衰减器示例 13、可变桥接 T 型衰减器4、完全可调衰减器5、可切换桥接 T 型衰减器Bridged-T 衰减器是另一种电阻衰减器设计,它是标准对称 T 垫衰减器的变体。 1、概述 顾名思义,桥接 T 形衰减器具有一个额外的电…

Chapter 39 Python多线程编程

欢迎大家订阅【Python从入门到精通】专栏&#xff0c;一起探索Python的无限可能&#xff01; 文章目录 前言一、并行执行二、threading模块 前言 现代操作系统如 macOS、UNIX、Linux 和 Windows 等&#xff0c;均支持多任务处理。本篇文章详细讲解了并行执行的概念以及如何在 …

苍穹外卖-day03(SpringBoot+SSM的企业级Java项目实战)

苍穹外卖-day03 课程内容 公共字段自动填充 新增菜品 菜品分页查询 删除菜品 修改菜品 功能实现&#xff1a;菜品管理 菜品管理效果图&#xff1a; 1. 公共字段自动填充 1.1 问题分析 在上一章节我们已经完成了后台系统的员工管理功能和菜品分类功能的开发&#xff0c…