Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >012. NIO 非阻塞网络编程

012. NIO 非阻塞网络编程

作者头像
山海散人
发布于 2021-03-03 02:51:47
发布于 2021-03-03 02:51:47
40800
代码可运行
举报
文章被收录于专栏:山海散人技术山海散人技术
运行总次数:0
代码可运行

1. Java NIO


  • 始于 Java1.4,提供了新的 JAVA IO 操作非阻塞 API。用意是替代 Java IO 和 Java Networking 相关的 API。
三个核心组件
  • Buffer 缓冲区
  • Channel 通道
  • Selector 选择器

2. Buffer 缓冲区


1. 介绍
  • 缓冲区本质上是一个可以写入数据的内存块(类似数组),然后可以再次读取。此内存块包含在 NIO Buffer 对象中,该对象提供了一组方法,可以更轻松地使用内存块。
  • 相比较直接对数组的操作,Buffer API 更加容易操作和管理。
  • 使用 Buffer 进行数据写入和读取,需要进行如下四个步骤:
    • 将数据写入缓冲区。
    • 调用 buffer.flip(),转化为读取模式。
    • 缓冲区读取数据。
    • 调用 buffer.clear() 或 buffer.compact() 清除缓冲区。
2. Buffer 工作原理
  • Buffer 三个重要属性
    • capacity 容量:作为一个内存块,Buffer 具有一定的固定大小,也称作容量。
    • position 位置:写入模式时代表写数据的位置。读取模式时代表读取数据的位置。
    • limit 限制:写入模式,限制等于 buffer 的容量。读取模式下,limit 等于写入的数据量。
3. ByteBuffer 内存类型
4. Buffer 的使用
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BufferDemo {
    public static void main(String[] args) {
        // 构建一个 byte 字节缓冲区,容量是 4
//        ByteBuffer byteBuffer = ByteBuffer.allocate(4);
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4);
        // 默认写入模式,查看三个重要指标
        System.out.println(String.format("初始化,capacity 容量:%s,position 位置:%s,limit 限制:%s",
                byteBuffer.capacity(), byteBuffer.position(), byteBuffer.limit()));
        // 写入三字节的数据
        byteBuffer.put((byte) 1);
        byteBuffer.put((byte) 2);
        byteBuffer.put((byte) 3);
        // 再看数据
        System.out.println(String.format("写入 3 字节后,capacity 容量:%s,position 位置:%s,limit 限制:%s",
                byteBuffer.capacity(), byteBuffer.position(), byteBuffer.limit()));

        // 转换为读取模式(不调用 flip 方法,也是可以读取数据的,但是 posiition 记录读取的位置不变)
        System.out.println("#### 开始读取 ####");
        byteBuffer.flip();
        byte a = byteBuffer.get();
        System.out.println(a);
        byte b = byteBuffer.get();
        System.out.println(b);
        System.out.println(String.format("读取 2 字节数据后,capacity 容量:%s,position 位置:%s,limit 限制:%s",
                byteBuffer.capacity(), byteBuffer.position(), byteBuffer.limit()));

        // 继续写入三字节,此时读模式下,limit=3,position=2,继续写入只能覆盖写入一条数据
        // clean() 方法清除整个缓冲区。compact() 方法仅清除已阅读的数据。转为写入模式
        byteBuffer.compact();
        byteBuffer.put((byte) 3);
        byteBuffer.put((byte) 4);
        byteBuffer.put((byte) 5);
        System.out.println(String.format("最终的情况,capacity 容量:%s,position 位置:%s,limit 限制:%s",
                byteBuffer.capacity(), byteBuffer.position(), byteBuffer.limit()));

        // rewind() 重置 position 位置为 0
        // mark() 标记 position 的位置
        // reset() 重置 position 为上次 mark() 标记的位置
    }
}

3. Channel 通道


1. 基本概述
  • Channel 的 API 涵盖了 UDP/TCP 网络和文件 IO
    • FileChannel
    • DatagramChannel
    • SocketChannel
    • ServerSocketChannel
  • 和标准 IO Stream 操作的区别:
    • 在一个通道内进行读取和写入。
    • stream 通道是单向的(input 或 output)。
    • 可以非阻塞读取和写入通道。
    • 通道始终读取和写入缓冲区。
