【Java基础】关于NIO的理解

    最近看《Redis设计与实现》的文件事件,回顾了一下I/O多路复用的知识,又接着看了一下之前使用Java NIO的代码,突然有了一点新的理解,在此简单记录一下,但这里并不介绍I/O多路复用,NIO相关的基础知识。关于NIO的相关知识使用等,有需要可参考https://segmentfault.com/a/1190000006824196(以下代码也出自该博客,感谢博主)
    下面这段代码是一个简单的Server端,可以接收Client端的连接请求,并建立一个与该Client的连接,从而实现Server与Client的数据传递。

public class EchoNIOServer {

    private final static int PORT = 8088;
    private final static int BUF_SIZE = 10240;

    public static void main(String[] args) {
        EchoNIOServer echoNIOServer = new EchoNIOServer();
        echoNIOServer.initServer();    //Server初始化
    }

    private void initServer() {

        try {
            //S1创建通道管理器,Selector就是NIO提供的通道管理器的实现
            Selector selector = Selector.open();

            //创建一个通道对象,这里创建的是ServerSocketChannel的实例,ServerSockerChannel允许监听TCP连接请求
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            //将刚刚创建的ServerSocketChannel对象绑定到PORT端口上,说明这个ServerSocketChannel实例监听的是来自PORT端口的连接,Client需要将连接发送到PORT端口
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));     

            //S2将创建的ServerSocketChannel通道实例注册到通道管理器上,并说明该通道实例监听的对象是OP_ACCEPT,告诉通道管理器,当我的通道上有OP_ACCEPT事件的时候要告诉我哦
            SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
           
           //接下来Server的任务,就是不断查询当前是否有事件到达了
            while (true) {
                selector.select();    //S3如果通道管理器上的任何一个通道收到了感兴趣的事件,该函数返回,否则程序阻塞等待
                Set<SelectionKey> keySet = selector.selectedKeys();    //这里说明通道管理器上的某个通道收到了感兴趣的事件了,也可能是多个通道都收到了,所以是一个Set
                Iterator<SelectionKey> iterator = keySet.iterator();
               
               //遍历收到的事件
                while (iterator.hasNext()) {    
                    SelectionKey key = (SelectionKey) iterator.next();
                    iterator.remove();

                    if (key.isAcceptable()) {    //S4当前事件类型是OP_ACCEPT,调用doAccept函数
                        doAccept(key);
                    } else if (key.isReadable()) {     //S6当前事件类型是OP_READ,调用doRead函数
                        doRead(key);
                    } else if (key.isWritable() && key.isValid()) {
                        doWrite(key);
                    }
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }

    private void doAccept(SelectionKey key) {
        try {
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            System.out.println("ServerSocketChannel正在循环监听");
            
            //S5服务器通道管理器上注册一个与发送OP_ACCEPT的客户端管理的通道,此时客户端会收到一个OP_CONNECT事件
            SocketChannel clientChannel = serverSocketChannel.accept();    
            clientChannel.configureBlocking(false);
            clientChannel.register(key.selector(), SelectionKey.OP_READ);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void doRead(SelectionKey key) {
        try {
            SocketChannel clientChannel = (SocketChannel) key.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
           
           //S7将客户端发送来的消息读到该客户端管理通道的buffer中
            long readBytes = clientChannel.read(byteBuffer);    
            while (readBytes > 0) {
                byteBuffer.flip();
                byte[] data = byteBuffer.array();
                String info = new String(data).trim();

                System.out.println("从客户端发送过来的消息是:" + info);
                byteBuffer.clear();
                readBytes = clientChannel.read(byteBuffer);
            }

            String info = "客户端你好!!!";

            byteBuffer.clear();
            byteBuffer.put(info.getBytes("UTF-8"));
            byteBuffer.flip();

            clientChannel.write(byteBuffer);    //S8给客户端通道实例中写入数据,此时客户端的通道会收到OP_READ事件
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void doWrite(SelectionKey key) {

        try {
            ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
            byteBuffer.flip();
            SocketChannel clientChannel = (SocketChannel) key.channel();

            while(byteBuffer.hasRemaining()) {
                clientChannel.write(byteBuffer);
            }

            byteBuffer.compact();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

    下面这段代码是一个简单的Client端

public class EchoNIOClient {

    private final static int PORT = 8088;
    private final static int BUF_SIZE = 10240;

    public static void main(String[] args) {
        EchoNIOClient echoNIOClient = new EchoNIOClient();
        echoNIOClient.initClient();    //Client初始化
    }
    
    private void initClient() {

        Selector selector = null;

        try {
            selector = Selector.open();

            SocketChannel clientChannel = SocketChannel.open();
            clientChannel.configureBlocking(false);
            clientChannel.connect(new InetSocketAddress("localhost", PORT));    //C1连接到服务器的PORT端口,此时监听服务器PORT端口的ServerSocketChannel通道实例会收到一个OP_ACCEPT事件
            clientChannel.register(selector, SelectionKey.OP_CONNECT);

            while (true) {
                selector.select();
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (key.isConnectable()) {    //C2当SocketChannel通道实例收到OP_CONNECT事件后,调用doConnect()处理
                        doConnect(key);
                    } else if(key.isReadable()) {    //C4
                        doRead(key);
                    }
                }

            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void doConnect(SelectionKey key) {
        try {
            ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);

            SocketChannel clientChannel = (SocketChannel) key.channel();
            if (clientChannel.isConnectionPending()) {
                clientChannel.finishConnect();
            }
            clientChannel.configureBlocking(false);

            String info = "服务器你好!!!";

            byteBuffer.clear();
            byteBuffer.put(info.getBytes("UTF-8"));
            byteBuffer.flip();

            clientChannel.write(byteBuffer);    //C3向通道的buffer空间写内容,这个时候,服务器上与该客户端关联的通道会收到一个OP_READ事件
            clientChannel.register(key.selector(), SelectionKey.OP_READ);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void doRead(SelectionKey key) {
        try {
            ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);

            SocketChannel clientChannel = (SocketChannel) key.channel();
            clientChannel.read(byteBuffer);    //C5

            byte[] data = byteBuffer.array();
            String msg = new String(data).trim();

            System.out.println("服务端发送消息:" + msg);
            clientChannel.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

以上Server/Client的执行顺序如下:
Server/Client的执行顺序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值