서버 소켓에서 클라이언트 연결 끊김을 즉시 감지


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와 같은 다른 네트워크 프로토콜은 그렇지 않음).
Noldorin

3
: 내 블로그에서 하트 비트 솔루션에 대한 자세한 반 공개 (손실 된) 연결의 탐지
스티븐 클리어 리

여기에 설명 된 솔루션이 저에게 잘 맞습니다. 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

7
@Smart Alec : 실제로 위에 표시된 예제를 사용해야합니다. 순서를 변경하면 잠재적 인 경쟁 조건이 있습니다. socket.Available0을 반환하고 socket.Poll호출 되기 직전에 패킷을 받으면 소켓이 실제로는 여전히 정상이지만 Polltrue를 반환하고 메서드는를 반환 false합니다.
Groo

4
이것은 99 %의 시간 동안 잘 작동하지만 때로는 잘못된 연결이 끊어집니다.
Matthew Finlay

5
Matthew Finlay가 알아 차린 것처럼, Poll 메서드의 결과와 Available 속성 확인 사이에 여전히 경쟁 조건이 있기 때문에 가끔 잘못된 연결 끊김을보고합니다. 패킷은 거의 읽을 준비 가되었지만 아직은 아닐 수 있으므로 Available은 0이지만 1 밀리 초 후에 읽을 데이터가 있습니다. 더 나은 옵션은 1 바이트와 SocketFlags.Peek 플래그를 수신하는 것입니다. 또는 어떤 형태의 하트 비트를 구현하고 연결 상태를 더 높은 수준으로 유지합니다. 또는 보내기 / 받기 메서드에서 오류 처리에 의존합니다 (비동기 버전을 사용하는 경우 콜백 및 콜백).
mbargiel 2015 년

22

누군가 TCP 소켓의 keepAlive 기능을 언급했습니다. 여기에 잘 설명되어 있습니다.

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);

그리고 콜백에서 여기 timeout을 잡았 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 밀리 초 후에 실패해야합니다. 10 번의 재 시도는 Vista 이후로 하드 코딩 된 것 같습니다. 대부분의 다른 답변과 달리 케이블을 뽑아도 작동합니다. 이것은 받아 들여진 대답이어야합니다.
toster-cx 2016

15

이것은 단순히 불가능합니다. 사용자와 서버간에 물리적 연결이 없습니다 (루프백 케이블로 두 컴퓨터간에 연결하는 매우 드문 경우 제외).

연결이 정상적으로 종료되면 상대방에게 알림이 전송됩니다. 그러나 다른 방법으로 연결이 끊어지면 (예 : 사용자 연결이 끊어진 경우) 서버는 시간이 초과 될 때까지 알 수 없습니다 (또는 연결에 쓰기를 시도하고 ack 시간이 초과 됨). 이것이 바로 TCP가 작동하는 방식이며 TCP와 함께 살아야합니다.

따라서 "즉시"는 비현실적입니다. 최선의 방법은 코드가 실행되는 플랫폼에 따라 제한 시간 내에하는 것입니다.

편집 : 정상적인 연결 만 찾고 있다면 클라이언트에서 서버로 "DISCONNECT"명령을 보내지 않는 이유는 무엇입니까?


1
감사합니다. 그러나 실제로는 물리적 연결 해제가 아니라 우아한 소프트웨어 연결 해제를 의미합니다. Windows를 실행하고 있습니다

1
우아한 연결 끊김만을 찾고 있다면 아무것도 보낼 필요가 없습니다. 그의 읽기는 스트림의 끝을 반환합니다.
user207421

6

"그것이 TCP가 작동하는 방식이며 TCP와 함께 살아야합니다."

네, 맞아요. 내가 깨닫게 된 삶의 사실입니다. 이 프로토콜 (및 기타)을 사용하는 전문 응용 프로그램에서도 동일한 동작이 나타납니다. 나는 심지어 온라인 게임에서 발생하는 것을 보았다. 친구가 "안녕"이라고 말하고 서버가 "집을 청소"할 때까지 1 ~ 2 분 더 온라인 상태 인 것처럼 보입니다.

