代码之家  ›  专栏  ›  技术社区  ›  notnoop

Java套接字和删除连接

  •  13
  • notnoop  · 技术社区  · 16 年前

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

    我有一个通过Apple Gatways向iPhone发送Apple推送通知的库( available on GitHub )客户机需要打开一个套接字并发送每个消息的二进制表示;但不幸的是,苹果没有返回任何确认信息。连接也可以重用以发送多条消息。我使用的是简单的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);
    

    在某些情况下,如果在发送消息时或之前中断了连接; 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
    
    3 回复  |  直到 16 年前
        1
  •  17
  •   Tzvetan Mikov    16 年前

    这对你没有多大帮助,但从技术上讲,你提出的两个解决方案都是错误的。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
  •  3
  •   Phil Calçado    14 年前

    在断开连接出现很多问题后,我将代码移动到 使用增强格式 ,这几乎意味着您将包更改为类似于S:。

    这样,如果发生错误,苹果不会断开连接,但会将反馈代码写入插座。

    ,这意味着您将包更改为如下所示:

    enter image description here

    这样,如果发生错误,苹果就不会断开连接,而是将反馈代码写入插座。

        3
  •  2
  •   Derek Litz    16 年前

    如果你使用TCP/IP协议向苹果发送信息,你必须得到确认。不管你怎么说:

    苹果没有退货 不管怎样的承认

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

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

    @意见澄清/问题

    好啊。你理解的第一部分是你对第二部分的回答。只有接收到ACK的数据包才被正确地发送和接收。我相信我们可以考虑一些非常复杂的方案来跟踪每个单独的包,但是TCP应该将这个层抽象出来并为您处理它。在您的末尾,您只需处理可能发生的大量失败(如果出现任何异常,则在Java中)。如果没有例外,您刚才尝试发送的数据将由TCP/IP协议保证发送。

    是否存在这样一种情况,即数据看起来是“发送”的,但不保证在不引发异常的情况下被接收?答案应该是否定的。

    @实例

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

    看起来你是正确的,如果没有协议确认他们不能保证苹果设备会收到通知。苹果也只排队等候最后一条消息。稍微看一下服务,我可以确定这个服务更方便客户,但不能用来保证服务,必须与其他方法结合。我从下面的资料中读到这个。

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

    似乎答案不在于你是否能确定地分辨出来。您可以使用类似wireshark的包嗅探器来判断它是否被发送了,但是由于服务的性质,这仍然不能保证它被接收并发送到设备。

    推荐文章