當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結束。
服務端的代碼(用戶端發送的資料大小未知)
<pre name="code" class="java">package com.myftpnio.handler;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import com.myftpnio.server.FtpNioServer;public class ClientHandler implements NioHandler {private SocketChannel sc;@SuppressWarnings("unused")private Selector selector;private ByteBuffer buf = ByteBuffer.allocate(1024);private long sum = 0;private static int count_zore = 0;public ClientHandler(SocketChannel sc, Selector selector) {this.sc = sc;this.selector = selector;}@Overridepublic void execute(SelectionKey key) {// TODO Auto-generated method stubif (key.isReadable()) {try {while(true) {buf.clear();int n = sc.read(buf);if (n > 0) {sum += n;System.out.println("sum=" + sum + " n=" + n + " " + FtpNioServer.ByteBufferToString(buf));} else if (n == 0) {if (count_zore++ < FtpNioServer.MAX) {continue;} else {key.interestOps(SelectionKey.OP_WRITE);break;}} else if (n == -1) {System.out.println("client close connect");sc.close();key.cancel();return;}}} catch (IOException e) {//處理捕獲到的IO異常System.out.println(e.getMessage());try {sc.close();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}key.cancel();return;}}if (key.isWritable()) {try {String ret = "hello " + sc.socket().getRemoteSocketAddress().toString();ByteBuffer send = ByteBuffer.wrap(ret.getBytes());sc.write(send);key.cancel();sc.close();FtpNioServer.connum--;count_zore = 0;} catch (Exception e) {e.printStackTrace();}}}}