BIO、NIO 和 AIO 基础介绍

BIO、NIO 和 AIO 基础介绍

在 Java 网络编程中,BIO、NIO、AIO 是三种核心的 I/O 模型,分别对应不同的阻塞/非阻塞、同步/异步特性,适用于不同的业务场景。

一、核心概念

1.1 基本定义

  • BIO:同步阻塞 I/O(Blocking I/O)
  • NIO:同步非阻塞 I/O(Non-blocking I/O),也叫 New I/O,核心是「多路复用」
  • AIO:异步非阻塞 I/O(Asynchronous I/O),也叫 NIO 2.0

1.2 重要概念区分

同步/异步 关注的是「是否需要等待 I/O 操作完成」

  • 同步:线程主动等待 I/O 结果,直到操作完成
  • 异步:线程无需等待,I/O 完成后由系统回调通知

阻塞/非阻塞 关注的是「等待时线程是否被挂起」

  • 阻塞:线程在 I/O 过程中被挂起,无法做其他事
  • 非阻塞:线程在 I/O 过程中不挂起,可继续执行其他逻辑

1.3 三种模型对比速览

模型 同步/异步 阻塞/非阻塞 线程模型 适用场景
BIO 同步 阻塞 一连接一线程 连接数少、低并发
NIO 同步 非阻塞 单线程处理多连接 高并发、短连接
AIO 异步 非阻塞 回调机制、无需轮询 高并发、长耗时 I/O

二、BIO(同步阻塞 I/O)

2.1 核心特点

  • 工作原理:线程发起 I/O 请求后,必须阻塞等待 I/O 操作完成(如读取数据、写入数据),期间线程无法做任何事;
  • 线程模型:每处理一个连接需要一个独立线程,连接数多的时候会导致线程数暴涨,性能瓶颈明显;
  • 编程模型:简单直观,适合连接数少、单次操作耗时短的场景(如简单的客户端-服务器通信)。

2.2 BIO 工作流程图

客户端1
服务器主线程
客户端2
客户端3
线程1
线程2
线程3
阻塞等待数据
阻塞等待数据
阻塞等待数据
处理数据
处理数据
处理数据

2.3 代码示例

(1)BIO 服务器
java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * BIO 同步阻塞服务器示例(优化后:使用线程池)
 * 每一个客户端连接对应一个线程,线程阻塞等待数据读取
 */
public class BioServer {
    private static final int PORT = 8080;
    private static final int MAX_THREADS = 100;
    
