Wie schreibe ich einen skalierbaren Socket-Server mit C # 4.0?

8

Ich möchte einen einfachen Socket-Server schreiben, aber ich möchte, dass er vertikal skalierbar ist, zum Beispiel kein Thread pro Verbindung oder sehr lange laufende Aufgaben, die alle Threads verbrauchen können.

Der Server empfängt eine Anfrage mit einer Anfrage und streamt ein beliebig großes Ergebnis.

Ich würde den idiomatischen Weg bevorzugen, dies mit Techniken und Bibliotheken zu tun, die in C # 4 verfügbar sind, mit Schwerpunkt auf einfachem Code und nicht auf roher Leistung.

Wiedereröffnung Ein Socket-Server ist ein nützlicher Teil eines skalierbaren Systems. Wenn Sie horizontal skalieren möchten, gibt es verschiedene Techniken. Sie sollten diese Frage wahrscheinlich nicht beantworten können, wenn Sie noch nie einen Socket-Server erstellt haben.

    
Dave Hillier 04.10.2011, 22:10
quelle

1 Antwort

15

Ich habe jetzt schon seit ein oder zwei Wochen an etwas ähnlichem gearbeitet, also hoffe ich, dass ich dir ein bisschen helfen kann.

Wenn Sie sich auf einfachen Code konzentrieren, sollten Sie die Klassen TcpClient und TcpListener verwenden. Beide machen die Arbeit mit Sockeln viel einfacher. Obwohl sie seit .NET Framework 1.1 existieren, wurden sie aktualisiert und sind immer noch die beste Wahl.

Was die Verwendung des .NET Framework 4.0 beim Schreiben von vereinfachten Code betrifft, sind Aufgaben die ersten, die einem einfallen. Sie machen das Schreiben von asynchronem Code viel weniger schmerzhaft und es wird viel einfacher, Ihren Code zu migrieren, sobald C # 5 herauskommt (new async und warten auf Keywords ). Hier ist ein Beispiel, wie Aufgaben Ihren Code vereinfachen können:

Anstatt tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state); zu verwenden und eine Callback-Methode bereitzustellen, die EndAcceptTcpClient(); aufruft und optional Ihr Zustandsobjekt ausgibt, ermöglicht C # 4 die Verwendung von Closures, lambdas und Tasks, um diesen Prozess deutlich lesbarer und skalierbarer zu machen. Hier ist ein Beispiel:

%Vor%

FromAsync ist sehr nützlich, da Microsoft viele zur Verfügung gestellt hat Überlastungen, die häufige asynchrone Operationen vereinfachen können. Hier ist ein anderes Beispiel:

%Vor%

State ist nur eine benutzerdefinierte Klasse, die normalerweise nur die TcpClient-Instanz, die Daten (Byte-Array) und möglicherweise auch die gelesenen Bytes enthält.

Wie Sie sehen, kann ContinueWith verwendet werden, um eine Menge umständlicher try-catches zu ersetzen, die bis jetzt ein notwendiges Übel waren.

Am Anfang Ihres Posts haben Sie erwähnt, dass Sie keinen Thread pro Verbindung erstellen oder sehr lange laufende Aufgaben erstellen möchten, und ich dachte, ich würde das an dieser Stelle ansprechen. Persönlich sehe ich nicht das Problem mit dem Erstellen eines Threads für jede Verbindung.

Worauf Sie jedoch achten müssen, ist die Verwendung von Tasks (eine Abstraktion über den ThreadPool) für lang andauernde Operationen. Der ThreadPool ist nützlich, da der Overhead beim Erstellen eines neuen Threads nicht vernachlässigbar ist und für kurze Aufgaben wie das Lesen oder Schreiben von Daten und das Behandeln einer Clientverbindung Tasks bevorzugt werden.

Sie müssen daran denken, dass der ThreadPool eine gemeinsam genutzte Ressource mit einer speziellen Funktion ist (wodurch der Aufwand vermieden wird, mehr Zeit mit der Erstellung eines Threads zu verbringen, als ihn tatsächlich zu nutzen). Da es geteilt wird, wenn Sie einen Thread verwenden, kann eine andere Ressource nicht und dies kann schnell zu Thread-Pool-Verhungern und Deadlock führen Szenarien.

    
Ryan Peschel 04.10.2011, 22:45
quelle