Java NIO中一方斷開連接或shutdownOutput 另一方不斷READ事件

select循環裏,不斷有READ就緒事件

服務端代碼:

package NonBlocking;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TestDisconnectServer {
    static ServerSocketChannel serverSocketChannel = null;
    static Selector selector = null;

    public static void main(String[] args) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8888));

        selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        int result = 0; int i = 1;
        while ((result = selector.select()) > 0) {
            System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
                SelectionKey sk = iterator.next();

                if (sk.isAcceptable()) {
                    ServerSocketChannel ss = (ServerSocketChannel)sk.channel();
                    SocketChannel socketChannel = ss.accept();
                    socketChannel.configureBlocking(false);  //也切換非阻塞
                    socketChannel.register(selector, SelectionKey.OP_READ);  //註冊read事件
                    System.out.println("接受到新的客戶端連接");
                } else if (sk.isReadable()) {
                    System.out.println("有數據可讀");
                }

                iterator.remove();
            }
        }
    }
}

客戶端代碼:

package NonBlocking;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TestDisconnectClient {
    static SocketChannel socketChannel = null;
    static Selector selector = null;

    public static void main(String[] args) throws IOException {
        socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
        socketChannel.configureBlocking(false);

        selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_READ);
        int result = 0; int i = 1;
        while((result = selector.select()) > 0) {
            System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sk = iterator.next();

                if (sk.isReadable()) {
                    System.out.println("有數據可讀");
                }

                iterator.remove();
            }
        }
    }
}
  • 先後運行服務端、客戶端,然後沒有什麼效果,因爲雙方都沒有給對方寫入數據。
  • 當你手動停止任意一方時,另一方都會不斷收到READ事件。

手動停止服務端,客戶端read拋出異常

package NonBlocking;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TestDisconnectClient {
    static SocketChannel socketChannel = null;
    static Selector selector = null;

    public static void main(String[] args) throws IOException {
        socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
        socketChannel.configureBlocking(false);

        selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_READ);
        int result = 0; int i = 1;
        while((result = selector.select()) > 0) {
            System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sk = iterator.next();

                if (sk.isReadable()) {
                    System.out.println("有數據可讀");
                    SocketChannel canReadChannel = (SocketChannel)sk.channel();
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    try {
                        while (canReadChannel.read(buf) > 0) {
                            buf.flip();
                            System.out.println(new String(buf.array()));
                            buf.clear();
                        }
                    } catch (IOException e) {
                        canReadChannel.close();
                        sk.cancel();
                        System.out.println("檢測到遠程連接斷開");
                        e.printStackTrace();
                    }
                }

                iterator.remove();
            }
        }
    }
}
  • canReadChannel.read(buf)拋出異常時,則是連接斷開了。需要關閉channel,且取消SelectionKey。

服務端調用close,客戶端read返回-1

服務端加一句:

if (sk.isAcceptable()) {
                    ServerSocketChannel ss = (ServerSocketChannel)sk.channel();
                    SocketChannel socketChannel = ss.accept();
                    socketChannel.configureBlocking(false);  //也切換非阻塞
                    socketChannel.register(selector, SelectionKey.OP_READ);  //註冊read事件
                    System.out.println("接受到新的客戶端連接");
                    
                    socketChannel.close();  //加一句。接受連接後,馬上close
                }

客戶端修改:

package NonBlocking;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TestDisconnectClient {
    static SocketChannel socketChannel = null;
    static Selector selector = null;

    public static void main(String[] args) throws IOException {
        socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
        socketChannel.configureBlocking(false);

        selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_READ);
        int result = 0; int i = 1;
        while((result = selector.select()) > 0) {
            System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sk = iterator.next();

                if (sk.isReadable()) {
                    System.out.println("有數據可讀");
                    SocketChannel canReadChannel = (SocketChannel)sk.channel();
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    try {
                        int readResult;
                        while ((readResult = canReadChannel.read(buf)) > 0) {
                            buf.flip();
                            System.out.println(new String(buf.array()));
                            buf.clear();
                        }
                        if (readResult == -1) {  //需要單獨檢測read返回-1的情況
                            System.out.println("readResult is -1");
                            canReadChannel.close();
                            sk.cancel();
                        }
                    } catch (IOException e) {
                        canReadChannel.close();
                        sk.cancel();
                        System.out.println("檢測到遠程連接斷開");
                        e.printStackTrace();
                    }
                }

                iterator.remove();
            }
        }
    }
}
  • 當服務端對SocketChannel調用close後,客戶端會不斷有READ事件,且read不拋異常,但返回值爲-1。
  • 所以客戶端需要加上單獨檢測read返回-1的邏輯。

