Java 套接字和断开的连接

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

我有一个库,用于通过Apple gatways(可在GitHub上找到)将Apple Push通知发送到iPhone。客户端需要打开一个套接字并发送每条消息的二进制表示形式;但不幸的是,苹果没有返回任何确认。该连接也可以重复用于发送多条消息。我正在使用简单的Java套接字连接。相关代码是:

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);

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

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

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

  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");
}

[顺便说一句,我还有一个并发测试库,这使得设置更好,更清晰。在要点中检查示例]。

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

Closed socket
writing new packets
Finished writing
Run without any errors

答案 1

这对你没有多大帮助,但从技术上讲,你提出的两个解决方案都是不正确的。OutputStream.flush() 和你能想到的任何其他 API 调用都不会做你需要的事情。

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

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

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

答案 2

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

enter image description here

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


推荐