2. SocketChannel
  • SocketChannel 用于建立 TCP 网络连接,类似 java.net.Socket。
  • 两种创建 SocketChannel 形式:
    • 客户端主动发起和服务端的连接。
    • 服务端获取的新连接。
  • write 写:write() 在尚未写入任何内容时就可能返回了。需要在循环中调用 write()。
  • read 读:read() 方法可能直接返回而根本不读取任何数据,根据返回的 int 值判断读取了多少字节。
3. ServerSocketChannel
  • ServerSocketChannel 可以监听新建的 TCP 连接通道,类似 ServerSocket。
  • serverSocketChannel.accept():如果该通道处于非阻塞状态,那么如果没有挂起的连接,该方法将立即返回 null。必须检查返回的 SocketChannel 是否为 null。

4. Selector 选择器


1. 基本知识
  • Selector 是一个 Java NIO 组件,可以检查一个或多个 NIO 通道,并确定哪些通道已准备好进行读取和写入。实现单个线程可以管理多个通道,从而管理多个网络连接。
  • 一个线程使用 Selector 监听多个 channel 的不同事件:四个事件分别对应 SelectionKey 四个常量。
    • Connect 连接(SelectionKey.OP_CONNECT)
    • Accept 准备就绪(OP_ACCEPT)
    • Read 读取(OP_READ)
    • Write 写入(OP_WRITE)
2. 核心概念
  • 实现一个线程处理多个通道的核心概念理解:事件驱动机制。
  • 非阻塞的网络通道下,开发者通过 Selector 注册对于通道感兴趣的事件类型,线程通过监听事件来触发相应的代码执行。(扩展:更底层是操作系统的多路复用机制)

5. NIO 对比 BIO


  • 假如你的程序需要支持大量的连接,使用 NIO 是最好的方式。
  • Tomcat8 中,已经完全去除了 BIO 相关的网络处理代码,默认采用 NIO 进行网络处理。

6. NIO 与多线程结合的改进方案


7. NIO 代码示例


1. 客户端代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class NIOClient {

    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
        while (! socketChannel.finishConnect()) {
            // 没连上,则一直等待
            Thread.yield();
        }
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入:");
        // 发送内容
        String msg = scanner.nextLine();
        ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
        while (buffer.hasRemaining()) {
            socketChannel.write(buffer);
        }
        // 读取响应
        System.out.println("收到服务器端响应...");
        ByteBuffer requestBuffer = ByteBuffer.allocate(1024);

        while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {
            // 长连接情况下,需要手动判断数据有没有读取结束(此处做一个简单的判断,超过 0 字节就认为请求结束了)
            if (requestBuffer.position() > 0) break;
        }
        requestBuffer.flip();
        byte[] content = new byte[requestBuffer.limit()];
        requestBuffer.get(content);
        System.out.println(new String(content));
        scanner.close();
        socketChannel.close();
    }

}
2. 服务器端代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 直接基于非阻塞的写法
 */
public class NIOServer1 {

    public static void main(String[] args) throws IOException {
        // 创建网络服务器
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式
        serverSocketChannel.socket().bind(new InetSocketAddress(8080)); // 绑定端口
        System.out.println("启动成功");
        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept(); // 获取新 tcp 连接通道
            // tcp 请求 读取/响应
            if (socketChannel != null) {
                System.out.println("收到新连接:" + socketChannel.getRemoteAddress());
                socketChannel.configureBlocking(false); // 默认阻塞,设置为非阻塞
                ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
                while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {
                    // 长连接情况下,需要手动判断有没有读取结束(此处做一个简单判断,超过 0 字节就认为请求结束)
                    if (requestBuffer.position() > 0) break;
                }
                if (requestBuffer.position() == 0) continue; // 如果没数据了,则不继续之后的处理
                requestBuffer.flip();
                byte[] content = new byte[requestBuffer.limit()];
                requestBuffer.get(content);
                System.out.println(new String(content));
                System.out.println("收到数据,来自:" + socketChannel.getRemoteAddress());

                // 响应结果 200
                String response = "HTTP/1.1 200 OK\r\n" +
                        "Content-Length: 11\r\n\r\n" +
                        "Hello World";
                ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
                while (buffer.hasRemaining()) {
                    socketChannel.write(buffer); // 非阻塞
                }
            }
        }
    }
    
}
3. 服务器端支持接收多个客户端连接
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 直接基于非阻塞的写法,一个线程轮询处理所有请求
 */
