检测套接字是否被丢弃的最合适方法是什么?或者数据包是否真的被发送了?
我有一个库,可以通过Apple gatways(available on GitHub)向Apple发送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);
在某些情况下,如果在发送消息时或之前删除了连接; Socket.getOutputStream().write()
虽然成功完成了。我预计这是由于TCP窗口尚未耗尽。
有没有办法可以确定数据包是否实际进入网络?我尝试了以下两种解决方案:
插入额外的socket.getInputStream().read()
操作,超时时间为250毫秒。这会强制读取操作在连接断开时失败,但会挂起另外250ms。
将TCP发送缓冲区大小(例如Socket.setSendBufferSize()
)设置为二进制文件大小。
这两种方法都有效,但它们会严重降低服务质量;吞吐量最多为100条/秒,最多为10条消息/秒。
有什么建议吗?
更新:
通过多个答案质疑所描述的可能性。我构建了我所描述的行为的“单元”测试。查看Gist 273786上的单位案例。
两个单元测试都有两个线程,一个服务器和一个客户端。客户端发送数据时服务器关闭,而不会抛出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");
}
[顺便提一下,我还有一个并发测试库,它使设置更好更清晰。在gist上查看样本]。
运行时,我得到以下输出:
Closed socket
writing new packets
Finished writing
Run without any errors
答案 0 :(得分:17)
这对您没有多大帮助,但从技术上讲,您提出的两种解决方案都是错误的。 OutputStream.flush()以及您可以想到的任何其他API调用都无法满足您的需求。
确定对等方是否已收到数据包的唯一可移植且可靠的方法是等待来自对等方的确认。此确认可以是实际响应,也可以是正常的套接字关闭。故事结束 - 真的没有别的办法,这不是特定于Java的 - 它是基础网络编程。
如果这不是持久连接 - 也就是说,如果你只是发送一些内容然后关闭连接 - 你这样做的方法就是捕获所有IOExceptions(其中任何一个指示错误)并执行正常的套接字关闭:
1. socket.shutdownOutput();
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket
答案 1 :(得分:3)
答案 2 :(得分:2)
如果您使用TCP / IP协议向Apple发送信息,则必须收到确认。不过你说:
Apple没有退货 无论如何确认
这是什么意思? TCP / IP保证交付,因此接收方必须确认收到。但是,它无法保证何时进行交付。
如果您向Apple发送通知并在收到ACK之前断开连接,则无法判断您是否成功,因此您只需再次发送即可。如果两次推送相同的信息是一个问题或设备未正确处理,则存在问题。解决方案是修复重复推送通知的设备处理:在推送方面你无能为力。
@Comment澄清/问题
确定。您理解的第一部分是您对第二部分的回答。仅已收到ACKS的数据包已正确发送和接收。我确信我们可以想到一些非常复杂的方法来自己跟踪每个数据包,但TCP假设将这一层抽象出来并为您处理。在您的最后,您只需要处理可能发生的大量故障(如果发生任何异常,则在Java中)。如果没有异常,您尝试发送的数据将由TCP / IP协议保证发送。
是否存在数据看似“已发送”但未保证在没有异常的情况下收到的情况?答案应该是否定的。
@Examples
很好的例子,这澄清了很多事情。我原本以为会抛出一个错误。在发布的示例中,在第二次写入时抛出错误,但不是第一次写入。这是一个有趣的行为......而且我无法找到解释其行为的原因。然而,它确实解释了为什么我们必须开发自己的应用程序级协议来验证交付。
看起来你是正确的,没有协议确认他们不能保证Apple设备将收到通知。 Apple也只排队了最后一条消息。看一下服务,我能够确定这项服务更方便客户,但不能用于保证服务,必须与其他方法结合使用。我从以下来源读到这篇文章。
似乎答案是关于你是否可以确定。您可以使用像Wireshark这样的数据包嗅探器来判断它是否已被发送,但由于服务的性质,这仍然不能保证它已被接收并发送到设备。