サーバーソケットからのクライアントの切断を即座に検出


82

クライアントがサーバーから切断されたことをどのように検出できますか?

私のAcceptCallBackメソッドには次のコードがあります

static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
  //Accept incoming connection
  Socket listener = (Socket)ar.AsyncState;
  handler = listener.EndAccept(ar);
}

クライアントがhandlerソケットから切断されたことをできるだけ早く発見する方法を見つける必要があります。

私はもう試した:

  1. handler.Available;
  2. handler.Send(new byte[1], 0, SocketFlags.None);
  3. handler.Receive(new byte[1], 0, SocketFlags.None);

上記のアプローチは、サーバーに接続していてサーバーの切断を検出したい場合は機能しますが、サーバーでありクライアントの切断を検出したい場合は機能しません

どんな助けでもありがたいです。


10
@Samuel:TCPと接続タグ、TCPが接続を維持するという点で、この投稿に非常に関連しています(UDPなどの他のネットワークプロトコルは維持しません)。
ノルドール2009

3
私のブログからのハートビートソリューションの詳細:ハーフオープン(ドロップ)接続の検出
Stephen Cleary 2010

ここで説明するソリューションは、私のためによく働く:stackoverflow.com/questions/1387459/...
Rawk

回答:


110

ソケットが切断されたときに通知できるイベントがないため、許容できる頻度でソケットをポーリングする必要があります。

この拡張方法を使用すると、ソケットが切断されているかどうかを検出するための信頼できる方法を使用できます。

static class SocketExtensions
{
  public static bool IsConnected(this Socket socket)
  {
    try
    {
      return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
    }
    catch (SocketException) { return false; }
  }
}

1
これはうまくいきました。ありがとう。メソッドを変更して!(socket.Available == 0 && socket.Poll(1、SelectMode.SelectRead));を返すようにしました。socket.AvailableはSocket.Poll()よりも高速であると思われるため

28
この方法は、接続のもう一方の端が実際にソケットを閉じたりシャットダウンしたりしない限り機能しません。タイムアウト期間が経過するまで、ネットワーク/電源ケーブルが抜かれていることに気付くことはありません。切断をすぐに通知する唯一の方法は、接続を継続的にチェックするハートビート機能を使用することです。
Kasper Holdum 2009

7
@Smart Alec:実際には、上記の例を使用する必要があります。順序を変更すると、競合状態が発生する可能性がありsocket.Availableます。ソケットが実際にはまだ正常であるにもかかわらず、0を返しsocket.Poll、呼び出される直前にパケットを受信すると、Polltrueが返されfalse、メソッドが返されます。
Groo 2010年

4
これは99%の確率でうまく機能しますが、誤って切断されることもあります。
マシューフィンレイ2013年

5
Matthew Finlayが気付い​​たように、Pollメソッドの結果とAvailableプロパティのチェックの間に競合状態がまだあるため、これは誤った切断を報告することがあります。パケットはほとんど読み取る準備ができている可能性がありますが、まだ読み取られていないため、Availableは0ですが、1ミリ秒後に、読み取るデータがあります。より良いオプションは、1バイトとSocketFlags.Peekフラグを受信しようとすることです。または、何らかの形式のハートビートを実装して、接続ステータスをより高いレベルに維持します。または、送信/受信メソッド(および非同期バージョンを使用している場合はコールバック)でのエラー処理に依存します。
mbargiel 2015年

22

誰かがTCPソケットのキープアライブ機能について言及しました。ここでそれはうまく説明されています:

http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html

