仅当远程TCP服务器关闭时,Java NIO客户端才会导致文件描述符泄漏

时间:2016-03-07 07:33:14

标签: java linux sockets tcp nio

以下程序充当TCP客户端,并使用NIO打开到远程服务器的套接字,如下所示

private Selector itsSelector;
private SocketChannel itsChannel;

public boolean getConnection(Selector selector, String host, int port)
{
    try
    {
        itsSelector = selector;
        itsChannel = SocketChannel.open();
        itsChannel.configureBlocking(false);
        itsChannel.register(itsSelector, SelectionKey.OP_CONNECT);
        itsChannel.connect(new InetSocketAddress(host, port));
        if (itsChannel.isConnectionPending())
        {
            while (!itsChannel.finishConnect())
            {
                // waiting until connection is finished
            }
        }
        itsChannel.register(itsSelector, SelectionKey.OP_WRITE);
        return (itsChannel != null);
    }
    catch (IOException ex)
    {
        close();
        if(ex instanceof ConnectException)
        {
            LOGGER.log(Level.WARNING, "The remoteserver cannot be reached");
        }
    }
}

public void close()
{
    try
    {
        if (itsChannel != null)
        {
            itsChannel.close();
            itsChannel.socket().close();
            itsSelector.selectNow();
        }
}   
    catch (IOException e)
    {
        LOGGER.log(Level.WARNING, "Connection cannot be closed");
    }
}

此程序在Red Hat Enterprise Linux Server 6.2版(圣地亚哥)上运行 当并发套接字的数量处于建立阶段时,文件描述符限制达到最大值,我在尝试建立更多套接字连接时看到异常。

java.net.SocketException: Too many open files
               at java.net.PlainSocketImpl.socketAccept(Native Method)
               at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408)

仅当远程节点关闭时才会发生这种情况,并且当它启动时,一切都很好。 当远程TCP服务器关闭时,抛出异常,因为在上面的代码

中处理为IOException
java.net.ConnectException: Connection refused: no further information
    at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
    at sun.nio.ch.SocketChannelImpl.finishConnect(Unknown Source)

在这种情况下,有没有办法强制关闭底层文件描述符。 提前感谢所有的帮助。

1 个答案:

答案 0 :(得分:2)

private Selector itsSelector;

我看不出这个宣言的重点。如果需要,您可以随时获取频道注册的选择器,您永远不会这样做。您可能正在泄漏选择器?

itsChannel.configureBlocking(false);
itsChannel.register(itsSelector, SelectionKey.OP_CONNECT);

您在这里注册OP_CONNECT但从未使用过该设施。

itsChannel.connect(new InetSocketAddress(host, port));

您正在开始待处理的连接。

if (itsChannel.isConnectionPending())

是的。你刚开始吧测试毫无意义。

{
    while (!itsChannel.finishConnect())
    {
        // waiting until connection is finished
    }
}

这完全是浪费时间和空间。如果您不想使用选择器来检测OP_CONNECT何时触发,则应在将频道设置为非阻止之前调用connect() ,并取消这个毫无意义的测试和循环。

    itsChannel.register(itsSelector, SelectionKey.OP_WRITE);
    return (itsChannel != null);
此时

itsChannel不可能为null。测试毫无意义。你最好允许IOExceptions传播出这种方法,以便调用者可以了解失败模式。这也使得调用者有责任关闭任何异常,而不仅仅是你在这里捕获的异常。

catch (IOException ex)
{
    close();
    if(ex instanceof ConnectException)
    {
        LOGGER.log(Level.WARNING, "The remoteserver cannot be reached");
    }
}

见上文。删除所有这些。如果您想区分ConnectException与其他IOExceptions捕获,请单独区分。并且您忘记记录不是 ConnectException的任何内容。

public void close()
{
    try
    {
        if (itsChannel != null)
        {
            itsChannel.close();
            itsChannel.socket().close();
            itsSelector.selectNow();

第二个close()电话无意义,因为频道已经关闭。

catch (IOException e)
{
    LOGGER.log(Level.WARNING, "Connection cannot be closed");
}

我很高兴看到你最终登录了IOException,但你不太可能在这里找到。

不要写这样的代码。