17

まず、動機を説明するための背景を少し説明します。私は、ファイアウォールで保護された 2 つのクライアントが間接的に相互に通信できるようにする、非常に単純な select() ベースの TCP「ミラー プロキシ」に取り組んでいます。両方のクライアントがこのサーバーに接続し、両方のクライアントが接続されるとすぐに、クライアント A からサーバーに送信された TCP バイトはすべてクライアント B に転送され、その逆も同様です。

クライアント A がサーバーに接続し、クライアント B が接続する前にデータの送信を開始した場合、サーバーにはデータを置く場所がありません。大量の RAM を使用してしまう可能性があるため、RAM にバッファリングしたくありません。クライアントBが必要とする可能性があるため、データをドロップしたくありません。したがって、クライアント B も接続されるまで、クライアント A のソケットで select()-for-read-ready を実行しないという 3 番目のオプションを選択します。そうすれば、クライアント A はすべての準備が整うまでブロックするだけです。

それも多かれ少なかれ機能しますが、クライアント A のソケットで読み取り可能を選択しないことの副作用は、クライアント A がサーバーへの TCP 接続を閉じることを決定した場合、サーバーはその事実について通知されないことです - - 少なくとも、クライアント B がやって来て、サーバーが最終的にクライアント A のソケットで読み取り準備を選択し、保留中のデータを読み取り、ソケットクローズ通知 (つまり、recv() が 0 を返す) を取得するまでは。

クライアントAがTCP接続を閉じたとき、サーバーが(タイムリーに)知る方法があればいいのですが。これを知る方法はありますか?この場合、ポーリングは受け入れられます (たとえば、select() を 1 分に 1 回起動し、そのような関数が存在する場合は、すべてのソケットで IsSocketStillConnected(sock) を呼び出すことができます)。

4

6 に答える 6

10

データではなくソケットが実際に閉じられているかどうかを確認したい場合は、MSG_PEEKフラグを追加しrecv()て、データが到着したかどうか、0またはエラーが発生したかどうかを確認できます。

/* handle readable on A */
if (B_is_not_connected) {
    char c;
    ssize_t x = recv(A_sock, &c, 1, MSG_PEEK);
    if (x > 0) {
        /* ...have data, leave it in socket buffer until B connects */
    } else if (x == 0) {
        /* ...handle FIN from A */
    } else {
        /* ...handle errors */
    }
}

A がいくつかのデータを送信した後に閉じたとしても、プロキシはおそらく B に FIN を転送する前にまずそのデータを B に転送することを望んでいるため、すべてのデータを読み取った後よりも早く A が接続で FIN を送信したことを知っても意味がありません。送信しました。

TCP 接続は、両側が FIN を送信するまで閉じられたとは見なされません。ただし、A がそのエンドポイントを強制的にシャットダウンした場合は、データを送信しようとしてから を受信するまで、そのことはわかりませんEPIPE( を抑制したと仮定しますSIGPIPE)。


ミラー プロキシ アプリケーションをもう少し読んだ後、これはファイアウォール トラバーサル アプリケーションであるため、これらのピアが実際に相互に通信できることを確認できる小さな制御プロトコルが実際に必要なようです。制御プロトコルがあれば、多くの解決策を利用できますが、私が推奨するのは、接続の 1 つを自分自身をサーバーとして記述し、もう 1 つの接続を自分自身をクライアントとして記述することです。次に、接続を取得するサーバーが存在しない場合は、クライアントの接続をリセットできます。サーバーがクライアント接続を待機するまで、ある程度のタイムアウトまで待機させることができます。サーバーはデータを開始してはなりません。クライアントが接続されていない場合は、サーバー接続をリセットできます。これにより、切断された接続のためにデータをバッファリングする問題が解消されます。

于 2013-07-17T16:42:54.083 に答える
1

あなたが見ているように、私は問題を見ていません。A がサーバーに接続し、データを送信して閉じたとします。メッセージを返す必要はありません。サーバーは、サーバーがソケット A を読み取り、データを B に送信すると、B が接続するまでデータを読み取りません。最初の読み取りでは、A が送信したデータが返され、2 回目の読み取りでは、ソケットがクローズ、サーバークローズ B. A が大量のデータを送信するとします。A の send() メソッドは、サーバーが読み取りを開始してバッファを消費するまでブロックされます。

0、1、2、11、22、または -1 を返す select を含む関数を使用します。

  • 0=どちらのソケットにもデータがありません (タイムアウト)
  • 1=A には読み取るデータがあります
  • 2=B には読み取るデータがあります
  • 11=ソケットにエラーがあります (切断されました)
  • 22=B ソケットにエラーがあります (切断)
  • -1: 1 つまたは両方のソケットが無効です

    int WhichSocket(int sd1, int sd2, int seconds, int microsecs) { 
       fd_set sfds, efds;
       struct timeval timeout={0, 0};
       int bigger;
       int ret;
    
    
       FD_ZERO(&sfds);
       FD_SET(sd1, &sfds);
       FD_SET(sd2, &sfds);
       FD_SET(sd1, &efds);
       FD_SET(sd2, &efds);
       timeout.tv_sec=seconds;
       timeout.tv_usec=microsecs;
       if (sd1 > sd2) bigger=sd1;
       else bigger=sd2;
       // bigger is necessary to be Berkeley compatible, Microsoft ignore this param.
       ret = select(bigger+1, &sfds, NULL, &efds, &timeout);
       if (ret > 0) {
           if (FD_ISSET(sd1, &sfds)) return(1); // sd1 has data
           if (FD_ISSET(sd2, &sfds)) return(2); // sd2 has data
           if (FD_ISSET(sd1, &efds)) return(11);    // sd1 has an error
           if (FD_ISSET(sd2, &efds)) return(22);    // sd2 has an error
       }
       else if (ret < 0) return -1; // one of the socket is not valid
       return(0); // timeout
    }
    
于 2013-07-17T17:52:30.133 に答える