public class NIOServer2 {
    // 已经建立连接的集合
    private static ArrayList<SocketChannel> channels = new ArrayList<>();

    public static void main(String[] args) throws IOException {
        // 创建网络客户端
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));
        System.out.println("启动成功");
        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel != null) {
                System.out.println("收到新连接:" + socketChannel.getRemoteAddress());
                socketChannel.configureBlocking(false);
                channels.add(socketChannel);
            } else {
                // 没有新连接的情况下,就去处理现有的连接的数据,处理完就删除掉
                Iterator<SocketChannel> iterator = channels.iterator();
                while (iterator.hasNext()) {
                    SocketChannel ch = iterator.next();
                    ByteBuffer requestBuffer = ByteBuffer.allocate(1024);

                    if (ch.read(requestBuffer) == 0) {
                        // 等于 0,代表这个通道没有数据需要处理,那就等会再处理
                        continue;
                    }
                    while (ch.isOpen() && ch.read(requestBuffer) != -1) {
                        if (requestBuffer.position() > 0) break;
                    }
                    if (requestBuffer.position() == 0) continue;
                    requestBuffer.flip();
                    byte[] content = new byte[requestBuffer.limit()];
                    requestBuffer.get(content);
                    System.out.println(new String(content));
                    System.out.println("收到数据,来自:" + ch.getRemoteAddress());

                    String response = response = "HTTP/1.1 200 OK\r\n" +
                            "Content-Length: 11\r\n\r\n" +
                            "Hello World";
                    ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
                    while (buffer.hasRemaining()) {
                        ch.write(buffer);
                    }
                    iterator.remove();
                }
            }
        }
    }

}
4. 结合 Selector 实现非阻塞服务器
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 结合 Selector 实现的非阻塞服务器(放弃对 Channel 的轮询,借助消息通知机制)
 */
public class NIOServer3 {
    public static void main(String[] args) throws IOException {
        // 1. 创建网络服务器
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false); // 设置非阻塞模式

        // 2. 构建一个 Selector 选择器,并且将 Channel 注册上去
        Selector selector = Selector.open();
        SelectionKey selectionKey = serverSocketChannel.register(selector, 0, serverSocketChannel); // 将 ServerSocketChannel 注册到 Selector
        selectionKey.interestOps(SelectionKey.OP_ACCEPT); // 对 ServerSocketChannel 上面的 accept 事件感兴趣(ServerSocketChannel 只支持 accept 操作)

        // 3. 绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));

        System.out.println("启动成功");

        while (true) {
            // 不再轮询通过,改用下面轮询事件的方式,select() 方法有阻塞作用,直到有事件通知才会有返回。
            selector.select();
            // 获取事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 遍历查询结果
            Iterator<SelectionKey> iter = selectionKeys.iterator();
            while (iter.hasNext()) {
                // 被封装的查询结果
                SelectionKey key = iter.next();
                iter.remove();
                // 关注 Read 和 Accept 两个事件
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.attachment();
                    // 将拿到的客户端连接到通道,注册到 Selector 上面
                    SocketChannel clientSocketChannel = server.accept();
                    clientSocketChannel.configureBlocking(false);
                    clientSocketChannel.register(selector, SelectionKey.OP_READ, clientSocketChannel);
                    System.out.println("收到新连接:" + clientSocketChannel.getRemoteAddress());
                }

                if (key.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) key.attachment();
                    ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
                    while (socketChannel.isOpen() && socketChannel.read(requestBuffer) != -1) {
                        if (requestBuffer.position() > 0) break;
                    }
                    if (requestBuffer.position() == 0) continue;
                    requestBuffer.flip();
                    byte[] content = new byte[requestBuffer.limit()];
                    requestBuffer.get(content);
                    System.out.println(new String(content));
                    System.out.println("收到数据,来自:" + socketChannel.getRemoteAddress());

                    // 响应结果 200
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Content-Length: 11\r\n\r\n" +
                            "Hello World";
                    ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
                    while (buffer.hasRemaining()) {
                        socketChannel.write(buffer);
                    }
                }
            }
            selector.selectNow();
        }
    }
}
5. NIO Selector 多路复用 Reactor 线程模型
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * NIO Selector 多路复用 Reactor 线程模型
 */
