Сокет не регистрируется, когда другой конец закрыт - PullRequest
2 голосов
/ 17 августа 2010

Я работаю над клиент-серверным приложением, в котором соединения от клиента к серверу остаются открытыми до тех пор, пока клиентское приложение не будет закрыто.

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

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

Важная часть теста выглядит следующим образом:

StreamingMonitor sm = new StreamingMonitor();
bool errored = false;
string msg = "";
sm.ErrorOccurred += (s, a) =>
{
    errored = true;
    msg = a.Exception.Message;
};
sm.Enabled = true;
client = listener.AcceptTcpClient();
client.GetStream().Write(BitConverter.GetBytes(10000), 0, 4);
client.Close();
while(!errored)
{}
Assert.AreEqual("A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call", msg);

Объект TcpListener listener прослушивает адрес обратной связи.

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

При получении длины сообщения вызывается этот метод.

    private void GotMessageLength(IAsyncResult asyncResult)
    {
        try
        {
            client.Client.EndReceive(asyncResult);
            if(firstMessage)
            {
                firstMessage = false;
                if (Connected != null)
                {
                    Connected(this, new EventArgs());
                }
            }
            int msgLen = BitConverter.ToInt32(messageLength, 0);
            byte[] message = new byte[msgLen];
            List<byte> lbMessage = new List<byte>();
            int bytesReturned = client.Client.Receive(message);
            int remaining = (msgLen < bytesReturned) ? bytesReturned - msgLen : msgLen - bytesReturned;
            if(remaining > 0)
            {
                if (bytesReturned > 0)
                {
                    for (int i = 0; i < bytesReturned; i++)
                    {
                        lbMessage.Add(message[i]);
                    }
                }
                while(remaining > 0)
                {
                    if(!client.Connected)
                    {
                        throw new SocketException((int)SocketError.Shutdown);
                    }
                    bytesReturned = client.Client.Receive(message);
                    remaining = (remaining < bytesReturned) ? bytesReturned - remaining : remaining - bytesReturned;
                    if (bytesReturned > 0)
                    {
                        for (int i = 0; i < bytesReturned; i++)
                        {
                            lbMessage.Add(message[i]);
                        }
                    }
                }
                message = lbMessage.ToArray();
            }
            MessageReceived(this, new MessageReceivedEventArgs(message));
            if (Enabled)
            {
                client.Client.BeginReceive(messageLength, 0, 4, SocketFlags.None, GotMessageLength, null);
            }
        }
        catch (SocketException ex)
        {
            if(ErrorOccurred != null)
            {
                ErrorOccurred(this, new ErrorEventArgs(ex));
            }
        }
        catch (ObjectDisposedException)
        {

        }
    }

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

Однако модульный тест попадает в бесконечный цикл, ожидая возникновения ошибки, поскольку сокет в StreamingMonitor никогда не осознает, чтодругой конец закрыт.

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

Возможно ли это по адресу обратной связи?

Извините за всекод, я не мог думать, как сократить метод.

1 Ответ

3 голосов
/ 17 августа 2010

Я могу дать некоторые общие указания на область, которая может помочь.

Loopback (или просто использование localhost) в целом не работает так же, как реальная сеть. Сценарии, например, сколько данных отправляется / принимается при каждом вызове сокета API. Так что всегда проверяйте или реально подключайтесь к сети.

Сокет api будет обнаруживать, только если другая сторона отключена при попытке отправить его (я думаю, что это правильно). Так что какая-то функциональность сердцебиения пригодится =)

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

protected void ReceiveCallback(IAsyncResult ar)
{
     var so = (StateObject)ar.AsyncState;
     if (!so.Socket.Connected) return;

     try
     {
         int read = so.Socket.EndReceive(ar);
         if (read > 0)
         ProcessBuffer(so, so.Buffer, read);

         so.Socket.BeginReceive(so.Buffer, 0, so.Buffer.Length, SocketFlags.None, ReceiveCallback, so);
      }
      catch (SocketException e)
      {
          Trace.WriteLine("[Networking]::NetBase.ReceiveCallback: SocketException");
          Output.WriteLine(e.Message);
          RaiseDisconnected();
      }
      catch (ObjectDisposedException e)
      {
          Trace.WriteLine("[Networking]::NetBase.ReceiveCallback: ObjectDisposedException");
          Output.WriteLine(e.Message);
          RaiseDisconnected();
       }
}

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

...