如何处理 ServerSocketChannel.accept() IOException: NIO 中打开的文件太多?

How do I handle ServerSocketChannel.accept() IOException: too many open files in NIO?

我的一台服务器出现问题,周五早上我收到以下 IOException:

11/Sep/2015 01:51:39,524 [ERROR] [Thread-1] - ServerRunnable: IOException: 
java.io.IOException: Too many open files
    at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method) ~[?:1.7.0_75]
    at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:241) ~[?:1.7.0_75]
    at com.watersprint.deviceapi.server.ServerRunnable.acceptConnection(ServerRunnable.java:162) [rsrc:./:?]
    at com.watersprint.deviceapi.server.ServerRunnable.run(ServerRunnable.java:121) [rsrc:./:?]
    at java.lang.Thread.run(Thread.java:745) [?:1.7.0_75]

ServerRunnable class 的第 162 行在下面的方法中,它是 ssc.accept() 调用。

private void acceptConnection(Selector selector, SelectionKey key) {

    try {
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        SocketChannel sc = ssc.accept();
        socketConnectionCount++;

        /*
         * Test to force device error, for debugging purposes
         */
        if (brokenSocket
                && (socketConnectionCount % brokenSocketNumber == 0)) {

            sc.close();

        } else {

            sc.configureBlocking(false);
            log.debug("*************************************************");
            log.debug("Selector Thread: Client accepted from "
                    + sc.getRemoteAddress());

            SelectionKey newKey = sc.register(selector,
                    SelectionKey.OP_READ);
            ClientStateMachine clientState = new ClientStateMachine();
            clientState.setIpAddress(sc.getRemoteAddress().toString());
            clientState.attachSelector(selector);
            clientState.attachSocketChannel(sc);
            newKey.attach(clientState);

        }

    } catch (ClosedChannelException e) {

        log.error("ClosedChannelException: ", e);
        ClientStateMachine clientState = (ClientStateMachine)key.attachment();
        database.insertFailedCommunication(clientState.getDeviceId(),
                clientState.getIpAddress(),
                clientState.getReceivedString(), e.toString());
        key.cancel();

    } catch (IOException e) {
        log.error("IOException: ", e);
        
    }

}

我该如何处理?阅读错误,它似乎是 Linux OS 中的一个设置,它限制了一个进程可以打开的文件的数量。 从这一点和 this question here 来看,我似乎没有正确关闭套接字(服务器目前为大约 50 个客户端提供服务)。在这种情况下,我需要一个计时器来监视打开的套接字并在长时间后超时吗?

在某些情况下,客户端可以连接,但在建立连接后不发送任何数据。我以为我已经妥善处理了那些案件。

据我了解,非阻塞 NIO 服务器的超时时间很长,如果我错过了这样的情况,它们是否可能会累积并导致此错误?

此服务器已经 运行 三个月没有任何问题。 在我检查我的代码并检查处理不当/遗漏的案例后,处理这个特定错误的最佳方法是什么?还有其他我应该考虑的因素吗?

此外,(也许这应该是另一个问题)我已将 log4j2 配置为针对错误日志级别和更高级别发送电子邮件,但我没有收到有关此错误的电子邮件。有什么原因吗?它通常有效,错误已按预期记录到日志文件中,但我从未收到过有关它的电子邮件。我应该得到很多,因为每次建立连接时都会发生错误。

您修复了套接字泄漏问题。当您在套接字上获得 EOS 或除 SocketTimeoutException, 以外的任何 IOException 时,您必须关闭它。在 SocketChannels, 的情况下,这意味着关闭通道。仅仅取消密钥,或忽略问题并希望它会消失,是不够的。连接已经消失。

您发现有必要对断开的套接字连接进行计数并捕获 ClosedChannelException, 这一事实已经表明您的应用程序中存在主要逻辑问题。你不应该需要这个。取消关闭频道的密钥并没有提供任何解决方案。

It's my understanding that a non-blocking NIO server has very long timeouts

非阻塞 NIO 服务器的唯一超时是您指定给 select() 的超时。无论您使用的是 NIO 还是非阻塞模式,TCP 堆栈中内置的所有超时都不会受到影响。