2

クライアント アプリケーションが閉じられるまで、クライアントからサーバーへの接続が開いたままになるクライアント/サーバー アプリケーションで作業しています。

クライアントがデータを読み取っている間にサーバー アプリケーションが予期せずダウンした場合、クライアントはこれを例外として扱い、例外をキャッチして例外を引数としてイベントを発生させます。

このシステムが機能することをテストする必要があると思われるテストを作成しましたが、テストしているオブジェクトは、ブレークポイントを設定して続行しない限り、ソケットが閉じていることを登録していないようです。

テストの重要な部分は次のようになります。

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サーバーがなくなったことをどのように認識できますか?

これはループバック アドレスで可能ですか?

すべてのコードで申し訳ありませんが、メソッドを削減する方法が思いつきませんでした。

4

1 に答える 1

3

役立つ可能性のある領域について、いくつかの一般的な指針を示すことができます。

通常、ループバック (または単に 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();
       }
}

何らかの理由で反対側がクラッシュした場合、これは私の切断機能を呼び出します。それが役に立てば幸い

于 2010-08-17T11:15:23.590 に答える