zoukankan      html  css  js  c++  java
  • Java NIO


    NIO是New IO的缩写,顾名思义,是用于输入输出的新的API,那么,这个NIO相较于旧的IO有什么差别呢?

       1、“阻塞”的通信机制

        在原有的IO下,我们要与A进行通信时,会怎么做呢?先创建一个线程,然后建立连接,然后不断轮询等待接收消息。当需要与另一个B进行通信时,仍然先创建一个线程,然后建立连接(accept),不断轮询等待接收消息(read)… 在这种情况下,若通信的对象变多时,需要的线程就相应增长,并且每个线程都需要不断轮询等待,若没有消息接受时,则阻塞线程。由于消息传送的频率往往不高,所以,线程大部分时间都处于阻塞状态。而阻塞态的线程仍占用着系统资源,多个线程的管理及线程状态的切换(阻塞-就绪-运行)是比较耗时的。

        由于这种方式在一个通信任务没有完成之前,是无法返回的,所以以上的这种方式称作“阻塞”的通信机制。

       2、观察者模式

        “阻塞”的通信机制,从本质上来看,就是每个通信的线程都需要询问“是否有我的数据?”,如果没有则将该线程阻塞,当有数据到来时,再唤醒线程。每个线程都在观察某个事物,等待自己的事件发生。这个描述,与观察者模式类似。我们可以使用观察者模式来进一步优化“阻塞”的通信机制。我们需要监听多个端口时,传统方式是建立多个线程进行监听。而现在,我们将每个端口封装为一个通道,所有传入该端口的数据都会出现在通道中,假设有3个需要监听的端口,则有3个通道,我们对这三个通道进行监听,使用Selector来注册我们对各个端口监听的行为。比如,对第一个端口(通道),需要监听他是否有可读数据,对于第二个端口(通道)需要监听他是否有accept请求… Selector记录了我们对各个通道的兴趣点,然后统一的对各个端口进行监听。当有相应的事件发生时,则调用相关的处理函数。这样,就完成了只用一个监听线程完成多个端口监听的任务。这种方式,就是“非阻塞”的通信机制。


       3、nio 的关键

        我们来想象一下读取文件的方式。首先,若直接从一个文件中读取数据,每次读取一个字符,若文件中有10个字符,那么就需要进行10次IO,而每一次IO都是很耗时的。为了减少IO的次数,我们使用了buffer,假定每次读取的数据都先存入buffer中,每次读取5个字符。那么,从buffer中间接读取字符,尽管同样是10次,但只需要2次的IO就够了,减少了IO次数,提高了效率。

        为了能够完成端到端的传输,需要有一定的媒介进行。就像计算机系统中,数据传输是通过总线一样,nio中,数据的传输是通过“通道”进行的。例如,需要将文件A的内容传到(写入)文件B,那么,通过buffer与通道,可以如下操作:



        在这里,通道作为中间的传输媒介,Buffer则附加在通道的两端,作为数据的“来源”与目标(真正的应该是File A与File B)。使用通道,则一方(File A)则只需要通过Buffer往通道里写,而另一方(File B)则只需要通过从通道里读即可,而无需关注二者的差异。

        在NIO中,通道的一端绑定了相应的目的或者源头,例如文件、socket等,而另一端则是让使用buffer来获取或者写入数据。如下图:


    缓冲区的工作与通道紧密联系。通道是I/O 传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。

        NIO中,有以下几个关键类型

        1)缓存buffer

        NIO中,提供了各个基本类型的buffer,提供给我们以不同的方式进行读写。例如可以用ByteBuffer进行字节(8bit为单位)的数据读写,使用CharByte进行字符(16bit为单位)的数据读写。

        2)通道Channel

        NIO中有以下几种通道:SocketChannel、ServerSocketChannel、DiagramChannel和FileChannel。

        FileChannel只能通过FileOutputStream或者FileInputStream的getChannel调用得到,是单向的,即一端与相应的文件绑定,另一端进行write或者read。

        ServerSocketChannel用于服务器端的通道创建,绑定了一个端口号后,所有接受的数据都会在该通道中,配合后面的Selector,可以直接获取封装好(SelectionKey)的某个客户发送的数据。

        虽说通道既可读又可写,但实际上,一个绑定了某个源或者目的的通道,是只有读或写的功能的。

        3)选择器Selector

        他是使得多元I/O成为可能的关键,使用Selector来注册对通道的某些行为的关注,使之可以同时管理监控多个通道,当监控的时间发生时,唤醒并调用统一的事件处理函数进行相应。

        选择器Selector:选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以选择将被激发的线程挂起,直到有就绪的的通道。

        可选择通道SelectableChannel:这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。

    选择键SelectionKey:选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()  返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。

     

    附上网上的一个NIO,Server和Client的代码:

     

    1. <span style="font-size:18px">package nio;  
    2.   
    3. import java.io.IOException;  
    4. import java.net.InetSocketAddress;  
    5. import java.nio.ByteBuffer;  
    6. import java.nio.channels.SelectionKey;  
    7. import java.nio.channels.Selector;  
    8. import java.nio.channels.ServerSocketChannel;  
    9. import java.nio.channels.SocketChannel;  
    10. import java.util.Iterator;  
    11.   
    12. /** 
    13.  * NIO服务端 
    14.  * @author 小路 
    15.  */  
    16. public class NIOServer {  
    17.     //通道管理器  
    18.     private Selector selector;  
    19.   
    20.     /** 
    21.      * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 
    22.      * @param port  绑定的端口号 
    23.      * @throws IOException 
    24.      */  
    25.     public void initServer(int port) throws IOException {  
    26.         // 获得一个ServerSocket通道  
    27.         ServerSocketChannel serverChannel = ServerSocketChannel.open();  
    28.         // 设置通道为非阻塞  
    29.         serverChannel.configureBlocking(false);  
    30.         // 将该通道对应的ServerSocket绑定到port端口  
    31.         serverChannel.socket().bind(new InetSocketAddress(port));  
    32.         // 获得一个通道管理器  
    33.         this.selector = Selector.open();  
    34.         //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,  
    35.         //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。  
    36.         serverChannel.register(selector, SelectionKey.OP_ACCEPT);  
    37.     }  
    38.   
    39.     /** 
    40.      * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 
    41.      * @throws IOException 
    42.      */  
    43.     @SuppressWarnings("unchecked")  
    44.     public void listen() throws IOException {  
    45.         System.out.println("服务端启动成功!");  
    46.         // 轮询访问selector  
    47.         while (true) {  
    48.             //当注册的事件到达时,方法返回;否则,该方法会一直阻塞  
    49.             System.out.println("开始阻塞");  
    50.             selector.select();  
    51.             System.out.println("解除阻塞");  
    52.             // 获得selector中选中的项的迭代器,选中的项为注册的事件  
    53.             Iterator ite = this.selector.selectedKeys().iterator();  
    54.             while (ite.hasNext()) {  
    55.                 SelectionKey key = (SelectionKey) ite.next();  
    56.                 // 删除已选的key,以防重复处理  
    57.                 ite.remove();  
    58.                 // 客户端请求连接事件  
    59.                 if (key.isAcceptable()) {  
    60.                     ServerSocketChannel server = (ServerSocketChannel) key  
    61.                             .channel();  
    62.                     // 获得和客户端连接的通道  
    63.                     SocketChannel channel = server.accept();  
    64.                     // 设置成非阻塞  
    65.                     channel.configureBlocking(false);  
    66.   
    67.                     //在这里可以给客户端发送信息哦  
    68.                     channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息")  
    69.                             .getBytes()));  
    70.                     //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。  
    71.                     channel.register(this.selector, SelectionKey.OP_READ);  
    72.   
    73.                     // 获得了可读的事件  
    74.                 } else if (key.isReadable()) {  
    75.                     read(key);  
    76.                 }  
    77.   
    78.             }  
    79.   
    80.         }  
    81.     }  
    82.   
    83.     /** 
    84.      * 处理读取客户端发来的信息 的事件 
    85.      * @param key 
    86.      * @throws IOException  
    87.      */  
    88.     public void read(SelectionKey key) throws IOException {  
    89.         // 服务器可读取消息:得到事件发生的Socket通道  
    90.         SocketChannel channel = (SocketChannel) key.channel();  
    91.         // 创建读取的缓冲区  
    92.         ByteBuffer buffer = ByteBuffer.allocate(1024);  
    93.         channel.read(buffer);  
    94.         byte[] data = buffer.array();  
    95.         String msg = new String(data).trim();  
    96.         System.out.println("服务端收到信息:" + msg);  
    97.         ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());  
    98.         channel.write(outBuffer);// 将消息回送给客户端  
    99.     }  
    100.   
    101.     /** 
    102.      * 启动服务端测试 
    103.      * @throws IOException  
    104.      */  
    105.     public static void main(String[] args) throws IOException {  
    106.         NIOServer server = new NIOServer();  
    107.         server.initServer(8001);  
    108.         server.listen();  
    109.     }  
    110.   
    111. }  
    112. </span>  
    1. <span style="font-size:18px">package nio;  
    2.   
    3. import java.io.IOException;  
    4. import java.net.InetSocketAddress;  
    5. import java.nio.ByteBuffer;  
    6. import java.nio.channels.SelectionKey;  
    7. import java.nio.channels.Selector;  
    8. import java.nio.channels.SocketChannel;  
    9. import java.util.Iterator;  
    10.   
    11. /** 
    12.  * NIO客户端 
    13.  * @author 小路 
    14.  */  
    15. public class NIOClient {  
    16.     //通道管理器  
    17.     private Selector selector;  
    18.   
    19.     /** 
    20.      * 获得一个Socket通道,并对该通道做一些初始化的工作 
    21.      * @param ip 连接的服务器的ip 
    22.      * @param port  连接的服务器的端口号          
    23.      * @throws IOException 
    24.      */  
    25.     public void initClient(String ip, int port) throws IOException {  
    26.         // 获得一个Socket通道  
    27.         SocketChannel channel = SocketChannel.open();  
    28.         // 设置通道为非阻塞  
    29.         channel.configureBlocking(false);  
    30.         // 获得一个通道管理器  
    31.         this.selector = Selector.open();  
    32.   
    33.         // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调  
    34.         //用channel.finishConnect();才能完成连接  
    35.         channel.connect(new InetSocketAddress(ip, port));  
    36.         //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。  
    37.         channel.register(selector, SelectionKey.OP_CONNECT);  
    38.     }  
    39.   
    40.     /** 
    41.      * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 
    42.      * @throws IOException 
    43.      */  
    44.     @SuppressWarnings("unchecked")  
    45.     public void listen() throws IOException {  
    46.         // 轮询访问selector  
    47.         while (true) {  
    48.             selector.select();  
    49.             // 获得selector中选中的项的迭代器  
    50.             Iterator ite = this.selector.selectedKeys().iterator();  
    51.             while (ite.hasNext()) {  
    52.                 SelectionKey key = (SelectionKey) ite.next();  
    53.                 // 删除已选的key,以防重复处理  
    54.                 ite.remove();  
    55.                 // 连接事件发生  
    56.                 if (key.isConnectable()) {  
    57.                     SocketChannel channel = (SocketChannel) key.channel();  
    58.                     // 如果正在连接,则完成连接  
    59.                     if (channel.isConnectionPending()) {  
    60.                         channel.finishConnect();  
    61.   
    62.                     }  
    63.                     // 设置成非阻塞  
    64.                     channel.configureBlocking(false);  
    65.   
    66.                     //在这里可以给服务端发送信息哦  
    67.                     channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息")  
    68.                             .getBytes()));  
    69.                     //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。  
    70.                     channel.register(this.selector, SelectionKey.OP_READ);  
    71.   
    72.                     // 获得了可读的事件  
    73.                 } else if (key.isReadable()) {  
    74.                     read(key);  
    75.                 }  
    76.   
    77.             }  
    78.   
    79.         }  
    80.     }  
    81.   
    82.     /** 
    83.      * 处理读取服务端发来的信息 的事件 
    84.      * @param key 
    85.      * @throws IOException  
    86.      */  
    87.     public void read(SelectionKey key) throws IOException {  
    88.         //和服务端的read方法一样  
    89.         // 服务器可读取消息:得到事件发生的Socket通道  
    90.         SocketChannel channel = (SocketChannel) key.channel();  
    91.         // 创建读取的缓冲区  
    92.         ByteBuffer buffer = ByteBuffer.allocate(1024);  
    93.         channel.read(buffer);  
    94.         byte[] data = buffer.array();  
    95.         String msg = new String(data).trim();  
    96.         System.out.println("服务端收到信息:" + msg);  
    97.         ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());  
    98.   
    99.     }  
    100.   
    101.     /** 
    102.      * 启动客户端测试 
    103.      * @throws IOException  
    104.      */  
    105.     public static void main(String[] args) throws IOException {  
    106.         NIOClient client = new NIOClient();  
    107.         client.initClient("localhost"8001);  
    108.         client.listen();  
    109.     }  
    110.   
    111. }  
    112. </span>  

    原文链接:http://blog.csdn.net/burningsheep/article/details/12918189

  • 相关阅读:
    设计模式学习心得5
    HTTP协议 (六) 状态码详解
    HTTP协议 (五) 代理
    HTTP协议 (四) 缓存
    HTTP协议 (三) 压缩
    HTTP协议 (二) 基本认证
    HTTP协议 (一) HTTP协议详解
    java中abstract和interface的區別(轉)
    基于TCP的字符串传输程序
    文件比较
  • 原文地址:https://www.cnblogs.com/kuyuyingzi/p/4266354.html
Copyright © 2011-2022 走看看