java nio之數據讀寫時無限循環分析與解決

    寫這片文章是因爲自己昨天剛解決了一個十個月前碰到的問題!當時苦於網上高手無人回答,也苦於自己當時沒有時間去鑽研爲什麼

   問題是:通過網上實例以及java網絡編程這本書寫java nio簡單的測試服務器時發現,都是註冊讀寫事件後然後分別處理相應的事件

就行了,這樣本沒錯,可對於後續對註冊事件的操作,再也沒有看到與之相關實例與說明。然而,就這樣完事以後,服務器端要麼是

無限讀事件與寫事件有效,要麼是客戶端接收的數據是重複的好幾條,在高併發時甚至上百上千。


   一般網絡通訊程序是,客戶端請求,然後服務端回覆響應,即key.isReadable()時,服務器端接收請求,key.isWriteable()時,服

務端返回響應!一般我們在處理完接收後,我們就註冊寫事件,然後有寫事件時處理寫事件,正常網上的實例都是這樣寫的,可是這

樣的話,客戶端第二次以後發送時,服務器端再也沒有進入到讀事件中,還寫事件回覆的數據也很怪異,返回很多重複的數據。

再改進一點的是,寫事件完事以後,又註冊一個讀事件,這樣又可以讀了,然後讀了以後再註冊寫,這樣反覆重複,的確,能很好的

解決問題了。可是是因爲什麼原因呢?當時我是這樣解決的,湊合用着,但是不知道爲什麼!

    昨天偶爾想起這個問題,然後查看mina源碼時,發現其只註冊過讀事件,而對寫事件並沒有註冊過,只是發現有一些

key.interestOps(SelectionKey.OP_WRITE)的操作,不是很明白,後來查看幫忙文檔,知道這是管道當前感興趣的事件時突然有些明

白了,後來又查看jdk源碼,發現,每當重複註冊時,都會檢測事件是否已經註冊過,如果已經註冊過的事件,會把當前感興趣的事

件切換成現在感興趣的事件,這樣所有的都明白了。

    即:管道註冊事件,可以有四種事件,它可以註冊所有的事件,但是管道每次只能對一種事件有效,即註冊讀事件時,此時只對讀事

件有效,註冊寫事件時,此時只對寫事件有效,這樣上面的笨方法來回註冊剛好達到這種效果,多的只是每次註冊時的一次判斷是否

已經註冊過。當然我們也有更好的方法,那就是通過key.interestOps方法來切換當前感興趣的事件,這樣就可以避免每次註冊時都

需要判斷了。

    以上把問題與解決方法說明,現在附一下網上經常看到的服務器端實例!只是做了一些簡單的修改。另外客戶端網上很多也用非阻塞

實現的,我感覺沒有必要,因爲非阻塞只是爲了解決高併發的問題。下面附上的客戶端是我用socket寫的一個簡單實例!

 

 

//服務器
package com.netease.nio;

import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.CharBuffer;

import java.util.Iterator;

import java.io.IOException;

//基於非阻塞的服務器實現
public class NioServer {
 
 private static int DEFAULT_PORT = 512;
 private static int DEFAULT_BUFFERSIZE = 1023;
 private static String DEFAULT_CHARSET = "UTF-8";
 
 private ServerSocketChannel ss;
 private Selector selector;
 
 private ByteBuffer buffer;
 private Charset charset;
 private CharsetDecoder decoder;
 private int port;
 public NioServer() throws IOException{
  
  this.port = DEFAULT_PORT;
  this.ss = null;
  this.selector = Selector.open();
  this.buffer = ByteBuffer.allocate(DEFAULT_BUFFERSIZE);
  this.charset = Charset.forName(DEFAULT_CHARSET);
  this.decoder = this.charset.newDecoder();
  
 }
 
