2013-07-17 12 views
13

最初に、モチベーションを説明する少しの背景:私は非常に単純なselect()ベースのTCP "ミラープロキシ"に取り組んでいます。これは、2つのファイアウォールクライアントが互いに間接的に会話できるようにします。両方のクライアントがこのサーバーに接続し、両方のクライアントが接続されるとすぐに、クライアントAによってサーバーに送信されたTCPバイトがクライアントBに転送されます。TCPソケットがリモートピアによって読み込まれずに閉じられたことを検出する方法はありますか?

クライアント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)を呼び出します)。

+0

あなたは検討する必要があります[この質問](のhttp://のstackoverflow。com/q/16320039/315052)。 – jxh

答えて

2

に(Bから)のデータを送信しよう必要な私的なソケット状態情報 "である。

私はこれを行うことができないので、プロキシサーバーを再設計して常にすべてのクライアントからデータを読み込み、パートナーがまだ接続していないクライアントから到着したデータをすべて破棄するようにしました。これは、プロキシを通過するTCPストリームが、TCP使用プログラムが期待する信頼性の高い順送りのストリーム状のプロパティをもはや持っていないことを意味するので、最適ではありませんが、私の目的には十分です。

8

あなたはソケットが実際にデータの代わりに閉じているかどうかを確認したい場合は、データが到着した場合や、0やエラーを取得するかどうかを確認するためにrecv()MSG_PEEKフラグを追加することができます。

/* 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つの接続はクライアントとして記述することです。接続が確立されているサーバーが存在しない場合は、クライアントの接続をリセットできます。サーバーがクライアント接続をいくつかのタイムアウトまで待つことができます。サーバーはデータを開始してはならず、接続されたクライアントがなければサーバー接続をリセットすることができます。これにより、デッド接続のデータをバッファリングする問題は解決されます。

+0

Aのソケットを読み込み可能にしてもデータを実際に読み取らない場合、select()は常にすぐに戻り、サーバープロセスはCPUコアの100%を噛んでいますか? –

+0

ええ、 'select'がレベルトリガーされているのを忘れてしまいました。あなたが得たものがすべてFINだったのかどうかを確認するために、最初の読み取りのために読み取り可能なままにしておきたいと思っています。その後、Bがまだ接続されていない場合、このチェックの後で削除することができます。私は通常、Linux上で 'epoll'でエッジトリガーセマンティクスを使用します。 – jxh

+0

私は参照してください。私はrecv(MSG_PEEK)コールがFINを示していないと思っています。しかし、Aソケットから保留中のTCPデータを読み込んだら、元の問題に戻っています。 Aのソケットから読み取ったすべてのデータ(Bを送信しない) –

-1

ソケットが接続されているかどうかは、各ソケットのファイル記述子に書き込むことで確認できます。 writeの戻り値が-1の場合、またはerrno = EPIPEの場合、ソケットが閉じられたことがわかります。
たとえば、

int isSockStillConnected(int *fileDescriptors, int numFDs){ 
    int i,n; 
    for (i=0;i<numFDs;i++){ 
      n = write(fileDescriptors+i,"heartbeat",9); 
      if (n < 0) return -1; 
      if (errno == EPIPE) return -1; 
    } 
    //made it here, must be okay 
    return 0; 
} 
+0

私はこの問題は、プロキシが透過的でなければならないと思っています。ソケットに "ハートビート"のような任意のデータを書き込むと、それを期待していないクライアントを混乱させるでしょう。 –

+0

正しくフォーマットされたデータを確認するためのチェックはありませんか?または、クライアントが処理できる方法でフォーマットしますが、何もしない方法でフォーマットします。クライアント側に追加するのは難しいはずはありません。 – Optox

+0

考えられるのは、(理想的には)TCP経由で特定のホスト名/ポートに接続するプログラムは、プロキシに直接接続するのではなくプロキシに接続していることを知らずにピア。 –

1

私はあなたがそれを見るように問題は表示されません。サーバーに接続してデータを送信して閉じると、メッセージを返す必要はありません。サーバはBが接続するまでデータを読み込みません。サーバがソケットAを読み込んでBにデータを送ります。最初の読み込みはAが送信したデータを返し、2番目の戻り値は0または-1のいずれかの場合にソケットがサーバーがデータの大部分を送信すると仮定しましょう。サーバーの読み取りとバッファーの消費が開始されるまで、Aのsend()メソッドはブロックされます。

私は0,1,2,11,22または-1を返すselect関数を使用します。 ここで、

  • 0 =いずれかのソケットにデータなし(タイムアウト)
  • 1 = Aは、
  • 2 = Bを読み取るデータ
  • 11を読み取るためのデータを有するを有し=ソケットエラー(切断)を有する
  • 22 = Bソケットエラー(切断)
  • -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 
    } 
    
  • 有効ではありませんされています
0

プロキシがプロトコル用の汎用プロキシでなければならない場合は、データを送信するクライアントも処理して、送信後にただちにcloseを呼び出します(一方向データ転送のみ)。

したがって、クライアントAがデータを送信して接続がBに開かれる前に接続を閉じると、データをBに正常に転送するだけで(Bへの接続が開かれたとき)、気にする必要はありません。

このシナリオで特別な処理を実装する必要はありません。 Bへの接続が開かれ、Aからの保留中のすべてのデータが読み込まれた後

  • readリターンがゼロ以下の場合

    あなたのプロキシが閉じられた接続を検出します。または

  • あなたのプログラムでは、私の質問への答えはノー、あなたがへのアクセスを得るためにあなたのTCPスタックを変更して喜んとできるわけではありませんしない限り、」ある表示されますA.
+0

潜在的な問題の場合は、Bがまったく接続しないケースです。 Aの接続は無期限に開かれています。 –

+0

@Jeremy Friesner:ああ、Bもクライアントです。すみません、私の間違いです。私はプロキシがBのクライアントであると仮定しました。 – SKi

1

解決策は、ソケットステータスをポーリングすることでした。

は、Windows 10では、次のコードが動作するように見えた(同等の実装は、他のシステムのために存在するように見える):

WSAPOLLFD polledSocket; 
polledSocket.fd = socketItf; 
polledSocket.events = POLLRDNORM | POLLWRNORM; 

if (WSAPoll(&polledSocket, 1, 0) > 0) 
{ 
    if (polledSocket.revents &= (POLLERR | POLLHUP)) 
    { 
     // socket closed 
     return FALSE; 
    } 
} 
+0

あなたの答えを改善するための貴重なリンクを提供してください。 –

関連する問題