Java 套接字和断开的连接 [英] Java Sockets and Dropped Connections

查看:34
本文介绍了Java 套接字和断开的连接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

What's the most appropriate way to detect if a socket has been dropped or not? Or whether a packet did actually get sent?

我有一个库,用于通过 Apple 网关向 iPhone 发送 Apple 推送通知(可在 GitHub 上获得).客户端需要打开一个套接字并发送每条消息的二进制表示;但不幸的是,Apple 没有回复任何确认.该连接也可以重复用于发送多条消息.我正在使用简单的 Java Socket 连接.相关代码为:

I have a library for sending Apple Push Notifications to iPhones through the Apple gatways (available on GitHub). Clients need to open a socket and send a binary representation of each message; but unfortunately Apple doesn't return any acknowledgement whatsoever. The connection can be reused to send multiple messages as well. I'm using the simple Java Socket connections. The relevant code is:

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 窗口还没有用完.

In some cases, if a connection is dropped while a message is sent or right before; Socket.getOutputStream().write() finishes successfully though. I expect it's due to the TCP window isn't exhausted yet.

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

Is there a way that I can tell for sure whether a packet actually got in the network or not? I experimented with the following two solutions:

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

  1. Insert an additional socket.getInputStream().read() operation with a 250ms timeout. This forces a read operation that fails when the connection was dropped, but hangs otherwise for 250ms.

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

set the TCP sending buffer size (e.g. Socket.setSendBufferSize()) to the message binary size.

这两种方法都有效,但它们显着降低了服务质量;吞吐量从 100 条消息/秒上升到最多约 10 条消息/秒.

Both of the methods work, but they significantly degrade the quality of the service; throughput goes from a 100 messages/second to about 10 messages/second at most.

有什么建议吗?

更新:

受到质疑所描述的可能性的多个答案的挑战.我构建了我描述的行为的单元"测试.在 Gist 273786 查看单元案例.

Challenged by multiple answers questioning the possibility of the described. I constructed "unit" tests of the behavior I'm describing. Check out the unit cases at Gist 273786.

两个单元测试都有两个线程,一个服务器和一个客户端.在客户端发送数据时服务器关闭,无论如何都不会抛出 IOException.主要方法如下:

Both unit tests have two threads, a server and a client. The server closes while the client is sending data without an IOException thrown anyway. Here is the main method:

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 处查看示例].

[Incidentally, I also have a concurrency testing library, that makes the setup a bit better and clearer. Checkout the sample at gist as well].

运行时我得到以下输出:

When run I get the following output:

Closed socket
writing new packets
Finished writing
Run without any errors

推荐答案

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

This not be of much help to you, but technically both of your proposed solutions are incorrect. OutputStream.flush() and whatever else API calls you can think of are not going to do what you need.

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

The only portable and reliable way to determine if a packet has been received by the peer is to wait for a confirmation from the peer. This confirmation can either be an actual response, or a graceful socket shutdown. End of story - there really is no other way, and this not Java specific - it is fundamental network programming.

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

If this is not a persistent connection - that is, if you just send something and then close the connection - the way you do it is you catch all IOExceptions (any of them indicate an error) and you perform a graceful socket shutdown:

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

这篇关于Java 套接字和断开的连接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