7

在 Java 1.4+ 中,有 3 种方法可以中断在套接字 I/O 上阻塞的流:

  1. 如果套接字是使用常规java.net.Socket(InetAddress, int)构造函数创建的,我可以从单独的线程中关闭它。结果,SocketException在阻塞的线程中抛出了 a 。
  2. 如果套接字是使用SocketChannel.open(...). socket()(非阻塞 I/O)——同样,可以从单独的线程中关闭它,但现在AsynchronousCloseException在阻塞的线程中会抛出一个不同的异常(an)。
  3. 此外,在使用非阻塞 I/O 的情况下,可以通过 throw 中断阻塞的线程ClosedByInterruptException。使用旧式 Java I/O 时中断阻塞的线程对线程没有影响。

问题:

  1. 使用旧式 I/O 时是否从单独的线程线程安全地关闭套接字?如果没有,有什么替代方案?
  2. 使用 NIO 时是否从单独的线程线程安全地关闭套接字/通道?
  3. Socket.close()与常规 IO 相比,使用 NIO 时的行为有什么不同吗?
  4. 除了可以通过简单地中断线程来终止阻塞的 I/O 操作(这样我不再需要保留对套接字的引用)之外,使用 NIO 进行网络还有什么好处?
4

3 回答 3

3

使用旧式 I/O 时是否从单独的线程线程安全地关闭套接字?如果没有,有什么替代方案?

是的。

另一种方法是使用阻塞 NIO(这是 SocketChannel BTW 的默认行为)我更喜欢这用于少量连接,因为它具有 NIO 的效率,但具有普通 IO 的一些简单性。

使用 NIO 时是否从单独的线程线程安全地关闭套接字/通道?

对于阻塞 NIO 和非阻塞 NIO,它们都是线程安全的。

使用 NIO 与常规 IO 相比,Socket.close() 行为有什么不同吗?

如果您需要了解详细信息,我建议您阅读代码,但它们基本上是相同的。

除了可以通过简单地中断线程来终止阻塞的 I/O 操作(这样我不再需要保留对套接字的引用)之外,使用 NIO 进行网络还有什么好处?

你如何关闭连接是最不值得关注的。所以是的,有很多理由考虑 NIO 而不是普通的 IO。

蔚来的优点

  • 使用直接存储器时速度更快、重量更轻
  • 可扩展到数万用户。
  • 支持高效的忙等待。

缺点

  • 普通 IO 更易于编码。
  • 由于这个原因,许多 API 仅支持普通 IO。
于 2013-10-15T12:04:12.370 回答
1

聚会有点晚了,答案已经涵盖了这个话题,但我想我仍然可以添加一些有用的东西。

我将尝试弄清楚 API 规范做出了哪些保证,以及实现特定的内容(使用 Oracle 的 Java 8 源代码了解详细信息)。例如,如果某些东西在 Oracle 的 Java 8 中是安全的,但 API 不能保证这一点,它可能会突然在其他供应商的实现或不同版本的 Java 中中断。