여기에서 제안 된 방법을 사용하거나 제안 된대로 "하트 비트"를 구현할 수 있습니다. 나는 전자를 선택합니다. 그러나 후자를 선택했다면 서버가 각 클라이언트를 매번 단일 바이트로 "핑"하고 타임 아웃이 있는지 또는 응답이 없는지 확인합니다. 정확한 타이밍으로이를 달성하기 위해 백그라운드 스레드를 사용할 수도 있습니다. 정말 걱정된다면 옵션 목록 (열거 형 플래그 등)에서 조합을 구현할 수도 있습니다. 그러나 업데이트하는 한 서버 업데이트에 약간의 지연이있는 것은 그리 큰 문제가 아닙니다. 그것은 인터넷이고 아무도 그것이 마법이 될 것이라고 기대하지 않습니다! :)


3

시스템에 하트 비트를 구현하는 것이 해결책이 될 수 있습니다. 이것은 클라이언트와 서버가 모두 귀하의 통제하에있는 경우에만 가능합니다. 소켓에서 마지막 바이트가 수신 된 시간을 추적하는 DateTime 객체를 가질 수 있습니다. 그리고 특정 간격 동안 응답하지 않은 소켓이 손실되었다고 가정합니다. 이것은 하트 비트 / 사용자 정의 연결 유지가 구현 된 경우에만 작동합니다.


2

나는 매우 유용하고 그것에 대한 또 다른 해결 방법을 찾았습니다!

연결이 종료 될 때마다 네트워크 소켓에서 데이터를 읽기 위해 비동기 메서드를 사용하는 경우 (즉, BeginReceive- EndReceive메서드 사용 ) 다음 상황 중 하나가 나타납니다. 메시지가 데이터없이 전송 되거나 (트리거 된 Socket.Available경우에도 BeginReceive값이 0이 됨) Socket.Connected이 호출에서 값이 거짓이됩니다 ( 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의 주석을 확장 하면 다음을 서버 끝의 비 차단 소켓과 함께 사용하여 클라이언트가 종료되었는지 여부를 알 수 있습니다.

이 접근 방식은 수락 된 답변의 Poll 방법에 영향을 미치는 경쟁 조건을 겪지 않습니다.

// 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원격 끝이 소켓을 종료하고 우리가 모든 데이터를 읽은 직후에 메서드가 0을 반환 한다는 사실을 기반으로 합니다. 에서 Socket.Receive 문서 :

원격 호스트가 Shutdown 메서드를 사용하여 Socket 연결을 종료하고 사용 가능한 모든 데이터를 수신 한 경우 Receive 메서드는 즉시 완료되고 0 바이트를 반환합니다.

비 차단 모드에 있고 프로토콜 스택 버퍼에 사용 가능한 데이터가 없으면 Receive 메서드가 즉시 완료되고 SocketException이 발생합니다.

두 번째 요점은 try-catch의 필요성을 설명합니다.

SocketFlags.Peek플래그를 사용 하면 별도의 수신 메커니즘이 읽을 수 있도록 수신 된 데이터가 그대로 유지됩니다.

위 의 코드는 차단 소켓에서도 작동 하지만 코드는 Receive 호출에서 차단됩니다 (데이터가 수신되거나 수신 시간 초과가 경과 할 때까지 다시 SocketException).


0

Select를 사용할 수 없습니까?

연결된 소켓에서 선택을 사용하십시오. 선택이 소켓을 준비로 반환하지만 후속 수신이 0 바이트를 반환하면 클라이언트가 연결을 끊었 음을 의미합니다. AFAIK는 클라이언트 연결이 끊어 졌는지 확인하는 가장 빠른 방법입니다.

C #을 모르기 때문에 내 솔루션이 C #에 맞지 않거나 (C #은 선택을 제공함 ) 컨텍스트를 오해 한 경우 무시합니다 .


0

SetSocketOption 메소드를 사용하면 소켓 연결이 끊어 질 때마다 알려주는 KeepAlive를 설정할 수 있습니다.

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-04-01

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-06-24

그것은 임의의 연결 끊김을 감지하지 못합니다. 여전히 I / O를 수행해야합니다.
user207421 2013-04-01

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.