Java 套接字和断开的连接
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2028620/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java Sockets and Dropped Connections
提问by notnoop
What's the most appropriate way to detect if a socket has been dropped or not? Or whether a packet did actually get sent?
检测套接字是否已丢弃的最合适方法是什么?或者是否确实发送了数据包?
I have a library for sending Apple Push Notifications to iPhones through the Apple gatways (available on GitHub). Clients need to open a socket and send a binary representation of each message; but unfortunately Apple doesn't return any acknowledgement whatsoever. The connection can be reused to send multiple messages as well. I'm using the simple Java Socket connections. The relevant code is:
我有一个库,用于通过 Apple 网关(可在 GitHub 上获得)向 iPhone 发送 Apple 推送通知。客户端需要打开一个套接字并发送每条消息的二进制表示;但不幸的是,Apple 没有回复任何确认。该连接也可以重复用于发送多条消息。我正在使用简单的 Java Socket 连接。相关代码是:
Socket socket = socket(); // returns an reused open socket, or a new one
socket.getOutputStream().write(m.marshall());
socket.getOutputStream().flush();
logger.debug("Message \"{}\" sent", m);
In some cases, if a connection is dropped while a message is sent or right before; Socket.getOutputStream().write()finishes successfully though. I expect it's due to the TCP window isn't exhausted yet.
在某些情况下,如果在发送消息时或之前断开连接;Socket.getOutputStream().write()虽然成功完成。我预计这是因为 TCP 窗口尚未耗尽。
Is there a way that I can tell for sure whether a packet actually got in the network or not? I experimented with the following two solutions:
有没有办法确定数据包是否真的进入了网络?我尝试了以下两种解决方案:
Insert an additional
socket.getInputStream().read()operation with a 250ms timeout. This forces a read operation that fails when the connection was dropped, but hangs otherwise for 250ms.set the TCP sending buffer size (e.g.
Socket.setSendBufferSize()) to the message binary size.
插入具有
socket.getInputStream().read()250 毫秒超时的附加操作。这会强制读取操作在连接断开时失败,否则会挂起 250 毫秒。将 TCP 发送缓冲区大小(例如
Socket.setSendBufferSize())设置为消息二进制大小。
Both of the methods work, but they significantly degrade the quality of the service; throughput goes from a 100 messages/second to about 10 messages/second at most.
这两种方法都有效,但它们显着降低了服务质量;吞吐量从 100 条消息/秒变为最多约 10 条消息/秒。
Any suggestions?
有什么建议?
UPDATE:
更新:
Challenged by multiple answers questioning the possibility of the described. I constructed "unit" tests of the behavior I'm describing. Check out the unit cases at Gist 273786.
受到质疑所描述的可能性的多个答案的挑战。我构建了我描述的行为的“单元”测试。在Gist 273786查看单元案例。
Both unit tests have two threads, a server and a client. The server closes while the client is sending data without an IOException thrown anyway. Here is the main method:
两个单元测试都有两个线程,一个服务器和一个客户端。在客户端发送数据时服务器关闭,无论如何都不会抛出 IOException。下面是主要方法:
public static void main(String[] args) throws Throwable {
final int PORT = 8005;
final int FIRST_BUF_SIZE = 5;
final Throwable[] errors = new Throwable[1];
final Semaphore serverClosing = new Semaphore(0);
final Semaphore messageFlushed = new Semaphore(0);
class ServerThread extends Thread {
public void run() {
try {
ServerSocket ssocket = new ServerSocket(PORT);
Socket socket = ssocket.accept();
InputStream s = socket.getInputStream();
s.read(new byte[FIRST_BUF_SIZE]);
messageFlushed.acquire();
socket.close();
ssocket.close();
System.out.println("Closed socket");
serverClosing.release();
} catch (Throwable e) {
errors[0] = e;
}
}
}
class ClientThread extends Thread {
public void run() {
try {
Socket socket = new Socket("localhost", PORT);
OutputStream st = socket.getOutputStream();
st.write(new byte[FIRST_BUF_SIZE]);
st.flush();
messageFlushed.release();
serverClosing.acquire(1);
System.out.println("writing new packets");
// sending more packets while server already
// closed connection
st.write(32);
st.flush();
st.close();
System.out.println("Sent");
} catch (Throwable e) {
errors[0] = e;
}
}
}
Thread thread1 = new ServerThread();
Thread thread2 = new ClientThread();
thread1.start();
thread2.start();
thread1.join();
thread2.join();
if (errors[0] != null)
throw errors[0];
System.out.println("Run without any errors");
}
[Incidentally, I also have a concurrency testing library, that makes the setup a bit better and clearer. Checkout the sample at gist as well].
[顺便说一句,我也有一个并发测试库,它使设置更好更清晰。也可以在 gist 处检查示例]。
When run I get the following output:
运行时,我得到以下输出:
Closed socket
writing new packets
Finished writing
Run without any errors
回答by Tzvetan Mikov
This not be of much help to you, but technically both of your proposed solutions are incorrect. OutputStream.flush() and whatever else API calls you can think of are not going to do what you need.
这对您没有多大帮助,但从技术上讲,您提出的两种解决方案都不正确。OutputStream.flush() 和您能想到的任何其他 API 调用都不会满足您的需求。
The only portable and reliable way to determine if a packet has been received by the peer is to wait for a confirmation from the peer. This confirmation can either be an actual response, or a graceful socket shutdown. End of story - there really is no other way, and this not Java specific - it is fundamental network programming.
确定对等方是否已收到数据包的唯一可移植且可靠的方法是等待对等方的确认。此确认可以是实际响应,也可以是正常的套接字关闭。故事结束 - 真的没有其他方法,这不是 Java 特有的 - 它是基本的网络编程。
If this is not a persistent connection - that is, if you just send something and then close the connection - the way you do it is you catch all IOExceptions (any of them indicate an error) and you perform a graceful socket shutdown:
如果这不是一个持久连接——也就是说,如果你只是发送一些东西然后关闭连接——你这样做的方式是你捕获所有 IOExceptions(它们中的任何一个都表示错误)并执行一个优雅的套接字关闭:
1. socket.shutdownOutput();
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket
回答by Phil Cal?ado
After much trouble with dropped connections, I moved my code to use the enhanced format, which pretty much means you change your package to look like this:
在断开连接遇到很多麻烦之后,我移动了我的代码以使用增强格式,这几乎意味着您将包更改为如下所示:


This way Apple will not drop a connection if an error happens, but will write a feedback code to the socket.
这样 Apple 不会在发生错误时断开连接,而是会向套接字写入反馈代码。
回答by Derek Litz
If you're sending information using the TCP/IP protocol to apple you have to be receiving acknowledgements. However you stated:
如果您使用 TCP/IP 协议向 Apple 发送信息,则您必须接收确认。但是你说:
Apple doesn't return any acknowledgement whatsoever
Apple 不会回复任何确认
What do you mean by this? TCP/IP guarantees delivery therefore receiver MUST acknowledge receipt. It does not guarantee when the delivery will take place, however.
你这是什么意思?TCP/IP 保证交付,因此接收方必须确认收到。但是,它不保证交货时间。
If you send notification to Apple and you break your connection before receiving the ACK there is no way to tell whether you were successful or not so you simply must send it again. If pushing the same information twice is a problem or not handled properly by the device then there is a problem. The solution is to fix the device handling of the duplicate push notification: there's nothing you can do on the pushing side.
如果您向 Apple 发送通知并在收到 ACK 之前断开连接,则无法判断您是否成功,因此您只需再次发送即可。如果两次推送相同的信息是一个问题,或者设备没有正确处理,那么就有问题了。解决方案是修复重复推送通知的设备处理:在推送方面您无能为力。
@Comment Clarification/Question
@Comment 澄清/问题
Ok. The first part of what you understand is your answer to the second part. Only the packets that have received ACKS have been sent and received properly. I'm sure we could think of some very complicated scheme of keeping track of each individual packet ourselves, but TCP is suppose to abstract this layer away and handle it for you. On your end you simply have to deal with the multitude of failures that could occur (in Java if any of these occur an exception is raised). If there is no exception the data you just tried to send is sent guaranteed by the TCP/IP protocol.
行。你理解的第一部分就是你对第二部分的回答。只有收到 ACKS 的数据包才被正确发送和接收。我相信我们可以想出一些非常复杂的方案来跟踪每个单独的数据包,但是 TCP 假设将这一层抽象出来并为您处理。最后,您只需要处理可能发生的大量失败(在 Java 中,如果其中任何一个发生,就会引发异常)。如果没有例外,您刚刚尝试发送的数据是由 TCP/IP 协议保证发送的。
Is there a situation where data is seemingly "sent" but not guaranteed to be received where no exception is raised? The answer should be no.
是否存在数据看似“发送”但不能保证在不引发异常的情况下收到的情况?答案应该是否定的。
@Examples
@例子
Nice examples, this clarifies things quite a bit. I would have thought an error would be thrown. In the example posted an error is thrown on the second write, but not the first. This is interesting behavior... and I wasn't able to find much information explaining why it behaves like this. It does however explain why we must develop our own application level protocols to verify delivery.
很好的例子,这使事情变得相当清晰。我原以为会抛出错误。在发布的示例中,在第二次写入时抛出错误,但不是第一次。这是一个有趣的行为......我无法找到很多信息来解释它为什么会这样。然而,它确实解释了为什么我们必须开发我们自己的应用程序级协议来验证交付。
Looks like you are correct that without a protocol for confirmation their is no guarantee the Apple device will receive the notification. Apple also only queue's the last message. Looking a little bit at the service I was able to determine this service is more for convenience for the customer, but cannot be used to guarantee service and must be combined with other methods. I read this from the following source.
看起来您是对的,如果没有确认协议,则不能保证 Apple 设备会收到通知。Apple 也只对最后一条消息进行排队。稍微看了一下服务,我可以确定这个服务更多是为了方便客户,但不能用来保证服务,必须结合其他方法。我从以下来源阅读了这篇文章。
Seems like the answer is no on whether or not you can tell for sure. You may be able to use a packet sniffer like Wireshark to tell if it was sent, but this still won't guarantee it was received and sent to the device due to the nature of the service.
似乎答案是否定的,是否可以确定。您可以使用像 Wireshark 这样的数据包嗅探器来判断它是否已发送,但由于服务的性质,这仍然不能保证它已被接收并发送到设备。

