【问题标题】:TCP Connection is reset after sending urgent data发送紧急数据后重置 TCP 连接
【发布时间】:2011-12-11 12:00:13
【问题描述】:

我目前正在调试两个通过 TCP 连接交换数据的 Java 应用程序。

其中一个应用程序 TCP 客户端通过调用 Socket#sendUrgentData(int) 定期向另一个应用程序 TCP 服务器发送紧急数据。第 18 次尝试发送紧急数据时,TCP 客户端抛出以下异常

java.io.IOException:BrokenPipe
    at java.net.PlainSocketImpl.socketSendUrgentData(Native Method)
    at java.net.PlainSocketImpl.sendUrgentData(PlainSocketImpl.java:541)
    at java.net.Socket.sendUrgentData(Socket.java:927)

TCP 服务器抛出此异常

java.net.SocketException: Software caused connection abort: recv failed
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(Unknown Source)
    at java.net.SocketInputStream.read(Unknown Source)

我相信异常是由尝试写入/读取关闭的连接/套接字引起的。我不明白为什么在调用 sendUrgentData() 17 次后连接或套接字会关闭。我能够重复它,它总是在 17 次之后发生。

如果我在 Windows 上运行客户端和服务器,就会出现问题。如果我在 Solaris 上运行客户端和服务器,则不会出现此问题。如果我在 Solaris 上运行客户端并在 Windows 上运行服务器,则会出现问题。如果我在 Windows 上运行客户端并在 Solaris 上运行服务器,则不会出现此问题。这让我觉得它可能与 Windows 相关?

使用 Wireshark 我在连接上看到以下流量

--> = from TCP client to TCP server
<-- = from TCP server to TCP client

-->  [PSH, ACK, URG] (Seq=1, Ack=1)
<--  [ACK] (Seq=1, Ack=2)
-->  [PSH, ACK, URG] (Seq=2, Ack=1)
<--  [ACK] (Seq=1, Ack=3)
...
-->  [PSH, ACK, URG] (Seq=17, Ack=1)
<--  [RST, ACK] (Seq=1, Ack=18)

我写了一些简单的测试类来显示这个问题。

TCPServer.java IP_Address 端口

public class TCPServer 
{
    public static void main(String[] args) throws Exception 
    {
        ServerSocket socket = new ServerSocket();
        socket.bind(new InetSocketAddress(args[0], Integer.parseInt(args[1])));
        System.out.println("BOUND/" + socket);
        Socket connection = socket.accept();
        System.out.println("CONNECTED/" + connection);
        int b;
        while ((b = connection.getInputStream().read()) != -1) {
            System.out.println("READ byte: " + b);
        }
        System.out.println("CLOSING ..");
        connection.close();
        socket.close();
}
}

TCPClient.java IP_Address Port Interval_Between_Urgent_Data

public class TCPClient 
{
    public static void main(String[] args) throws Exception 
    {
        final Socket socket = new Socket();
        socket.connect(new InetSocketAddress(InetAddress.getByName(args[0]), Integer.parseInt(args[1])));
        System.out.println("CONNECTED/"+socket);
        Timer urgentDataTimer = new Timer(true);
        urgentDataTimer.scheduleAtFixedRate(new TimerTask() 
        {       
            int n = 0;  
            public void run() {
                try {
                    System.out.println("SENDING URGENT DATA ("+(++n)+") ..");
                    socket.sendUrgentData(1);
                    System.out.println("SENT URGENT DATA");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 1000, Integer.parseInt(args[2]));

        int b;
        while ((b = socket.getInputStream().read()) != 1) {
            System.out.println("READ byte: " + b);
        }
        System.out.println("CLOSING ..");
        urgentDataTimer.cancel();
        socket.close();
    }
}

有人能解释一下这里发生了什么吗?

谢谢。

【问题讨论】:

    标签: java windows sockets networking tcp


    【解决方案1】:

    Java 在线接收紧急数据,这会使数据流无序。可能接收方不理解乱序数据并关闭了连接。然后你一直写它,这可能会导致“对等方重置连接”。道德是你基本上不能在Java中使用紧急TCP数据,除非接收者写得很仔细。

    【讨论】:

      【解决方案2】:

      我假设您实际上在失败的应用程序中正确接收了紧急数据,并且数据与您预期的一样?

      失败的原因有很多,特别是如果您在跨平台情况下尝试它:在 TCP 中有两个关于紧急数据如何工作的相互矛盾的描述,RFC 793 详细说明 TCP 表示紧急指针指示紧跟在紧急数据之后的字节,但RFC 1122 更正了这一点,并指出紧急指针指示紧急数据的最后一个字节。如果一个对等方使用 RFC 793 定义而另一个使用 RFC 1122 定义,则会导致互操作性问题。

      因此,首先确认您的应用程序实际上正在获取正确字节的紧急数据。是的,我说的是字节,因为 Windows 只支持带外数据的单个字节,而 RFC 1122 指定 TCP 必须支持任何长度的紧急数据字节序列,所以兼容性更加复杂。 Windows 也没有指定如何或是否会缓冲后续的带外数据,因此如果您在读取一个字节的紧急数据时速度很慢并且另一个字节的紧急数据到达,那么其中一个字节可能会丢失;尽管我们的测试表明 Windows 确实缓冲了紧急数据。这一切都使得使用紧急数据的带外信令在带有 TCP 的 Windows 上有些不可靠。

      如果您碰巧使用重叠 I/O,还会出现所有其他问题。

      尽管从 C++ 的角度来看,我已经更深入地介绍了这一点,这里:http://www.serverframework.com/asynchronousevents/2011/10/out-of-band-data-and-overlapped-io.html

      【讨论】:

      • 感谢您的回答。应用程序没有收到紧急数据,因为 setOOBInline()) 未设置 (false)。发送紧急数据只是为了防止在一段时间不活动后断开连接。设置setKeepAlive) 会是一个更好的解决方案。如果我设置 setOOBInline() (true),则不会发生断开连接并接收到正确的字节。
      • 那你为什么还要使用紧急数据呢?只需发送应用程序级别的 ping 消息。紧急数据仅在您有大量数据排队等待处理(或发送)并且您想立即通知另一端而不是在它处理完所有数据之后才有用。对于保持活力,情况并非如此;根据定义,您没有待处理的数据,并且可以正常发送应用程序级别的消息。恕我直言,鉴于 RFC 不匹配和平台问题导致的紧急数据问题,我会避开它。
      猜你喜欢
      • 2020-06-23
      • 1970-01-01
      • 2017-03-07
      • 2011-09-20
      • 2014-07-01
      • 1970-01-01
      • 2013-08-31
      • 2012-02-27
      • 2017-09-08
      相关资源
      最近更新 更多