13

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

我有一个库,用于通过 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);

在某些情况下,如果在发送消息时或之前断开连接;Socket.getOutputStream().write()虽然成功完成。我预计这是由于 TCP 窗口尚未耗尽。

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

  1. socket.getInputStream().read()插入具有 250 毫秒超时的附加操作。这会强制执行在连接断开时失败的读取操作,否则会挂起 250 毫秒。

  2. 将 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
4

3 回答 3

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
于 2010-01-09T03:02:43.670 回答
3

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

在此处输入图像描述

这样,如果发生错误,Apple 不会断开连接,而是会向套接字写入反馈代码。

于 2011-12-20T18:34:17.103 回答
2

如果您使用 TCP/IP 协议向苹果发送信息,您必须收到确认。但是你说:

Apple 不返回任何确认

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

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

@评论澄清/问题

行。你理解的第一部分是你对第二部分的回答。只有收到 ACKS 的数据包才被正确发送和接收。我相信我们可以想出一些非常复杂的方案来自己跟踪每个单独的数据包,但是 TCP 应该抽象出这一层并为您处理它。最后,您只需要处理可能发生的大量故障(在 Java 中,如果其中任何一个发生,则会引发异常)。如果没有例外,您刚刚尝试发送的数据将由 TCP/IP 协议保证发送。

是否存在数据看似“发送”但不能保证在不引发异常的情况下被接收的情况?答案应该是否定的。

@例子

很好的例子,这很清楚地说明了一些事情。我原以为会抛出错误。在发布的示例中,第二次写入时会引发错误,但不是第一次。这是一个有趣的行为......我找不到太多信息来解释它为什么会这样。然而,它确实解释了为什么我们必须开发自己的应用程序级协议来验证交付。

看起来你是对的,没有确认协议,他们并不能保证 Apple 设备会收到通知。苹果也只排队的最后一条消息。稍微看了一下服务,我可以确定这个服务更多是为了方便客户,但不能用来保证服务,必须与其他方法结合使用。我从以下来源阅读了这篇文章。

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

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

于 2010-01-10T04:40:02.350 回答