Das Problem :
Um ein effizientes und sehr schnelles Named-Pipes-Client-Server-Framework zu entwerfen.
Aktueller Status :
Ich habe bereits ein erprobtes produktionsgetestetes Framework. Es ist schnell, jedoch verwendet es einen Thread pro einer Pipe-Verbindung und wenn es viele Clients gibt, könnte die Anzahl der Threads schnell zu hoch sein. Ich benutze bereits Smart-Thread-Pool (Task-Pool in der Tat), die mit Bedarf skalieren kann.
Ich benutze bereits OVERLAPED-Modus für Pipes, aber dann blockiere ich mit WaitForSingleObject oder WaitForMultipleObjects, deshalb brauche ich einen Thread pro Verbindung auf der Serverseite
Gewünschte Lösung:
Der Client ist in Ordnung, aber auf der Serverseite möchte ich nur einen Thread pro Clientanforderung und nicht pro Verbindung verwenden. Anstatt also einen Thread für den gesamten Lebenszyklus des Clients zu verwenden (connect / disconnect), würde ich einen Thread pro Task verwenden. Also nur wenn der Client Daten anfordert und nicht mehr.
Ich habe ein Beispiel in MSDN gesehen, das ein Array von OVERLAPED-Strukturen verwendet und dann WaitForMultipleObjects verwendet, um auf alle zu warten. Ich finde das ein schlechtes Design. Zwei Probleme sehe ich hier. Zuerst müssen Sie ein Array pflegen, das ziemlich groß werden kann, und das Löschen wird teuer. Zweitens haben Sie viele Ereignisse, eines für jedes Array-Mitglied.
Ich habe auch Completion-Ports gesehen, wie CreateIoCompletionPort und GetQueuedCompletionStatus , aber ich Sehe nicht, wie es ihnen besser geht.
Was ich möchte, ist etwas ReadFileEx und WriteFileEx , rufen sie an eine Rückrufroutine wenn die Operation abgeschlossen ist. Dies ist ein echter Async-Programmierstil. Aber das Problem ist, dass ConnectNamedPipe dies nicht unterstützt und außerdem habe ich gesehen, dass der Thread im Alarmzustand sein muss und Sie einige der * Ex-Funktionen aufrufen müssen, um das zu haben.
Wie kann ein solches Problem am besten gelöst werden?
Hier ist, wie MSDN es macht: Ссылка
Das Problem, das ich bei diesem Ansatz sehe, ist, dass ich nicht sehen kann, wie Sie 100 Clients gleichzeitig verbinden können, wenn das Limit auf WaitForMultipleObjects ist 64 Handles. Sicher kann ich die Leitung nach jeder Anfrage trennen, aber die Idee ist, eine dauerhafte Klientenverbindung genau wie in TCP-Server zu haben und den Klienten während des ganzen Lebenszyklus zu verfolgen, während jeder Klient eindeutige Identifikations- und Klienten spezifische Daten hat.
Der ideale Pseudocode sollte wie folgt aussehen:
%Vor%Auf diese Weise haben wir nur einen Listener-Thread, der connect / disconnect / onData-Ereignisse akzeptiert. Thread-Pool (Worker-Thread) verarbeitet nur die eigentliche Anfrage. Auf diese Weise können 5 Worker-Threads viele Clients bedienen, die verbunden sind.
P.S. Mein aktueller Code sollte nicht wichtig sein. Ich schreibe dies in Delphi, aber seine reine WinAPI, so dass die Sprache keine Rolle spielt.
BEARBEITEN:
Für jetzt sieht IOCP wie die Lösung aus:
E / A-Completion-Ports bieten ein effizientes Threading-Modell für Verarbeiten mehrerer asynchroner E / A-Anforderungen auf einem Multiprozessor System. Wenn ein Prozess einen E / A-Abschluss-Port erstellt, das System Erstellt ein zugeordnetes Warteschlangenobjekt für Anforderungen, deren einziger Zweck es ist um diese Anfragen zu bearbeiten. Prozesse, die viele gleichzeitig behandeln asynchrone E / A-Anfragen können dies schneller und effizienter erledigen Verwenden von I / O-Completion-Ports in Verbindung mit einem vorab zugewiesenen Thread Pool als durch das Erstellen von Threads zu dem Zeitpunkt, zu dem sie eine E / A-Anforderung erhalten.
Wenn der Server mehr als 64 Ereignisse (Lesen / Schreiben) verarbeiten muss, wird jede Lösung, die WaitForMultipleObjects verwendet, undurchführbar. Dies ist der Grund, warum Microsoft IO-Completion-Ports für Windows eingeführt hat. Es kann eine sehr hohe Anzahl von IO-Operationen mit der am besten geeigneten Anzahl von Threads verarbeiten (normalerweise ist es die Anzahl der Prozessoren / Kerne).
Das Problem mit IOCP ist, dass es sehr schwierig ist, richtig zu implementieren. Versteckte Probleme werden wie Minen im Feld verbreitet: [ 1 ], [2 ] (Abschnitt 3.6). Ich würde empfehlen, ein Framework zu verwenden. Kleines googeln deutet auf etwas, das für Delphi-Entwickler
An dieser Stelle würde ich die Anforderung für Named Pipes ignorieren, wenn dies bedeutet, dass ich meine eigene IOCP-Implementierung codiere. Es ist die Trauer nicht wert.
Ich denke, was Sie übersehen, ist, dass Sie nur einige wenige Named-Pipe-Instanzen gleichzeitig hören müssen. Sobald eine Pipe-Instanz verbunden ist, können Sie diese Instanz deaktivieren und eine neue Listening-Instanz erstellen, um sie zu ersetzen.
Wenn MAXIMUM_WAIT_OBJECTS
(oder weniger) benannte Pipe-Instanzen abhören, können Sie einen einzigen Thread für das Abhören mit WaitForMultipleObjectsEx
verwenden. Derselbe Thread kann auch den Rest des I / O mit ReadFileEx
und WriteFileEx
und APCs verarbeiten. Die Worker-Threads würden APCs in den E / A-Thread einreihen, um E / A zu initiieren, und der E / A-Thread kann den Task-Pool verwenden, um die Ergebnisse zurückzugeben (und die Worker-Threads über neue Verbindungen informieren) / p>
Die Hauptfunktion des I / O-Threads würde etwa so aussehen:
%Vor% Die einzige echte Komplikation ist, dass beim Aufruf von ConnectNamedPipe
möglicherweise ERROR_PIPE_CONNECTED
zurückgegeben wird, um anzuzeigen, dass der Aufruf sofort erfolgreich war, oder ein anderer Fehler als ERROR_IO_PENDING
, wenn der Aufruf sofort fehlgeschlagen ist. In diesem Fall müssen Sie das Ereignis zurücksetzen und dann die Verbindung bearbeiten:
Die Funktion connect_pipe_completion
erstellt eine neue Task im Task-Pool, um die neu verbundene Pipe-Instanz zu verarbeiten, und weist dann eine APC an, um new_pipe
aufzurufen, um eine neue abhörende Pipe am selben Index zu erstellen.
Es ist möglich, vorhandene Pipe-Instanzen wiederzuverwenden, sobald sie geschlossen sind, aber in dieser Situation denke ich nicht, dass es sich lohnt.
Tags und Links named-pipes windows delphi asynchronous winapi