资讯专栏INFORMATION COLUMN

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

RyanHoo / 1270人阅读

摘要:这里只读数据,未作任何处理读完成这里我根据返回值来抛出异常,使得下面的语句块捕捉并关闭连接,也可以不抛出异常,直接在里处理。

我这篇文章想讲的是编程时如何正确关闭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 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。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73544.html

相关文章

  • BIO、伪异步 IO、AIO和NIO

    摘要:采用通信模型的服务端通常由一个独立的线程负责监听客户端的连接它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理处理完成之后通过输出流返回应答给客户端线程销毁这就是典型的一请求一应答通信模型该模型最大的问题就是缺乏弹性伸缩能力 BIO 采用 BIO 通信模型的服务端, 通常由一个独立的 Acceptor 线程负责监听客户端的连接, 它接收到客户端连接请求之后为每个客户端创...

    ideaa 评论0 收藏0
  • java同步非阻塞IO

    摘要:的异步即是异步的,也是非阻塞的。但是,也可以进行一层稍微薄点的封装,保留这种多路复用的模型,比如的,是一种同步非阻塞的模型。系统调用操作系统的系统调用提供了多路复用的非阻塞的系统调用,这也是机制实现需要用到的。 异步IO编程在javascript中得到了广泛的应用,之前也写过一篇博文进行梳理。js的异步IO即是异步的,也是非阻塞的。非阻塞的IO需要底层操作系统的支持,比如在linux上...

    caoym 评论0 收藏0
  • Java NIO详解

    摘要:前言本篇主要讲解中的机制和网络通讯中处理高并发的分为两块第一块讲解多线程下的机制第二块讲解如何在机制下优化资源的浪费服务器单线程下的机制就不用我介绍了,不懂得可以去查阅下资料那么多线程下,如果进行套接字的使用呢我们使用最简单的服务器来帮助大 前言 本篇主要讲解Java中的IO机制和网络通讯中处理高并发的NIO 分为两块:第一块讲解多线程下的IO机制第二块讲解如何在IO机制下优化CPU资...

    rickchen 评论0 收藏0
  • Java NIO之Selector(选择器)

    摘要:抽象类有一个方法用于使通道处于阻塞模式或非阻塞模式。注意抽象类的方法是由抽象类实现的,都是直接继承了抽象类。大家有兴趣可以看看的源码,各种抽象类和抽象类上层的抽象类。 历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂redis集群原理及搭建...

    xiaokai 评论0 收藏0
  • 一文理解:Java NIO 核心组件

    摘要:的出现解决了这尴尬的问题,非阻塞模式下,通过,我们的线程只为已就绪的通道工作,不用盲目的重试了。注意要将注册到,首先需要将设置为非阻塞模式,否则会抛异常。 showImg(https://segmentfault.com/img/remote/1460000017053374); 背景知识 同步、异步、阻塞、非阻塞 首先,这几个概念非常容易搞混淆,但NIO中又有涉及,所以总结一下。 ...

    Coding01 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<