私はそれをこのように使用しています:ソケットが接続された後、私はこの関数を呼び出して、keepAliveをオンに設定します。このkeepAliveTimeパラメーターは、最初のキープアライブパケットが送信されるまでアクティビティがないタイムアウトをミリ秒単位で指定します。このkeepAliveIntervalパラメーターは、確認応答が受信されない場合に、連続するキープアライブパケットが送信される間隔をミリ秒単位で指定します。

    void SetKeepAlive(bool on, uint keepAliveTime, uint keepAliveInterval)
    {
        int size = Marshal.SizeOf(new uint());

        var inOptionValues = new byte[size * 3];

        BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
        BitConverter.GetBytes((uint)keepAliveTime).CopyTo(inOptionValues, size);
        BitConverter.GetBytes((uint)keepAliveInterval).CopyTo(inOptionValues, size * 2);

        socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
    }

非同期読み取りも使用しています:

socket.BeginReceive(packet.dataBuffer, 0, 128,
                    SocketFlags.None, new AsyncCallback(OnDataReceived), packet);

そして、コールバックでは、ここでキャッチタイムアウトSocketExceptionが発生します。これは、キープアライブパケットの後にソケットがACK信号を取得しない場合に発生します。

public void OnDataReceived(IAsyncResult asyn)
{
    try
    {
        SocketPacket theSockId = (SocketPacket)asyn.AsyncState;

        int iRx = socket.EndReceive(asyn);
    }
    catch (SocketException ex)
    {
        SocketExceptionCaught(ex);
    }
}

このようにして、TCPクライアントとサーバー間の切断を安全に検出できます。


6
はい、keepalive + intervalを低い値に設定します。すべてのポーリング/送信はkeepalive + 10 * intervalミリ秒後に失敗するはずです。Vista以降、10回の再試行はハードコーディングされているようです。他のほとんどの回答とは異なり、ケーブルを抜いても機能します。これは受け入れられた答えでなければなりません。
toster-cx 2016

15

これは単に不可能です。ユーザーとサーバーの間に物理的な接続はありません(ループバックケーブルを使用して2つのコンピューター間を接続している非常にまれなケースを除く)。

接続が正常に閉じられると、反対側に通知されます。ただし、接続が他の方法で切断された場合(たとえば、ユーザー接続が切断された場合)、サーバーはタイムアウトするまで(または、接続に書き込もうとしてackがタイムアウトするまで)わかりません。これがTCPの動作方法であり、TCPを使用する必要があります。

したがって、「即座に」は非現実的です。最善の方法は、コードが実行されているプラ​​ットフォームに応じて、タイムアウト期間内にすることです。

編集:正常な接続のみを探している場合は、クライアントからサーバーに「DISCONNECT」コマンドを送信してみませんか?


1
おかげで、私は実際には物理的な切断ではなく、優雅なソフトウェアの切断を意味します。私はWindowsを実行しています

1
彼が優雅な切断を探しているだけなら、彼は何も送る必要はありません。彼の読み取りはストリームの終わりを返します。
user207421 2017

6

「これがTCPの仕組みであり、TCPを使用する必要があります。」

うん、その通りです。それは私が気付いた人生の事実です。このプロトコル(および他のプロトコル)を使用するプロのアプリケーションでも同じ動作が見られます。私はそれがオンラインゲームで発生するのを見たことさえあります。あなたは相棒が「さようなら」と言い、サーバーが「家を掃除する」まで、彼はさらに1〜2分間オンラインになっているように見えます。

ここで提案されている方法を使用するか、提案されているように「ハートビート」を実装することもできます。前者を選びます。しかし、後者を選択した場合は、サーバーが各クライアントに1バイトで頻繁に「ping」を実行し、タイムアウトがあるか応答がないかを確認します。バックグラウンドスレッドを使用して、正確なタイミングでこれを実現することもできます。本当に心配しているのであれば、組み合わせでさえ、ある種のオプションリスト(列挙型フラグなど)に実装できます。ただし、更新する限り、サーバーの更新を少し遅らせることはそれほど大きな問題ではありません。それはインターネットであり、誰もそれが魔法であるとは期待していません!:)


3

