it-swarm.com.de

Richtiger Weg, um TcpListener zu stoppen

Derzeit verwende ich TcpListener, um eingehende Verbindungen zu adressieren, von denen jede einen Thread für die Kommunikation erhält und diese einzelne Verbindung dann herunterfährt. Code sieht wie folgt aus:

TcpListener listener = new TcpListener(IPAddress.Any, Port);
System.Console.WriteLine("Server Initialized, listening for incoming connections");
listener.Start();
while (listen)
{
     // Step 0: Client connection
     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

Die Variable listen ist ein boolescher Wert, der ein Feld in der Klasse ist. Wenn das Programm jetzt heruntergefahren wird, möchte ich, dass es nicht mehr nach Clients sucht. Durch das Festlegen von listen auf false wird verhindert, dass mehr Verbindungen hergestellt werden. Da AcceptTcpClient jedoch ein blockierender Aufruf ist, werden mindestens der nächste Client und THEN-Exit angenommen. Gibt es eine Möglichkeit, es zu zwingen, einfach auszubrechen und sofort dort anzuhalten? Wie wirkt sich der Aufruf von listener.Stop () aus, während der andere blockierende Aufruf ausgeführt wird?

56
Christian P.

Es gibt 2 Vorschläge, die ich unter Berücksichtigung des Codes machen würde, und ich gehe davon aus, dass es sich um Ihr Design handelt. Ich möchte jedoch zunächst darauf hinweisen, dass Sie bei der Arbeit mit E/A-ähnlichen Netzwerken oder Dateisystemen wirklich nicht blockierende E/A-Rückrufe verwenden sollten. Es ist weit WEIT effizienter und Ihre Anwendung wird viel besser funktionieren, obwohl sie schwerer zu programmieren sind. Ich werde am Ende kurz auf eine vorgeschlagene Designänderung eingehen.

  1. Verwenden Sie Using () {} für TcpClient
  2. Thread.Abort ()
  3. TcpListener.Pending ()
  4. Asynchrones Umschreiben

Verwenden Sie Using () {} für TcpClient

*** Beachten Sie, dass Sie Ihren TcpClient-Aufruf wirklich in einen using () {} -Block einschließen sollten, um sicherzustellen, dass die Methoden TcpClient.Dispose () oder TcpClient.Close () auch im Falle einer Ausnahme aufgerufen werden. Alternativ können Sie dies in den finally-Block eines try {} finally {} -Blocks einfügen.

Thread.Abort ()

Ich sehe, Sie könnten zwei Dinge tun. 1 ist, dass, wenn Sie diesen TcpListener-Thread von einem anderen aus gestartet haben, Sie einfach die Thread.Abort-Instanzmethode für den Thread aufrufen können, wodurch eine Threadabort-Ausnahme innerhalb des Blockierungsaufrufs ausgelöst wird und der Stapel aufgerollt wird.

TcpListener.Pending ()

Die zweite kostengünstige Lösung wäre die Verwendung der Methode listener.Pending (), um ein Abfragemodell zu implementieren. Sie würden dann einen Thread.Sleep verwenden, um zu "warten", bevor Sie feststellen, ob eine neue Verbindung ansteht. Sobald Sie eine ausstehende Verbindung haben, würden Sie AcceptTcpClient aufrufen, wodurch die ausstehende Verbindung freigegeben würde. Der Code würde ungefähr so ​​aussehen.

while (listen){
     // Step 0: Client connection
     if (!listener.Pending())
     {
          Thread.Sleep(500); // choose a number (in milliseconds) that makes sense
          continue; // skip to next iteration of loop
     }

     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

Asynchrones Umschreiben

Schließlich würde ich empfehlen, dass Sie wirklich zu einer nicht blockierenden Methodik für Ihre Anwendung übergehen. Unter den Deckblättern verwendet das Framework überlappende E/A- und E/A-Abschluss-Ports, um nicht blockierende E/A-Vorgänge für Ihre asynchronen Aufrufe zu implementieren. Es ist auch nicht besonders schwierig, man muss nur ein wenig anders über den Code nachdenken.

Grundsätzlich würden Sie Ihren Code mit der BeginAcceptTcpClient-Methode starten und das zurückgegebene IAsyncResult nachverfolgen. Sie weisen darauf hin, dass bei einer Methode, deren Aufgabe es ist, den TcpClient abzurufen und weiterzuleiten, NOT auf einen neuen Thread, sondern auf einen Thread aus dem ThreadPool.QueueUserWorkerItem, damit Sie nicht einen hochfahren und schließen neuer Thread für jede Client-Anforderung (Beachten Sie, dass Sie möglicherweise Ihren eigenen Thread-Pool verwenden müssen, wenn Sie besonders langlebige Anforderungen haben, weil der Thread-Pool gemeinsam genutzt wird und wenn Sie alle Threads monopolisieren, die anderen Teile Ihrer Anwendung, die vom System implementiert werden, möglicherweise ausgehungert sind.) . Sobald die Listener-Methode Ihren neuen TcpClient auf eine eigene ThreadPool-Anforderung gestartet hat, ruft sie BeginAcceptTcpClient erneut auf und verweist den Delegaten auf sich selbst zurück.

Tatsächlich teilen Sie Ihre aktuelle Methode in drei verschiedene Methoden auf, die dann von den verschiedenen Teilen aufgerufen werden. 1. um bootstrap alles zu tun, 2. um EndAcceptTcpClient aufzurufen, starten Sie den TcpClient zu seinem eigenen Thread und rufen Sie ihn dann erneut auf, 3. um die Client-Anfrage zu verarbeiten und zu schließen, wenn Sie fertig sind.

57
Peter Oehlert

listener.Server.Close() aus einem anderen Thread unterbricht den blockierenden Aufruf.

A blocking operation was interrupted by a call to WSACancelBlockingCall
47
zproxy

Sockets bieten leistungsstarke asynchrone Funktionen. Werfen Sie einen Blick auf Verwenden eines asynchronen Serversockels

Hier sind einige Hinweise zum Code.

Die Verwendung von manuell erstellten Threads kann in diesem Fall ein Aufwand sein. 

Der folgende Code unterliegt den Race-Bedingungen - TcpClient.Close () schließt den Netzwerk-Stream, den Sie über TcpClient.GetStream () erhalten. Ziehen Sie in Betracht, den Client zu schließen, wo Sie definitiv sagen können, dass er nicht mehr benötigt wird.

 clientThread.Start(client.GetStream());
 client.Close();

TcpClient.Stop () schließt das darunterliegende Socket. TcpCliet.AcceptTcpClient () verwendet die Socket.Accept () -Methode für den zugrunde liegenden Socket, die SocketException auslöst, sobald sie geschlossen wird. Sie können es von einem anderen Thread aus aufrufen.

Trotzdem empfehle ich asynchrone Sockets.

3
Dzmitry Huba

Siehe meine Antwort hier https://stackoverflow.com/a/17816763/2548170TcpListener.Pending() ist keine gute Lösung

2
Andriy Vandych

Verwenden Sie keine Schleife. Rufen Sie stattdessen BeginAcceptTcpClient () ohne eine Schleife auf. Geben Sie im Rückruf einfach einen weiteren Aufruf an BeginAcceptTcpClient () ab, wenn Ihr Listenzeichen noch gesetzt ist.

Um den Listener anzuhalten, kann Ihr Code, da Sie nicht gesperrt sind, einfach Close () aufrufen.

2
Mike Scott

Ich bin mir ziemlich sicher, dass Thread.Abort nicht funktionieren wird, weil der Aufruf im Stack der Betriebssystemebene TCP blockiert ist. 

Außerdem ... Wenn Sie BeginAcceptTCPClient im Callback aufrufen, um auf jede Verbindung zu warten, achten Sie jedoch darauf, dass der Thread, der den ersten BeginAccept-Befehl ausgeführt hat, nicht beendet wird. Andernfalls wird der Listener automatisch vom Framework verworfen. Ich vermute, das ist eine Funktion, aber in der Praxis nervt es sehr. In Desktop-Apps ist dies normalerweise kein Problem, aber im Internet möchten Sie möglicherweise den Thread-Pool verwenden, da diese Threads nicht wirklich beendet werden.

1
Eric Nicholson

Wie bereits erwähnt, verwenden Sie stattdessen BeginAcceptTcpClient. Die asynchrone Verwaltung ist viel einfacher.

Hier ist ein Beispielcode: 

        ServerSocket = new TcpListener(endpoint);
        try
        {
            ServerSocket.Start();
            ServerSocket.BeginAcceptTcpClient(OnClientConnect, null);
            ServerStarted = true;

            Console.WriteLine("Server has successfully started.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Server was unable to start : {ex.Message}");
            return false;
        }
0
Peter Suwara