 //處理key
 public void handlerKey(SelectionKey key) throws IOException{
  
  if(key.isAcceptable()){
   
   System.out.println("one client coming");
   //取出對應的服務器通道
   ServerSocketChannel server = (ServerSocketChannel)key.channel();
   //對應socketchannel
   SocketChannel channel = server.accept();
   channel.configureBlocking(false);
   //可讀
   channel.register(selector, SelectionKey.OP_READ);
  }else if(key.isReadable()){
   
    SocketChannel channel = (SocketChannel) key.channel();
   
             int count = channel.read(this.buffer);
             System.out.println("開始read");
             if (count > 0)
              {
              //把極限設爲位置,把位置設爲0,這樣讀取buffer時,剛好讀取其已經讀取多少內容的buffer
              this.buffer.flip();
              
               CharBuffer charBuffer = decoder.decode(this.buffer);
               String message = charBuffer.toString();
               //服務端接收客戶端來的數據
               System.out.println("server:" + message);
            //   NioSession data = new NioSession(message);
              
             //切換爲寫事件
               key.interestOps(SelectionKey.OP_WRITE);
      //        channel.register(selector, SelectionKey.OP_WRITE);
               key.attach(message);
              
             }
             else
             { //客戶已經斷開
                 channel.close();
             }
             //把極限設爲容量,再把位置設爲0。
      //       this.buffer.clear();//清空緩衝區,爲下一次接收數據做準備
            
             //極限不變,再把位置設爲0,這樣跟初始化時buffer是一樣的
             this.buffer.rewind();
  }else if(key.isWritable() && key.isValid()){
   
   SocketChannel channel = (SocketChannel) key.channel();
  // NioSession handle = (NioSession) key.attachment();//取出處理者
   String message = "re:"+((String)key.attachment());
  // handle.setGreeting(message);
            ByteBuffer block = ByteBuffer.wrap(message.getBytes());
            channel.write(block);
          //切換爲讀事件
            key.interestOps(SelectionKey.OP_READ);
            //此時重新註冊一個讀事件時,當調用此方法時,register當檢測讀事件已經存在時,
    //        channel.register(selector,SelectionKey.OP_READ);
  }
 }
 
 public void listen() throws IOException
    { //服務器開始監聽端口,提供服務
       ServerSocket socket;
       ss = ServerSocketChannel.open(); // 打開通道
       socket = ss.socket();   //得到與通到相關的socket對象
       socket.bind(new InetSocketAddress(port));   //將scoket榜定在制定的端口上
       //配置通到使用非阻塞模式,在非阻塞模式下,可以編寫多道程序同時避免使用複雜的多線程
       ss.configureBlocking(false);   
       ss.register(selector, SelectionKey.OP_ACCEPT);
       try
        {
           while(true)
            {//     與通常的程序不同,這裏使用channel.accpet()接受客戶端連接請求,而不是在socket對象上調用accept(),

這裏在調用accept()方法時如果通道配置爲非阻塞模式,那麼accept()方法立即返回null,並不阻塞
               this.selector.select();
               Iterator iter = this.selector.selectedKeys().iterator();
               while(iter.hasNext())
                {
                   SelectionKey key = (SelectionKey)iter.next();
                   iter.remove();
                   this.handlerKey(key);
                  
               }
           }
       }
       catch(IOException ex)
        {
           ex.printStackTrace();
           this.ss.close();
       }
   }

 public static void main(String[] args) throws IOException
    {
       System.out.println("服務器啓動 ");
       NioServer server = new NioServer();
       server.listen(); //服務器開始監聽端口,提供服務
   }

 
}

//客戶端

package com.netease.nio;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.nio.CharBuffer;

import java.util.Scanner;

//nio 連接服務器
public class NioClient {

 
 public static void main(String[] args) throws IOException{
  
  String host = "127.0.0.1";
  int port = 512;
  
  Socket socket = new Socket(host,port);
  System.out.println("客戶端起動");
  
  while(true){
   
   Scanner in = new Scanner(System.in);
   
   String message = in.nextLine();
   
   PrintWriter pw = new PrintWriter(socket.getOutputStream());
   
   pw.write(message);
   
   pw.flush();
   
   InputStreamReader reader = new InputStreamReader(socket.getInputStream());
   
   CharBuffer buf = CharBuffer.allocate(100);
   reader.read(buf);
   
   buf.flip();
   System.out.println("client:"+String.valueOf(buf));
   
   if(message.equals("quit")){
    
    break;
   }
   buf.clear();
   
   
  }
  
  socket.close();
  
 }
}


以上有什麼問題,請指出,謝謝!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章