在套接字上发送对象会导致SocketException

时间:2012-12-11 20:04:27

标签: java sockets serversocket

如何停止发生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();
        }
    }
}

3 个答案:

答案 0 :(得分:2)

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

请注意flush() - 网络OutputStream 意味着或保证实际上已传输任何/所有数据。在最佳方案中,这只能确保所有数据都在上传递到本地计算机上的网络堆栈,这将自行决定何时实际传输数据。

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

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

作为参考,有一些套接字选项会影响连接行为,例如:

SO_LINGERTCP_NODELAY

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

编辑:

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

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

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

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

  3. 因此,通常需要启用RST以允许对等方处理数据,然后干净地拆除连接。如果未启用SO_LINGER 并且 SO_LINGER在之前被称为,则对等方处理了所有数据(即“太早”)命名的异常重置连接发生在对等体上。

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

答案 1 :(得分:-1)

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

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

答案 2 :(得分:-1)

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

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

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

编辑:对于“早期关闭”谬误的追随者,请参与此线索的其他地方:

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

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

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

  4. 在TCP中没有“太早”关闭连接的事情。