web-dev-qa-db-ja.com

Linux Socket:クライアントプログラムで切断されたネットワークを検出する方法

CベースのLinuxソケットプログラムをデバッグしています。ウェブサイトで利用可能なすべての例として、私は次の構造を適用しました:

sockfd= socket(AF_INET, SOCK_STREAM, 0);

connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));

send_bytes = send(sockfd, sock_buff, (size_t)buff_bytes, MSG_DONTWAIT);

削除サーバーがサーバープログラムを閉じるときに切断を検出できます。しかし、イーサネットケーブルを取り外しても、送信関数は-1ではなく正の値を返します。

サーバー側を変更できないと想定して、クライアントプログラムのネットワーク接続を確認するにはどうすればよいですか?

14
user2052197

しかし、イーサネットケーブルを取り外しても、送信関数は-1ではなく正の値を返します。

まず最初に知っておくべきことは、sendは実際には何も送信せず、単なるメモリコピー関数/システムコールであることです。プロセスからカーネルにデータをコピーします-後でカーネルはそのデータをフェッチし、セグメントとパケットにパッケージ化した後、反対側に送信します。したがって、sendは次の場合にのみエラーを返すことができます。

  • ソケットが無効です(たとえば、偽のファイル記述子)
  • 接続が明らかに無効である。たとえば、確立されていないか、何らかの方法ですでに終了している(FIN​​、RST、タイムアウト-下記を参照)
  • データをコピーする余地はもうありません

重要な点は、sendは何も送信しないため、その戻りコードは実際に反対側に到達するデータについて何も通知しないです。

質問に戻りますが、TCPがデータを送信するとき、妥当な時間内に有効な確認応答を期待しています。取得できない場合は、再送信します。どのくらいの頻度で再送信しますか?各TCPスタックは動作が異なりますが、通常は指数バックオフを使用します。つまり、最初に1秒間待機し、次に2秒間待機し、次に4秒間待機します。一部のスタックでは、このプロセスに数分かかる場合があります。

重要な点は、中断が発生した場合、TCPは接続が停止したと宣言することです非常に長い沈黙時間の後のみ(Linuxでは、15回の再試行のようなものを実行します-以上5分)。

これを解決する1つの方法は、アプリケーションに確認応答メカニズムを実装することです。たとえば、サーバーにリクエストを送信することができます "5秒以内に返信するか、この接続を宣言します"そしてrecvタイムアウト付き。

37
cnicutar

リモート切断を検出するには、read()を実行します

詳細については、このスレッドを確認してください:

接続されたソケットのread()関数は0バイトを返すことができますか?

2
Forhad Ahmed

未接続のイーサネットケーブルは、write()関数を呼び出すだけでは検出できません。これは、意識せずにtcpスタックによってtcp再送信が行われるためです。ここに解決策があります。

アプリケーションソケットにキープアライブオプションをすでに設定している場合でも、アプリがソケットに書き込み続けている場合、ソケットのデッド接続状態を時間内に検出できません。これは、カーネルtcpスタックによるtcp再送信が原因です。 tcp_retries1およびtcp_retries2は、tcp再送信タイムアウトを構成するためのカーネルパラメータです。 RTTメカニズムによって計算されるため、再送信タイムアウトの正確な時間を予測することは困難です。この計算はrfc793で確認できます。 (3.7。データ通信)

https://www.rfc-editor.org/rfc/rfc793.txt

各プラットフォームには、tcp再送信用のカーネル構成があります。

Linux : tcp_retries1, tcp_retries2 : (exist in /proc/sys/net/ipv4)

http://linux.die.net/man/7/tcp

HPUX : tcp_ip_notify_interval, tcp_ip_abort_interval

http://www.hpuxtips.es/?q=node/5

AIX : rto_low, rto_high, rto_length, rto_limit

http://www-903.ibm.com/kr/event/download/200804_324_swma/socket.pdf

死んだ接続を早期に検出したい場合は、tcp_retries2の値を低く設定する必要があります(デフォルトは15)。さらに、現在、これらの値を単一のソケットにのみ設定することはできません。これらはグローバルカーネルパラメータです。単一のソケット( http://patchwork.ozlabs.org/patch/55236/ )にtcp再送信ソケットオプションを適用するいくつかの試みがありましたが、カーネルのメインラインに適用されたとは思いません。システムヘッダーファイルでこれらのオプション定義が見つかりません。

参考までに、以下のように 'netstat --timers'を使用してキープアライブソケットオプションを監視できます。 https://stackoverflow.com/questions/34914278

netstat -c --timer | grep "192.0.0.1:43245             192.0.68.1:49742"

tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (1.92/0/0)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (0.71/0/0)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (9.46/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (8.30/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (7.14/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (5.98/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (4.82/0/1)

また、キープアライブタイムアウトが発生した場合、使用しているプラ​​ットフォームに応じてさまざまなリターンイベントに対応できるため、リターンイベントだけでデッド接続の状態を判断しないでください。たとえば、キープアライブタイムアウトが発生すると、HPはPOLLERRイベントを返し、AIXはPOLLINイベントのみを返します。そのとき、recv()呼び出しでETIMEDOUTエラーに遭遇します。

最近のカーネルバージョン(2.6.37以降)では、TCP_USER_TIMEOUTオプションを使用できます。このオプションは、単一ソケットに使用できます。

最後に、MSG_PEEKフラグを指定した読み取り関数を使用して、ソケットに問題がないことを確認できます。 (MSG_PEEKは、データがカーネルスタックバッファーに到着した場合にのみピークし、データをユーザーバッファーにコピーすることはありません。)したがって、このフラグを使用して、ソケットをチェックしても問題はありません。副作用はありません。

2
cloudrain21

戻り値を確認し、それがこの値と等しいかどうかを確認します。

EPIPE
このソケットは接続されましたが、接続が切断されました。この場合、sendは最初にSIGPIPEシグナルを生成します。そのシグナルが無視またはブロックされた場合、またはそのハンドラーが戻った場合、送信はEPIPEで失敗します。

また、ハンドラーにSIGPIPEシグナルのチェックを追加して、より制御しやすくします。

1
Ramy Al Zuhouri