public class NIOServer4 {
    // 处理业务操作的线程
    private static ExecutorService workPool = Executors.newCachedThreadPool();

    // 封装了 Selector.select 等事件轮询的代码
    abstract class ReactorThread extends Thread {

        Selector selector;
        LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();

        /**
         * Selector 监听到事件后,调用这个方法
         */
        public abstract void handler(SelectableChannel channel) throws Exception;

        private ReactorThread() throws IOException {
            this.selector = Selector.open();
        }

        volatile boolean running = false;

        @Override
        public void run() {
            // 轮询 Selector 事件
            while (running) {
                try {
                    // 执行队列中的任务
                    Runnable task;
                    while ((task = taskQueue.poll()) != null) {
                        task.run();
                    }
                    selector.select(1000);

                    // 获取查询结果
                    Set<SelectionKey> selected = selector.selectedKeys();
                    // 遍历查询结果
                    Iterator<SelectionKey> iter = selected.iterator();
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();
                        iter.remove();
                        int readyOps = key.readyOps();
                        // 关注 Read 和 Accept 两个事件
                        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                            SelectableChannel channel = (SelectableChannel) key.attachment();
                            channel.configureBlocking(false);
                            handler(channel);
                            if (! channel.isOpen()) {
                                key.cancel(); // 如果关闭了,就取消这个 key 的订阅
                            }
                        }
                    }
                    selector.selectNow();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private SelectionKey register(SelectableChannel channel) throws Exception {
            // 为什么 register 要以任务提交的形式,让 reactor 线程去处理?
            // 因为线程在执行 channel 注册到 selector 的过程中,会和调用 selector.select() 方法的线程争用同一把锁
            // 而 select() 方法实在 eventLoop 中通过 while 循环调用的,争抢的可能性很高,为了让 register 能更快的执行,就放到同一个线程来处理
            FutureTask<SelectionKey> futureTask = new FutureTask<>(() -> channel.register(selector, 0, channel));
            taskQueue.add(futureTask);
            return futureTask.get();
        }

        private void doStart() {
            if (!running) {
                running = true;
                start();
            }
        }
    }

    private ServerSocketChannel serverSocketChannel;
    // 1、创建多个线程 - accept处理reactor线程 (accept线程)
    private ReactorThread[] mainReactorThreads = new ReactorThread[1];
    // 2、创建多个线程 - io处理reactor线程  (I/O线程)
    private ReactorThread[] subReactorThreads = new ReactorThread[8];

