Java套接字和丢弃的连接

not*_*oop 13 java sockets networking apple-push-notifications

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

我有一个库,可以通过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);
Run Code Online (Sandbox Code Playgroud)

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

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

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

  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");
}
Run Code Online (Sandbox Code Playgroud)

[顺便说一句,我还有一个并发测试库,它使设置更好,更清晰.在gist上查看样本].

运行时,我得到以下输出:

Closed socket
writing new packets
Finished writing
Run without any errors
Run Code Online (Sandbox Code Playgroud)

小智 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
Run Code Online (Sandbox Code Playgroud)