    public static void main(String[] args) throws IOException {
        // 使用线程池限制线程数量,避免无限创建线程
        ExecutorService threadPool = Executors.newFixedThreadPool(MAX_THREADS);
        
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("BIO 服务器已启动,监听端口 " + PORT + "...");
            System.out.println("最大并发线程数:" + MAX_THREADS);

            while (true) {
                // 阻塞点1:accept() 阻塞直到有客户端连接
                Socket socket = serverSocket.accept();
                System.out.println("新客户端连接:" + socket.getInetAddress() + 
                                 ":" + socket.getPort());

                // 将连接提交给线程池处理
                threadPool.submit(new ClientHandler(socket));
            }
        }
    }
    
    /**
     * 客户端处理器(每个连接一个任务)
     */
    static class ClientHandler implements Runnable {
        private final Socket socket;
        
        public ClientHandler(Socket socket) {
            this.socket = socket;
        }
        
        @Override
        public void run() {
            try (InputStream inputStream = socket.getInputStream()) {
                byte[] buffer = new byte[1024];
                while (true) {
                    // 阻塞点2:read() 阻塞直到有数据可读
                    int len = inputStream.read(buffer);
                    if (len == -1) { // 客户端关闭连接
                        System.out.println("客户端断开连接:" + 
                                         socket.getInetAddress() + ":" + socket.getPort());
                        break;
                    }
                    String msg = new String(buffer, 0, len);
                    System.out.println("[" + Thread.currentThread().getName() + 
                                     "] 收到消息:" + msg);
                    
                    // TODO: 业务处理
                    
                    // 模拟处理耗时
                    Thread.sleep(100);
                }
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
(2)BIO 客户端
java 复制代码
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * BIO 客户端示例
 */
public class BioClient {
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 8080;
    
    public static void main(String[] args) {
        try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT);
             OutputStream outputStream = socket.getOutputStream();
             Scanner scanner = new Scanner(System.in)) {
            
            System.out.println("已连接到服务器 " + SERVER_HOST + ":" + SERVER_PORT);
            System.out.println("请输入要发送的消息(输入 'exit' 退出):");
            
            while (true) {
                System.out.print("> ");
                String msg = scanner.nextLine();
                
                if ("exit".equalsIgnoreCase(msg)) {
                    break;
                }
                
                // 发送数据(阻塞,直到数据写入完成)
                outputStream.write(msg.getBytes());
                outputStream.flush();
                System.out.println("消息已发送");
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("客户端已关闭");
    }
}

2.4 BIO 优缺点

优点 缺点
编程模型简单,易于理解和实现 线程资源消耗大,连接数多时性能急剧下降
代码调试方便,执行流程清晰 线程阻塞造成资源浪费,CPU 利用率低
适合低并发、小数据量场景 系统稳定性差,容易因线程过多导致宕机

三、NIO(同步非阻塞 I/O)

3.1 核心特点

  • 工作原理:线程发起 I/O 请求后,无需阻塞等待,可先去处理其他任务,定期轮询查看 I/O 是否完成;
  • 核心组件 :
    • Channel(通道):双向读写,替代 BIO 的 Stream
    • Buffer(缓冲区):数据读写的载体
    • Selector(多路复用器):核心组件,单线程监听多个 Channel 的事件
  • 多路复用:一个 Selector 可以监听多个 Channel 的事件(连接、读、写),单个线程就能处理大量连接,解决 BIO 线程膨胀问题;
  • 适用场景:连接数多、单次操作耗时短的场景(如高并发的 Web 服务器)。

3.2 NIO 多路复用核心流程图

无
有
ACCEPT(新连接)
READ(可读)
是
否
WRITE(可写)
是
否
创建 ServerSocketChannel
配置为非阻塞模式
绑定监听端口
创建 Selector(多路复用器)
将 ServerSocketChannel 注册到 Selector,关注 ACCEPT 事件
Selector 轮询就绪事件 select()
是否有就绪事件?
获取就绪的 SelectionKey 集合
遍历 SelectionKey 集合
判断事件类型
通过 ServerSocketChannel 接收 SocketChannel
配置 SocketChannel 为非阻塞模式
将 SocketChannel 注册到 Selector,关注 READ 事件
通过 SocketChannel 读取数据到 Buffer
处理业务逻辑
是否需要返回数据?
修改关注事件为 WRITE
通过 SocketChannel 写入 Buffer 数据
数据写入完成?
修改关注事件为 READ

3.3 NIO 代码示例

java 复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * NIO 多路复用服务器示例(完整版)
 * 单个线程通过 Selector 处理所有客户端连接和读写事件
 */
public class NioServer {
    private static final int PORT = 8080;
    private static final int BUFFER_SIZE = 1024;
    private static final long SELECTOR_TIMEOUT = 1000; // 1秒超时
    
    // 统计在线客户端数
    private static final ConcurrentHashMap<SocketChannel, String> clients = 
        new ConcurrentHashMap<>();
    
    public static void main(String[] args) {
        try {
            new NioServer().start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public void start() throws IOException {
        // 1. 创建 ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        
        // 2. 配置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        
        // 3. 绑定端口
        serverSocketChannel.bind(new InetSocketAddress(PORT));
        System.out.println("NIO 服务器已启动,监听端口 " + PORT + "...");
        
        // 4. 创建 Selector
        Selector selector = Selector.open();
        
        // 5. 注册 ServerSocketChannel 到 Selector,关注 ACCEPT 事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        // 6. 主循环:处理就绪事件
        while (true) {
            // 阻塞等待就绪事件(带超时)
            int readyChannels = selector.select(SELECTOR_TIMEOUT);
            
            if (readyChannels == 0) {
                continue; // 无就绪事件,继续轮询
            }
            
            // 获取所有就绪的 SelectionKey
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            
            // 遍历处理每个就绪事件
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove(); // 必须移除,避免重复处理
                
                try {
                    // 处理事件
                    if (!key.isValid()) {
                        continue;
                    }
                    
                    if (key.isAcceptable()) {
                        handleAccept(key, selector);
                    } else if (key.isReadable()) {
                        handleRead(key);
                    } else if (key.isWritable()) {
                        handleWrite(key);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    // 异常时关闭连接
                    key.cancel();
                    closeChannel(key.channel());
                }
            }
            
            // 可选:打印在线客户端数
            System.out.println("当前在线客户端数:" + clients.size());
        }
    }
    
    /**
     * 处理新连接
     */
    private void handleAccept(SelectionKey key, Selector selector) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        
        // 接收新连接(非阻塞)
        SocketChannel socketChannel = serverChannel.accept();
        if (socketChannel == null) return;
        
        // 配置为非阻塞模式
        socketChannel.configureBlocking(false);
        
        // 注册 READ 事件
        socketChannel.register(selector, SelectionKey.OP_READ, 
                              ByteBuffer.allocate(BUFFER_SIZE));
        
        // 记录客户端
        String clientInfo = socketChannel.getRemoteAddress().toString();
        clients.put(socketChannel, clientInfo);
        System.out.println("新客户端连接:" + clientInfo);
        System.out.println("当前在线客户端数:" + clients.size());
    }
    
    /**
     * 处理读事件
     */
    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        
        // 读取数据(非阻塞)
        int bytesRead = socketChannel.read(buffer);
        
        if (bytesRead == -1) {
            // 客户端关闭连接
            String clientInfo = clients.remove(socketChannel);
            System.out.println("客户端断开连接:" + clientInfo);
            key.cancel();
            closeChannel(socketChannel);
            return;
        }
        
        if (bytesRead > 0) {
            // 切换到读模式
            buffer.flip();
            
            // 读取数据
            byte[] data = new byte[buffer.limit()];
            buffer.get(data);
            String message = new String(data).trim();
            
            // 获取客户端信息
            String clientInfo = clients.get(socketChannel);
            System.out.println("收到来自 " + clientInfo + " 的消息:" + message);
            
            // 清空缓冲区,准备下次读取
            buffer.clear();
            
            // 准备响应数据
            String response = "服务器已收到: " + message;
            ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
            
            // 如果需要返回数据,修改关注事件为 WRITE
            key.interestOps(SelectionKey.OP_WRITE);
            key.attach(responseBuffer);
        }
    }
    
    /**
     * 处理写事件
     */
    private void handleWrite(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        
        // 写入数据(非阻塞)
        socketChannel.write(buffer);
        
        if (!buffer.hasRemaining()) {
            // 数据写入完成,重新关注 READ 事件
            key.interestOps(SelectionKey.OP_READ);
            key.attach(ByteBuffer.allocate(BUFFER_SIZE));
        }
    }
    
    /**
     * 关闭通道
     */
    private void closeChannel(Channel channel) {
        if (channel != null && channel.isOpen()) {
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.4 NIO 优缺点

优点 缺点
单线程可处理大量连接,资源消耗小 编程复杂度高,需要处理各种边界情况
高并发下性能优秀,CPU 利用率高 调试会比较困难,容易出现 bug
数据读写灵活,可精确控制缓冲区 需要处理半包/粘包问题

四、AIO(异步非阻塞 I/O)

4.1 核心特点

  • 工作原理:线程发起 I/O 请求后,直接返回,无需轮询,I/O 操作由操作系统完成后,通过回调函数通知线程处理结果;
  • 核心组件 :
    • AsynchronousServerSocketChannel:异步服务器通道
    • AsynchronousSocketChannel:异步客户端通道
    • CompletionHandler:回调处理器
  • 优势:无需手动多路复用,由系统底层完成,编程模型更简洁;
  • 适用场景:连接数多、单次操作耗时长的场景(如文件下载、大数据传输)。

4.2 AIO 工作流程图

回调处理器 操作系统内核 应用程序 回调处理器 操作系统内核 应用程序 立即返回,不阻塞 整个过程应用程序无需阻塞等待 1. 发起异步读操作 2. 继续执行其他任务 3. 内核准备数据 4. 内核将数据拷贝到缓冲区 5. I/O 完成,触发回调 6. 处理数据

4.3 AIO 代码示例

java 复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * AIO 异步非阻塞服务器示例(完整版)
 * 基于回调处理连接和读写事件,无需轮询
 */
public class AioServer {
    private static final int PORT = 8080;
    private static final int BUFFER_SIZE = 1024;
    private static final ConcurrentHashMap<AsynchronousSocketChannel, String> clients = 
        new ConcurrentHashMap<>();
    
    // 用于处理回调的业务线程池
    private ExecutorService businessThreadPool = Executors.newFixedThreadPool(10);
    
    public static void main(String[] args) {
        try {
            new AioServer().start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public void start() throws IOException {
        // 1. 创建异步服务器通道
        AsynchronousServerSocketChannel serverChannel = 
            AsynchronousServerSocketChannel.open();
        
        // 2. 绑定端口
        serverChannel.bind(new InetSocketAddress(PORT));
        System.out.println("AIO 服务器已启动,监听端口 " + PORT + "...");
        
        // 3. 异步接收连接
        serverChannel.accept(null, new AcceptCompletionHandler(serverChannel));
        
        // 4. 防止主线程退出
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            businessThreadPool.shutdown();
        }
    }
    
    /**
     * 连接接收回调处理器
     */
    class AcceptCompletionHandler implements 
            CompletionHandler<AsynchronousSocketChannel, Void> {
        
        private final AsynchronousServerSocketChannel serverChannel;
        
        public AcceptCompletionHandler(AsynchronousServerSocketChannel serverChannel) {
            this.serverChannel = serverChannel;
        }
        
        @Override
        public void completed(AsynchronousSocketChannel socketChannel, Void attachment) {
            // 立即再次接收新连接(关键:必须再次调用accept,形成循环)
            serverChannel.accept(null, this);
            
            try {
                // 处理新连接
                String clientInfo = socketChannel.getRemoteAddress().toString();
                clients.put(socketChannel, clientInfo);
                System.out.println("新客户端连接:" + clientInfo);
                System.out.println("当前在线客户端数:" + clients.size());
                
                // 创建缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
                
                // 异步读取数据
                socketChannel.read(buffer, buffer, new ReadCompletionHandler(socketChannel));
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        @Override
        public void failed(Throwable exc, Void attachment) {
            System.err.println("接收连接失败:" + exc.getMessage());
            // 继续接收(失败后重试)
            serverChannel.accept(null, this);
        }
    }
    
    /**
     * 读取数据回调处理器
     */
    class ReadCompletionHandler implements 
            CompletionHandler<Integer, ByteBuffer> {
        
        private final AsynchronousSocketChannel socketChannel;
        
        public ReadCompletionHandler(AsynchronousSocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }
        
        @Override
        public void completed(Integer bytesRead, ByteBuffer buffer) {
            if (bytesRead == -1) {
                // 客户端关闭连接
                String clientInfo = clients.remove(socketChannel);
                System.out.println("客户端断开连接:" + clientInfo);
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return;
            }
            
            if (bytesRead > 0) {
                // 切换到读模式
                buffer.flip();
                
                // 读取数据
                byte[] data = new byte[buffer.limit()];
                buffer.get(data);
                String message = new String(data).trim();
                
                // 获取客户端信息
                String clientInfo = clients.get(socketChannel);
                
                // 业务处理(提交到业务线程池,避免阻塞回调线程)
                businessThreadPool.submit(() -> {
                    try {
                        System.out.println("收到来自 " + clientInfo + " 的消息:" + message);
                        
                        // 模拟业务处理
                        String processedMsg = processMessage(message);
                        
                        // 准备响应
                        String response = "服务器响应: " + processedMsg;
                        ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
                        
                        // 异步写入响应
                        socketChannel.write(responseBuffer, null, 
                            new WriteCompletionHandler(socketChannel, buffer));
                        
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
        }
        
        @Override
        public void failed(Throwable exc, ByteBuffer buffer) {
            System.err.println("读取数据失败:" + exc.getMessage());
            try {
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        /**
         * 模拟消息处理
         */
        private String processMessage(String message) {
            // TODO: 实际的业务处理
            return "processed: " + message;
        }
    }
    
    /**
     * 写入数据回调处理器
     */
    class WriteCompletionHandler implements 
            CompletionHandler<Integer, ByteBuffer> {
        
        private final AsynchronousSocketChannel socketChannel;
        private final ByteBuffer buffer;
        
        public WriteCompletionHandler(AsynchronousSocketChannel socketChannel, 
                                     ByteBuffer buffer) {
            this.socketChannel = socketChannel;
            this.buffer = buffer;
        }
        
        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            // 数据写入完成后,清空缓冲区并继续读取下一条消息
            buffer.clear();
            socketChannel.read(buffer, buffer, new ReadCompletionHandler(socketChannel));
        }
        
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            System.err.println("写入数据失败:" + exc.getMessage());
            try {
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4.4 AIO 优缺点

优点 缺点
真正的异步 I/O,无需轮询 编程复杂度高,回调嵌套容易造成混乱
适合长连接、大文件传输 JDK 对 AIO 的支持不如 NIO 成熟
系统资源利用率高 调试困难,错误处理复杂

五、三种 I/O 模型深度对比

5.1 数据流对比图

AIO 流程
发起异步读请求
立即返回
继续其他任务
内核准备数据并拷贝
回调通知
处理数据
NIO 流程
否
是
发起读请求
数据是否就绪?
立即返回

继续其他任务
轮询检查
阻塞等待数据拷贝
处理数据
BIO 流程
发起读请求
阻塞等待内核准备数据
阻塞等待内核拷贝数据
处理数据

5.2 详细对比表

对比维度 BIO NIO AIO
I/O 模型 同步阻塞 同步非阻塞 异步非阻塞
线程模型 一个连接一个线程 一个线程处理多个连接 回调机制,不占用线程
数据拷贝 应用线程负责 应用线程负责 内核完成,回调通知
编程复杂度 简单 中等 复杂
调试难度 容易 中等 困难
CPU 利用率 低(线程阻塞) 高 高
内存占用 高(线程多) 低 低
并发能力 低 高 高
吞吐量 低 高 高
响应时间 中等 低 低
典型应用 传统 Socket,JDBC Tomcat,Netty,Jetty 文件服务器,专业中间件
Java 版本 JDK 1.0+ JDK 1.4+ JDK 1.7+

5.3 性能对比场景

场景 BIO NIO AIO 推荐方案
连接数 < 1000 较好 好 好 BIO(简单)
连接数 1000-10000 差 好 好 NIO
连接数 > 10000 不可用 较好 好 AIO
短连接、高频请求 差 优秀 好 NIO
长连接、低频交互 差 好 优秀 AIO
大文件传输 差 中等 优秀 AIO
实时性要求高 中等 优秀 优秀 NIO/AIO

六、实际应用建议

6.1 技术选型指南

  1. 简单应用,连接数少(<100):

    • 选择 BIO,开发简单,维护容易
    • 示例:小型管理系统、内部工具
  2. 高并发 Web 服务(1000-10000 连接):

    • 选择 NIO,性能好,生态丰富
    • 示例:Web 服务器、API 网关、即时通讯
  3. 大文件传输、长连接:

    • 选择 AIO,真正的异步 I/O
    • 示例:文件服务器、视频流媒体
  4. 中间件开发:

    • 选择 NIO(如 Netty),稳定性好,社区活跃
    • 示例:RPC 框架、消息队列
  • 大多数场景:选择 NIO(通过 Netty 等框架)
  • 简单场景:BIO 足够
  • 特殊场景(大文件、长连接):考虑 AIO

6.2 常见框架对比

框架 底层 I/O 模型 适用场景
Tomcat NIO(默认)/BIO Web 应用服务器
Netty NIO 高性能网络应用
Jetty NIO 嵌入式 Web 服务器
Undertow NIO 高并发 Web 服务器
Grizzly NIO 通用网络框架

6.3 最佳实践

  1. 避免在 NIO 的 Handler 中做耗时操作,应提交到业务线程池
  2. 合理设置缓冲区大小,避免内存浪费
  3. 处理半包/粘包问题,使用消息定长或分隔符,发送消息的时候先发送数据长度,再发送消息,根据消息长度再获取消息
  4. 做好异常处理,避免资源泄露
  5. 监控线程池和连接数,及时发现问题

相关推荐
汤姆yu5 小时前
Gemini 4 Argon模型综述:技术、能力与实际使用指南
网络·安全·web安全·ai·大模型
Seraphina366 小时前
SQL注入(二)盲注
网络·数据库·经验分享·笔记·sql
2401_868534786 小时前
无线网络规划设计
服务器·网络
螺蛳粉 螺蛳粉7 小时前
第二篇:keepalived VRRP 原理篇:协议与 VIP 漂移机制深入解析
网络·智能路由器·负载均衡·keepalived·高可用
盟接之桥7 小时前
AI助手:你的7×24小时智能管家——让异常预警无处不在
大数据·网络·数据库·人工智能·制造·ai编程
2603_969579289 小时前
内网数据自动备份工具:隔离网络下的数据保护方案
网络
逗脑IDE9 小时前
零基础学ESP32:RFID无线射频卡——让ESP32拥有“刷卡“能力!
开发语言·网络·人工智能·python·esp32·硬件开发
cws2004019 小时前
监控摄像头选型指南
运维·网络·监控
和裕9 小时前
定制纸箱刀模费全解析:费用定义与可减免合作场景
大数据·运维·网络·人工智能·算法
troy12810 小时前
智能体沙箱:如何为 Agent 模型搭建安全试验场,降低幻觉带来的损失
网络·安全