2016-05-16 5 views
1

私はTCPの上にネットワーク層を作成しています。私はUnitTest段階でいくつかの問題に直面しています。すべてのTcpClientは注文を処理します

private const int SERVER_PORT = 15000; 
private const int CLIENT_PORT = 16000; 
private const string LOCALHOST = "127.0.0.1"; 

private TcpClient Client { get; set; } 
private TcpListener ServerListener { get; set; } 
private TcpClient Server { get; set; } 

[TestInitialize] 
public void MyTestInitialize() 
{ 
    this.ServerListener = new TcpListener(new IPEndPoint(IPAddress.Parse(LOCALHOST), SERVER_PORT)); 
    this.Client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT)); 

    this.ServerListener.Start(); 
} 

// In this method, I just try to connect to the server 
[TestMethod] 
public void TestConnect1() 
{ 
    var connectionRequest = this.ServerListener.AcceptTcpClientAsync(); 

    this.Client.Connect(LOCALHOST, SERVER_PORT); 

    connectionRequest.Wait(); 

    this.Server = connectionRequest.Result; 
} 

// In this method, I assume there is an applicative error within the client and it is disposed 
[TestMethod] 
public void TestConnect2() 
{ 
    var connectionRequest = this.ServerListener.AcceptTcpClientAsync(); 

    this.Client.Connect(LOCALHOST, SERVER_PORT); 

    connectionRequest.Wait(); 

    this.Server = connectionRequest.Result; 

    this.Client.Dispose(); 
} 

[TestCleanup] 
public void MyTestCleanup() 
{ 
    this.ServerListener?.Stop(); 
    this.Server?.Dispose(); 
    this.Client?.Dispose(); 
} 

まず:ここ

は、私は(私のライブラリが複数のクラスで構成されているが、私は唯一のポストのサイズを制限するために、あなたに私の問題を引き起こしてネイティブ命令を示して)やっているものです、があります同じエンドポイントから同じポートのサーバに先に接続したい場合は、先にサーバを破棄してください。

このようなテストを実行すると、初めて正常に実行されます。 2回目のテストでは、ポートがすでに使用中であると主張して、Connectメソッドで例外がスローされます。

この例外を回避し(同じエンドポイントから同じリスナーに接続できるようにするために)、唯一の方法は、サーバー内でSocketExceptionを引き起こすことです。 、問題はありません。例外は2回目の送信時にのみスローされます)。

私も、私は例外を引き起こす場合...

はなぜServer.Dispose()が接続を閉じるとポートを解放されていないServerを廃棄する必要はありません?例外を引き起こすよりもポートを解放する方がいいですか?

ありがとうございます。

は(私の英語のため申し訳ありませんが、私はネイティブスピーカーではないよ)


ここでチェックアウトより簡単にするために、メインfonction内の例です:

private const int SERVER_PORT = 15000; 
private const int CLIENT_PORT = 16000; 
private const string LOCALHOST = "127.0.0.1"; 

static void Main(string[] args) 
{ 
    var serverListener = new TcpListener(new IPEndPoint(IPAddress.Parse(LOCALHOST), SERVER_PORT)); 
    var client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT)); 

    serverListener.Start(); 

    var connectionRequest = client.ConnectAsync(LOCALHOST, SERVER_PORT); 

    var server = serverListener.AcceptTcpClient(); 

    connectionRequest.Wait(); 

    // Oops, something wrong append (wrong password for exemple), the client has to be disposed (I really want this behavior) 
    client.Dispose(); 

    // Uncomment this to see the magic happens 
    //try 
    //{ 
     //server.Client.Send(Encoding.ASCII.GetBytes("no problem")); 
     //server.Client.Send(Encoding.ASCII.GetBytes("oops looks like the client is disconnected")); 
    //} 
    //catch (Exception) 
    //{ } 

    // Lets try again, with a new password for example (as I said, I really want to close the connection in the first place, and I need to keep the same client EndPoint !) 
    client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT)); 

    connectionRequest = client.ConnectAsync(LOCALHOST, SERVER_PORT); 

    // If the previous try/catch is commented, you will stay stuck here, 
    // because the ConnectAsync has thrown an exception that will be raised only during the Wait() instruction 
    server = serverListener.AcceptTcpClient(); 

    connectionRequest.Wait(); 

    Console.WriteLine("press a key"); 
    Console.ReadKey(); 
} 

