zoukankan      html  css  js  c++  java
  • JAVA:NIO初步了解

    • 简介:

    Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。

    Java NIO: Channels and Buffers(通道和缓冲区)

    标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

    Java NIO: Non-blocking IO(非阻塞IO)

    Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。

    Java NIO: Selectors(选择器)

    Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。

    • 工作原理:
    1. 阻塞IO工作:

        阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;

        同样,在调用ServerSocket.accept()方法时,也会一直阻塞到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。

        阻塞I/O的通信模型示意图如下:

        

      阻塞I/O存在一些缺点:
        1. 当客户端多时,会创建大量的处理线程且每个线程都要占用栈空间和一些CPU时间;
        2. 阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。

    1. 非阻塞IO工作:

      Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。
        1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。 
        2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。 
        3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。 

      

    • 示例用法:
    1.   客户端:

        

      NIOClient.java

    package com.niotest;
    
    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.channels.SocketChannel;
    import java.util.Iterator;
    
    public class NIOServer {
        /**
         * Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。
         * ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。
         * */    
        public void listen(int port) throws IOException{
            // Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。
            ServerSocketChannel serverSocketChannel= ServerSocketChannel.open();
            // 为什么使用Selector?
            // 仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。
            // 事实上,可以只用一个线程处理所有的通道。
            // 对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。
            // 但是,需要记住,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。
            // 实际上,如果一个CPU有多个内核,不使用多任务可能是在浪费CPU能力。
            // Selector能够处理多个通道就足够了。
            // Selector的创建:通过调用Selector.open()方法创建一个Selector
            Selector selector = Selector.open();
    
            // 向Selector注册通道:为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现。
            // 设置通道为非阻塞  
            serverSocketChannel.configureBlocking(false); 
            // 将该通道对应的ServerSocket绑定到port端口  
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            // 注册该事件后,当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。  
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
            
            // 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
            System.out.println("服务端启动成功 ... ");
            while (true) {            
                 //当注册的事件到达时,方法返回;否则,该方法会一直阻塞  
                selector.select();  
                // 获得selector中选中的项的迭代器,选中的项为注册的事件  
                Iterator iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key=(SelectionKey)iterator.next();
                    // 删除已选的key,以防重复处理  
                    iterator.remove();  
                    // 客户端请求连接事件 
                    if(key.isAcceptable()){
                        ServerSocketChannel serverChannel=(ServerSocketChannel) key.channel();
                        // 获得和客户端连接的通道  
                        SocketChannel clientConnectChannel = serverChannel.accept();  
                        // 设置成非阻塞  
                        clientConnectChannel.configureBlocking(false);    
                        //在这里可以给客户端发送信息哦  
                        clientConnectChannel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));  
                        //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。  
                        clientConnectChannel.register(selector, SelectionKey.OP_READ);  
                    }
                    // 获得了可读的事件 
                    else if(key.isReadable()){
                        // 服务器可读取消息,得到事件发生的Socket通道 
                        SocketChannel channel = (SocketChannel)key.channel();  
    
                        // 创建读取的缓冲区  
                        ByteBuffer buffer = ByteBuffer.allocate(64);  
                        
                        try{  
                            channel.read(buffer);
                            byte[] data = buffer.array();  
                            String msg = new String(data).trim();  
                            System.out.println("服务端收到信息:"+msg);  
                            ByteBuffer outBuffer = ByteBuffer.wrap("我(服务器端)已经接收到客户端消息".getBytes());
                            // 将消息回送给客户端
                            channel.write(outBuffer);  
                        }catch(IOException ex){
                            ex.printStackTrace();
                        }catch(Exception ex){
                            ex.printStackTrace();
                        }                   
                    }
                    
                }
            }
        }
        
        
    }

    Main.java

    package com.niotest;
    
    import java.io.IOException;
    
    public class Main {
        public static void main(String[] args) throws IOException {
            // TODO Auto-generated method stub
            NIOServer server=new NIOServer();
            server.listen(8999);
        }
    }
    1.   服务器端:

        

    NIOClient.java

    package com.niotest;
    
    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;
    
    public class NIOClient 
    {
        public void listen(String ip,int port) throws IOException
        {
            // 获得一个Socket通道 
            SocketChannel socketChannel=SocketChannel.open();
            // 设置为非阻塞
            socketChannel.configureBlocking(false);
            // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调  
            //用channel.finishConnect();才能完成连接  
            socketChannel.connect(new InetSocketAddress(ip,port));  
            
            // 获得一个通道管理器 
            Selector selector=Selector.open();
            // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。  
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            
            // 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 
             // 轮询访问selector  
            while (true) {  
                selector.select();  
                // 获得selector中选中的项的迭代器  
                Iterator ite = selector.selectedKeys().iterator();  
                while (ite.hasNext()) {  
                    SelectionKey key = (SelectionKey) ite.next();  
                    // 删除已选的key,以防重复处理  
                    ite.remove();  
                    // 连接事件发生  
                    if (key.isConnectable()) {  
                        SocketChannel channel = (SocketChannel) key.channel();  
                        // 如果正在连接,则完成连接  
                        if(channel.isConnectionPending()){  
                            channel.finishConnect();                            
                        }  
                        // 设置成非阻塞  
                        channel.configureBlocking(false);  
      
                        //在这里可以给服务端发送信息哦  
                        channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));  
                        //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。  
                        channel.register(selector, SelectionKey.OP_READ);    
                    }                
                    // 获得了可读的事件
                    else if (key.isReadable()) {  
                        // 服务器可读取消息,得到事件发生的Socket通道 
                        SocketChannel channel = (SocketChannel)key.channel();  
                        // 创建读取的缓冲区  
                        ByteBuffer buffer = ByteBuffer.allocate(64);  
                        channel.read(buffer);  
                        byte[] data = buffer.array();  
                        String msg = new String(data).trim();  
                        System.out.println("客户端接收到服务器端消息:"+msg);
                        
    //                    ByteBuffer outBuffer = ByteBuffer.wrap("我(客户端)已经收到服务器端消息".getBytes());
    //                    // 将消息回送给服务器端
    //                    channel.write(outBuffer);  
                    }  
      
                }  
      
            }  
        }
    }

    Main.java

    package com.niotest;
    
    import java.io.IOException;
    
    public class Main {
    
        public static void main(String[] args) throws IOException {
            // TODO Auto-generated method stub
            NIOClient client=new NIOClient();
            client.listen("127.0.0.1", 8999);
        }
    
    }
    1.   测试:

      客户端结果:  

    客户端接收到服务器端消息:向客户端发送了一条信息
    客户端接收到服务器端消息:我(服务器端)已经接收到客户端消息

      服务器端结果:  

    服务端启动成功 ... 
    服务端收到信息:向服务端发送了一条信息
    • 参考资料:

    http://tutorials.jenkov.com/java-nio/index.html

    http://ifeve.com/java-nio-all/

    http://ifeve.com/selectors/

    http://ifeve.com/server-socket-channel/

    http://www.iteye.com/magazines/132-Java-NIO

    • 声明:

    本编学习主要参考:http://weixiaolu.iteye.com/blog/1479656

  • 相关阅读:
    LeetCode Best Time to Buy and Sell Stock
    LeetCode Scramble String
    LeetCode Search in Rotated Sorted Array II
    LeetCode Gas Station
    LeetCode Insertion Sort List
    LeetCode Maximal Rectangle
    Oracle procedure
    浏览器下载代码
    Shell check IP
    KVM- 存储池配置
  • 原文地址:https://www.cnblogs.com/yy3b2007com/p/5839124.html
Copyright © 2011-2022 走看看