java nio中,为什么客户端一方正常关闭了Socket,而服务端的isReadable()还总是返回true?

栏目: 服务器 · 发布时间: 5年前

内容简介:我这篇文章想讲的是编程时如何正确关闭tcp连接。首先给出一个网络上绝大部分的java nio代码示例:服务端:

我这篇文章想讲的是编程时如何正确关闭tcp连接。

首先给出一个网络上绝大部分的java nio代码示例:

服务端:

1首先实例化一个多路I/O复用器Selector

2然后实例化一个ServerSocketChannel

3ServerSocketChannel注册为非阻塞(channel.configureBlocking(false);)

4ServerSocketChannel注册到Selector,并监听连接事件(serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);)

5Selector开始轮询,如果监听到了isAcceptable()事件,就建立一个连接,如果监听到了isReadable()事件,就读数据。

6处理完或者在处理每个事件之前将SelectionKey移除出Selector.selectedKeys()

代码:

package qiuqi.main;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

public class NioServer {
    public static void main(String[] args) throws IOException {
        startServer();
    }

    static void startServer() throws IOException {

        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(999));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (selector.select() > 0) {
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sk = iterator.next();
                iterator.remove();

                if (sk.isAcceptable()) {
                    SocketChannel channel = serverSocketChannel.accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);

                } else if (sk.isReadable()) {
                    System.out.println("读事件!!!");
                    SocketChannel channel = (SocketChannel) sk.channel();
                    try {
                        ByteBuffer byteBuffer = ByteBuffer.allocate(200);
                        //这里只读数据,未作任何处理
                        channel.read(byteBuffer);
          
                    } catch (IOException e) {
                        //手动关闭channel
                        System.out.println(e.getMessage());
                        sk.cancel();
                        if (channel != null)
                            channel.close();
                    }
                }


            }
        }
    }
}

还有说明一下,为什么在if (sk.isReadable()){}这个里面加上异常捕捉,因为可能读数据的时候客户端突然断掉,如果不捕捉这个异常,将会导致整个程序结束。

而客户端如果使用NIO编程,那么和服务端很像,然鹅,我们并不需要使用NIO编程,因为这里我想讲的问题和NIO或是普通IO无关,在我想讲的问题上,他俩是一样的,那么我就用普通socket编程来讲解,因为这个好写:)。

直接给代码如下:

package qiuqi.main;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

public class TraditionalSocketClient {

    public static void main(String[] args) throws IOException {

        startClient();
    }
    static void startClient() throws IOException {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(999));
        socket.getOutputStream().write(new byte[100]);
        //要注意这个close方法,这是正常关闭socket的方法
        //也是导致这个错误的根源
        socket.close();
    }
}

我们运行客户端和服务端的代码,输出的结果是:

读事件!!!

读事件!!!

读事件!!!

读事件!!!

读事件!!!

读事件!!!

....

读事件!!!

读事件!!!

无限个 读事件!!!

why???

客户端正常关闭,然后显然客户端不可能再给服务端发送任何数据了,服务端怎么可能还有读响应呢?

我们现在把客户端代码的最后一行 socket.close(); 这个去掉, 再运行一次! 输出结果是:

读事件!!!

读事件!!!

远程主机强迫关闭了一个现有的连接。

然后。。。就正常了(当然代码里会有异常提示的),这里的正常指的是不会输出多余的 读事件!!! 了。

这又是怎么回事?

我们知道如果去掉socket.close();那么客户端是非正常关闭,服务端这边会引发IOException。

引发完IOExpection之后,我们的程序在catch{}语句块中手动关闭了channel。

既然非正常关闭会引发异常,那么正常关闭呢?什么都不引发?但是这样服务端怎么知道客户端已经关闭了呢?

显然服务端会收到客户端的关闭信号(可读数据),而网络上绝大多数代码并没有根据这个关闭信号来结束channel。

那么关闭信号是什么?

channel.read(byteBuffer);

这个语句是有返回值的,大多数情况是返回一个大于等于0的值,表示将多少数据读入byteBuffer缓冲区。

然鹅,当客户端正常断开连接的时候,它就会返回-1。虽然这个断开连接信号也是可读数据(会使得isReadable()为true),但是

这个信号无法被读入byteBuffer,也就是说一旦返回-1,那么无论再继续读多少次都是-1,并且会引发可读事件isReadable()。

因此,这样写问题就能得到解决,下面的代码在try语句块里。

SocketChannel channel = (SocketChannel) sk.channel();
            try {
                ByteBuffer byteBuffer = ByteBuffer.allocate(200);
                int num;
                //这里只读数据,未作任何处理
                num = channel.read(byteBuffer);
                if(num == -1)
                    throw new IOException("读完成");

            } catch (IOException e) {
                System.out.println(e.getMessage());
                sk.cancel();
                if (channel != null)
                    channel.close();
            }

这里我根据返回值-1来抛出异常,使得下面的catch语句块捕捉并关闭连接,也可以不抛出异常,直接在try{}里处理。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Security Testing Cookbook

Web Security Testing Cookbook

Paco Hope、Ben Walther / O'Reilly Media / 2008-10-24 / USD 39.99

Among the tests you perform on web applications, security testing is perhaps the most important, yet it's often the most neglected. The recipes in the Web Security Testing Cookbook demonstrate how dev......一起来看看 《Web Security Testing Cookbook》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具