13

ソケットがドロップされたかどうかを検出する最も適切な方法は何ですか? または、パケットが実際に送信されたかどうか?

Apple ゲートウェイ ( GitHub で入手可能)を介して Apple プッシュ通知を iPhone に送信するためのライブラリがあります。クライアントはソケットを開き、各メッセージのバイナリ表現を送信する必要があります。残念ながら、Apple は何の確認応答も返しません。接続を再利用して、複数のメッセージを送信することもできます。私は単純な Java Socket 接続を使用しています。関連するコードは次のとおりです。

Socket socket = socket();   // returns an reused open socket, or a new one
socket.getOutputStream().write(m.marshall());
socket.getOutputStream().flush();
logger.debug("Message \"{}\" sent", m);

場合によっては、メッセージの送信中または直前に接続が切断された場合。Socket.getOutputStream().write()でも無事終了。TCPウィンドウがまだ使い果たされていないためだと思います。

パケットが実際にネットワークに到達したかどうかを確実に判断できる方法はありますか? 次の2つのソリューションを試しました。

  1. socket.getInputStream().read()250 ミリ秒のタイムアウトで追加の操作を挿入します。これにより、接続が切断されたときに失敗する読み取り操作が強制されますが、それ以外の場合は 250 ミリ秒ハングします。

  2. TCP 送信バッファ サイズ (例: Socket.setSendBufferSize()) をメッセージ バイナリ サイズに設定します。

どちらの方法も機能しますが、サービスの品質が大幅に低下します。スループットは 1 秒あたり 100 メッセージから、最大で 1 秒あたり約 10 メッセージになります。

助言がありますか?

アップデート:

説明の可能性を疑問視する複数の回答に挑戦。私が説明している動作の「単体」テストを作成しました。Gist 273786でユニットケースをチェックしてください。

どちらの単体テストにも、サーバーとクライアントの 2 つのスレッドがあります。クライアントがデータを送信している間、IOException がスローされずにサーバーが終了します。主な方法は次のとおりです。

public static void main(String[] args) throws Throwable {
    final int PORT = 8005;
    final int FIRST_BUF_SIZE = 5;

    final Throwable[] errors = new Throwable[1];
    final Semaphore serverClosing = new Semaphore(0);
    final Semaphore messageFlushed = new Semaphore(0);

    class ServerThread extends Thread {
        public void run() {
            try {
                ServerSocket ssocket = new ServerSocket(PORT);
                Socket socket = ssocket.accept();
                InputStream s = socket.getInputStream();
                s.read(new byte[FIRST_BUF_SIZE]);

                messageFlushed.acquire();

                socket.close();
                ssocket.close();
                System.out.println("Closed socket");

                serverClosing.release();
            } catch (Throwable e) {
                errors[0] = e;
            }
        }
    }

    class ClientThread extends Thread {
        public void run() {
            try {
                Socket socket = new Socket("localhost", PORT);
                OutputStream st = socket.getOutputStream();
                st.write(new byte[FIRST_BUF_SIZE]);
                st.flush();

                messageFlushed.release();
                serverClosing.acquire(1);

                System.out.println("writing new packets");

                // sending more packets while server already
                // closed connection
                st.write(32);
                st.flush();
                st.close();

                System.out.println("Sent");
            } catch (Throwable e) {
                errors[0] = e;
            }
        }
    }

    Thread thread1 = new ServerThread();
    Thread thread2 = new ClientThread();

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();

    if (errors[0] != null)
        throw errors[0];
    System.out.println("Run without any errors");
}

[ちなみに、私は並行性テスト ライブラリも持っています。これにより、セットアップが少し良くなり、より明確になります。gist のサンプルもチェックアウトしてください]。

実行すると、次の出力が得られます。

Closed socket
writing new packets
Finished writing
Run without any errors
4

3 に答える 3

17

これはあまり役に立ちませんが、技術的には、提案されたソリューションの両方が正しくありません。OutputStream.flush()や、考えられる他のAPI呼び出しは、必要なことを実行しません。

パケットがピアによって受信されたかどうかを判断するための唯一のポータブルで信頼性の高い方法は、ピアからの確認を待つことです。この確認は、実際の応答または正常なソケットシャットダウンのいずれかです。話の終わり-他に方法はありません。これはJava固有ではありません-これは基本的なネットワークプログラミングです。

これが持続的接続ではない場合、つまり、何かを送信してから接続を閉じる場合は、すべてのIOException(エラーを示すもの)をキャッチし、正常なソケットシャットダウンを実行します。

1. socket.shutdownOutput();
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket
于 2010-01-09T03:02:43.670 に答える
3

切断された接続で多くの問題が発生した後、コードを拡張フォーマットを使用するように移動しました。これは、パッケージを次のように変更することを意味します。

ここに画像の説明を入力

このようにして、Apple はエラーが発生した場合に接続を切断しませんが、フィードバック コードをソケットに書き込みます。

于 2011-12-20T18:34:17.103 に答える
2

TCP/IP プロトコルを使用して Apple に情報を送信している場合は、確認応答を受信する必要があります。ただし、次のように述べました。

Appleは承認を一切返さない

これはどういう意味ですか?TCP/IP は配信を保証するため、受信者は受信を確認する必要があります。ただし、いつ配信されるかを保証するものではありません。

Apple に通知を送信し、ACK を受信する前に接続を切断した場合、成功したかどうかを判断する方法がないため、単純に再送信する必要があります。同じ情報を 2 回プッシュすることが問題であるか、デバイスによって適切に処理されない場合は、問題があります。解決策は、重複したプッシュ通知のデバイス処理を修正することです。プッシュ側でできることは何もありません。

@コメントの説明/質問

Ok。あなたが理解していることの最初の部分は、2番目の部分に対するあなたの答えです. ACKS を受信したパケットだけが正しく送受信されています。個々のパケットを自分で追跡するための非常に複雑なスキームを考えることができると確信していますが、TCP はこのレイヤーを抽象化して処理することを想定しています。あなたの側では、発生する可能性のある多数の障害に対処する必要があります (Java では、これらのいずれかが発生すると例外が発生します)。例外がなければ、送信しようとしたデータは TCP/IP プロトコルによって保証されて送信されます。

データが「送信」されているように見えるが、例外が発生しない場合に受信が保証されない状況はありますか? 答えはノーであるべきです。

@例

良い例です。これは物事をかなり明確にします。エラーがスローされると思っていたでしょう。投稿された例では、2 回目の書き込みでエラーがスローされますが、最初の書き込みではスローされません。これは興味深い動作です...そして、なぜこのように動作するのかを説明する多くの情報を見つけることができませんでした. ただし、配信を検証するために独自のアプリケーションレベルのプロトコルを開発する必要がある理由を説明しています.

確認のためのプロトコルがなければ、Apple デバイスが通知を受け取る保証はないというのは正しいようです。Apple はまた、最後のメッセージのみをキューに入れます。サービスを少し見てみると、このサービスは顧客にとってより便利であると判断できましたが、サービスを保証するために使用することはできず、他の方法と組み合わせる必要があります. 以下のソースから読みました。

http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/

確かに言えるかどうかについては、答えはノーのようです。Wireshark などのパケット スニファを使用して、送信されたかどうかを確認できる場合がありますが、サービスの性質上、受信してデバイスに送信されたことを保証することはできません。

于 2010-01-10T04:40:02.350 に答える