3

如何阻止 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)

这是客户端代码,似乎根本没有抱怨: public class MattClient { Socket client; 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();
        }
    }
}
4

3 回答 3

2

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

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

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

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

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

SO_LINGERTCP_NODELAY

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

编辑:

似乎SO_LINGER观察到的行为的选项的相关性并不像我认为的参考文档那样明显。因此,我将尝试更清楚地说明这一点:

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

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

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

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

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

于 2012-12-11T20:28:55.077 回答
-1

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

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

于 2012-12-11T20:23:04.143 回答
-1

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

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

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

编辑:对于在此线程中其他地方所暗示的“提前关闭”谬误的拥护者:

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

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

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

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

于 2012-12-11T21:12:33.580 回答