當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);來讀下次數據怎麼會不必要?只要你懂這段代碼的意思就不可能說出這樣的話。說出這樣的話只能代表你不懂
服務端的代碼(客戶端發送的數據大小未知)
- 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;
- }
- @Override
- public void execute(SelectionKey key) {
- // TODO Auto-generated method stub
- if (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 block
- e1.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();
- }
- }
- }
- }