あなたがする必要があるかもしれませんバグを引き起こし、プログラムが接続を拒否した場合、Visual Studioを再起動してください(またはしばらくお待ちください)。

+1

.NETのテストを行っているようです'TcpClient'と' TcpListener'です。フレームワークではなく、独自のコードをテストする必要があります。 –

+0

私が言ったように、私はTcpの上にネットワーク層を書いています。しかし、私は悩みの原因となる指示だけをあなたに示します。私はカプセル化されたコードの中に、あなたを溢れさせないためにそれらを見せません。より明示的に私の紹介を編集します! – fharreau

答えて

1

ポートは既に使用中ですです。 netstatを実行し、を参照してください。 TIME_WAIT州にまだポートが開いています。

ソケットを正常に閉じていないため、リモートエンドポイントがさらにデータを送信する場合には、ネットワークレイヤーである必要があります。そうでなければ、ソケットは何かのための偽のデータを受け取って、データストリームを破壊する可能性があります。

これを修正する正しい方法は、接続を正常に閉じることです(Socket.Shutdown()メソッドを使用するなど)。リモートエンドポイントのクラッシュに関するテストを含める場合は、そのシナリオを正しく処理する必要があります。 1つは、実際にクラッシュする可能性のある独立したリモートプロセスを設定する必要があります。別の場合は、適切な時間が経過するまでポートを再度使用しないように(つまり、ポートが実際に閉じて、TIME_WAITになっていない)、状況を正しく処理する必要があります。

この点については、発見した回避策を実際に使用することを検討することをお勧めします。TIME_WAITは、リモートエンドポイントのステータスが不明なシナリオを含みます。データを送信すると、ネットワーク層は失敗した接続を検出して、先にソケットクリーンアップを実行できます。追加の洞察力のために

、例えば以下を参照してください
Port Stuck in Time_Wait
Reconnect to the server
How can I forcibly close a TcpListener
How do I prevent Socket/Port Exhaustion?

(しかしSO_REUSEADDR/SocketOptionName.ReuseAddress&hellipを使用することが答えの中から見つかった勧告を使用していない。しても、そのすべてがあります問題を隠して、実際のコードでデータが破損する可能性があります)。

+0

'Dispose()'を呼んでも、ソケットが正常に閉じるわけではありませんか?私は.NETフレームワークT_Tに対する私の信念を失っています。 TcpSocket.Dispose()の前に 'TcpSocket.Client.Shutdown()'を呼び出すことはどちらも助けにはなりません。私のポートはまだTIME_WAITです... – fharreau

+0

いいえ...なぜでしょうか?グレースフルシャットダウンには、両方のエンドポイントの協力が必要です。 'Dispose()'は片面操作です。 _ "TcpSocket.Client.Shutdown()を呼び出しても助けにならない" _ - それであなたはそれを間違ってやっています。両者は 'Shutdown()'を使う必要があります。開始側は '... Send'という値で呼び出します。応答側は「両方...」を使用します。各側はデータを送信した後にのみメソッドを呼び出します。応答側は、ソケットのデータストリームの終わりに達すると(すなわち、受信動作がゼロバイトの長さで完了する)「...両方」を呼び出す。 –

+0

私はあなたの解決策を試しました。これは、サーバ(TcpListenerによってインスタンス化されたTcpClient)がシャットダウンを開始した場合にのみ機能します。サーバー上で 'Dispose'を呼び出すと、最初に動作します。 – fharreau

関連する問題