2016-05-16 9 views
1

Ich schreibe eine Netzwerkschicht über TCP und ich habe einige Probleme während meiner UnitTest-Phase. HierTcpClient entsorgen

ist, was ich tue (Meine Bibliothek besteht aus mehreren Klassen bestehen aber zeige ich Ihnen nur die nativen Befehle meine Probleme zu verursachen, um die Größe der Post zu begrenzen):

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(); 
} 

Zu allererst , HAVE TO Entsorgen Sie den Server zuerst, wenn ich eine frühere Verbindung vom gleichen Endpunkt zum selben Server herstellen möchte:

Wenn Sie meine Tests so ausführen, wird es beim ersten Mal erfolgreich ausgeführt. Beim zweiten Mal wird in beiden Tests eine Ausnahme auf die Methode Connect ausgelöst, da der Port bereits verwendet wird. Die einzige Möglichkeit, diese Ausnahme zu vermeiden (und eine Verbindung auf demselben Listener vom selben Endpunkt aus herstellen zu können) besteht darin, eine SocketException innerhalb des Servers durch zweimaliges Senden von Bytes an den entsorgten Client zu provozieren (beim ersten Senden) es gibt kein Problem, die Ausnahme wird nur beim zweiten Senden ausgelöst).

Ich brauche nicht einmal die Server zu entsorgen, wenn ich eine Ausnahme provozieren ...

Warum ist die Server.Dispose() schließt nicht die Verbindung und den Anschluss zu befreien ??? Gibt es eine bessere Möglichkeit, den Hafen zu befreien, als eine Ausnahme zu provozieren?

Vielen Dank im Voraus.

(Sorry für mein Englisch, ich bin kein Muttersprachler)


Hier ist ein Beispiel innerhalb eines Haupt fonction, um Kasse leichter:

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(); 
} 

Sie müssen möglicherweise zu Starten Sie Visual Studio neu (oder warten Sie einige Zeit), wenn Sie den Fehler auslösen und das Programm sich weigert, eine Verbindung herzustellen.

+1

Sie scheinen .NET zu testen 'TcpClient' und' TcpListener'. Sie sollten Ihren eigenen Code testen, nicht das Framework. –

+0

Wie gesagt, ich schreibe eine Netzwerkschicht über Tcp. Aber ich zeige dir nur die Anweisungen, die mir Probleme bereiten. Ich zeige sie dir nicht in meinem ganzen eingekapselten Code, um dich nicht zu überfluten. Ich werde meine Einführung bearbeiten, um expliziter zu sein! – fharreau

Antwort

1

Ihr Port ist bereits in Verwendung. Führen Sie netstat und sehen Sie. Im TIME_WAIT-Zustand werden immer noch Ports geöffnet.

Da Sie die Sockets nicht ordnungsgemäß geschlossen haben, muss die Netzwerkschicht diese Ports offen lassen, falls der Remote-Endpunkt mehr Daten sendet. Wenn es anders wäre, könnten die Sockets falsche Daten empfangen, die für etwas anderes bestimmt sind und den Datenstrom beschädigen.

Der richtige Weg, um dies zu beheben, besteht darin, die Verbindungen ordnungsgemäß zu schließen (d. H. Die Methode Socket.Shutdown() verwenden). Wenn Sie einen Test einbeziehen möchten, bei dem der Remote-Endpunkt abstürzt, müssen Sie auch dieses Szenario korrekt behandeln. Zum einen sollten Sie einen unabhängigen Remote-Prozess einrichten, den Sie tatsächlich abstürzen können. Zum anderen sollte Ihr Server die Situation korrekt berücksichtigen, indem er nicht versucht, den Port erneut zu verwenden, bis eine angemessene Zeit verstrichen ist (d. H. Der Port ist tatsächlich geschlossen und befindet sich nicht mehr in TIME_WAIT).

Zu diesem letzten Punkt möchten Sie möglicherweise die Verwendung des Work-arounds, den Sie entdeckt haben, in Erwägung ziehen: TIME_WAIT umfasst das Szenario, in dem der Status des Remote-Endpunkts unbekannt ist.Wenn Sie Daten senden, kann die Netzwerkschicht die fehlgeschlagene Verbindung erkennen und die Socket-Bereinigung früher durchführen.

Weitere Einsichten, siehe zB:
Port Stuck in Time_Wait
Reconnect to the server
How can I forcibly close a TcpListener
How do I prevent Socket/Port Exhaustion?

(aber nicht die Empfehlung unter den Antworten verwenden Sie SO_REUSEADDR/SocketOptionName.ReuseAddress & hellip zu bedienen, alles, was tut, ist Verbergen Sie das Problem, und Sie können beschädigte Daten in realem Code erhalten.)

+0

Aufruf 'Dispose()' schließt nicht anmutig den Sockel? Ich verliere mein Vertrauen in das .NET Framework T_T. Witze beiseite, der Aufruf von 'TcpSocket.Client.Shutdown()' vor 'TcpSocket.Dispose()' hilft auch nicht. Mein Port ist immer noch in TIME_WAIT ... – fharreau

+0

Nein ... warum sollte es? Ein ordnungsgemäßes Herunterfahren erfordert die Zusammenarbeit beider Endpunkte. 'Dispose()' ist eine einseitige Operation. _ "TcpSocket.Client.Shutdown() aufrufen ... hilft nicht" _ - dann machst du es falsch. Beide Seiten müssen 'Shutdown()' verwenden: die initiierende Seite ruft sie mit dem Wert '... Send' auf; die antwortende Seite wird "... Beide" verwenden. Jede Seite ruft die Methode erst auf, nachdem sie Daten gesendet hat. die antwortende Seite wird "... Both" nur aufrufen, wenn sie das Ende des Datenstroms des Sockets erreicht hat (d. h. eine Empfangsoperation wird mit einer Länge von null Bytes abgeschlossen). –

+0

Ich habe Ihre Lösung versucht. Es funktioniert nur, wenn der Server (der vom TcpListener instanziierte TcpClient) das Herunterfahren initiiert. Der erste Aufruf von 'Dispose' auf dem Server funktioniert ebenfalls. – fharreau