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

发布时间:2019-11-20 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了java nio中,为什么客户端一方正常关闭了Socket,而服务端的isReadable()还总是返回true?脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

我这篇文章想讲的是编程时如何正确关闭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{}里处理。
还要注意一点的是,假如说bytebuffer已经满了,也就是channel.read(byteBuffer)返回0,那么即使客户端正常关闭,也无法收到-1。因此当bytebuffer满的时候需要及时清空,或者一开始就开一个大一点的bytebuffer。

脚本宝典总结

以上是脚本宝典为你收集整理的java nio中,为什么客户端一方正常关闭了Socket,而服务端的isReadable()还总是返回true?全部内容,希望文章能够帮你解决java nio中,为什么客户端一方正常关闭了Socket,而服务端的isReadable()还总是返回true?所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。