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

클라이언트와 내 서버의 연결이 끊어 졌음을 어떻게 감지 할 수 있습니까?

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

위의 접근 방식은 서버에 연결 중이고 서버 연결이 끊어지는시기를 감지하려고 할 때 작동하지만 서버 인 경우에는 작동하지 않고 클라이언트 연결 끊김을 감지하려고 할 때 작동 합니다.

어떤 도움을 주시면 감사하겠습니다.



답변

소켓 연결이 끊어졌을 때 신호를 보낼 수있는 이벤트가 없기 때문에 허용 가능한 주파수로 폴링해야합니다.

이 확장 방법을 사용하면 소켓 연결이 끊어 졌는지 감지하는 신뢰할 수있는 방법을 가질 수 있습니다.

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


답변

누군가 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 클라이언트와 서버 간의 연결 끊김을 안전하게 감지 할 수 있습니다.


답변

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

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

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

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


답변

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

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

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


답변

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


답변

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

연결이 종료 될 때마다 네트워크 소켓에서 데이터를 읽기 위해 비동기 메서드를 사용하는 경우 (즉, BeginReceiveEndReceive메서드 사용 ) 다음 상황 중 하나가 나타납니다. 메시지가 데이터없이 전송 되거나 (트리거 된 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.
}


답변

이것은 나를 위해 일했습니다. 핵심은 폴링으로 소켓 상태를 분석하기 위해 별도의 스레드가 필요하다는 것입니다. 소켓과 동일한 스레드에서 수행하면 감지에 실패합니다.

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