Ich bin in der Entwurfsphase des Schreibens einer neuen Windows-Dienstanwendung, die TCP / IP-Verbindungen für lang laufende Verbindungen akzeptiert (dh dies ist nicht wie HTTP, wo es viele kurze Verbindungen gibt, sondern ein Client stellt eine Verbindung her und bleibt stunden- oder tagelang oder in Verbindung sogar Wochen).
Ich suche nach Ideen, wie die Netzwerkarchitektur am besten gestaltet werden kann. Ich muss mindestens einen Thread für den Dienst starten. Ich denke darüber nach, die Asynch-API (BeginRecieve usw.) zu verwenden, da ich nicht weiß, wie viele Clients ich zu einem bestimmten Zeitpunkt verbunden haben werde (möglicherweise Hunderte). Ich möchte definitiv nicht für jede Verbindung einen Thread starten.
Daten fließen hauptsächlich von meinem Server an die Clients, aber gelegentlich werden einige Befehle von den Clients gesendet. Dies ist in erster Linie eine Überwachungsanwendung, bei der mein Server regelmäßig Statusdaten an die Clients sendet.
Irgendwelche Vorschläge, wie dies am besten so skalierbar wie möglich gemacht werden kann? Grundlegender Workflow? Vielen Dank.
EDIT: Um klar zu sein, suche ich nach .net-basierten Lösungen (C # wenn möglich, aber jede .net Sprache wird funktionieren)
BOUNTY HINWEIS: Um das Kopfgeld zu erhalten, erwarte ich mehr als eine einfache Antwort. Ich würde ein funktionierendes Beispiel für eine Lösung benötigen, entweder als Zeiger auf etwas, das ich herunterladen könnte, oder als kurzes Beispiel inline. Und es muss .net und Windows-basiert sein (jede .net-Sprache ist akzeptabel)
EDIT: Ich möchte mich bei allen bedanken, die gute Antworten gegeben haben. Leider konnte ich nur eine akzeptieren und entschied mich für die bekanntere Begin / End-Methode. Esacs Lösung mag zwar besser sein, aber sie ist noch neu genug, dass ich nicht genau weiß, wie sie funktionieren wird.
Ich habe alle Antworten, die ich für gut hielt, positiv bewertet. Ich wünschte, ich könnte mehr für euch tun. Danke noch einmal.
quelle
Antworten:
Ich habe in der Vergangenheit etwas Ähnliches geschrieben. Meine Recherchen vor Jahren haben gezeigt, dass das Schreiben einer eigenen Socket-Implementierung mit den asynchronen Sockets die beste Wahl ist. Dies bedeutete, dass Kunden, die eigentlich nichts taten, relativ wenig Ressourcen benötigten. Alles, was passiert, wird vom .net-Thread-Pool behandelt.
Ich habe es als Klasse geschrieben, die alle Verbindungen für die Server verwaltet.
Ich habe einfach eine Liste verwendet, um alle Clientverbindungen zu speichern. Wenn Sie jedoch schnellere Suchvorgänge für größere Listen benötigen, können Sie diese nach Belieben schreiben.
Außerdem muss der Socket tatsächlich für eingehende Verbindungen aufgelistet sein.
Die Startmethode startet tatsächlich den Server-Socket und wartet auf eingehende Verbindungen.
Ich möchte nur darauf hinweisen, dass der Code für die Ausnahmebehandlung schlecht aussieht, aber der Grund dafür ist, dass ich dort einen Code zur Unterdrückung von Ausnahmen hatte, damit alle Ausnahmen unterdrückt werden und zurückkehren,
false
wenn eine Konfigurationsoption festgelegt wurde, aber ich wollte ihn entfernen Kürze.Das oben beschriebene _serverSocket.BeginAccept (neues AsyncCallback (acceptCallback)), _serverSocket) setzt unseren Server-Socket im Wesentlichen so, dass die acceptCallback-Methode aufgerufen wird, wenn ein Benutzer eine Verbindung herstellt. Diese Methode wird aus dem .NET-Threadpool ausgeführt, der automatisch das Erstellen zusätzlicher Arbeitsthreads übernimmt, wenn Sie viele Blockierungsvorgänge ausführen. Dies sollte jede Belastung des Servers optimal bewältigen.
Der obige Code hat im Wesentlichen gerade die Annahme der eingehenden Verbindung beendet, Warteschlangen, bei
BeginReceive
denen es sich um einen Rückruf handelt, der ausgeführt wird, wenn der Client Daten sendet, und dann die nächste Warteschlange, dieacceptCallback
die nächste eingehende Clientverbindung akzeptiert.Der
BeginReceive
Methodenaufruf teilt dem Socket mit, was zu tun ist, wenn er Daten vom Client empfängt. FürBeginReceive
, müssen Sie es ein Byte - Array geben, das ist , wo er die Daten kopiert werden , wenn der Client Daten sendet. DieReceiveCallback
Methode wird aufgerufen. So gehen wir mit dem Empfang von Daten um.EDIT: In diesem Muster habe ich vergessen, das in diesem Bereich des Codes zu erwähnen:
Im Allgemeinen würde ich in dem Code, den Sie möchten, Pakete wieder zu Nachrichten zusammenfügen und sie dann als Jobs im Thread-Pool erstellen. Auf diese Weise wird das BeginReceive des nächsten Blocks vom Client nicht verzögert, während der Nachrichtenverarbeitungscode ausgeführt wird.
Der Rückruf zum Akzeptieren beendet das Lesen des Daten-Sockets durch Aufrufen von Ende Empfangen. Dies füllt den Puffer, der in der Startempfangsfunktion bereitgestellt wird. Sobald Sie tun, was Sie wollen, wo ich den Kommentar hinterlassen habe, rufen wir die nächste
BeginReceive
Methode auf, die den Rückruf erneut ausführt, wenn der Client weitere Daten sendet. Hier ist der wirklich schwierige Teil: Wenn der Client Daten sendet, wird Ihr Empfangsrückruf möglicherweise nur mit einem Teil der Nachricht aufgerufen. Der Zusammenbau kann sehr, sehr kompliziert werden. Ich habe meine eigene Methode verwendet und dazu eine Art proprietäres Protokoll erstellt. Ich habe es weggelassen, aber wenn Sie es wünschen, kann ich es hinzufügen. Dieser Handler war tatsächlich der komplizierteste Code, den ich jemals geschrieben habe.Die obige Sendemethode verwendet tatsächlich einen synchronen
Send
Aufruf, was für mich aufgrund der Nachrichtengröße und des Multithread-Charakters meiner Anwendung in Ordnung war. Wenn Sie an jeden Client senden möchten, müssen Sie lediglich die _sockets-Liste durchlaufen.Die xConnection-Klasse, auf die oben verwiesen wird, ist im Grunde ein einfacher Wrapper für einen Socket, der den Bytepuffer enthält, und in meiner Implementierung einige Extras.
Als Referenz dienen hier auch die
using
s, die ich einbinde, da ich mich immer ärgere, wenn sie nicht enthalten sind.Ich hoffe das ist hilfreich, es ist vielleicht nicht der sauberste Code, aber es funktioniert. Es gibt auch einige Nuancen im Code, die Sie beim Ändern müde sein sollten. Zum einen muss immer nur eine Person
BeginAccept
angerufen werden. Früher gab es einen sehr nervigen .net-Fehler, der vor Jahren aufgetreten ist, sodass ich mich nicht an die Details erinnere.Außerdem
ReceiveCallback
verarbeiten wir im Code alles, was vom Socket empfangen wurde, bevor wir den nächsten Empfang in die Warteschlange stellen. Dies bedeutet, dass wir für einen einzelnen SocketReceiveCallback
zu jedem Zeitpunkt tatsächlich immer nur einmal sind und keine Thread-Synchronisation verwenden müssen. Wenn Sie dies jedoch neu anordnen, um den nächsten Empfang unmittelbar nach dem Abrufen der Daten aufzurufen, was möglicherweise etwas schneller ist, müssen Sie sicherstellen, dass Sie die Threads ordnungsgemäß synchronisieren.Außerdem habe ich einen Großteil meines Codes gehackt, aber die Essenz dessen, was passiert, an Ort und Stelle belassen. Dies sollte ein guter Anfang für Ihr Design sein. Hinterlasse einen Kommentar, wenn du weitere Fragen dazu hast.
quelle
Send
füllen , werden die Methoden auf beiden Seiten auf unbestimmte Zeit blockiert, da niemand die Eingabedaten liest.Es gibt viele Möglichkeiten, Netzwerkoperationen in C # durchzuführen. Alle von ihnen verwenden unterschiedliche Mechanismen unter der Haube und leiden daher unter großen Leistungsproblemen bei hoher Parallelität. Begin * -Operationen sind eine davon, die viele Leute oft für die schnellere / schnellste Art der Vernetzung halten.
Um diese Probleme zu lösen, führten sie die * Async-Methoden ein: Von MSDN http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx
Die SocketAsyncEventArgs-Klasse ist Teil einer Reihe von Verbesserungen an der System.Net.Sockets .. ::. Socket-Klasse, die ein alternatives asynchrones Muster bereitstellen, das von speziellen Hochleistungs-Socket-Anwendungen verwendet werden kann. Diese Klasse wurde speziell für Netzwerkserveranwendungen entwickelt, die eine hohe Leistung erfordern. Eine Anwendung kann das erweiterte asynchrone Muster ausschließlich oder nur in bestimmten heißen Bereichen verwenden (z. B. beim Empfang großer Datenmengen).
Das Hauptmerkmal dieser Verbesserungen ist die Vermeidung der wiederholten Zuordnung und Synchronisation von Objekten während asynchroner Socket-E / A mit hohem Volumen. Für das derzeit von der System.Net.Sockets .. ::. Socket-Klasse implementierte Begin / End-Entwurfsmuster muss für jede asynchrone Socket-Operation ein System .. ::. IAsyncResult-Objekt zugewiesen werden.
Unter dem Deckmantel verwendet die * Async-API E / A-Abschlussports, die die schnellste Methode zum Ausführen von Netzwerkvorgängen darstellen (siehe http://msdn.microsoft.com/en-us/magazine/cc302334.aspx)
Und um Ihnen zu helfen, füge ich den Quellcode für einen Telnet-Server hinzu, den ich mit der * Async-API geschrieben habe. Ich beziehe nur die relevanten Teile ein. Um die Daten nicht inline zu verarbeiten, entscheide ich mich stattdessen dafür, sie in eine Warteschlange ohne Sperren (ohne Wartezeiten) zu verschieben, die in einem separaten Thread verarbeitet wird. Beachten Sie, dass ich nicht die entsprechende Pool-Klasse einbeziehe, bei der es sich nur um einen einfachen Pool handelt, der ein neues Objekt erstellt, wenn es leer ist, und die Buffer-Klasse, bei der es sich nur um einen selbstexpandierenden Puffer handelt, der nur dann wirklich benötigt wird, wenn Sie eine Unbestimmtheit erhalten Datenmenge. Wenn Sie weitere Informationen wünschen, senden Sie mir bitte eine PM.
quelle
Früher gab es eine wirklich gute Diskussion über skalierbares TCP / IP mit .NET, geschrieben von Chris Mullins von Coversant. Leider scheint sein Blog von seinem vorherigen Speicherort verschwunden zu sein, daher werde ich versuchen, seine Ratschläge aus dem Speicher zusammenzufassen (einige nützliche Kommentare) von ihm erscheinen in diesem Thread: C ++ vs. C #: Entwicklung eines hoch skalierbaren IOCP-Servers )
Beachten Sie in erster Linie, dass sowohl using
Begin/End
als auch dieAsync
Methoden in derSocket
Klasse IOP Completion Ports (IOCP) verwenden, um Skalierbarkeit zu gewährleisten. Dies macht einen viel größeren Unterschied (bei korrekter Verwendung; siehe unten) zur Skalierbarkeit als die beiden Methoden, die Sie tatsächlich zur Implementierung Ihrer Lösung auswählen.Die Beiträge von Chris Mullins basierten auf der Verwendung
Begin/End
, mit der ich persönlich Erfahrung habe. Beachten Sie, dass Chris eine darauf basierende Lösung zusammengestellt hat, die bis zu 10.000 gleichzeitige Clientverbindungen auf einem 32-Bit-Computer mit 2 GB Speicher und bis zu 100.000 auf einer 64-Bit-Plattform mit ausreichend Speicher skaliert. Aus meiner eigenen Erfahrung mit dieser Technik (obwohl bei weitem nicht in der Nähe dieser Art von Last) habe ich keinen Grund, an diesen indikativen Zahlen zu zweifeln.IOCP versus Thread-per-Connection oder 'Select'-Primitive
Der Grund, warum Sie einen Mechanismus verwenden möchten, der IOCP unter der Haube verwendet, besteht darin, dass ein Windows-Thread-Pool auf sehr niedriger Ebene verwendet wird, der keine Threads aufweckt, bis tatsächlich Daten auf dem E / A-Kanal vorhanden sind, aus dem Sie lesen möchten ( Beachten Sie, dass IOCP auch für Datei-E / A verwendet werden kann. Dies hat den Vorteil, dass Windows nicht nur zu einem Thread wechseln muss, um festzustellen, dass ohnehin noch keine Daten vorhanden sind. Dadurch wird die Anzahl der Kontextwechsel, die Ihr Server durchführen muss, auf das erforderliche Minimum reduziert.
Kontextwechsel sind definitiv das, was den "Thread-per-Connection" -Mechanismus zunichte macht, obwohl dies eine praktikable Lösung ist, wenn Sie nur mit ein paar Dutzend Verbindungen arbeiten. Dieser Mechanismus ist jedoch keineswegs "skalierbar".
Wichtige Überlegungen bei der Verwendung von IOCP
Erinnerung
In erster Linie ist es wichtig zu verstehen, dass IOCP unter .NET leicht zu Speicherproblemen führen kann, wenn Ihre Implementierung zu naiv ist. Jeder IOCP-
BeginReceive
Aufruf führt dazu, dass der Puffer, in den Sie lesen, " fixiert" wird. Eine gute Erklärung, warum dies ein Problem ist, finden Sie unter: Yun Jins Weblog: OutOfMemoryException and Pinning .Glücklicherweise kann dieses Problem vermieden werden, aber es erfordert einen gewissen Kompromiss. Die vorgeschlagene Lösung besteht darin, eine große zuzuweisen
byte[]
beim Start der Anwendung (oder in der Nähe davon) Puffer von mindestens 90 KB zuzuweisen (ab .NET 2 kann die erforderliche Größe in späteren Versionen größer sein). Der Grund dafür ist, dass große Speicherzuordnungen automatisch in einem nicht komprimierenden Speichersegment (The Large Object Heap) landen, das effektiv automatisch fixiert wird. Indem Sie beim Start einen großen Puffer zuweisen, stellen Sie sicher, dass sich dieser Block unbeweglichen Speichers an einer relativ niedrigen Adresse befindet, an der er nicht im Weg ist und eine Fragmentierung verursacht.Sie können dann Offsets verwenden, um diesen einen großen Puffer für jede Verbindung, die einige Daten lesen muss, in separate Bereiche zu segmentieren. Hier kommt ein Kompromiss ins Spiel; Da dieser Puffer vorab zugewiesen werden muss, müssen Sie entscheiden, wie viel Pufferplatz Sie pro Verbindung benötigen und welche Obergrenze Sie für die Anzahl der Verbindungen festlegen möchten, auf die Sie skalieren möchten (oder Sie können eine Abstraktion implementieren das kann zusätzliche angeheftete Puffer zuweisen, sobald Sie sie benötigen).
Die einfachste Lösung wäre, jeder Verbindung ein einzelnes Byte mit einem eindeutigen Versatz innerhalb dieses Puffers zuzuweisen. Anschließend können Sie
BeginReceive
ein einzelnes zu lesendes Byte aufrufen und den Rest des Lesevorgangs als Ergebnis des erhaltenen Rückrufs ausführen.wird bearbeitet
Wenn Sie den Rückruf von dem von
Begin
Ihnen getätigten Anruf erhalten, ist es sehr wichtig zu wissen, dass der Code im Rückruf auf dem IOCP-Thread auf niedriger Ebene ausgeführt wird. Es ist absolut notwendig , dass Sie lange Operationen in diesem Rückruf vermeiden. Die Verwendung dieser Threads für die komplexe Verarbeitung beeinträchtigt Ihre Skalierbarkeit genauso effektiv wie die Verwendung von "Thread pro Verbindung".Die vorgeschlagene Lösung besteht darin, den Rückruf nur zum Einreihen eines Arbeitselements in die Warteschlange zu verwenden, um die eingehenden Daten zu verarbeiten, die in einem anderen Thread ausgeführt werden. Vermeiden Sie potenziell blockierende Vorgänge innerhalb des Rückrufs, damit der IOCP-Thread so schnell wie möglich zu seinem Pool zurückkehren kann. In .NET 4.0 würde ich vorschlagen, dass die einfachste Lösung darin besteht, a zu erzeugen
Task
und ihm einen Verweis auf den Client-Socket und eine Kopie des ersten Bytes zu geben, das bereits vomBeginReceive
Aufruf gelesen wurde . Diese Aufgabe ist dann dafür verantwortlich, alle Daten aus dem Socket zu lesen, die die von Ihnen verarbeitete Anforderung darstellen, sie auszuführen und dann eine neue zu erstellenBeginReceive
Aufruf zu tätigen, um den Socket erneut für IOCP in die Warteschlange zu stellen. Vor .NET 4.0 können Sie den ThreadPool verwenden oder eine eigene Implementierung der Thread-Arbeitswarteschlange erstellen.Zusammenfassung
Grundsätzlich würde ich vorschlagen, Kevins Beispielcode für diese Lösung mit den folgenden zusätzlichen Warnungen zu verwenden:
BeginReceive
bereits fixiert ist.BeginReceive
lediglich eine Aufgabe in die Warteschlange stellt, um die eigentliche Verarbeitung der eingehenden Daten zu übernehmenWenn Sie das tun, können Sie ohne Zweifel die Ergebnisse von Chris replizieren und auf potenziell Hunderttausende gleichzeitiger Clients skalieren (vorausgesetzt, die richtige Hardware und eine effiziente Implementierung Ihres eigenen Verarbeitungscodes;).
quelle
Sie haben den größten Teil der Antwort bereits über die obigen Codebeispiele erhalten. Die Verwendung der asynchronen E / A-Operation ist hier absolut der richtige Weg. Async IO ist die Art und Weise, wie Win32 intern maßstabsgetreu konzipiert ist. Die bestmögliche Leistung wird durch die Verwendung von Abschlussports erzielt, indem Ihre Sockets an Abschlussports gebunden werden und ein Thread-Pool auf die Fertigstellung des Abschlussports wartet. Es ist allgemein bekannt, dass 2-4 Threads pro CPU (Kern) auf den Abschluss warten. Ich empfehle dringend, diese drei Artikel von Rick Vicik vom Windows Performance-Team zu lesen:
Die genannten Artikel behandeln hauptsächlich die native Windows-API, sind jedoch ein Muss für jeden, der versucht, die Skalierbarkeit und Leistung zu verstehen. Sie haben auch einige Informationen über die verwaltete Seite der Dinge.
Als zweites müssen Sie sicherstellen , dass Sie das online verfügbare Buch zur Verbesserung der Leistung und Skalierbarkeit von .NET-Anwendungen lesen . In Kapitel 5 finden Sie sachdienliche und gültige Ratschläge zur Verwendung von Threads, asynchronen Aufrufen und Sperren. Die wahren Juwelen finden Sie jedoch in Kapitel 17, in dem Sie nützliche Informationen wie die praktische Anleitung zum Optimieren Ihres Thread-Pools finden. Meine Apps hatten einige schwerwiegende Probleme, bis ich die maxIothreads / maxWorkerThreads gemäß den Empfehlungen in diesem Kapitel angepasst habe.
Sie sagen, dass Sie einen reinen TCP-Server verwenden möchten, daher ist mein nächster Punkt falsch. Allerdings , wenn Sie finden sich die Enge getrieben und verwenden Sie die WebRequest - Klasse und deren Derivate, seien Sie gewarnt , dass es einen Drachen , die Tür ist bewacht: die Servicepoint . Dies ist eine Konfigurationsklasse, die einen Lebenszweck hat: Ihre Leistung zu ruinieren. Stellen Sie sicher, dass Sie Ihren Server von dem künstlich auferlegten ServicePoint.ConnectionLimit befreien, da Ihre Anwendung sonst niemals skaliert wird (ich lasse Sie selbst herausfinden, was der Standardwert ist ...). Sie können auch die Standardrichtlinie zum Senden eines Expect100Continue-Headers in den http-Anforderungen überdenken.
Was nun die Core Socket Managed API betrifft, sind die Dinge auf der Sendeseite ziemlich einfach, auf der Empfangsseite jedoch wesentlich komplexer. Um einen hohen Durchsatz und eine hohe Skalierbarkeit zu erzielen, müssen Sie sicherstellen, dass der Socket nicht durchflussgesteuert ist, da kein Puffer für den Empfang gebucht ist. Ideal für eine hohe Leistung sollten Sie 3-4 Puffer vorab veröffentlichen und neue Puffer veröffentlichen, sobald Sie einen zurückerhalten ( bevor Sie den zurückerhaltenen verarbeiten), damit Sie sicherstellen, dass der Socket immer einen Ort hat, an dem die vom Netzwerk kommenden Daten abgelegt werden können. Sie werden sehen, warum Sie dies wahrscheinlich in Kürze nicht erreichen können.
Nachdem Sie mit der BeginRead / BeginWrite-API fertig sind und mit der ernsthaften Arbeit beginnen, werden Sie feststellen, dass Sie Sicherheit für Ihren Datenverkehr benötigen, d. H. NTLM / Kerberos-Authentifizierung und Verkehrsverschlüsselung oder zumindest Schutz vor Verkehrsmanipulationen. Auf diese Weise verwenden Sie den integrierten System.Net.Security.NegotiateStream (oder SslStream, wenn Sie unterschiedliche Domänen überqueren müssen). Dies bedeutet, dass Sie sich nicht auf asynchrone Operationen mit geradem Socket, sondern auf asynchrone AuthenticatedStream-Operationen verlassen. Sobald Sie einen Socket erhalten (entweder von Connect on Client oder von Accept on Server), erstellen Sie einen Stream auf dem Socket und senden ihn zur Authentifizierung, indem Sie entweder BeginAuthenticateAsClient oder BeginAuthenticateAsServer aufrufen. Nachdem die Authentifizierung abgeschlossen ist (zumindest Ihr Safe vor dem nativen Wahnsinn von InitiateSecurityContext / AcceptSecurityContext ...), führen Sie Ihre Autorisierung durch, indem Sie die RemoteIdentity-Eigenschaft Ihres authentifizierten Streams überprüfen und die ACL-Überprüfung durchführen, die Ihr Produkt unterstützen muss. Danach senden Sie Nachrichten mit BeginWrite und empfangen sie mit BeginRead. Dies ist das Problem, von dem ich zuvor gesprochen habe, dass Sie nicht mehrere Empfangspuffer veröffentlichen können, da die AuthenticateStream-Klassen dies nicht unterstützen. Der BeginRead-Vorgang verwaltet intern alle E / A-Vorgänge, bis Sie einen gesamten Frame erhalten haben. Andernfalls kann die Nachrichtenauthentifizierung (Frame entschlüsseln und Signatur im Frame validieren) nicht verarbeitet werden. Meiner Erfahrung nach ist die Arbeit der AuthenticatedStream-Klassen jedoch ziemlich gut und sollte kein Problem damit haben. Dh. Sie sollten in der Lage sein, das GB-Netzwerk mit nur 4-5% CPU zu sättigen. Die AuthenticatedStream-Klassen legen Ihnen auch die protokollspezifischen Frame-Größenbeschränkungen auf (16 KB für SSL, 12 KB für Kerberos).
Damit sollten Sie auf dem richtigen Weg beginnen. Ich werde hier keine Postleitzahl posten, es gibt ein perfektes Beispiel für MSDN . Ich habe viele Projekte wie dieses durchgeführt und konnte ohne Probleme auf etwa 1000 verbundene Benutzer skalieren. Darüber hinaus müssen Sie die Registrierungsschlüssel ändern, damit der Kernel mehr Socket-Handles erhält. und stellen Sie sicher, dass Sie auf einem Server- Betriebssystem bereitstellen , dh W2K3, nicht XP oder Vista (dh Client-Betriebssystem). Dies macht einen großen Unterschied.
Stellen Sie übrigens sicher, dass Sie bei Datenbankoperationen auf dem Server oder in der Datei-E / A auch die asynchrone Variante für diese verwenden, da sonst der Thread-Pool in kürzester Zeit entleert wird. Stellen Sie für SQL Server-Verbindungen sicher, dass Sie der Verbindungszeichenfolge 'Asyncronous Processing = true' hinzufügen.
quelle
In einigen meiner Lösungen läuft ein solcher Server. Hier finden Sie eine sehr detaillierte Erklärung der verschiedenen Möglichkeiten in .net: Mit Hochleistungs-Sockets in .NET näher am Kabel
In letzter Zeit habe ich nach Möglichkeiten gesucht, unseren Code zu verbessern, und werde dies untersuchen: " Verbesserungen der Socket-Leistung in Version 3.5 ", die speziell "für Anwendungen verwendet wurden, die asynchrone Netzwerk-E / A verwenden, um die höchste Leistung zu erzielen".
"Das Hauptmerkmal dieser Verbesserungen ist die Vermeidung der wiederholten Zuordnung und Synchronisation von Objekten während der asynchronen Socket-E / A mit hohem Volumen. Das derzeit von der Socket-Klasse für asynchrone Socket-E / A implementierte Entwurfsmuster" Begin / End "erfordert ein System. IAsyncResult-Objekt wird für jede asynchrone Socket-Operation zugewiesen. "
Sie können weiterlesen, wenn Sie dem Link folgen. Ich persönlich werde ihren Beispielcode morgen testen, um ihn mit dem zu vergleichen, was ich habe.
Bearbeiten: Hier finden Sie Arbeitscode für Client und Server mit den neuen 3.5 SocketAsyncEventArgs, sodass Sie ihn innerhalb weniger Minuten testen und den Code durchgehen können. Dies ist ein einfacher Ansatz, aber die Grundlage für den Start einer viel größeren Implementierung. Auch dieser Artikel von vor fast zwei Jahren im MSDN Magazine war eine interessante Lektüre.
quelle
Haben Sie darüber nachgedacht, nur eine WCF-Netz-TCP-Bindung und ein Publish / Subscribe-Muster zu verwenden? WCF würde es Ihnen ermöglichen, sich [meistens] auf Ihre Domain zu konzentrieren, anstatt Klempnerarbeiten durchzuführen.
Es gibt viele WCF-Beispiele und sogar ein Publish / Subscribe-Framework im Download-Bereich von IDesign, das nützlich sein kann: http://www.idesign.net
quelle
Ich wundere mich über eine Sache:
Warum ist das so? Windows konnte seit mindestens Windows 2000 Hunderte von Threads in einer Anwendung verarbeiten. Ich habe es geschafft. Es ist wirklich einfach, damit zu arbeiten, wenn die Threads nicht synchronisiert werden müssen. Insbesondere angesichts der Tatsache, dass Sie viele E / A-Vorgänge ausführen (Sie sind also nicht an die CPU gebunden und viele Threads auf der Festplatten- oder Netzwerkkommunikation blockiert), verstehe ich diese Einschränkung nicht.
Haben Sie den Multithread-Weg getestet und festgestellt, dass ihm etwas fehlt? Beabsichtigen Sie, für jeden Thread auch eine Datenbankverbindung zu haben (dies würde den Datenbankserver zerstören, daher ist dies eine schlechte Idee, die jedoch mit einem dreistufigen Design leicht gelöst werden kann). Befürchten Sie, dass Sie Tausende von Kunden anstelle von Hunderten haben und dann wirklich Probleme haben? (Obwohl ich tausend Threads oder sogar zehntausend versuchen würde, wenn ich mehr als 32 GB RAM hätte - auch hier sollte die Thread-Wechselzeit absolut irrelevant sein, da Sie nicht an die CPU gebunden sind.)
Hier ist der Code - um zu sehen, wie dies aussieht, gehen Sie zu http://mdpopescu.blogspot.com/2009/05/multi-threaded-server.html und klicken Sie auf das Bild.
Serverklasse:
Server Hauptprogramm:
Client-Klasse:
Client-Hauptprogramm:
quelle
Verwenden von .NETs integriertem Async IO (
BeginRead
usw.) ist eine gute Idee, wenn Sie alle Details richtig machen können. Wenn Sie Ihre Socket- / Datei-Handles ordnungsgemäß eingerichtet haben, wird die zugrunde liegende IOCP-Implementierung des Betriebssystems verwendet, sodass Ihre Vorgänge ohne Verwendung von Threads abgeschlossen werden können (oder im schlimmsten Fall mithilfe eines Threads, von dem ich glaube, dass er stattdessen aus dem IO-Thread-Pool des Kernels stammt des Thread-Pools von .NET, wodurch die Überlastung des Thread-Pools verringert wird.)Das wichtigste Problem ist, sicherzustellen, dass Sie Ihre Sockets / Dateien im nicht blockierenden Modus öffnen. Die meisten Standardfunktionen (wie z
File.OpenRead
) tun dies nicht, daher müssen Sie Ihre eigenen schreiben.Eines der anderen Hauptprobleme ist die Fehlerbehandlung - die ordnungsgemäße Behandlung von Fehlern beim Schreiben von asynchronem E / A-Code ist sehr viel schwieriger als bei synchronem Code. Es ist auch sehr einfach, mit Rennbedingungen und Deadlocks zu enden, obwohl Sie möglicherweise keine Threads direkt verwenden. Daher müssen Sie sich dessen bewusst sein.
Wenn möglich, sollten Sie versuchen, eine Komfortbibliothek zu verwenden, um die Durchführung skalierbarer asynchroner E / A zu vereinfachen.
Die Concurrency Coordination Runtime von Microsoft ist ein Beispiel für eine .NET-Bibliothek, mit der die Schwierigkeit dieser Art der Programmierung verringert werden soll. Es sieht gut aus, aber da ich es nicht benutzt habe, kann ich nicht sagen, wie gut es skalieren würde.
Für meine persönlichen Projekte, die asynchrone Netzwerk- oder Festplatten-E / A ausführen müssen , verwende ich eine Reihe von .NET-Parallelitäts- / E / A-Tools namens Squared.Task , die ich im letzten Jahr erstellt habe . Es ist inspiriert von Bibliotheken wie imvu.task und twisted , und ich habe einige Arbeitsbeispiele in das Repository aufgenommen, die Netzwerk-E / A ausführen . Ich habe es auch in einigen Anwendungen verwendet, die ich geschrieben habe - die größte öffentlich veröffentlichte ist NDexer (die es für Threadless Disk I / O verwendet). Die Bibliothek wurde aufgrund meiner Erfahrungen mit imvu.task geschrieben und enthält eine Reihe ziemlich umfassender Komponententests. Ich empfehle Ihnen daher dringend, sie auszuprobieren. Wenn Sie Probleme damit haben, biete ich Ihnen gerne Unterstützung an.
Aufgrund meiner Erfahrung mit asynchronen / threadlosen E / A anstelle von Threads ist es meiner Meinung nach ein lohnendes Unterfangen auf der .NET-Plattform, solange Sie bereit sind, sich mit der Lernkurve zu befassen. Auf diese Weise können Sie die Skalierbarkeitsprobleme vermeiden, die durch die Kosten von Thread-Objekten entstehen. In vielen Fällen können Sie die Verwendung von Sperren und Mutexen vollständig vermeiden, indem Sie Parallelitätsprimitive wie Futures / Promises sorgfältig verwenden.
quelle
Ich habe Kevins Lösung verwendet, aber er sagt, dass der Lösung Code zum Zusammensetzen von Nachrichten fehlt. Entwickler können diesen Code zum Zusammensetzen von Nachrichten verwenden:
quelle
Eine schöne Übersicht über die Techniken finden Sie auf der C10k-Problemseite .
quelle
Sie können versuchen, ein Framework namens ACE (Adaptive Communications Environment) zu verwenden, das ein generisches C ++ - Framework für Netzwerkserver ist. Es ist ein sehr solides, ausgereiftes Produkt und wurde entwickelt, um hochzuverlässige Anwendungen mit hohem Volumen bis hin zu Telekommunikationsqualität zu unterstützen.
Das Framework befasst sich mit einer Vielzahl von Parallelitätsmodellen und verfügt wahrscheinlich über ein Modell, das sofort für Ihre Anwendung geeignet ist. Dies sollte das Debuggen des Systems erleichtern, da die meisten Probleme mit der Parallelität bereits behoben wurden. Der Nachteil hierbei ist, dass das Framework in C ++ geschrieben ist und nicht die warmeste und flauschigste Codebasis ist. Auf der anderen Seite erhalten Sie eine getestete Netzwerkinfrastruktur in Industriequalität und eine hoch skalierbare Architektur.
quelle
Ich würde SEDA oder eine leichtgewichtige Threading-Bibliothek verwenden (erlang oder neueres Linux siehe NTPL-Skalierbarkeit auf der Serverseite ). Asynchrone Codierung ist sehr umständlich, wenn Ihre Kommunikation nicht ist :)
quelle
Nun, .NET-Sockets scheinen select () bereitzustellen - das ist am besten für die Verarbeitung von Eingaben geeignet. Für die Ausgabe hätte ich einen Pool von Socket-Writer-Threads, die eine Arbeitswarteschlange abhören und den Socket-Deskriptor / das Socket-Objekt als Teil des Arbeitselements akzeptieren, sodass Sie keinen Thread pro Socket benötigen.
quelle
Ich würde die AcceptAsync / ConnectAsync / ReceiveAsync / SendAsync-Methoden verwenden, die in .Net 3.5 hinzugefügt wurden. Ich habe einen Benchmark durchgeführt und sie sind ungefähr 35% schneller (Antwortzeit und Bitrate), da 100 Benutzer ständig Daten senden und empfangen.
quelle
Wenn Personen die akzeptierte Antwort einfügen möchten, können Sie die acceptCallback-Methode neu schreiben und alle Aufrufe von _serverSocket.BeginAccept (new AsyncCallback (acceptCallback), _serverSocket) entfernen. und füge es wie folgt in eine finally {} -Klausel ein:
Sie können sogar den ersten Fang entfernen, da sein Inhalt derselbe ist, es sich jedoch um eine Vorlagenmethode handelt. Sie sollten eine typisierte Ausnahme verwenden, um die Ausnahmen besser zu behandeln und zu verstehen, was den Fehler verursacht hat. Implementieren Sie diese Fänge also einfach mit nützlichem Code
quelle
Ich würde empfehlen, diese Bücher über ACE zu lesen
um Ideen zu Mustern zu erhalten, mit denen Sie einen effizienten Server erstellen können.
Obwohl ACE in C ++ implementiert ist, enthalten die Bücher viele nützliche Muster, die in jeder Programmiersprache verwendet werden können.
quelle
Sie erhalten nicht die höchste Skalierbarkeit, wenn Sie nur mit .NET arbeiten. GC-Pausen können die Latenz beeinträchtigen.
Überlappende E / A gelten im Allgemeinen als die schnellste Windows-API für die Netzwerkkommunikation. Ich weiß nicht, ob dies mit Ihrer Asynch-API identisch ist. Verwenden Sie select nicht, da jeder Anruf jeden geöffneten Socket überprüfen muss, anstatt Rückrufe an aktiven Sockets zu haben.
quelle
Sie können das Open Source-Framework Push Framework für die Entwicklung von Hochleistungsservern verwenden. Es basiert auf IOCP und eignet sich für Push-Szenarien und Nachrichtenübertragungen.
http://www.pushframework.com
quelle