【问题标题】:Java NIO: A serverSocketChannel accepts a socket request and client receives the acceptance but the server does not log itJava NIO:一个 serverSocketChannel 接受一个套接字请求,客户端接收到接受,但服务器没有记录它
【发布时间】:2015-10-11 13:23:46
【问题描述】:

我正在尝试基于非阻塞 NIO 消息开发自己的通信库。我已经阅读了 1000 篇关于它的教程和书籍章节,我认为最后我有一些可以同时连接很少的东西。但是,当我在服务器端同时存在许多连接时,我遇到了一些问题。

我有 4 个私有方法的典型选择器实现:accept、finishConnect、read 和 write。我的问题在于前两个问题:Accept 和 finishConnect。

当客户端打开一个新的套接字,并且一个可接受的键唤醒选择器时,执行以下代码。

private void accept(SelectionKey key) {
    try {
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        SocketChannel sc = ssc.accept();
        sc.configureBlocking(false);
        LOGGER.debug("Socket " + sc.hashCode() + "-" + sc.socket().toString() + " connexion completed");
        changeInterest(sc, SelectionKey.OP_READ);
        eventManager.addEvent(new ConnectionEstablished(sc));
    } catch (Throwable e) {
        NIOException ne = new NIOException(NIOException.ErrorType.ACCEPTING_CONNECTION, e);
        eventManager.addEvent(new ErrorEvent(null, ne));
    }
}

在客户端,我有一个 connect 方法的实现,一旦服务器处理其可接受的套接字密钥,就会调用该方法。

private void finishConnect(SelectionKey key) {
    SocketChannel sc = (SocketChannel) key.channel();
    try {
        if (sc.finishConnect()) {
            eventManager.addEvent(new ConnectionEstablished(sc));
            LOGGER.debug("Socket " + sc.hashCode() + "-" + sc.socket().toString() + " connection finished");
        } else {
            LOGGER.debug("REFUSED " + sc + " - " + sc.socket().toString());
            refusedConnection(sc, null);
            key.cancel();
        }
    } catch (Exception e) {
        refusedConnection(sc, e);
        key.cancel();
    }
}

问题是,当我创建一些连接被接受时,客户端会执行 finishConnect 消息(我可以看到使用所用端口建立的套接字连接)。但是我在服务器端找不到这个连接接受,没有使用这些端口的连接完成日志消息!!

我怀疑 ssc.accept() 和日志调用之间可能会出现异常,因此我添加了一些额外的日志消息来检查是哪条指令破坏了所有内容。进入接受方法的所有键的序列都已完成。

如果我什至在日志上看不到任何错误消息,这怎么可能?

编辑:我对一次打开的套接字数量进行了一些测试。当客户端开始运行时,服务器上只有一个 openSocket:服务器套接字。之后,它有多达 200 个同时打开的套接字,并且在客户端执行结束时,服务器返回到 1 个打开的套接字。我猜他们从来没有被计算在内

到目前为止,我已经制定了一种解决方法,可以监控节点上共存连接的数量,并延迟新连接的接受,直到该数量减少到给定阈值。但是我想了解发生了什么问题。

感谢您的帮助。

【问题讨论】:

  • 在问题出现之前您获得了多少连接?
  • 没有准确检查过,但我猜大概在 400-500 之间。我怀疑可能是所有打开的文件描述符都已经很忙了。但我想在这种情况下它会引发异常
  • 这是开源项目吗?可以分享完整的代码吗?
  • 嗨彼得,它还没有发布。但我希望它会在某个时候发生

标签: java selector nio


【解决方案1】:

因为有积压队列,所以在accept()被执行之前完成大量的客户端连接是完美的。所以这里没有实际问题需要解决。

但是您曾经执行过accept() 方法吗?这是您需要调查的错误。

【讨论】:

  • 对于大多数连接,我到达了接受方法,并得到了相应的日志消息。我刚刚检查过(我现在要编辑我的问题),我用一个打开的套接字(serversocket)启动我的服务器,我发现我同时打开了多达 200 个套接字。实际上,当客户端尝试通过此套接字发送消息时,就会出现该错误。由于客户端知道套接字已被接受,它通过网络提交一些字节并关闭通道。由于服务器的兴趣在于 OP_ACCEPT 并且永远不会更改,因此它永远不会读取它们。
  • 客户端知道套接字已被接受,因为积压队列。它不可能知道。接受的套接字应该在 OP_READ 的选择器中注册。
【解决方案2】:

正如 EJP 所说,问题出在积压队列上。我正在使用 bind(SocketAddress local) 方法绑定 ServerSocketChannel。

当一个套接字请求到达 JVM 时,它会被排入 backlog 队列并在那里等待,直到 Listener 触发相应键的进程被接受。实际问题在于这个队列的大小,使用 bind 方法,它最多存储 50 个连接。

当连接请求达到峰值时,队列会溢出,其中一些会丢失。为避免这种情况发生,方法 bind(SocketAddress local, int backlog) 允许更改队列的容量并增加它。

另一方面,在非阻塞模式下工作时,客户端节点上的选择器不需要接受连接来处理 OP_CONNECT 键。 SYN-ACK TCP 消息的接收将触发选择器中的相应键。

【讨论】:

  • bind(SocketAddress) 使用平台默认积压,在我使用多年的每个系统上至少有 50 个。实际问题在于您处理 OP_ACCEPT 的速度不够快。积压队列的大小是一个特性,而不是一个问题。在某些平台上默认为 500 或更多。
  • 真的!就我而言,它是 50。当您有许多节点在短时间内打开许多连接时,显然还不够。我正在开发的这个通信库旨在为分布式计算密集型应用程序传输数据和命令;因此,在通信中使用的线程数量越少越好,因为处理器处理应用程序的计算部分。如您所见,“选择器”线程仅捕获事件并将它们添加到事件的同步队列中。您是否有任何其他方法可以提高 OP_ACCEPT 处理速率,但要专门为其设置一个特殊线程?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多