2

異なるスレッドから同時に安全であるべきだと読みましたが、私のプログラムには奇妙な動作があり、何が問題なのかわかりません。

クライアントソケットと通信する同時スレッドがあります

  1. ソケットへの送信を行うもの
  2. 同じソケットから select を実行してから recv を実行するもの

私がまだ送信しているので、クライアントはすでにデータを受信して​​ソケットを閉じています。同時に、そのソケットで select と recv を実行していますが、(閉じているため) 0 を返すため、このソケットを閉じます。ただし、送信はまだ返されていません...そして、このソケットで close を呼び出しているため、send 呼び出しは EBADF で失敗します。

ソケットを閉じた後にデータを出力したので、クライアントがデータを正しく受信したことがわかり、正しいです。ただし、私の側では、send 呼び出しがまだエラー (EBADF) を返しているので、失敗しないように修正したいと考えています。

これは常に起こるわけではありません。おそらく40%の確率で発生します。私はどこでもスリープを使用しません。送信や受信などの間に一時停止する必要がありますか?

ここにいくつかのコードがあります:

送信:

while(true)
{
    // keep sending until send returns 0
    n = send(_sfd, bytesPtr, sentSize, 0);

    if (n == 0)
    {
        break;
    }
    else if(n<0)
    {
        cerr << "ERROR: send returned an error "<<errno<< endl; // this case is triggered
        return n;
    }

    sentSize -= n;
    bytesPtr += n;
}

受信:

 while(true)
{
    memset(bufferPointer,0,sizeLeft);
    n = recv(_sfd,bufferPointer,sizeLeft, 0);
    if (debug) cerr << "Receiving..."<<sizeLeft<<endl;
    if(n == 0)
    {
        cerr << "Connection closed"<<endl; // this case is triggered
        return n;
    }
    else if (n < 0)
    {
        cerr << "ERROR reading from socket"<<endl;
        return n;
    }
     bufferPointer += n;
     sizeLeft -= n;
     if(sizeLeft <= 0) break;

}

クライアントでは、同じ受信コードを使用してから、ソケットで close() を呼び出します。次に、私の側では、受信呼び出しから 0 を取得し、ソケットで close() も呼び出します。その後、送信が失敗します。まだ終わらない!? しかし、私のクライアントはすでにデータを取得しています!

4

2 に答える 2

5

この問題を頻繁に目にすることに驚いていることを認めなければなりませんが、スレッドを扱っている場合は常に可能性があります。呼び出すsend()と、カーネルに入ってそこにあるソケット バッファにデータを追加することになるため、おそらくシステム内の別のプロセスへのコンテキスト スイッチが発生する可能性が非常に高くなります。その間、カーネルはおそらくパケットを非常に迅速にバッファリングして送信しました。ローカル ネットワークでテストしていると思われるため、相手側はデータを受信して​​接続を閉じ、適切な FIN を非常に迅速に送信します。ローカル イーサネット ネットワークの待機時間は非常に短いため、送信側のマシンがまだ他のスレッドまたはプロセスを実行している間に、これがすべて発生する可能性があります。

FIN が到着しました。受信スレッドは、入力を待っているため、最近あまり処理を行っていません。そのため、多くのスケジューリング システムでは優先度がかなり高くなり、次に実行される可能性が高くなります (たとえば、使用している OS を指定しませんが、これは少なくとも Linux で発生する可能性があります)。このスレッドは、読み取りがゼロであるため、ソケットを閉じます。この直後のある時点で、送信スレッドが再び起動されますが、おそらくカーネルは、ソケットがブロックされた状態から戻る前に閉じられていることに気づきsend()EBADF.

現在、これは正確な原因に関する憶測にすぎません。特に、プラットフォームに大きく依存しています。しかし、これがどのように起こるかを見ることができます。

最も簡単な解決策は、おそらくpoll()送信スレッドでも使用することですが、ソケットが読み取り可能ではなく書き込み可能になるまで待ちます。明らかに、送信するバッファリングされたデータが存在するまで待機する必要があります。その方法は、どのスレッドがデータをバッファリングするかによって異なります。このpoll()呼び出しにより、 でフラグを立てることにより、接続が閉じられたことを検出できます。POLLHUPこれは、 を試す前に検出できますsend()

原則として、送信バッファが完全にフラッシュされたことを確認するまで、ソケットを閉じてはなりません。これは、send()呼び出しが返され、残りのデータがすべて送信されたことを示して初めて確認できます。私はこれまで、ゼロ読み取りを取得したときに送信バッファをチェックすることでこれを処理し、空でない場合は「終了」フラグを設定しました。あなたの場合、送信スレッドはこれをヒントとして使用して、すべてがフラッシュされると終了します。これは重要です。なぜなら、リモート エンドがハーフ クローズを行うとshutdown()、まだ読み取り中であっても読み取りがゼロになるからです。ただし、ハーフ クローズは気にしないかもしれませんが、その場合は上記の戦略で問題ありません。

最後に、私は個人的には、スレッドの送受信の煩わしさを回避し、両方を実行する単一のスレッドを使用するだけです。これは多かれ少なかれ and のポイントでselect()ありpoll()、単一の実行スレッドが実行を心配することなく 1 つ以上のファイルハンドルを処理できるようにします。他の接続をブロックして枯渇させる操作。

于 2013-03-13T00:43:31.743 に答える
4

問題が見つかりました。それは私のループです。無限ループであることに注意してください。送信するものが残っていない場合、sentSize は 0 ですが、ループしてさらに送信しようとします。この時点で、他のスレッドはすでにこのスレッドを閉じているため、0 バイトの送信呼び出しはエラーで返されます。

sentSize が 0 のときにループを停止するようにループを変更して修正し、問題を修正しました。

于 2013-03-13T01:29:05.233 に答える