Netty学习之路(二)-异步IO(NIO)编程

NIO到底是什么简称?有人称之为New I/O,原因为他相对于之前的I/O类库来说是新增的。这是官方叫法。但是,由于之前老的I/O类库是阻塞I/O,New I/O类库的目标就是让java支持非阻塞I/O,所以更多的人称之为非阻塞I/O(Non-block I/O)。在开始进行NIO编程之前,先得了解几个NIO类库的概念:

  1. 通道(Channel):通道是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
    通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。
    通道包括以下类型:
    FileChannel:从文件中读写数据;
    DatagramChannel:通过 UDP 读写网络中数据;
    SocketChannel:通过 TCP 读写网络中数据;
    ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创一个 SocketChannel。

  2. 缓冲区(Buffer):发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。缓冲区包括以下类型:
    ByteBuffer
    CharBuffer
    ShortBuffer
    IntBuffer
    LongBuffer
    FloatBuffer
    DoubleBuffer
    缓冲区状态变量:
    capacity:最大容量;
    position:当前已经读写的字节数;
    limit:还可以读写的字节数。

  3. 选择器(Selector):NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。

编程实践

NIOServer

package com.ph.NIO;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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;

/**
 * Create by PH on 2018/10/4 0004
 */
public class NIOServer {

    public static void main(String[] args) throws IOException {
        int port = 8080;
        if(args !=null && args.length>0) {
            try {
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e) {
                //采用默认值
            }
        }
        MultiNioServer server = new MultiNioServer(port);
        //启用服务端线程
        new Thread(server, "server-01").start();
    }
}

class MultiNioServer implements Runnable {
    private Selector selector;
    private ServerSocketChannel ssChannel;
    private volatile boolean stop;

