Подтвердить что ты не робот

Мгновенно обнаруживать отключение клиента от серверного сокета

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

У меня есть следующий код в моем методе AcceptCallBack

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

Мне нужно найти способ как можно скорее обнаружить, что клиент отключился от handler Socket.

Я пробовал:

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Когда соединение закрыто грациозно, другая сторона уведомляется. Но если соединение отключено каким-либо другим способом (скажем, соединение с пользователями отключено), то сервер не будет знать, пока он не истечет (или пытается записать в соединение и время ожидания ack). Это то, как работает TCP, и вам нужно жить с ним.

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

EDIT: Если вы ищете только изящные соединения, то почему бы просто не отправить команду "DISCONNECT" на сервер с вашего клиента?

Ответ 3

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

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

Я использую его так: после подключения сокета я вызываю эту функцию, которая устанавливает keepAlive. Параметр keepAliveTime указывает время ожидания в миллисекундах без активности до тех пор, пока не будет отправлен первый пакет keep-alive. Параметр keepAliveInterval указывает интервал в миллисекундах между тем, когда отправляются последовательные пакеты keep-alive, если подтверждение не получено.

    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-сигнал после сохранения содержимого.

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

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

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

Ответ 4

"Это то, как работает TCP, и вам нужно жить с ним".

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

//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 still running
                        socket.Close();
                        socket = null;    
                    } else {
                        //socket still connected
                    }    
               }
           } catch {
               socket.Close();
            } finally {
                nextCheckTime = DateTime.Now.AddSeconds(5);
            }
        }
        Thread.Sleep(1000);
    }
}

Ответ 8

Разве вы не можете использовать Select?

Используйте выбор в подключенном сокете. Если выбор возвращается с вашим сокетом Ready, а последующий Receive возвращает 0 байт, что означает, что клиент отключил соединение. AFAIK, это самый быстрый способ определить, отключен ли клиент.

Я не знаю С#, поэтому просто игнорирую, если мое решение не вписывается в С# (С# предоставляет select хотя), или если Я неправильно понял контекст.

Ответ 9

Пример кода здесь http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx показывает, как определить, подключен ли Socket, не отправляя никаких данных.

Если вы вызывали Socket.BeginReceive() в серверной программе, а затем клиент закрывал соединение "изящно", ваш обратный вызов будет вызван, а EndReceive() вернет 0 байт. Эти 0 байтов означают, что клиент "может" отключиться. Затем вы можете использовать технику, показанную в примере кода MSDN, чтобы точно определить, было ли соединение закрыто.

Ответ 10

Используя метод SetSocketOption, вы сможете установить KeepAlive, который сообщит вам, когда Socket будет отключен

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

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

Ответ 11

Расширяя комментарии 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 время ожидания приема, что снова приведет к возникновению SocketException).

Ответ 12

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

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
        }
    }
}

Ответ 13

Вы также можете проверить свойство .IsConnected сокета, если вы должны были опросить.