Netty服务端请求未响应链接关闭-客户端半关闭问题

问题表现:

有一个服务是使用Netty 做 Socket 服务端,对接的第三方客户端调用时,服务端能收到请求,但是客户端未收到响应。

排查过程:

  • 自己写客户端测试正常,使用JDK Socket客户端和Netty客户端均正常,并且服务端代码有很多业务方已经接入,线上运行一年多,服务正常,暂时先排除服务端代码问题。

  • 怀疑与客户端代码有关系,让客户将客户端代码发过来验证

    1
    2
    3
    4
    5
    6
    7
    8
    //....省略业务代码
    Socket socket = new Socket(ip, port);
    socket.setSoTimeout(30000);
    DataOutputStream out = new DataOutputStream(socket.getOutputStream());
    out.write(send.getBytes("GBK"));
    out.flush();
    socket.shutdownOutput();//问题关键代码
    //....省略业务代码

    经过对比,这个客户的客户端多了socket.shutdownOutput(),去掉之后就正常。

    我们来先看下这个方法的源码注释:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /**
    * 将此套接字的输入流置于“流的结尾”。
    * 确认发送到套接字输入流端的任何数据
    * 然后默默丢弃。
    * <p>
    * 如果在调用此方法后从套接字输入流中读取
    * 套接字,流的 {@code available} 方法将返回 0,它的
    * {@code read} 方法将返回 {@code -1}(流结束)。
    *
    * @exception IOException if an I/O error occurs when shutting down this
    * socket.
    *
    * @since 1.3
    * @see java.net.Socket#shutdownOutput()
    * @see java.net.Socket#close()
    * @see java.net.Socket#setSoLinger(boolean, int)
    * @see #isInputShutdown
    */
    public void shutdownInput() throws IOException
    {
    if (isClosed())
    throw new SocketException("Socket is closed");
    if (!isConnected())
    throw new SocketException("Socket is not connected");
    if (isInputShutdown())
    throw new SocketException("Socket input is already shutdown");
    getImpl().shutdownInput();
    shutIn = true;
    }

    我的理解是socket.shutdownOutput()方法,它是一种单向关闭流的方法,即关闭客户端的输出流并不会关闭服务端的输出流。通过shutdownOutput()方法只是关闭了输出流,但socket仍然是连接状态,连接并未关闭。正常来说是不应该影响正常消息接收的。

    那么为什么会收不到响应呢?接下来继续验证

  • 将Netty服务端替换为JDK net包的SocketServer实现后,此时接收发送消息均正常了

    那么问题基本可以定位到是Netty的问题了。查询资料

    TCP和SCTP允许用户关闭一个socket的出站流量而不用完全关闭它。这样的socket被称为“半关闭socket”,同时用户能够通过调用SocketChannel.shutdownOutput()方法来获取一个半关闭socket。如果一个远端关闭了出站通道,SocketChannel.read(..)会返回-1,这看上去并没有和一个关闭了的链接有什么区别。

    3.x没有shutdownOutput()操作。同样,它总是在SocketChannel.read(..)返回-1的时候关闭链接。

    要支持半关闭socket,4.0增加了SocketChannel.shutdownOutput()方法,同时用户能设置“ALLOW_HALF_CLOSURE”的ChanneOption来阻止Netty在SocketChannel.read(..)返回-1的时候自动关闭链接

解决方案:

尝试设置ChannelOption参数ALLOW_HALF_CLOSURE=true ,允许半关闭socket即可。默认为false。