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。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值