調用shutdownOutput後,不斷的READ事件

如果一方調用shutdownOutput後,那麼連接會處於半關閉狀態,另一方也會不斷收到READ事件,但表現是canReadChannel.read(buf)的返回值爲-1.注意也是不拋出異常。

修改服務端代碼:

package NonBlocking;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TestDisconnectServer {
    static ServerSocketChannel serverSocketChannel = null;
    static Selector selector = null;

    public static void main(String[] args) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8888));

        selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        int result = 0; int i = 1;
        while ((result = selector.select()) > 0) {
            System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
                SelectionKey sk = iterator.next();

                if (sk.isAcceptable()) {
                    ServerSocketChannel ss = (ServerSocketChannel)sk.channel();
                    SocketChannel socketChannel = ss.accept();
                    socketChannel.configureBlocking(false);  //也切換非阻塞
                    socketChannel.register(selector, SelectionKey.OP_READ);  //註冊read事件
                    System.out.println("接受到新的客戶端連接");

                    socketChannel.shutdownOutput();
                } else if (sk.isReadable()) {
                    System.out.println("有數據可讀");
                    SocketChannel canReadChannel = (SocketChannel)sk.channel();
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    try {
                        int readResult;
                        while ((readResult = canReadChannel.read(buf)) > 0) {
                            buf.flip();
                            System.out.println(new String(buf.array()));
                            buf.clear();
                        }
                        if (readResult == -1) {  //需要單獨檢測read返回-1的情況
                            System.out.println("readResult is -1");
                            canReadChannel.close();
                            sk.cancel();
                        }
                    } catch (IOException e) {
                        canReadChannel.close();
                        sk.cancel();
                        System.out.println("檢測到遠程連接斷開");
                        e.printStackTrace();
                    }
                }

                iterator.remove();
            }
        }
    }
}

修改客戶端代碼:

package NonBlocking;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class TestDisconnectClient {
    static SocketChannel socketChannel = null;
    static Selector selector = null;

    public static void main(String[] args) throws IOException {
        socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
        socketChannel.configureBlocking(false);

        selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_READ);
        int result = 0; int i = 1;
        while((result = selector.select()) > 0) {
            System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sk = iterator.next();

                if (sk.isReadable()) {
                    System.out.println("有數據可讀");
                    SocketChannel canReadChannel = (SocketChannel)sk.channel();
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    try {
                        int readResult;
                        while ((readResult = canReadChannel.read(buf)) > 0) {
                            buf.flip();
                            System.out.println(new String(buf.array()));
                            buf.clear();
                        }
                        if (readResult == -1) {  //需要單獨檢測read返回-1的情況
                            System.out.println("readResult is -1");
                            canReadChannel.close();
                            sk.cancel();
                        }
                    } catch (IOException e) {
                        canReadChannel.close();
                        sk.cancel();
                        System.out.println("檢測到遠程連接斷開");
                        e.printStackTrace();
                    }
                }

                iterator.remove();
            }
        }
    }
}

先後運行服務端、客戶端。

客戶端效果:

selector 1th loop, ready event number is 1
有數據可讀
readResult is -1
//然後程序阻塞在下一次循環的select那裏

服務端效果:

selector 1th loop, ready event number is 1
接受到新的客戶端連接
selector 2th loop, ready event number is 1
有數據可讀
readResult is -1
//然後程序阻塞在下一次循環的select那裏

整個流程是這樣的:

  1. 三次握手建立成功,服務端檢測到ACCEPT事件。
  2. 服務端獲得SocketChannel實例後,馬上調用了shutdownOutput。
  3. 這使得客戶端檢測到READ事件,且read結果爲-1。
  4. 然後客戶端執行canReadChannel.close(),這又使得服務端檢測到了READ事件,且read結果也爲-1。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章