    /**
     * 初始化多路复用器,绑定监听端口
     */
    public MultiNioServer(int port) {
        try {
            //获得一个选择器
            selector = Selector.open();
            //打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道
            ssChannel = ServerSocketChannel.open();
            //设置为非阻塞
            ssChannel.configureBlocking(false);
            //将通道注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件
            ssChannel.register(selector, SelectionKey.OP_ACCEPT);
            //获得与通道关联的服务端套接字
            ServerSocket serverSocket = ssChannel.socket();
            InetSocketAddress address = new InetSocketAddress("127.0.0.1", port);
            //绑定监听端口
            serverSocket.bind(address);
        }catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop() {
        this.stop = true;
    }

    public void run() {
        while (!stop) {
            try {
                //轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被轮询出来
                selector.select(1000);
                //通过SelectionKey获取就绪的Channel集合
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = keys.iterator();
                SelectionKey key = null;
                //遍历就绪的Channel
                while (keyIterator.hasNext()) {
                    key = keyIterator.next();
                    keyIterator.remove();
                    try {
                        handleInput(key);
                    } catch (Exception e) {
                        e.printStackTrace();
                        if(key != null) {
                            key.cancel();
                            if(key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            }catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void handleInput(SelectionKey key) throws IOException {
        if(key.isValid()) {
            //处理新接入的请求消息
            if (key.isAcceptable()) {
                System.out.println("New Client connect ...");
                //监听到有新的客户端接入
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                // 处理新的接入请求,完成TCP三次握手,建立物理链路,服务器会为每个新连接创建一个 SocketChannel
                SocketChannel sc = ssc.accept();
                //设置客户端链路为非阻塞模式
                sc.configureBlocking(false);
                // 将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,读取客户端发送的网络消息
                sc.register(selector, SelectionKey.OP_READ);
            }
            if (key.isReadable()) {
                SocketChannel sChannel = (SocketChannel) key.channel();
                System.out.println("Receive :" + readDataFromSocketChannel(sChannel));
                doWrite(sChannel, "Server receive succeed");
            }
        }
    }

    private String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        StringBuilder data = new StringBuilder();
        while (true) {
            buffer.clear();
            int n = sChannel.read(buffer);
            //返回值大于0:读到了字节
            //返回值等于0:没有读取到字节,属于正常场景,忽略
            //返回值为-1: 链路已经关闭,需要关闭SocketChannel,释放资源
            if (n == 0) {
                break;
            } else if(n < 0) {
                sChannel.close();
            }
            //将缓冲区当前的limit设置为position,position设置为0,用于后续对缓冲去的读取操作。
            buffer.flip();
            //获取缓冲区可读字节个数
            int limit = buffer.limit();
            char[] dst = new char[limit];
            for (int i = 0; i < limit; i++) {
                dst[i] = (char) buffer.get(i);
            }
            data.append(dst);
            buffer.clear();
        }
        return data.toString();
    }

    private void doWrite(SocketChannel sc, String response) throws IOException{
        if (response != null && response.trim().length() > 0) {
            byte[] bytes = response.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            //将数据写入输出缓冲区中
            writeBuffer.put(bytes);
            writeBuffer.flip();
            sc.write(writeBuffer);
            if(!writeBuffer.hasRemaining()) {
                System.out.println("Send message succeed.");
            }
        }
    }
}

NIOClient

package com.ph.NIO;

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.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * Create by PH on 2018/10/4 0004
 */
public class NIOClient {
    public static void main(String[] args) throws IOException {
        int port = 8080;
        if(args !=null && args.length>0) {
            try {
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e) {
                //采用默认值
            }
        }
        //启用客户端
        new Thread(new Client(null, port)).start();
    }
}

class Client implements Runnable {

    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private volatile boolean stop;

    public Client(String host, int port ) {
        this.host = host == null? "127.0.0.1": host;
        this.port = port;
        try {
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while (!stop) {
            try {
                selector.select(1000);
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = keys.iterator();
                SelectionKey key = null;
                while (iterator.hasNext()) {
                    key = iterator.next();
                    iterator.remove();
                    try {
                        handleInput(key);
                    } catch (Exception e) {
                       if(key != null) {
                           key.cancel();
                           key.channel().close();
                       }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
        if(selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void doConnect() throws IOException{
        //如果连接成功则注册读事件,反之注册连接事件
        if(socketChannel.connect(new InetSocketAddress(host, port))) {
            socketChannel.register(selector, SelectionKey.OP_READ);
            System.out.println("Connect to server succeed ...");
            doWrite(socketChannel);
        } else {
            System.out.println("Reconnect to server...");
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
        }
    }

    private void handleInput(SelectionKey key) throws IOException {
        if(key.isValid()) {
            SocketChannel sc = (SocketChannel) key.channel();
            if(key.isConnectable()) {
                //如果完成连接客户端注册读事件,并向服务端发送消息
                if(sc.finishConnect()) {
                    System.out.println("Reconnect succeed ...");
                    sc.register(selector, SelectionKey.OP_READ);
                    doWrite(sc);
                }else {
                    //连接失败,进程退出
                    System.exit(1);
                }
            } else if(key.isReadable()) {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                StringBuilder data = new StringBuilder();
                while (true) {
                    buffer.clear();
                    int n = sc.read(buffer);
                    if(n > 0) {
                        buffer.flip();
                        //获取缓冲区可读字节个数
                        int limit = buffer.limit();
                        char[] dst = new char[limit];
                        for (int i = 0; i < limit; i++) {
                            dst[i] = (char) buffer.get(i);
                        }
                        data.append(dst);
                        buffer.clear();
                        System.out.println("Receive : " + data.toString());
                    } else {
                        break;
                    }
                }
            }
        }
    }

    private void doWrite(SocketChannel sc) throws IOException {
        byte[] req = "QUERY MESSAGE".getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
        writeBuffer.put(req);
        writeBuffer.flip();
        sc.write(writeBuffer);
        if(!writeBuffer.hasRemaining()) {
            System.out.println("Send message succeed.");
        }
    }
}

猜你喜欢

转载自blog.csdn.net/PH15045125/article/details/83662542
今日推荐