【问题标题】:Sending Object over socket results in SocketException通过套接字发送对象导致 SocketException
【发布时间】:2012-11-29 10:34:55
【问题描述】:

如何阻止 SocketException 发生?

我正在尝试将序列化对象从客户端简单传输到本地计算机上的服务器。

我已经能够使用以下代码的细微变化来发送字符串,但是当我尝试发送对象时

Customer customerToReceive = (Customer) input.readObject();// EXCEPTION OCCURS RIGHT HERE

我收到一个我不明白如何解释的 SocketException。

java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(Unknown Source)
at java.io.ObjectInputStream$PeekInputStream.read(Unknown Source)
at java.io.ObjectInputStream$BlockDataInputStream.read(Unknown Source)
at java.io.ObjectInputStream$BlockDataInputStream.readFully(Unknown Source)
at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
at java.io.ObjectInputStream.readSerialData(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at MattServer.runCustomerServer(MattServer.java:44)
at MattServer.<init>(MattServer.java:14)
at MattServerTest.main(MattServerTest.java:10)

这是客户端代码,它似乎根本没有抱怨: 公共类 MattClient { 套接字客户端; ObjectOutputStream 输出; ObjectInputStream 输入; 字符串消息;

public MattClient()
{
    runCustomerClient();
}

public void runCustomerClient()
{
    try
    {
        //Connection:
        System.out.println("Attempting connection...");
        client = new Socket("localhost",12345);
        System.out.println("Connected to server...");

        //Connect Streams:

        //output.flush();
        System.out.println("Got IO Streams...");

        //SEND MESSAGES:
        try
        {
            for(int i = 1;i<=10;i++)
            {
                output = new ObjectOutputStream(client.getOutputStream());
                Customer customerToSend = new Customer("Matt", "1234 fake street", i);
                System.out.println("Created customer:");
                System.out.println(customerToSend.toString());
                output.writeObject(customerToSend);
                output.flush();
            };
            message = "TERMINATE";
            System.out.println(message);
            output.writeObject(message);
            output.reset();
            output.flush();
        }
        catch (IOException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch(Exception e2)
        {
            e2.printStackTrace();
        }
        finally
        {

        }
    }
    catch (IOException e1)
    {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    catch(Exception e3)
    {
        e3.printStackTrace();
    }
    finally
    {

    }
}

还有反抗的服务器:

public class MattServer
{
ServerSocket server;
Socket socket;
ObjectInputStream input;
ObjectOutputStream output;
String message;

public MattServer()
{
    runCustomerServer();
}

public void runCustomerServer()
{
    try
    {
        server = new ServerSocket(12345,100000);
        while(true)
        {
            //CONNECTION:
            System.out.println("Waiting for connection");
            socket = server.accept();
            System.out.println("Connection received...");

            //CONNECT STREAMS:
            //output = new ObjectOutputStream(socket.getOutputStream());
            //output.flush();
            input = new ObjectInputStream(socket.getInputStream());
            System.out.println("Got IO Streams...");

            //PROCESS STREAMS:
            System.out.println("Connection successful!");
            do
            {
                System.out.println("Started loop");
                try
                {
                    System.out.println("in try...");
                    System.out.println(socket.getInetAddress().getHostName());
                    Customer customerToReceive = (Customer) input.readObject();// EXCEPTION OCCURS RIGHT HERE
                    Object o = input.readObject();
                    System.out.println("Object of class " + o.getClass().getName() + " is " + o);
                    System.out.println("Got customer object");
                    System.out.println(customerToReceive.toString());
                }
                catch(ClassNotFoundException cnfE)
                {
                    System.out.println("Can't convert input to string");
                }
            } while(!message.equals("TERMINATE"));

            System.out.println("Finished.");

        }
    }
    catch(IOException ioE)
    {
        ioE.printStackTrace();
    }
    finally
    {
        try
        {
            input.close();
            socket.close();
            server.close();
        }
        catch (IOException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

【问题讨论】:

标签: java sockets serversocket


【解决方案1】:

这个异常是过早关闭连接的结果,即客户端发送了自己的数据,立即关闭了连接。在这种情况下,服务器通常无法读取客户端发送的所有数据,并且连接的终止发生在服务器端所看到的传输中间。

请注意,flush()-ing 网络 OutputStream 并不意味着或保证任何/所有数据都已实际传输。在最好的情况下,这只确保所有数据都通过传递到本地计算机上的网络堆栈,本地计算机将自行决定何时实际传输数据。

对此的一种解决方案是让服务器在准备好时关闭连接。然后客户端应该等待,例如在阻塞 read() 操作中,然后当 服务器 发出传输结束信号时,将通过命名异常通知。

另一种方法是实现自己的确认,这样客户端会等待服务器发回确认消息,之后双方可以安全地关闭连接。

作为参考,在像您这样的情况下,有一些套接字选项会影响连接的行为,即:

SO_LINGERTCP_NODELAY

(这些套接字选项不是解决您的问题的方法,但在某些情况下可能会改变观察到的行为。)

编辑:

看来SO_LINGER 选项与观察到的行为的相关性并不像我认为的参考文档那样明显。因此,我会尽量说清楚一点:

在 Java 中,基本上有 两种 方法可以通过close() 终止 TCP 连接:

  1. 启用SO_LINGER 选项。在这种情况下,会立即发送 TCP 连接 reset (RST) 并返回对 close() 的调用。在收到RST 后尝试使用连接(读取或写入)时,连接的对端将收到一个异常,指出“连接已重置”。

  2. 启用SO_LINGER 选项。在这种情况下,会生成一个 TCPFIN 以有序地关闭连接。然后,如果对等方未确认在给定时间范围内发送的数据,发生超时,发出close() 的本地方继续像情况#1 一样,发送RST,然后声明连接“死亡”。

因此,通常希望启用SO_LINGER 以允许对等方处理数据,然后干净地断开连接。如果SO_LINGER 未启用并且close() 被调用之前所有数据都已被对等方处理(即“太早”),命名异常重新reset 连接发生在对等端。

如上所述,TCP_NODELAY 选项可能以不确定的方式改变观察到的行为,因为写入的数据更有可能已经通过网络传输之前 调用close() 会导致连接重置。

【讨论】:

  • 所以一个快速的解决方案是打乒乓球?客户端发送一个对象,然后在尝试发送另一个对象之前收到确认它已被接收?我试试看。
  • 越来越近了。我能够使用 read() write() 块语句发送一个对象。现在客户端在 input.read() 处无限期地等待,即使我让服务器执行 output.write(1)。嗯.. 正在研究,但如果您有任何建议,请在@Hanno 中提出建议。
  • 没关系!我明白了……主要是!我使用了 readObject/writeObject 而不是常规的读/写。 10 个对象正确传递!
  • 这个答案完全不正确。接收方在读取所有未决数据之前不会获得 FIN。 TCP 不会因为发送方“过早”关闭而丢失数据或给出错误。乱用SO_LINGERTCP_NODELAY 并不能解决这个不存在的问题,发送冗余的应用程序确认也不能解决。考虑 FTP。
  • @EJP:那么,您肯定给定的 Java 代码会导致发送 FIN 而不是 RST? - 请与我们分享您的来源。
【解决方案2】:

不要从客户端调用 output.reset();似乎这可能导致服务器认为连接已重置。无关紧要,但您可能也不需要 flush()。

我也不认为你应该在 for 循环的每个循环中创建一个新的 ObjectOutputStream。在循环外部(上方)初始化它,然后重用它。

【讨论】:

  • ObjectOutputStream 上,reset() 只会导致流清除其传输对象的缓存,即“忘记”所有已传输的对象。因此,在丢弃流之前调用reset() 有点无用,但不会干扰对象到底层流的传输。 - 但是,刷新ObjectOutputStream 是一个好主意,以确保所有数据都从ObjectOutputStream 传递到底层流; close()-ing OOStream 也应该隐式刷新它。
【解决方案3】:

这个异常有几个原因,但最常见的一个是你写了一个已经被另一端关闭的连接。换句话说,一个应用程序协议问题:您写入的数据多于读取的数据。

在这种情况下,这是因为您在套接字的整个生命周期中都使用一个 ObjectInputStream,但每条消息都使用一个新的 ObjectOutputStream,这不可能工作。在连接的整个生命周期中,在两端各使用一个。

您还必须在接收方的 IOException 中收到“无效类型代码 AC”消息。你知道吗?

编辑:对于本主题其他地方所暗示的“提前结束”谬误的拥护者:

  1. RFC 793 #3.5 声明 (a) 'CLOSE 是一个操作,意思是“我没有更多数据要发送”',(b) 'TCP 将在连接关闭之前可靠地传送所有已发送的缓冲区',以及(c) '本地用户发起关闭:在这种情况下,可以构造一个 FIN 段并将其放置在传出段队列中' [我的重点]。 FIN 不会在任何未决数据之前到达,并且关闭不会中止未决数据的传输。

  2. 设置一个正的 SO_LINGER 超时会导致 close() 阻塞,直到所有待处理的数据都发送完毕或超时到期。完全不设置SO_LINGER,无论如何都会发送未决数据,但异步关闭,除非设置了+ve linger timeout。第三种可能性是故意丢弃未决数据并发送 RST 而不是 FIN。 OP 在这里没有这样做,它不是解决已经涉及 RST 的问题的任何一种解决方案。

  3. 几年前,我在 Internet 上进行了一个吞吐量实验,其中我更改了所有可能的 TCP 参数除了 SO_LINGER,我将其保留为默认状态。每个测试都包括几兆字节的定时单向传输,然后是正常关闭,没有任何积极的“逗留”超时或任何等待对等方。在 1700 个连接中,遇到“连接重置”的次数为零。追随者有责任解释这些结果,还要解释为什么绝大多数 TCP 问题不会与 SO_LINGER 混淆。他们也确实有责任制作一个确实表现出所声称行为的测试用例。条件:客户端连接、发送大量数据并关闭,而不会弄乱 SO_LINGER 或任何套接字选项,以及侦听、接受、读取 EOS 并关闭的服务器。交给你了。

在 TCP 中没有“过早”关闭连接这样的事情。

【讨论】:

  • “您写入的数据多于读取的数据” - 不,服务器正在读取比“可用”更多的数据。
  • "one ObjectInputStream" vs. "a new ObjectOutputStream for each message" - 另一个错误,真的;但与观察到的SocketException无关。
  • @HannoBinder (a) 服务器不可能读取比可用数据更多的数据。如果它试图阻止或获得 EOS 条件。 (b) 这个答案不是“无关的”。它会导致上述IOException,这应该会导致接收者关闭套接字,这会导致发送者在继续发送时获得“连接重置”。
  • "第三种可能性是故意丢弃未决数据并发送 RST 而不是 FIN。OP 在这里没有这样做,它不是解决问题的任何一种解决方案已经涉及到 RST。” - 错误的。这正是 OP 通过 not 启用延迟所做的(无意中)。这个,只有这个,是导致发送RST而不是FIN的原因,这反过来又导致命名IOException与更高层的OOStream有任何关系。
  • "服务器读取的数据不可能多于可用数据。" - 不,当然不能。但它仍在尝试RST 发生的那一刻,这再次导致精确的IOException 引用。 - 请注意,在原始代码中,客户端仅写入,而服务器仅读取。然后,当客户端发出RST 时,服务器仍然期望接收数据。 - 除此之外,我很欣赏你的扩展解释,我不怀疑w.r.t。一般网络细节;只有与 OP 的原始问题的关系似乎是有争议的。
猜你喜欢
  • 1970-01-01
  • 2012-03-13
  • 2019-04-21
  • 2014-01-11
  • 2022-01-15
  • 2021-01-11
  • 2014-11-13
  • 1970-01-01
相关资源
最近更新 更多