• NIO 源码分析(01) NIO 最简用法


    NIO 源码分析(01) NIO 最简用法

    Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

    Java NIO 主要由三个部分组成:Channel、Buffer 和 Selector。在分析源码前最好对 NIO 的基本用法和 Linux NIO 在一个基本的了解。

    1. NIO 入门
    2. Linux NIO

    本文会提供一个 NIO 最简使用示例,之后的源码分析都会基于该示例及其扩展进行。

    一、服务端

    public class Server implements Runnable {
        public static void main(String[] args) {
            new Thread(new Server(8765)).start();
        }
    
        // 1 多路复用器(管理所有的通道)
        private Selector selector;
        // 2 建立缓冲区
        private ByteBuffer readBuf = ByteBuffer.allocate(1024);
        private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
    
        public Server(int port) {
            try {
                //1. 获取多路复用器
                this.selector = Selector.open();
    
                //2. 获取服务器端通道
                ServerSocketChannel ssChannel = ServerSocketChannel.open();
    
                //3. 切换成非阻塞模式
                ssChannel.configureBlocking(false);
    
                //4. 绑定端口号
                ssChannel.bind(new InetSocketAddress(port));
    
                //5. 把服务器通道注册到多路复用器上,并且监听阻塞事件
                ssChannel.register(this.selector, SelectionKey.OP_ACCEPT);
    
                System.out.println("Server start, port :" + port);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void run() {
            while (true) {
                try {
                    // 1. 阻塞到至少有一个通道在你注册的事件上就绪,返回的 int 值表示有多少通道已经就绪
                    int wait = selector.select();
                    if (wait == 0) continue;
    
                    //2. 遍历多路复用器上已经准备就绪的通道
                    Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
    
                        if (key.isValid()) {
                            if (key.isAcceptable()) {
                                // 3.1 如果为 accept 状态,就获取客户端通道并注册到selector上
                                this.accept(key);
                            } else if (key.isReadable()) {
                                // 3.2 如果为可读状态
                                this.read(key);
                            } else if (key.isWritable()) {
                                // 3.3 写数据
                                this.write(key);
                            }
                        }
    
                        // 4. 注意必须在处理完通道时自己移除
                        iterator.remove();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private void write(SelectionKey key) {
            //ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
            //ssc.register(this.seletor, SelectionKey.OP_WRITE);
        }
    
        private void read(SelectionKey key) {
            try {
                //1. 清空缓冲区旧的数据
                this.readBuf.clear();
    
                //2. 获取之前注册的socket通道对象
                SocketChannel sChannel = (SocketChannel) key.channel();
    
                //3. 读数据
                int len = sChannel.read(this.readBuf);
                if (len == -1) {
                    key.channel().close();
                    key.cancel();
                    return;
                }
    
                //5 有数据则进行读取 读取之前要flip()复位
                this.readBuf.flip();
                byte[] bytes = new byte[this.readBuf.remaining()];
                this.readBuf.get(bytes);
    
                System.out.println("Server : " + new String(bytes).trim());
                //6. 可以写回给客户端数据
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        private void accept(SelectionKey key) {
            try {
                //1. 获取服务通道ServerSocketChannel
                ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
    
                //2. 获取客户端通道SocketChannel
                SocketChannel sChannel = ssChannel.accept();
    
                //3. 客户端通道SocketChannel也要设置成非阻塞模式
                sChannel.configureBlocking(false);
    
                //4 注册到多路复用器上,并设置读取标识
                sChannel.register(this.selector, SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    二、客户端

    public static void main(String[] args) throws IOException {
        SocketChannel sChannel = null;
        try {
            //1. 获取通道
            sChannel = SocketChannel.open(
                    new InetSocketAddress("127.0.0.1", 8765));
    
            //2. 切换成非阻塞模式
            sChannel.configureBlocking(false);
    
            //3. 分配缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
    
            //4. 把从控制台读到数据发送给服务器端
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String str = scanner.next();
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                buf.put((dateFormat.format(new Date()) + ":" + str).getBytes());
                buf.flip();// 非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了
                while (buf.hasRemaining()) {
                    sChannel.write(buf);
                }
                buf.clear();
            }
        } finally {
            sChannel.close();
        }
    }
    

    ok,完成!!!


    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    mysql索引数据结构
    29. 使用参数化编写自动化测试用例
    28. Python编写自动化测试用例
    27. Unittest单元测试框架的介绍与使用
    26. 什么是单元测试
    25. Postman的使用
    24. 接口测试的意义
    23. requests安装与使用
    22. 如何编写接口文档
    21. Blog接口开发
  • 原文地址:https://www.cnblogs.com/binarylei/p/11134979.html
Copyright © 2020-2023  润新知