システムにハートビートを実装することが解決策になる場合があります。これは、クライアントとサーバーの両方が管理下にある場合にのみ可能です。最後のバイトがソケットから受信された時刻を追跡するDateTimeオブジェクトを持つことができます。そして、特定の間隔で応答しなかったソケットが失われたと仮定します。これは、ハートビート/カスタムキープアライブが実装されている場合にのみ機能します。


2

私は非常に便利で、そのための別の回避策を見つけました!

ネットワークソケットからデータを読み取るために非同期メソッドを使用する場合(つまり、BeginReceive-EndReceive メソッドを使用する場合)、接続が終了するたびに。次のいずれかの状況が発生します。メッセージがデータなしで送信されるか(トリガーされSocket.AvailableてもBeginReceive、値はゼロになります)Socket.Connected、この呼び出しで値がfalseになります(その場合は使用しないでくださいEndReceive)。

私が使用した関数を投稿しています。私がそれから何を意味していたかがよくわかると思います。


private void OnRecieve(IAsyncResult parameter) 
{
    Socket sock = (Socket)parameter.AsyncState;
    if(!sock.Connected || sock.Available == 0)
    {
        // Connection is terminated, either by force or willingly
        return;
    }

    sock.EndReceive(parameter);
    sock.BeginReceive(..., ... , ... , ..., new AsyncCallback(OnRecieve), sock);

    // To handle further commands sent by client.
    // "..." zones might change in your code.
}

2

これは私にとってはうまくいきました。重要なのは、ポーリングでソケットの状態を分析するために別のスレッドが必要なことです。ソケットと同じスレッドでそれを行うと、検出に失敗します。

//open or receive a server socket - TODO your code here
socket = new Socket(....);

//enable the keep alive so we can detect closure
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

//create a thread that checks every 5 seconds if the socket is still connected. TODO add your thread starting code
void MonitorSocketsForClosureWorker() {
    DateTime nextCheckTime = DateTime.Now.AddSeconds(5);

    while (!exitSystem) {
        if (nextCheckTime < DateTime.Now) {
            try {
                if (socket!=null) {
                    if(socket.Poll(5000, SelectMode.SelectRead) && socket.Available == 0) {
                        //socket not connected, close it if it's still running
                        socket.Close();
                        socket = null;    
                    } else {
                        //socket still connected
                    }    
               }
           } catch {
               socket.Close();
            } finally {
                nextCheckTime = DateTime.Now.AddSeconds(5);
            }
        }
        Thread.Sleep(1000);
    }
}

1

http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspxのサンプルコード は、データを送信せずにソケットがまだ接続されているかどうかを判断する方法を示しています。

サーバープログラムでSocket.BeginReceive()を呼び出した後、クライアントが接続を「正常に」閉じた場合、受信コールバックが呼び出され、EndReceive()は0バイトを返します。これらの0バイトは、クライアントが切断された可能性があることを意味します。次に、MSDNサンプルコードに示されている手法を使用して、接続が閉じられたかどうかを確認できます。


1

受け入れられた回答に対するmbargielmyceloのコメントを拡張すると、サーバー側の非ブロッキングソケットで以下を使用して、クライアントがシャットダウンしたかどうかを通知できます。

このアプローチは、受け入れられた回答の投票方法に影響を与える競合状態に影響されません。

// Determines whether the remote end has called Shutdown
public bool HasRemoteEndShutDown
{
    get
    {
        try
        {
            int bytesRead = socket.Receive(new byte[1], SocketFlags.Peek);

            if (bytesRead == 0)
                return true;
        }
        catch
        {
            // For a non-blocking socket, a SocketException with 
            // code 10035 (WSAEWOULDBLOCK) indicates no data available.
        }

        return false;
    }
}

このアプローチはSocket.Receive、リモートエンドがソケットをシャットダウンした直後にメソッドがゼロを返し、そこからすべてのデータを読み取ったという事実に基づいています。Socket.Receiveドキュメントから:

