【问题标题】:java.nio vs new thread for each socket [closed]每个套接字的 java.nio 与新线程 [关闭]
【发布时间】:2011-06-20 02:56:37
【问题描述】:

我正在开发一个一对多的服务器-客户端应用程序,这是一个小项目。

由于套接字 IO 阻塞。我正在为此寻找解决方案。

谁能告诉我这两种解决方案的优缺点是什么?

  1. 使用 java.nio
  2. 为每个连接的客户端新建一个线程。

谢谢

【问题讨论】:

  • 每个客户端一个线程?不是每个客户端有两个线程,一个用于读取,一个用于写入吗?否则,只要线程在 read() 中被阻塞,你就什么都做不了。
  • @Sergey,写阻塞是真正的罪魁祸首,因为它不能被中断,并且在 IO 错误的情况下,线程会阻塞,直到操作系统认为它很酷。可以使用 SO_TIMEOUT 管理读取线程。所以每个套接字有 2 个线程。
  • @bestsss,SO_TIMEOUT 不也会影响 write() 吗?如果 IO 不好,通常线程没有什么比尝试写更好的事情了。
  • @Sergey,不,它没有。没有中断写操作,即使有,你也不知道写了多少。与每秒读取几个字节的任意“坏”客户端相比,这样的设计真的很糟糕。您甚至无法在写入期间停止线程。通过读取线程,您甚至可以使用可用的线程(但它会降低一次 JNI 调用的性能)

标签: java multithreading sockets nio


【解决方案1】:

这两种方法都没有错。如果您的客户端有限,则第二种选择就足够了(甚至可能基于多核架构蓬勃发展),否则让 java.nio 管理您的资源可能会有所帮助。

请参阅同一主题的 this question 以及 this other 帖子,或者为什么不考虑 this blog post,它反对在大多数情况下使用 java.nio。

【讨论】:

    【解决方案2】:

    个别线程

    • 您可以使用简单的 InputStream/OutputStream(或 Reader/Writer)API,并将 Streams 相互包装。在my current project,我用的是一堆

      • MessageFormatter,自定义格式化类
      • PrintWriter
      • OutputStreamWriter
      • DebugOutputStream(自定义类,为调试目的制作副本)
      • DeflatorOutputStream(实际上是一个自定义子类,支持刷新)
      • SSLSocket 的 OutputStream

      在接收端反之亦然。这很舒服,因为您只需要处理程序逻辑中的顶层(其余的主要是每个构造函数调用)。

    • 每个连接都需要一个新线程(甚至是一对线程,具体取决于架构)。

      (在我的项目中,我为每个连接都有一个 MessageParser-Thread,然后将单个作业提供给 ThreadPool,然后这些作业可能会写入一个或多个打开的连接,而不仅仅是产生它们的那个。当然,写作是同步的。)

    • 每个线程都需要相当多的堆栈空间,如果您使用的是资源有限的机器,这可能会出现问题。

    在短期连接的情况下,您实际上不希望每个连接都有一个新线程,而只希望在 ThreadPool 上执行一个新的 Runnable,因为构建新线程需要一些时间。

    非阻塞 IO

    • 如果你有这样一个多转换的多层架构,你必须自己安排。有可能:

      • 我的 MessageFormatter 可以写入 CharBuffer 和 Writer。
      • 对于 Char-to-Byte 格式,使用 CharsetEncoder/CharsetDecoder(在 CharBuffer 和 ByteBuffer 之间传输数据)。
      • 对于压缩,我使用了 Deflater/Inflater 周围的包装类,它在两个 ByteBuffer 之间传输数据。
      • 对于加密,我使用了 SSLEngine(每个连接一个),它还使用 ByteBuffers 进行输入和输出。
      • 然后,写入 SocketChannel(或在另一个方向,从中读取)。

      不过,管理开销还是很麻烦的,因为您必须跟踪数据的位置。 (实际上,每个连接我有两个管道,一个或两个线程管理所有管道中的数据,在这之间等待选择器上的套接字上的新数据(或在传出的情况下在那里等待新空间)。(解析 Messages 后的实际处理仍然发生在 ThreadPool 中的衍生 Runnables 中。)

    • 您只需要少量线程。这实际上是我尝试异步执行此操作的原因。它工作(使用相同的同步客户端),但比我的多线程解决方案慢得多,所以我把它放回去,直到我们用完太多线程的内存。 (到目前为止,同时没有那么多连接。)

    【讨论】:

      【解决方案3】:

      NIO 的胜利,我可以在我想要的时候停止线程。学习 NIO 一点都不难,但是几乎从未在任何地方解释过正确使用缓冲区。我相信,原因之一是人们无法从 NIO 榨取额外收益。

      NIO 的其他部分是大多数开发人员通常无法编写代码和使用状态机,因此他们最终会过多地复制缓冲区。

      【讨论】:

        【解决方案4】:

        NIO 在性能方面是合理的,并且应该使用不超过 #cores 个线程,乘以应用程序的 IO 因子,其中 IO 因子是应用程序等待磁盘 IO 完成的百分比。

        原因很简单。当你有#cores worker 时,每个worker 很可能被绑定到一个cpu 核心并且可以将它单元化到最大。工人越多,上下文切换越多,这正是您不想要的,也是您首先使用 NIO 的原因。

        如果工作人员必须等待 IO,他们可以处理其他请求,因此使用比核心更多的工作人员来充分利用 CPU。

        如果使用线程,您将获得以下优势:

        • 您可以将会话信息存储在 ThreadLocals 中。
        • 您不必以其他方式管理会话信息。

        【讨论】:

        【解决方案5】:

        我试过Apache MINA,真的很好。我强烈推荐它。

        【讨论】:

          猜你喜欢
          • 2016-01-13
          • 2011-11-10
          • 2016-04-19
          • 2011-05-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-12-03
          • 2016-08-06
          相关资源
          最近更新 更多