TL;DR:即使对于阻塞基于流的 IO,NIO 恕我直言要好得多。只要确保使用Socket.getInputStream()andSocket.getOutputStream()而不是Channels.newInputStream()and Channels.newOutputStream()。并针对可能的 API 规范违规(例如抛出或null意外返回错误异常)进行防御性编码。

  1. 是的,old-schoolSocket.close()是线程安全的。

    API 方面:引用文档,“当前在此套接字上的 I/O 操作中阻塞的任何线程都将抛出SocketException.” 它在任何地方都没有说“安全”,但是如果没有线程安全,这样的保证是不可能的。

    实现方面:Oracle 的 Java 8Socket实现在线程安全方面相当糟糕。在随机的地方使用或不使用许多不同的锁。一个主要问题是是否可以在两个线程之间的同一个套接字上拆分读取和写入。这似乎可行,但 API 规范不能保证,而且代码看起来随时可能随机中断。但是,就close()其本身而言,它通过锁定Socket自身和一些内部锁来保护,使其非常安全。

    但是,一个(明显的)警告适用:close()它本身是线程安全的,但为了使其按预期工作,访问套接字实例时必须遵循一般线程安全规则,也就是说,它应该正确发布到线程执行close().

  2. NIO 通常在规范和实际实现中都是非常线程安全的。close()没有什么不同。实现方面,SocketChannel.socket()返回一个实例,SocketAdaptor它是适配器模式的一个实例,适应SocketChannelAPI Socket。NIO 根本不使用旧的Socket实现,所有Socket操作都委托给底层的SocketChannel.

  3. API 在这里没有什么帮助,因为SocketChannel.socket()“官方”返回一个对 a 的引用Socket,它的文档根本没有提到 NIO。一方面,考虑到向后兼容性和接口编程,它应该是这样的。另一方面,实现方面SocketAdaptor在适应接口方面做得很差Socket,至少在 Oracle 的 Java 8 中是这样。例如,SocketInputStream并没有真正尝试将异常包装在它们的Socket等价物中。这就是为什么您会看到AsynchronousCloseException文档承诺SocketException抛出 a 的原因。

    好消息是 NIO 的实现通常更好。因此,只要您不介意抛出错误的异常(以及为什么有人会关心IOException它是什么类型的?),NIOSocket就可以完成它的工作。就目前close()而言,它只是关闭了关联的频道,所以无论你打电话Socket.close()还是SocketChannel.close().

  4. @Peter Lawrey 很好地介绍了 NIO-vs-IO 的一般优缺点。因此,与其说一般的 NIO-vs-IO 我假设我们使用基于流的阻塞 IO。在这种情况下,蔚来有以下优点:

    • 它是完全线程安全的。我已经提到了并行读取和写入(异步协议的典型情况,当您发送长数据流并且必须准备好从进程中接收来自另一端的消息时)。虽然它似乎适用于 IO,但它保证适用于 NIO。

    • 您提到了通过中断线程来关闭套接字的能力,但它通常被低估了。Thread.interrupt()有人可能认为和之间没有太大区别Socket.close(),但实际上确实如此。如果您的线程不仅仅是 IO,您将不得不同时调用两者(顺序重要吗?不是很明显。)另一个考虑因素:如果您使用Executors线程(您应该这样做),他们对您的套接字一无所知,但是他们知道关于中断线程的一切。使用 NIO,您只需取消aFuture或. 使用 IO,您也必须以某种方式处理您的套接字。shutdown()Executor

    缺点:

    • 将 NIO 与 IO 混合使用可能会很棘手。您可能认为这SocketChannel.socket().getInputStream()相当于Channels.newInputStream(SocketChannel). 好吧,它不是。前者支持SocketTimeoutException,后者不支持(它只是永远阻塞)。当您应该接收心跳消息并在它们停止出现时关闭连接时,这对于协议非常重要。

    • NIO 破坏 IO API 规范的另一种情况是Socket.getRemoteSocketAddress()/ SocketChannel.getRemoteAddress()。根据Socket文档,“如果套接字在关闭之前已连接,则此方法将在套接字关闭后继续返回连接的地址。” 好吧,对于 NIO 来说,这不是真的。SocketChannel.getRemoteAddress()将 throw ClosedChannelException,正如它应该的那样,但Socket.getRemoteSocketAddress()将返回null而不是正确的地址。这可能看起来没什么大不了的,但它可能会在您最不期望的地方导致 NPE。

于 2020-06-05T08:24:38.910 回答
0

1)由于引发异常的底层操作系统调用错误来自多线程感知的 TCP 堆栈,因此应该没有任何问题。

2)不确定 - 没有尝试过,但如果有任何问题会感到惊讶。

3) 可能存在一些性能差异。操作系统 close() 调用需要与 TCP 对等方进行 4 次握手 - 不确定哪个操作系统以非阻塞方式支持它(与连接相同)。

4)线程..或套接字。您必须保留对某些内容的引用。由于您正在关闭套接字,因此保留对它的引用似乎是合理的:)

于 2013-10-15T12:09:50.883 回答