java nio socketChannel read返回值代表的意思

當socketChannel爲阻塞方式時(默認就是阻塞方式)read函數,不會返回0,阻塞方式的socketChannel,若沒有數據可讀,或者緩衝區滿了,就會阻塞,直到滿足讀的條件,所以一般阻塞方式的read是比較簡單的,不過阻塞方式的socketChannel的問題也是顯而易見的。這裏我結合基於NIO 寫ftp服務器調試過程中碰到的問題,總結一下非阻塞場景下的read碰到的問題。注意:這裏的場景都是基於客戶端以阻塞socket的方式發送數據。

1、read什麼時候返回-1

read返回-1說明客戶端的數據發送完畢,並且主動的close socket。所以在這種場景下,(服務器程序)你需要關閉socketChannel並且取消key,最好是退出當前函數。注意,這個時候服務端要是繼續使用該socketChannel進行讀操作的話,就會拋出“遠程主機強迫關閉一個現有的連接”的IO異常。

2、read什麼時候返回0

其實read返回0有3種情況,一是某一時刻socketChannel中當前(注意是當前)沒有數據可以讀,這時會返回0,其次是bytebuffer的position等於limit了,即bytebuffer的remaining等於0,這個時候也會返回0,最後一種情況就是客戶端的數據發送完畢了(注意看後面的程序裏有這樣子的代碼,這個時候客戶端想獲取服務端的反饋調用了recv函數,若服務端繼續read,這個時候就會返回0。

總結:當客戶端發送的是文件,而且大小未知的情況,服務端如何判斷對方已經發送完畢。如單純的判斷是否等於0,可能會導致客戶端發送的數據不完整。所以,這裏加了一個檢測0出現次數的判斷,來判斷客戶端是否確實是數據發送完畢了,當然這個方法是比較笨拙的方法,大家若有更好的方法,期待大家給我答案。

網上也有類似的建議,比如自定義協議,在數據頭部帶上文件大小等。

注意:這裏有一個問題就是通過這種while循環讀取的方式,實際上它只有一次NIO事件通知,而且在這個處理過程中,其他事件就得不到及時處理,除非while結束。



更好的方式是整個體系中只有一個大的bytebuffer封裝體,然後每次使用都從這個封裝體中去取然後手工釋放也就有些人說的“memcache”。

你說這句話的時候已經說明連入門級還沒到。多個線程中不同的KEY讀取的數據在同一個bytebuffer中交叉存放,然後如果分配給每個具體的處理程序?“手工釋放”這種搞笑的話也說得出來?如何手工?用手去操作內存?除了因爲理屈詞窮而想出這種混亂思維,你說的有些人在哪裏見到這種使用方式?

對sk.interestOps(SelectionKey.OP_READ);的兩點你說明什麼?說明你去看了底層的操作?底層如何操作了?只說明你根本沒有能力看懂sk.interestOps(SelectionKey.OP_READ);的作用。

對於非阻塞IO的讀操作(寫相反),一次select只是通知載協議棧的讀操作有數據可讀,至於這次讀到的數據是多少,是否是一次完整的交互數據,selector並不關心。如果客戶端在發送了1024字節的數據,這次只讀到512字節,那麼餘下的512只能等下次select,除非是阻塞方式可以等到一直讀完。

那麼到底是重新註冊READ事件還是sk.interestOps(SelectionKey.OP_READ);,如果重新註冊當然可以讀到下面的數據,但是你就無法和上次的那部份數據合併,因爲多個KEY的不完整數據同時存在,你不知道要合併到哪個現有上去,所以用sk.interestOps(SelectionKey.OP_READ);的意思其實就是用同一個KEY重新註冊,下次讀到的餘下的數據合併到上次這個KEY的部分數據上,代表同一客戶端的一次完整的發送。

對於同一次交互中比較大的數據,必須使用sk.interestOps(SelectionKey.OP_READ);來多次讀取。無論你出於什麼原因只要你知道他的作用就不可能說它不必要。就象阻塞方法中我們while((len = in.read(buf)) > 0){....};如果你說while不必要的話,那隻能說明你根本不懂IO操作。

理論上即使對方發兩個字節的數據,一次select也可能只讀到第一個字節,除非在第一次select時,已經讀到的數據中包含中約定好的結束標記或已經知道的長度。而樓主的代碼只是將讀到的數據打印出來,既沒有判斷是否已經讀到結束標記,也沒判斷已經讀到的數據長度是否達到多少而不需再讀,那麼sk.interestOps(SelectionKey.OP_READ);來讀下次數據怎麼會不必要?只要你懂這段代碼的意思就不可能說出這樣的話。說出這樣的話只能代表你不懂

服務端的代碼(客戶端發送的數據大小未知)

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片

  1.   
  2. import java.io.IOException;  
  3. import java.nio.ByteBuffer;  
  4. import java.nio.channels.SelectionKey;  
  5. import java.nio.channels.Selector;  
  6. import java.nio.channels.SocketChannel;  
  7.   
  8. import com.myftpnio.server.FtpNioServer;  
  9.   
  10. public class ClientHandler implements NioHandler {  
  11.   
  12.     private SocketChannel sc;  
  13.     @SuppressWarnings("unused")  
  14.     private Selector selector;  
  15.     private ByteBuffer buf = ByteBuffer.allocate(1024);  
  16.     private long sum = 0;  
  17.     private static int count_zore = 0;  
  18.     public ClientHandler(SocketChannel sc, Selector selector) {  
  19.         this.sc = sc;  
  20.         this.selector = selector;  
  21.     }  
  22.       
  23.     @Override  
  24.     public void execute(SelectionKey key) {  
  25.         // TODO Auto-generated method stub  
  26.           
  27.         if (key.isReadable()) {  
  28.               
  29.             try {  
  30.                 while(true) {  
  31.                     buf.clear();  
  32.                     int n = sc.read(buf);  
  33.                     if (n > 0) {  
  34.                         sum += n;  
  35.                         System.out.println("sum=" + sum + " n=" + n + " " + FtpNioServer.ByteBufferToString(buf));  
  36.                     } else if (n == 0) {  
  37.                         if (count_zore++ < FtpNioServer.MAX) {  
  38.                             continue;  //這裏表明還需要讀取數據
  39.                         } else {  
  40.                             key.interestOps(SelectionKey.OP_WRITE);  
  41.                             break;  //這裏表明已經讀取到了所有需要的數據
  42.                         }  
  43.                     } else if (n == -1) {  
  44.                         System.out.println("client close connect");  
  45.                         sc.close();  
  46.                         key.cancel();  
  47.                         return;  
  48.                     }  
  49.                 }  
  50.             } catch (IOException e) {  
  51.                 //處理捕獲到的IO異常  
  52.                 System.out.println(e.getMessage());  
  53.                 try {  
  54.                     sc.close();  
  55.                 } catch (IOException e1) {  
  56.                     // TODO Auto-generated catch block  
  57.                     e1.printStackTrace();  
  58.                 }  
  59.                 key.cancel();  
  60.                 return;  
  61.             }  
  62.         }  
  63.           
  64.         if (key.isWritable()) {  
  65.             try {  
  66.                 String ret = "hello " + sc.socket().getRemoteSocketAddress().toString();  
  67.                 ByteBuffer send = ByteBuffer.wrap(ret.getBytes());  
  68.                 sc.write(send);  
  69.                   
  70.                 key.cancel();  
  71.                 sc.close();  
  72.                   
  73.                 FtpNioServer.connum--;  
  74.                 count_zore = 0;  
  75.             } catch (Exception e) {  
  76.                 e.printStackTrace();  
  77.             }  
  78.         }  
  79.           
  80.     }  
  81.   
  82. }  
發佈了180 篇原創文章 · 獲贊 52 · 訪問量 92萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章