【问题标题】:Java: Managing more connections than there are threads, using a queueJava:使用队列管理比线程更多的连接
【发布时间】:2017-10-10 12:01:47
【问题描述】:

作为一个练习,我们要实现一个服务器,它有一个线程来监听连接,接受它们并将套接字扔到 BlockingQueue 中。然后池中的一组工作线程通过队列并处理通过套接字进入的请求。

每个客户端都连接到服务器,发送大量请求(在发送下一个请求之前等待响应)并最终在完成后断开连接。

我目前的方法是让每个工作线程在队列中等待,获取一个套接字,然后处理一个请求,最后将(仍然打开的)套接字放回队列中,然后再处理可能来自不同客户端的另一个请求。客户端的数量比工作线程的数量多,所以很多连接排队。

这种方法的问题:即使客户端没有发送任何东西,线程也会被客户端阻塞。可能的伪解决方案,都不尽如人意:

  • inputStream上调用available(),如果返回0,则将连接放回队列。问题:无法检测客户端是否仍然连接。
  • 同上,但使用socket.isClosed()socket.isConnected() 来确定客户端是否仍然连接。问题:这两种方法都没有检测到客户端挂断,正如 EJP 在Java socket API: How to tell if a connection has been closed? 中很好地描述的那样
  • 通过读取或写入来探测客户端是否仍然存在。问题:读取块(即返回到不活动的客户端阻塞队列的原始情况)和写入实际上向客户端发送了一些东西,导致测试失败。

有没有办法解决这个问题? IE。是否可以在不阻塞或发送内容的情况下区分断开连接的客户端和被动客户端?

【问题讨论】:

    标签: java multithreading sockets


    【解决方案1】:

    简短的回答:不。如需更长的答案,请参阅 EJP 提供的答案。

    这就是为什么您可能根本不应该将套接字放回队列,而是处理来自套接字的所有请求,然后关闭它。将连接传递给不同的工作线程以分别处理请求不会给您带来任何优势。

    如果您的客户端行为不端,您可以在套接字上使用读取超时,因此只有在超时发生之前读取才会阻塞。然后你可以关闭那个套接字,因为你的服务器没有时间去迎合那些表现不佳的客户端。

    【讨论】:

      【解决方案2】:

      有没有办法解决这个问题? IE。是否可以在不阻塞或发送内容的情况下区分断开连接的客户端和被动客户端?

      在使用阻塞 IO 时并非如此。

      您可以查看非阻塞 (NIO) 包,它处理事情的方式略有不同。

      本质上,您有一个可以使用“选择器”注册的套接字。如果您为“准备好读取数据”注册套接字,则可以确定要从哪些套接字读取,而无需单独轮询。

      写作也是如此。

      这是tutorial on writing NIO servers

      【讨论】:

      • 如果我理解正确,这个问题与阻塞 IO 无关,在问题中 available() 用于解决这个问题。相反,问题是如何判断客户端是否意外关闭,或者只是安静。我认为使用 NIO 对此无济于事。
      • 我试图处理更大的查询The problem with this approach: A thread will be blocked by a client even if the client doesn't send anything。两种模型中都存在断开连接的客户端,但至少在 NIO 中您不需要等待它们
      • 同样适用于使用 available() 不是吗?仍然没有等待。
      • 也许单独这是一个小的等待,但它是累积的(每个连接)
      • 除了来自现场的轶事外,别无其他。但这里是另一个看本质上相同的东西stackoverflow.com/a/13712950/134894
      【解决方案3】:

      事实证明,这个问题可以通过一些技巧来解决。经过与几个人的长时间讨论,我结合了他们的想法,以在合理的时间内完成工作:

      • 在创建套接字后,将其配置为阻塞读取只会阻塞一段时间,比如 100 毫秒:socket.setSoTimeout(100);
      • 另外,记录最后一次成功读取每个连接的时间戳,例如与System.currentTimeMillis()
      • 原则上(此原则的例外见下文),阅读前在连接上运行available()。如果返回 0,则将连接放回队列中,因为没有可读取的内容。
      • 在不使用available() 的情况下,上述原则的例外情况:如果时间戳太旧(例如,超过1 秒),则使用read() 实际阻塞连接。这不会比您在上面为套接字设置的SoTimeout 花费更长的时间。如果您收到 TimeoutException,请将连接放回队列中。如果您读取 -1,请丢弃连接,因为它已被远程端关闭。

      使用这种策略,大多数读取尝试会立即终止,要么返回一些数据,要么什么都不返回,因为它们被跳过了,因为没有 available()。如果另一端关闭了它的连接,我们将在一秒钟内检测到这一点,因为最后一次成功读取的时间戳太旧了。在这种情况下,我们执行将返回 -1 的实际读取,并且相应地更新套接字的 isClosed()。在套接字仍然打开但队列很长以至于我们有超过一秒的延迟的情况下,我们需要额外花费 100 毫秒才能发现连接仍然存在但尚未准备好。

      编辑:对此的增强是将“最后一次成功读取”更改为“最后一次阻塞读取”,并在收到 TimeoutException 时更新时间戳。

      【讨论】:

        【解决方案4】:

        不,区分非活动客户端和未正确关闭其套接字的客户端的唯一方法是发送 ping 或其他东西以检查它们是否仍然存在。

        我能看到的可能解决方案是

        • 踢出一段时间未发送任何内容的客户端。您必须跟踪他们已经安静了多长时间,一旦他们达到限制,您就会认为他们已经断开连接。
        • ping 客户端以查看它们是否仍然存在。我知道您要求在不发送任何内容的情况下执行此操作,但如果这确实是一个问题,即您不能使用上述解决方案,这可能是根据具体情况执行此操作的最佳方法(因为这是一个练习你可能不得不想象细节)。
        • 两者兼而有之,实际上这可能更好。跟踪他们安静了多长时间,然后给他们发送一个 ping 消息,看看他们是否还活着。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-01-27
          • 1970-01-01
          • 1970-01-01
          • 2014-06-12
          • 2010-10-08
          • 2018-06-27
          • 2013-01-14
          • 2011-05-05
          相关资源
          最近更新 更多