サーバー(192.168.1.5:3001)は、Linux 3.2を実行しており、一度に1つの接続のみを受け入れるように設計されています。クライアント(192.168.1.18)は、Windows7を実行しています。接続はワイヤレス接続です。どちらのプログラムもC++で記述されています。
これは、10回の接続/切断サイクルで9回うまく機能します。10番目の(ランダムに発生する)接続では、サーバーが接続を受け入れ、後で実際に書き込みを行うと(通常、30秒以上後)、Wireshark(スクリーンショットを参照)によると、古い古い接続に書き込みを行っているように見えます。クライアントが(しばらく前に)FINしたポート番号を使用しますが、サーバーはまだFINしていません。そのため、クライアントとサーバーの接続が同期していないように見えます。クライアントは新しい接続を確立し、サーバーは前の接続への書き込みを試みます。この壊れた状態になると、後続のすべての接続試行は失敗します。壊れた状態は、最大ワイヤレス範囲を30分超えることで開始できます(10の場合の9の前のように、これは機能しますが、壊れた状態が発生する場合があります)。
スクリーンショットの赤い矢印は、サーバーがデータの送信を開始したとき(Len!= 0)を示しています。これは、クライアントがデータを拒否し、サーバーにRSTを送信した時点です。右端にある色付きの点は、使用されているクライアントポート番号ごとに1つの色を示しています。その色の残りのドットがあった後、1つまたは2つのドットがどのように表示されるかに注意してください(そして時間列に注意してください)。
サーバープロセスを強制終了して再起動すると、(次に発生するまで)自動的に解決されるため、問題はサーバー側にあるように見えます。
うまくいけば、コードはあまりにも異常ではありません。listen()のキューサイズパラメータを0に設定しました。これは、現在の接続を1つだけ許可し、保留中の接続を許可しないことを意味すると思います(代わりに、1を試しましたが、問題はまだありました)。コード内で「//error」が表示されているトレースプリントとして表示されるエラーはありません。
// Server code
mySocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (mySocket == -1)
{
// error
}
// Set non-blocking
const int saveFlags = ::fcntl(mySocket, F_GETFL, 0);
::fcntl(mySocket, F_SETFL, saveFlags | O_NONBLOCK);
// Bind to port
// Union to work around pointer aliasing issues.
union SocketAddress
{
sockaddr myBase;
sockaddr_in myIn4;
};
SocketAddress address;
::memset(reinterpret_cast<Tbyte*>(&address), 0, sizeof(address));
address.myIn4.sin_family = AF_INET;
address.myIn4.sin_port = htons(Port);
address.myIn4.sin_addr.s_addr = INADDR_ANY;
if (::bind(mySocket, &address.myBase, sizeof(address)) != 0)
{
// error
}
if (::listen(mySocket, 0) != 0)
{
// error
}
// main loop
{
...
// Wait for a connection.
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(mySocket, &readSet);
const int aResult = ::select(getdtablesize(), &readSet, NULL, NULL, NULL);
if (aResult != 1)
{
continue;
}
// A connection is definitely waiting.
const int fileDescriptor = ::accept(mySocket, NULL, NULL);
if (fileDescriptor == -1)
{
// error
}
// Set non-blocking
const int saveFlags = ::fcntl(fileDescriptor, F_GETFL, 0);
::fcntl(fileDescriptor, F_SETFL, saveFlags | O_NONBLOCK);
...
// Do other things for 30+ seconds.
...
const int bytesWritten = ::write(fileDescriptor, buffer, bufferSize);
if (bytesWritten < 0)
{
// THIS FAILS!! (but succeeds the first ~9 times)
}
// Finished with the connection.
::shutdown(fileDescriptor, SHUT_RDWR);
while (::close(fileDescriptor) == -1)
{
switch(errno)
{
case EINTR:
// Break from the switch statement. Continue in the loop.
break;
case EIO:
case EBADF:
default:
// error
return;
}
}
}
したがって、accept()呼び出し(SYNパケットが送信される時点であると想定)とwrite()呼び出しの間のどこかで、クライアントのポートが以前に使用されたクライアントポートに変更されます。
したがって、問題は、サーバーが接続を受け入れて(したがって、ファイル記述子を開き)、以前の(現在は古くて死んでいる)接続/ファイル記述子を介してデータを送信するのはどうしてですか?欠落しているシステムコールに何らかのオプションが必要ですか?