Мгновенно обнаружить отключение клиента от сокета сервера - PullRequest
67 голосов
/ 06 апреля 2009

Как я могу определить, что клиент отключился от моего сервера?

У меня есть следующий код в моем 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);

Вышеуказанные подходы работают, когда вы подключаетесь к серверу и хотите определить, когда сервер отключается, но они не работают , когда вы являетесь сервером и хотите обнаружить отключение клиента.

Любая помощь будет оценена.

Ответы [ 13 ]

96 голосов
/ 06 апреля 2009

Поскольку при отключении розетки нет доступных событий, вы должны будете опрашивать ее с приемлемой для вас частотой.

Используя этот метод расширения, вы можете иметь надежный метод для определения, отключен ли сокет.

static class SocketExtensions
{
  public static bool IsConnected(this Socket socket)
  {
    try
    {
      return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
    }
    catch (SocketException) { return false; }
  }
}
12 голосов
/ 06 апреля 2009

Это просто невозможно. Между вами и сервером нет физического соединения (за исключением крайне редкого случая, когда вы соединяетесь между двумя компьютерами с помощью кабеля обратной связи).

Когда соединение закрыто изящно, другая сторона получает уведомление. Но если соединение разрывается каким-либо другим способом (скажем, соединение пользователя сбрасывается), то сервер не узнает об этом, пока не истечет время ожидания (или не попытается выполнить запись в соединение и тайм-аут подтверждения). Именно так работает TCP, и вы должны с этим жить.

Следовательно, «мгновенно» нереально. Лучшее, что вы можете сделать, это в течение периода ожидания, который зависит от платформы, на которой выполняется код.

EDIT: Если вы ищете только изящные соединения, то почему бы просто не отправить команду «ОТКЛЮЧИТЬ» на сервер с вашего клиента?

11 голосов
/ 27 августа 2015

Кто-то упомянул возможность keepAlive TCP Socket. Здесь это хорошо описано:

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

Я использую его следующим образом: после подключения сокета я вызываю эту функцию, которая устанавливает keepAlive. Параметр keepAliveTime указывает время ожидания в миллисекундах без активности до отправки первого пакета keep-alive. Параметр 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)time).CopyTo(inOptionValues, size);
        BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, size * 2);

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

Я также использую синхронное чтение:

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

А при обратном вызове здесь фиксируется тайм-аут SocketException, который возникает, когда сокет не получает сигнал ACK после пакета keep-alive.

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

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

Таким образом, я могу безопасно обнаружить разрыв между TCP-клиентом и сервером.

6 голосов
/ 07 ноября 2009

«Именно так работает TCP, и вы должны жить с ним».

Да, ты прав. Это факт жизни, который я осознал. Такое же поведение вы увидите даже в профессиональных приложениях, использующих этот протокол (и даже в других). Я даже видел, как это происходит в онлайн-играх; ваш приятель говорит "до свидания", и он, кажется, находится в сети еще 1-2 минуты, пока сервер не "очистит дом".

Вы можете использовать предложенные здесь методы или реализовать «сердцебиение», как также предлагалось. Я выбираю первое. Но если бы я выбрал последнее, я бы просто заставил сервер «пинговать» каждого клиента очень часто одним байтом и посмотреть, есть ли у нас таймаут или нет ответа. Вы можете даже использовать фоновый поток для достижения этого с точным временем. Возможно, даже комбинация может быть реализована в каком-то списке опций (перечислить флаги или что-то в этом роде), если вы действительно беспокоитесь об этом. Но не так уж и сложно иметь небольшую задержку при обновлении сервера, если вы действительно обновляете. Это Интернет, и никто не ожидает, что он будет волшебным! :)

2 голосов
/ 28 июля 2015

Это сработало для меня, ключ в том, что вам нужен отдельный поток для анализа состояния сокета с помощью опроса. делать это в том же потоке, что и сокет, не удается обнаружить.

//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);
    }
}
2 голосов
/ 06 января 2010

Я нашел весьма полезным, еще один обходной путь для этого!

Если вы используете асинхронные методы для чтения данных из сетевого сокета (я имею в виду, используйте BeginReceive - EndReceive методы), всякий раз, когда соединение прерывается; возникает одна из следующих ситуаций: либо сообщение отправляется без данных (вы можете увидеть его с помощью Socket.Available - даже если BeginReceive запущено, его значение будет равно нулю), либо значение 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 голосов
/ 06 апреля 2009

Внедрение пульса в вашу систему может быть решением. Это возможно, только если клиент и сервер находятся под вашим контролем. Вы можете иметь объект DateTime, отслеживающий время, когда последние байты были получены из сокета. И предположим, что сокет, не ответивший в течение определенного интервала, теряется. Это будет работать только в том случае, если у вас реализовано поддержание пульса / пользовательского режима.

0 голосов
/ 26 ноября 2018

У меня была такая же проблема, попробуйте это:

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 голосов
/ 15 ноября 2018

Расширяя комментарии на mbargiel и mycelo для принятого ответа, следующее можно использовать с неблокирующим сокетом на стороне сервера, чтобы сообщить, выключился ли клиент .

При таком подходе не учитывается состояние гонки, которое влияет на метод опроса в принятом ответе.

// 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 методом Shutdown и все доступные данные были получены, метод Receive завершится немедленно и вернет ноль байтов.

Если вы находитесь в неблокирующем режиме, и в буфере стека протоколов нет доступных данных, метод Receive завершится немедленно и выдаст исключение SocketException.

Второй пункт объясняет необходимость в try-catch.

Использование флага SocketFlags.Peek оставляет все полученные данные без изменений для отдельного механизма приема для чтения.

Вышеуказанное будет работать и с блокирующим сокетом , но имейте в виду, что код будет блокироваться при вызове приема (до тех пор, пока не будут получены данные или не истечет время ожидания приема, что снова приведет к SocketException ).

0 голосов
/ 05 января 2011

Используя метод 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

Надеюсь, это поможет! Рамиро Ринальди

...