nio最大的特点在于不阻塞,但是如果要写入一个很大的文件,缓冲区满了,所以,程序还是会阻塞。解决的办法是一次只写一小块,这么做的话,就要把每次写的进度保存下来。我们可以利用SelectionKey.attach()方法,把一个selectionKey和一个对象“绑定”起来。

下面写一个小小的DEMO,服务器发送三个字符符给客户机,每次发送一个。

   
   
   
   
  1. import java.io.BufferedReader; 
  2. import java.io.IOException; 
  3. import java.net.InetSocketAddress; 
  4. import java.net.ServerSocket; 
  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. public class Ex3Server { 
  14.  
  15.     public static void main(String[] args) throws IOException { 
  16.          
  17.         class Task{ 
  18.             String[] testList = { 
  19.                     "测试1\r\n"
  20.                     "测试2\r\n"
  21.                     "测试3\r\n" 
  22.             }; 
  23.             ByteBuffer buf; 
  24.             int pos = -1
  25.              
  26.             public Task(){ 
  27.                 buf = ByteBuffer.allocate(1024); 
  28.             } 
  29.              
  30.             public ByteBuffer getBuffer(){ 
  31.                 buf.clear(); 
  32.                 pos++; 
  33.                 if(pos >= testList.length) 
  34.                     return null
  35.                  
  36.                 buf.put(testList[pos].getBytes()); 
  37.                 buf.flip(); 
  38.                 return buf; 
  39.             } 
  40.         } 
  41.          
  42.         //第一件事是创建一个 Selector 
  43.         Selector selector = Selector.open(); 
  44.          
  45.         //要监听的每一个端口都需要有一个 ServerSocketChannel  
  46.         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
  47.         serverSocketChannel.configureBlocking(false); 
  48.  
  49.         ServerSocket serverSocket = serverSocketChannel.socket(); 
  50.         serverSocket.bind(new InetSocketAddress(12345));//绑定一个端口 
  51.          
  52.         //SelectionKey 代表这个通道在此 Selector 上的这个注册。 
  53.         //当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。 
  54.         //SelectionKey 还可以用于取消通道的注册。 
  55.         SelectionKey selectionKey = serverSocketChannel.register( 
  56.                 selector, SelectionKey.OP_ACCEPT);//注册accept事件 
  57.          
  58.         int num = 0
  59.         ByteBuffer buffer = ByteBuffer.allocate(1024); 
  60.         while(true){ 
  61.             //select方法会阻塞,直到至少有一个已注册的事件发生 
  62.             num = selector.select(); 
  63.              
  64.             Iterator iterator = selector.selectedKeys().iterator(); 
  65.             SelectionKey key = null
  66.             while(iterator.hasNext()){ 
  67.                  key = iterator.next(); 
  68.                  if(key.isAcceptable()){//accept 
  69.                      ServerSocketChannel channel = (ServerSocketChannel)key.channel(); 
  70.                      //相当于从ServerSocket中accept一个socket 
  71.                      SocketChannel socketChannel = channel.accept(); 
  72.                      socketChannel.configureBlocking(false);//设为非阻塞 
  73.                      socketChannel.register(selector, SelectionKey.OP_READ); 
  74.                       
  75.                  }else if(key.isReadable()){ 
  76.                      SocketChannel channel = (SocketChannel)key.channel(); 
  77.                      int readNum = 1
  78.                      while(readNum > 0){ 
  79.                          buffer.clear(); 
  80.                          readNum = channel.read(buffer);//读出 
  81.                           
  82.                          buffer.flip(); 
  83.                           
  84.                           
  85.                      } 
  86.                       
  87.                      //attach一个对象来保存写入进度 
  88.                      channel.register(selector, SelectionKey.OP_WRITE) 
  89.                         .attach(new Task()); 
  90.                      //channel.close(); 
  91.                       
  92.                  }else if(key.isWritable()){ 
  93.                      SocketChannel channel = (SocketChannel)key.channel(); 
  94.                      Task task = (Task) key.p_w_upload(); 
  95.                      ByteBuffer buf = task.getBuffer(); 
  96.                      if(buf == null){ 
  97.                           key.cancel() ; //cancel会把channel也关掉
  98.  
  99.                          key.interestOps(SelectionKey.OP_READ);//取消掉OP_WRITE
  100.                      }else
  101.                          channel.write(buf); 
  102. //buf.hasRemaining()最好判断一下,因为为了不阻塞可能一次没写完
  103.                      } 
  104.                       
  105.                  } 
  106.                  iterator.remove(); 
  107.             } 
  108.         } 
  109.  
  110.     } 
  111.  

 

   
   
   
   
  1. import java.io.BufferedReader; 
  2. import java.io.IOException; 
  3. import java.io.InputStreamReader; 
  4. import java.io.OutputStreamWriter; 
  5. import java.io.PrintWriter; 
  6. import java.net.InetSocketAddress; 
  7. import java.net.Socket; 
  8. import java.net.SocketAddress; 
  9.  
  10.  
  11. public class Ex3Client { 
  12.  
  13.     /** 
  14.      * @param args 
  15.      * @throws IOException  
  16.      */ 
  17.     public static void main(String[] args) throws IOException { 
  18.         // TODO Auto-generated method stub 
  19.         SocketAddress address = new InetSocketAddress("localhost"12345); 
  20.         Socket s = new Socket(); 
  21.         s.connect(address); 
  22.         PrintWriter writer = new PrintWriter( new OutputStreamWriter(s.getOutputStream())); 
  23.         writer.println("abc"); 
  24.         writer.flush(); 
  25.          
  26.         BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); 
  27.          
  28.         String str; 
  29.         do
  30.             str = reader.readLine();  
  31.             System.out.println(str); 
  32.         }while(str != null); 
  33.          
  34.          
  35.         writer.close(); 
  36.         reader.close(); 
  37.         s.close(); 
  38.     } 
  39.  

对于读大文件,也可以作类似的处理。

到目前为止,我们都只有利用一个线程来处理所有请求,显然,这样会浪费多核CPU的优势。所以,我们要利用起多线程来。我想到的一种多线程的方式是多线程select(),因为一个channel可以注册到多个Selector上,把ServerSocketChannel注册到多个线程上的Selector上,让多线程竞争accept。另一种多线程的方式:把I/0操作及其它占用时间的操作丢给线程池去完成。还有一种方式,主线程的Selector负责accept,这样可以保证所有连接都被响应,工作线程的Selector去处理读/写请求。不管怎么样,总是不要让多线程去处理同一个SelectionKey(尽管它是线程安全的),因为readyOps( )方法返回的就绪状态指示只是一个提示,不是保证。底层的通道在任何时候都会不断改变。其他线程可能在通道上执行操作并影响它的就绪状态。即使使用线程池的方式,在对一个OP进程响应的时候,反注册掉这个OP,线程工作完后,再注册上这个OP,防止在这个线程工作时,其它线程也操作同一个channel。

另外,当一个请求完成后怎么办?SelectionKey和attach的对象不会自动回收。我的意见是,给每个selectionKey attach一个对象,来记录它们的活动情况,遍历selector.keys(),关闭长时间没活动的channel,相当于keepAlive机制。

一个SelectionKey对应一个Selector上的Channel,通过channel.keyFor(selector)取出相应的SelectionKey。也就是说,一个channel在一个Selector上,读/写都是一个SelectionKey。所以SelectionKey的cancel方法会把整个channel也取消掉,而不是只取消读或写。