    /**
     * 初始化线程组
     */
    private void newGroup() throws IOException {
        // 创建IO线程,负责处理客户端连接以后socketChannel的IO读写
        for (int i = 0; i < subReactorThreads.length; i++) {
            subReactorThreads[i] = new ReactorThread() {
                @Override
                public void handler(SelectableChannel channel) throws IOException {
                    // work线程只负责处理IO处理,不处理accept事件
                    SocketChannel ch = (SocketChannel) channel;
                    ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
                    while (ch.isOpen() && ch.read(requestBuffer) != -1) {
                        // 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
                        if (requestBuffer.position() > 0) break;
                    }
                    if (requestBuffer.position() == 0) return; // 如果没数据了, 则不继续后面的处理
                    requestBuffer.flip();
                    byte[] content = new byte[requestBuffer.limit()];
                    requestBuffer.get(content);
                    System.out.println(new String(content));
                    System.out.println(Thread.currentThread().getName() + "收到数据,来自:" + ch.getRemoteAddress());

                    // TODO 业务操作 数据库、接口...
                    workPool.submit(() -> {
                    });

                    // 响应结果 200
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Content-Length: 11\r\n\r\n" +
                            "Hello World";
                    ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
                    while (buffer.hasRemaining()) {
                        ch.write(buffer);
                    }
                }
            };
        }

        // 创建mainReactor线程, 只负责处理serverSocketChannel
        for (int i = 0; i < mainReactorThreads.length; i++) {
            mainReactorThreads[i] = new ReactorThread() {
                AtomicInteger incr = new AtomicInteger(0);

                @Override
                public void handler(SelectableChannel channel) throws Exception {
                    // 只做请求分发,不做具体的数据读取
                    ServerSocketChannel ch = (ServerSocketChannel) channel;
                    SocketChannel socketChannel = ch.accept();
                    socketChannel.configureBlocking(false);
                    // 收到连接建立的通知之后,分发给I/O线程继续去读取数据
                    int index = incr.getAndIncrement() % subReactorThreads.length;
                    ReactorThread workEventLoop = subReactorThreads[index];
                    workEventLoop.doStart();
                    SelectionKey selectionKey = workEventLoop.register(socketChannel);
                    selectionKey.interestOps(SelectionKey.OP_READ);
                    System.out.println(Thread.currentThread().getName() + "收到新连接 : " + socketChannel.getRemoteAddress());
                }
            };
        }


    }

    /**
     * 初始化channel,并且绑定一个eventLoop线程
     *
     * @throws IOException IO异常
     */
    private void initAndRegister() throws Exception {
        // 1、 创建ServerSocketChannel
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        // 2、 将serverSocketChannel注册到selector
        int index = new Random().nextInt(mainReactorThreads.length);
        mainReactorThreads[index].doStart();
        SelectionKey selectionKey = mainReactorThreads[index].register(serverSocketChannel);
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
    }

    /**
     * 绑定端口
     *
     * @throws IOException IO异常
     */
    private void bind() throws IOException {
        //  1、 正式绑定端口,对外服务
        serverSocketChannel.bind(new InetSocketAddress(8080));
        System.out.println("启动完成,端口8080");
    }

