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()ネットワークを -ing しても、一部またはすべてのデータが実際に送信されたことを意味または保証するものでOutputStreamないことに注意してください。最善のシナリオでは、これにより、すべてのデータがローカル マシンのネットワーク スタックに確実に渡され、データを実際にいつ転送するかが決定されます。

これに対する 1 つの解決策は、準備が整ったときにサーバーが接続を閉じるようにすることです。クライアントは、たとえばブロッキング操作で待機する必要があり、サーバーが送信の終了をread()通知すると、指定された例外によって通知されます。

もう 1 つの方法は、独自の確認を実装して、サーバーが確認メッセージを送り返すのをクライアントが待機するようにすることです。その後、両側で接続を安全に閉じることができます。

参考までに、あなたのような場合に接続の動作に影響を与えるソケットオプションがあります。

SO_LINGERおよび TCP_NODELAY

(これらのソケット オプションは問題の解決策ではありませんが、場合によっては観察された動作を変更する可能性があります。)

編集:

観察された動作に対するオプションの関連性はSO_LINGER、参照ドキュメントからだと思っていたほど明白ではないようです。したがって、私はポイントをより明確にしようとします:

Java でTCP 接続を終了するには、基本的に次の 2 つのclose()方法があります。

  1. SO_LINGERオプションを有効にせずに。この場合、TCP 接続のリセット( RST) がすぐに送信され、呼び出しがclose()返されます。接続のピア側は、受信後に接続を使用 (読み取りまたは書き込み) しようとすると、「接続がリセットされました」という例外をRST受け取ります。

  2. 有効な SO_LINGERオプション付き。この場合、FIN接続を正常にシャットダウンするために TCP が生成されます。次に、指定された時間枠内に送信されたデータをピアが確認しない場合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

この例外にはいくつかの原因がありますが、最も一般的な原因は、相手側で既に閉じられている接続に書き込んだことです。つまり、アプリケーション プロトコルの問題です。読み取りよりも多くのデータを書き込んでいます。

この場合、ソケットの存続期間中に 1 つの ObjectInputStream を使用しているが、メッセージごとに新しい ObjectOutputStream を使用しているため、これは機能しない可能性があります。両端で、接続の存続期間中、それぞれ 1 つを使用します。

また、受信側の IOException で「無効な型コード AC」メッセージを受け取っている必要があります。あなたはそれを知っていましたか?

編集:このスレッドの他の場所で指摘されている「早期終了」の誤謬の支持者向け:

  1. RFC 793 #3.5には、(a) 「CLOSE は、「送信するデータがない」ことを意味する操作である」、(b) 「TCP は、接続が閉じられる前に SENT されたすべてのバッファを確実に配信する」、および (c) 「ローカルユーザーがクローズを開始します。この場合、FIN セグメントを作成して発信セグメント キューに配置できます[強調]。保留中のデータの前に FIN が到着することはなく、クローズによって保留中のデータの送信が中止されることはありません。

  2. 正のSO_LINGERタイムアウトを設定すると、保留中のデータがすべて送信されるかタイムアウトになるまで close() がブロックされます。まったく設定SO_LINGERしない場合、保留中のデータはとにかく送信されますが、+ve linger タイムアウトが設定されていない限り、最後まで非同期で送信されます。保留中のデータを故意に破棄し、FIN の代わりに RST を送信する 3 つ目の可能性があります。OPはここではそれを行っておらず、すでにRSTが関係している問題の解決策ではありません。

  3. 私は数年前にインターネット上でスループットの実験を行いました。この実験では、可能なすべての TCP パラメータ SO_LINGER変更しましたが、デフォルトの状態のままにしました。すべてのテストは、数メガバイトの時間指定された一方向の送信と、それに続く正の「リンガー」タイムアウトやピアの待機なしの通常のクローズで構成されていました。1700 の接続で、「接続のリセット」が 0 回発生しました。これらの結果を説明する責任は支持者にあり、TCP の問題の圧倒的多数が SO_LINGER を台無しにしない理由も説明する必要があります。要求された動作を実際に示すテスト ケースを作成する責任も実際に彼らにあります。条件: 接続し、大量のデータを送信し、SO_LINGER または実際にはソケット オプションをまったくいじらずに閉じるクライアントと、リッスンして受け入れるサーバー。EOS に読み込み、閉じます。あなたに。

TCP では、「早すぎる」接続を閉じることはありません。

于 2012-12-11T21:12:33.580 に答える