Java套接字和丢弃的连接

检测套接字是否被丢弃的最合适方法是什么? 或者是否确实发送了数据包?

我有一个库,可以通过Apple gatways( 在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窗口尚未耗尽。

有没有办法可以确定数据包是否实际进入网络? 我尝试了以下两种解决方案:

  1. 插入额外的socket.getInputStream().read()操作,超时250ms。 这会强制在删除连接时失败的读取操作,否则会挂起250ms。

  2. 将TCP发送缓冲区大小(例如Socket.setSendBufferSize() )设置为消息二进制大小。

这两种方法都有效,但它们会严重降低服务质量; 吞吐量最多从100条消息/秒到大约10条消息/秒。

有什么建议么?

更新:

受到多个答案的质疑,质疑所描述的可能性。 我构建了我所描述的行为的“单元”测试。 查看Gist 273786的单元案例。

两个unit testing都有两个线程,一个服务器和一个客户端。 客户端发送数据时服务器关闭,而不会抛出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 

这对您没有多大帮助,但从技术上讲,您提出的解决方案都不正确。 OutputStream.flush()以及您可以想到的任何其他API调用都不会满足您的需求。

确定对等方是否已收到数据包的唯一可移植且可靠的方法是等待来自对等方的确认。 此确认可以是实际响应,也可以是正常的套接字关闭。 故事结束 – 真的没有别的办法,这不是Java特有的 – 它是基础网络编程。

如果这不是一个持久连接 – 也就是说,如果你只是发送一些东西然后关闭连接 – 你这样做就是捕获所有IOExceptions(其中任何一个表示错误)并执行正常的套接字关闭:

 1. socket.shutdownOutput(); 2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket 

在连接断开之后遇到很多麻烦,我移动了我的代码以使用增强格式 ,这几乎意味着您将包更改为如下所示:

在此处输入图像描述

这样,如果发生错误,Apple将不会丢弃连接,但会将反馈代码写入套接字。

如果您使用TCP / IP协议向Apple发送信息,则必须接收确认。 不过你说:

Apple不会回复任何确认

这是什么意思? TCP / IP保证交付,因此接收方必须确认收到。 但是,它并不保证何时进行交付。

如果您向Apple发送通知并在收到ACK之前断开连接,则无法判断您是否成功,因此您只需再次发送即可。 如果两次推送相同的信息是一个问题或设备未正确处理,则存在问题。 解决方案是修复重复推送通知的设备处理:在推送方面你无能为力。

@Comment澄清/问题

好。 您理解的第一部分是您对第二部分的回答。 仅已收到ACKS的数据包已正确发送和接收。 我确信我们可以想到一些非常复杂的方法来自己跟踪每个数据包,但TCP假设将这一层抽象出去并为您处理。 在您的最后,您只需处理可能发生的大量故障(如果发生任何exception,则在Java中)。 如果没有exception,您尝试发送的数据将由TCP / IP协议保证发送。

是否存在数据看似“已发送”但未保证在没有exception的情况下收到的情况? 答案应该是否定的。

@例子

很好的例子,这澄清了很多事情。 我原本以为会抛出一个错误。 在发布的示例中,在第二次写入时抛出错误,但不是第一次写入。 这是一个有趣的行为……而且我无法找到解释其行为的原因。 然而,它确实解释了为什么我们必须开发自己的应用程序级协议来validation交付。

看起来你是正确的,没有协议确认他们不能保证Apple设备将收到通知。 Apple也只排队了最后一条消息。 看一下服务,我能够确定这项服务更方便客户,但不能用于保证服务,必须与其他方法结合使用。 我从以下来源读到这个。

http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/

似乎答案是肯定的,你是否可以肯定。 您可以使用像Wireshark这样的数据包嗅探器来判断它是否已被发送,但由于服务的性质,这仍然无法保证它已被接收并发送到设备。