    public static void main(String[] args) throws Exception {
        NIOServer4 nioServer = new NIOServer4();
        nioServer.newGroup(); // 1、 创建main和sub两组线程
        nioServer.initAndRegister(); // 2、 创建serverSocketChannel,注册到mainReactor线程上的selector上
        nioServer.bind(); // 3、 为serverSocketChannel绑定端口
    }
}
6. HTTP NIO 服务端
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyNIOHttpServer {

    public static Selector selector;

    // 定义线程池
    public static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(25, 25, 25,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    private static ServerSocketChannel serverSocketChannel;

    private static final int port = 8080;

    public static void main(String[] args) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(port));

        System.out.println("NIO 启动:" + port);

        // 获取一个选择器
        // 底层的事件通知机制
        MyNIOHttpServer.selector = Selector.open();

        // 登记:表示对这个通道上的 OP_ACCEPT 事件感兴趣,并且返回一个标记
        // 此处表示,希望收到 socket 通道 8080 端口上建立连接这个通知
        SelectionKey selectionKey = serverSocketChannel.register(MyNIOHttpServer.selector, 0);
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true) {
            // 如果没有新的 socket 与服务器连接或者数据交互,这里会等待 1 秒
            MyNIOHttpServer.selector.select(1000);

            // 开始处理
            Set<SelectionKey> selected = MyNIOHttpServer.selector.selectedKeys();
            Iterator<SelectionKey> iter = selected.iterator();
            while (iter.hasNext()) {
                // 获取注册在上面标记
                SelectionKey key = iter.next();

                if (key.isAcceptable()) {
                    System.out.println("有新的连接,当前线程数量:" + MyNIOHttpServer.threadPoolExecutor.getActiveCount());
                    SocketChannel chan = serverSocketChannel.accept();
                    chan.configureBlocking(false);
                    // 注册一个新监听
                    // 表示希望收到该连接上 OP_READ 数据传输事件的通知
                    chan.register(MyNIOHttpServer.selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    // 取出附着在上面的信息,也就是上面代码中附着的连接信息
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    // 处理中,不需要收到任何通知
                    key.cancel();
                    socketChannel.configureBlocking(false);
                    MyNIOHttpServer.threadPoolExecutor.execute(() -> {
                        try {
                            // 读取里面的内容,请注意,此处大小写随意
                            // tomcat 中会根据 http 协议中定义的长度来读取数据,或者一直读到通道内无数据为止
                            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                            socketChannel.read(byteBuffer);
                            byteBuffer.flip(); // 转为读模式
                            String request = new String(byteBuffer.array());

                            System.out.println("收到新数据,当前线程数:" + MyNIOHttpServer.threadPoolExecutor.getActiveCount()
                                    + ",请求内容:" + request);
                            // 给一个当前时间作为返回值
                            // 随意返回,不是 http 的协议
                            byteBuffer.clear();
                            ByteBuffer wrap = ByteBuffer.wrap(("hello" + System.currentTimeMillis()).getBytes());
                            socketChannel.write(wrap);
                            wrap.clear();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + " 服务器线程处理完毕,当前线程数:"
                                + threadPoolExecutor.getActiveCount());
                    });
                }
                // 取出后删除
                iter.remove();
            }
            selected.clear();
            // 过掉cancelled keys
            MyNIOHttpServer.selector.selectNow();
        }
    }

}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/04/14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
NIO非阻塞网络编程三大核心理念
1.capacity 容量:作为一个内存块,Buffer具有一定的固定大小,也称为【容量】。 2.position 位置:写入模式时代表写数据的位置。读取模式时代表读取数据的位置。 3.limit 限制:写入模式,限制等于buffer的容量,读取模式下,limit等于写入的数据量。
IT架构圈
2020/12/02
4010
NIO非阻塞网络编程三大核心理念
BIO、NIO、IO多路复用模型的演进&Java NIO 网络编程
上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题,从而造成服务端崩溃的现象。怎么解决这一问题呢?优化呗,所以后面就有了NIO、AIO、IO多路复用。本文将对这几个模型详细说明并基于 Java 编写 NIO。
王二蛋
2024/01/18
7780
Java网络编程--NIO非阻塞网络编程
从Java1.4开始,为了替代Java IO和网络相关的API,提高程序的运行速度,Java提供了新的IO操作非阻塞的API即Java NIO。NIO中有三大核心组件:Buffer(缓冲区),Channel(通道),Selector(选择器)。NIO基于Channel(通道)和Buffer(缓冲区))进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中,而Selector(选择器)主要用于监听多个通道的事件,实现单个线程可以监听多个数据通道。
CodingDiray
2019/09/25
9380
Java网络编程--NIO非阻塞网络编程
使用NIO实现非阻塞式(相对的)多人聊天室
1.服务端 package d02_test; 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.chan
2020/10/23
5790
Netty 入门篇 Day 3---网络编程
在阻塞模式下,会导致 线程暂停 ssc.accept(); // 阻塞的方法 会导致线程暂停,一直等到有client连接 才继续工作 channel.read(buffer); // 阻塞的方法 会导致线程暂停,一直等client发送信息 才继续进行读操作 服务器端的单线程模式下,阻塞方法会导致这个线程暂停(闲置); 同时 多个client相到受影响,几乎不能正确工作,需要服务器端的多线程支持 服务器端的多线程模式缺点:1) 占用内存多 2)多线程切换,带来比较大的内存开销
猫头虎
2024/04/08
1150
Netty 入门篇 Day 3---网络编程
NIO核心组件
上节我们讲述了NIO以及IO多路复用的的基础知识,本来这节是要讲述Reactor模式,但是我在写Reactor模式的时候,发现关于NIO的核心组件没有讲述,为了避免读者一头雾水,我们临时插入一片关于NIO核心组件的文章。
shysh95
2020/06/04
5230
NIO核心组件
【Netty】NIO 网络编程 聊天室案例
① 服务器 客户端 通信 : 服务器 与 客户端 实现 双向通信 ; 服务器可以写出数据到客户端 , 也能读取客户端的数据 ; 客户端可以写出数据到服务器端 , 也可以读取服务器端的数据 ;
韩曙亮
2023/03/27
1.4K0
【Netty】NIO 网络编程 聊天室案例
Java NIO 核心组件学习笔记
对于I/O操作,根据Oracle官网的文档,同步异步的划分标准是“调用者是否需要等待I/O操作完成”,这个“等待I/O操作完成”的意思不是指一定要读取到数据或者说写入所有数据,而是指真正进行I/O操作时,比如数据在TCP/IP协议栈缓冲区和JVM缓冲区之间传输的这段时间,调用者是否要等待。
Java团长
2018/08/07
4600
网络编程模式
今晚是个下雨天,写完今天最后一行代码,小鲁班起身合上电脑,用滚烫的开水为自己泡制了一桶老坛酸菜牛肉面。这大概是苦逼程序猿给接下来继续奋战的自己最好的馈赠。年轻的程序猿更偏爱坐在窗前,在夜晚中静静的享受独特的泡面香味。。。
php007
2019/08/05
4900
网络编程模式
Java NIO?看这一篇就够了![通俗易懂]
大家好,又见面了,我是你们的朋友全栈君。 ✎前言 现在使用NIO的场景越来越多,很多网上的技术框架或多或少的使用NIO技术,譬如Tomcat,Jetty。学习和掌握NIO技术已经不是一个JAVA攻城
全栈程序员站长
2022/09/08
3490
Java NIO?看这一篇就够了![通俗易懂]
NIO详解
NIO (New lO)也有人称之为java non-blocking lO是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java lO API。
冬天vs不冷
2025/01/21
1870
NIO详解
Netty3学习笔记(一) --- 传统IO与NIO比较
  (4)使用传统的I/O程序读取文件内容, 并写入到另一个文件(或Socket), 如下程序:
