多线程笔记 - NIO

 随着访问量越来越多, BIO 和 伪异步IO 已经不能满足需求了. 所以后面又出了个 NIO.

1. NIO 使用了一个 通道Channel 的概念, 他是一个双向通道, 可以读取和写入数据.

  程序中使用的 SocketChannel 读写操作都是异步的, 没有读写的数据最直接返回.

2. NIO 使用了 多路复用器Selector, 轮询客户端的连接, 根据不同的标识, 干不同的事情.

 服务端创建序列图

 server: 

public class Server implements Runnable{

    //多路复用器, 管理所有通道
    private Selector selector;

    private ServerSocketChannel serverChannel;

    private volatile boolean stop;

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

    public Server(int port){
        try{
         this.selector = Selector.open();
            //打开服务器通道
            serverChannel = ServerSocketChannel.open();
            //设置非阻塞模式
            serverChannel.configureBlocking(false);
            //绑定地址
            serverChannel.socket().bind(new InetSocketAddress(1234));
            //把服务器通道注册到多路复用器上, 监听 SelectionKey.OP_ACCEPT操作位
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("服务器端已启动 : " + port);
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (!stop) {
            try {
                //让多路复用器开始监听, 并设置休眠时间为1s,
                // 无论是否有读写事件发生, 每隔1s他都会被唤醒1次
                selector.select(1000);
                Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                //遍历多路复用器
                while (keys.hasNext()) {
                    SelectionKey selectionKey = keys.next();
                    keys.remove();
                    //判断是否有效
                    if (!selectionKey.isValid()) {
                        continue;
                    }

                    //处理新接入的请求消息
                    if (selectionKey.isAcceptable()) {
                        accept(selectionKey);
                    }

                     if (selectionKey.isReadable()) {
                        read(selectionKey);
                    }
                }
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }

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

    private  void read(SelectionKey selectionKey) {
        try {
            //读取缓冲区
            ByteBuffer readBuf = ByteBuffer.allocate(1024);
            //获取通道内注册的socket对象
            SocketChannel sc = (SocketChannel) selectionKey.channel();
            //读取数据
            int count = sc.read(readBuf);
            //没有数据
            if(count == -1){
                selectionKey.channel().close();
                selectionKey.cancel();
                return;
            }
            //有数据, 则进行读取, 且读取后要进行复位
            readBuf.flip();
            byte[] bytes = new byte[readBuf.remaining()];
            readBuf.get(bytes);
            String msg = new String(bytes, "UTF-8").trim();
            System.out.println("from client : " + msg);

            if("几点了".equals(msg)){
                String response = new DateTime(2020, 1, 1, 1, 1, 1).toString("yyyy-MM-dd HH:mm:ss");
                byte[] resBytes = response.getBytes();
                ByteBuffer outBuffer = ByteBuffer.allocate(resBytes.length);
                outBuffer.put(resBytes);
                outBuffer.flip();
                sc.write(outBuffer);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    private  void accept(SelectionKey selectionKey) {
        try {
            //获取服务通道
            ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
            //执行阻塞方法, 等待客户端接入, 接入成功时, 返回 SocketChannel 实例
            SocketChannel client = serverChannel.accept();
            //设置非阻塞
            client.configureBlocking(false);
            //将新连接注册到多路复用器上, 且注册为 OP_READ
            client.register(selector, SelectionKey.OP_READ);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        new Thread(new Server(1234)).start();
    }
}

 

客户端创建序列图:

Netty 权威指南里, 将客户端也加入了 Selector,进行轮询, 根据客户端与服务器连接的不同状态, 干不同的事情. 

 我这里偷个懒, 客户端就直接上了, 不加Selector

public class ClientA {
    public static void main(String[] args) {
        InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 1234);
        ByteBuffer buf = ByteBuffer.allocate(1024);
        SocketChannel socketChannel = null;
        try {
            socketChannel = SocketChannel.open();
            socketChannel.connect(addr);

            String msg = "几点了";
            System.out.println("client : " + msg);
            byte[] bytes = msg.getBytes();
            //将数据放入缓冲区
            buf.put(bytes);
            //对缓冲区进行复位
            buf.flip();
            //写出数据
            socketChannel.write(buf);
            //清空缓冲区数据
            buf.clear();

            while (true) {
                //读数据
                ByteBuffer readBuf = ByteBuffer.allocate(1024);
                int readBytes = socketChannel.read(readBuf);
                if (readBytes == -1) {
                    break;
                }
                readBuf.flip();
                byte[] bytes1 = new byte[readBuf.remaining()];
                readBuf.get(bytes1);
                String msg1 = new String(bytes1, "UTF-8").trim();
                System.out.println("from server : " + msg1);

            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (socketChannel != null) {
                try {
                    socketChannel.close();
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果:

client:

 

server: 

 

参考:

Netty权威指南

posted @ 2020-03-01 15:29  Sniper_ZL  阅读(456)  评论(0编辑  收藏  举报