リモートホストがShutdownメソッドを使用してSocket接続をシャットダウンし、使用可能なすべてのデータが受信された場合、Receiveメソッドはすぐに完了し、ゼロバイトを返します。

非ブロッキングモードで、プロトコルスタックバッファに使用可能なデータがない場合、Receiveメソッドはすぐに完了し、SocketExceptionをスローします。

2番目のポイントは、try-catchの必要性を説明しています。

SocketFlags.Peekフラグを使用すると、受信したデータはそのままになり、別の受信メカニズムで読み取ることができます。

上記はブロッキングソケットでも機能しますが、コードは受信呼び出しでブロックされることに注意してください(データが受信されるか、受信タイムアウトが経過するまで、再び結果になりますSocketException)。


0

Selectだけでは使えませんか?

接続されたソケットでselectを使用します。selectがソケットをReadyとして返したが、後続のReceiveが0バイトを返した場合、クライアントが接続を切断したことを意味します。AFAIK、これはクライアントが切断されたかどうかを判断するための最速の方法です。

私はC#を知らないので、私のソリューションがC#に適合しない場合(C#は選択を提供ます)、またはコンテキストを誤解した場合は無視してください。


0

SetSocketOptionメソッドを使用すると、ソケットが切断されたときに通知するキープアライブを設定できます。

Socket _connectedSocket = this._sSocketEscucha.EndAccept(asyn);
                _connectedSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);

http://msdn.microsoft.com/en-us/library/1011kecd(v=VS.90).aspx

それが役に立てば幸い!ラミロリナルディ


2
「ソケットが切断されるたびに通知する」ことはありません。キープアライブタイマーが期限切れになると、最終的にはそれを検出します。デフォルトでは2時間に設定されています。それを「いつでも」と表現することはできません。また、それを「通知」として説明することもできません。障害を検出するには、読み取りまたは書き込みを行う必要があります。-1
user207421 2013

0

私は同じ問題を抱えていました、これを試してください:

void client_handler(Socket client) // set 'KeepAlive' true
{
    while (true)
    {
        try
        {
            if (client.Connected)
            {

            }
            else
            { // client disconnected
                break;
            }
        }
        catch (Exception)
        {
            client.Poll(4000, SelectMode.SelectRead);// try to get state
        }
    }
}

0

これはVBにありますが、私にとってはうまくいくようです。前の投稿のように0バイトのリターンを探します。

Private Sub RecData(ByVal AR As IAsyncResult)
    Dim Socket As Socket = AR.AsyncState

    If Socket.Connected = False And Socket.Available = False Then
        Debug.Print("Detected Disconnected Socket - " + Socket.RemoteEndPoint.ToString)
        Exit Sub
    End If
    Dim BytesRead As Int32 = Socket.EndReceive(AR)
    If BytesRead = 0 Then
        Debug.Print("Detected Disconnected Socket - Bytes Read = 0 - " + Socket.RemoteEndPoint.ToString)
        UpdateText("Client " + Socket.RemoteEndPoint.ToString + " has disconnected from Server.")
        Socket.Close()
        Exit Sub
    End If
    Dim msg As String = System.Text.ASCIIEncoding.ASCII.GetString(ByteData)
    Erase ByteData
    ReDim ByteData(1024)
    ClientSocket.BeginReceive(ByteData, 0, ByteData.Length, SocketFlags.None, New AsyncCallback(AddressOf RecData), ClientSocket)
    UpdateText(msg)
End Sub

-1

ポーリングする場合は、ソケットの.IsConnectedプロパティを確認することもできます。


2
これは、上記で示したシナリオ(サーバー/クライアント)のいずれでも機能しません

1
また、プロパティは.Connectedと呼ばれます。
qwertie 2011年

それは任意の切断を検出しません。あなたはまだいくつかのI / Oをしなければなりません。
user207421 2013

@ジェイいいえ、そうではありません。プロパティの値は、Connected最新の操作の時点での接続の状態を反映しています。ポーリングはありません。
user207421 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.