挽风
2021/04/13
2650
Netty3学习笔记(一) --- 传统IO与NIO比较
JavaIO流:NIO梳理
NIO 也叫 Non-Blocking IO 是同步非阻塞的 IO 模型。线程发起 IO 请求后,立即返回。同步指的是必须等待 IO 缓冲区内的数据就绪,而非阻塞指的是,用户线程不原地等待 IO 缓冲区,可以先做一些其他操作,但是要定时轮询检查 IO 缓冲区数据是否就绪。
栗筝i
2022/12/02
3240
JavaIO流:NIO梳理
Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO
本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看
Java技术江湖
2019/11/21
5370
Netty-nio
channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层
sgr997
2022/11/10
7170
Netty-nio
Netty网络编程第一卷
channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层
大忽悠爱学习
2022/05/06
7140
Netty网络编程第一卷
Java NIO 同步非阻塞应用实例
项目地址:https://github.com/windwant/windwant-service/tree/master/io-service
WindWant
2020/09/11
5900
NIO从入门到踹门
java.nio全称java non-blocking IO,是指JDK1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络(来源于百度百科)。
java技术爱好者
2020/09/22
9680
NIO从入门到踹门
NIO学习四-Selector
前面我们已经简单的学习了channel,知道channel作为通道,可以在通道中进行读写操作,同时知道ByteChannel是双向的。对于NIO的优势在于多路复用选择器上,在Nginx、Redis、Netty中都有多路复用的体现。因此学习Selector是有必要的。
路行的亚洲
2020/07/16
3930
🎯 Java NIO 基础
✏️ 写在前面的话: Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景。 Netty作为一款基于Java开发的高性能网络框架,想要从认识到熟悉再到掌握最终理解,因此我们需要从最基础的NIO开始学习。如果你已经学习并掌握了NIO相关知识,那么可以直接进入Netty相关文章的学习;如果没有了解过也没有关系,那我们就从当前文章开始学习吧!🎉🎉🎉 这里我们先简单了解一下这一篇文章中我们将要学习的内容: 首先是NIO的基本介绍,了解NIO的三大组件 ByteBuffer 字节缓冲区的基本使用
爱吃糖的范同学
2023/02/11
8270
相关推荐
NIO非阻塞网络编程三大核心理念
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验