Java TCP/IP套接字如何向应用程序报告传输成功或失败?

6

我遇到了Java TCP/IP sockets的问题:即使服务器在此期间被关闭(没有正确的TCP/IP断开连接),我的Java应用程序仍会无休止地向服务器发送数据。

我正在使用以下代码发送数据:

PrintWriter out = PrintWriter(socket.getOutputStream(), true);
out.write("text");

if (out.checkError()) {
   System.err.println("Error sending string!");
}

另一个Stack Overflow问题中,我找到了以下答案:

TCP/IP协议(因此Java套接字)将确保您成功发送数据或最终收到错误(在Java的情况下是异常)。

我的代码足以获知TCP/IP堆栈无法成功发送我的字符串吗?还需要做其他事情吗?
顺便说一句:即使其他问题类似,开一个新问题是否正确?它没有令我满意地回答我的问题,我只能添加一个新答案,而不是新评论。

我喜欢你的第一件事是...你的名字TAJMAHAL :). 其次,你可以免费提问,而且问题没有数量限制。但是,你不应该这样做。如果你发现一个类似的问题,请尝试为你的版本找到解决方案。如果无论如何都不可能,就像你所做的那样。参考其他已经看过的问题,列出你尝试过的所有方法以及你认为自己卡住的地方。如果你有一个真正/好的问题,Stackoverflow的人们非常棒。否则,你的问题可能会被关闭/移动/降级等等。我曾经遭受过所有这些痛苦 :-) - Mayank
5个回答

4
您可能会遇到两个问题:PrintWriter 是 Java Stream/Reader/Writer 中的一个奇怪的存在,因为它会吞噬异常并要求您明确检查错误。
唯一使用它的情况(在我看来)是标准流(System.outSystem.err),在这种情况下,无法写入输出不应该停止应用程序(例如如果没有可用的标准输出)。
将其替换为 OutputStreamWriter,您将在 Java 发现错误时立即被通知。
这就带来了第二个可能的问题:TCP/IP 没有任何自动保持活动的数据包:因此,如果以某种方式断开连接,则直到尝试发送数据时才会注意到。
因此,如果连接到某个套接字,发送一些数据包,等待一段时间然后再断开连接,只有在下次尝试发送数据时才会通知您。这是 TCP/IP 协议固有的问题,而不是 Java 的问题。
如果要减少问题,则可以发送定期的保持活动/ ping 消息,除了检查连接是否仍然存活之外,不会产生任何实际效果。

但是,如果我拔掉服务器的网络电缆并继续每秒发送一条消息,out.checkError()需要多长时间才能发现传输失败?几秒钟?几分钟?几个小时? - tajmahal
1
我用OutputStreamWriter替换了PrintWriter,但是我仍然可以拔掉网络电缆并长时间传输而不会出现异常。 - tajmahal

1

我之前没有提到(因为当时认为这不相关),是我正在尝试在Android上实现这个。

经过几周的测试,似乎Android对标准Java网络类的实现与Oracle JRE有很大不同。在Android上,即使我自己关闭了连接,也显然无法可靠地检测到连接是否已关闭。 [Stream].write() 将尝试写入数分钟。因此,在Android上,似乎您总是需要发送自己的保持活动(并检查接收!)以检测断开的连接。

对于Oracle JRE,本问题的其他答案将正常工作。再次感谢!

如果有人能够提供有关此主题的进一步信息,请务必这样做。


我同意,在Android上的套接字存在缺陷,它们经常在应该引发异常时没有引发任何异常。 - Guillaume Brunerie

1

你的问题还是有所不同的。Google 对这种事情有很多说法。保持连接(SO_KEEPALIVE)并不是为这种情况而设计的。根据我所读到的 TCP 规范,它们应该每两个小时才发送一次,并且由操作系统来管理,因此你没有太多控制权。我强调“根据我所读到的”。

然而,在你不断发送数据的情况下,你是否可以每两个小时使用它们超过一次并不重要。只有在你不发送数据时,才需要保持连接以检测断开的连接。

如果你直接使用 SocketOutputStream,当你尝试向不可用的目标发送数据时,它会抛出异常(无论出于什么原因)。由于你正在使用 PrintWriter,你需要使用 checkError() 手动检查错误。

因此,总之:是的,对于你的目的来说足够了。


那么为什么对我不起作用呢?我可以拔掉服务器的电缆并继续向其发送数据数分钟而不出现错误。我目前每秒钟发送一条消息。难道TCP/IP协议栈不应该注意到缺失的ACK并通知应用层吗? - tajmahal
是的,应该这样做。尝试直接使用OutputStream,看看是否能改善你的情况:socket.getOutputStream().write("text".toBytes());。它应该会抛出一个异常。或者使用@Joachim建议的类。如果我看到他的答案,我可能就不会费心写我的了。 - Gyan aka Gary Buyn

0
有时即使服务器已经死了,你的客户端应用程序也不会得到通知。通常这是因为某个坏路由器未能关闭连接的两端。你应该设置保持活动状态以便检测这种故障。
根据你的操作系统,有方法可以更改保持活动测试间隔。

0

当你关闭套接字时,有一个特殊的条件适用。根据套接字选项SO_LINGER(请参见此文章)设置的不同,close()调用将立即返回或等待直到所有挂起的TCP传输成功或发生错误,然后通过从close()实现抛出的IOException来发出信号。

SO_TIMEOUT也会影响重试何时超时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接