![]() |
Delphi-Version: 10 Seattle
Klassendesign: Blockierender Constructor?
Hallo zusammen,
ich re-designe grade eine Klasse, die eine Netzwerk-Protokoll-Schicht implementiert. Dabei wollte ich beim Klassendesign folgende Aspekte vereinen:
Delphi-Quellcode:
, an die der Benutzer manuell die Daten übergeben musste, die er mit seinem Socket empfangen hat.
HandleData
Für ausgehende Daten hatte ich bisher zwei Ansätze:
Delphi-Quellcode:
loswerden und anderseits, dass der IO-Handler selbstständig senden kann (also eher Ansatz 1). Sprich: Der IO-Handler soll "Besitzer" des Sockets sein.
HandleData
Meine Idee war jetzt das Socket im Constructor zu übergeben und dann selbstständig zu pullen:
Delphi-Quellcode:
Meiner Intuition nach empfinde ich es allerdings als schlechtes Design im Constructor zu blockieren und komplexe Operationen auszuführen. Was sagt ihr dazu? Alternative wäre das Pullen in eine Methode auszulagern, aber dann habe ich wieder eine Art
type
ISocket = interface function Send(Data: Pointer; Size: UInt32): UInt32; function Receive(Data: Pointer; Size: UInt32): Integer; end; TIOHandler = class(TObject) private FSocket: ISocket; public constructor Create(Socket: ISocket); end; constructor TIOHandler.Create(Socket: ISocket); var Data: array[0..1024 * 16] of Byte; Size: Integer; begin inherited Create; FSocket := ISocket; // .. // internen Send-Thread erzeugen // .. repeat Size := FSocket.Receive(@Data[0], SizeOf(Data)); // blocking if (Size > 0) then begin InternalHandleData(@Data[0], Size); end; until (Size < 0); end;
Delphi-Quellcode:
Methode, die extra aufgerufen werden müsste (was ich gerne vermeiden will).
HandleData
Viele Grüße Zacherl |
AW: Klassendesign: Blockierender Constructor?
Da fiel mir sofort das hier ein:
![]() |
AW: Klassendesign: Blockierender Constructor?
Zitat:
Vielleicht möchte ich, als Benutzer der Klasse noch gar nichts senden. Vielleicht möchte ich die Klasse nur testen und sitze grade irgendwo ohne Netz. Es gibt viele Gründe, Google, Microsoft und Konsorten raten in ihren Ratgebern explizit immer davon ab, "real work" im constructor zu erledigen. Der hier z.B. fasst das ganz nett zusammen: ![]() |
AW: Klassendesign: Blockierender Constructor?
Es gibt auch noch AfterConstruction, aber aus sicht des Programmierers wird das auch "im" TMyClass.Create aufgerufen.
Alternativ eine Start/Init-Methode, welche von Extern nach dem Create aufgeufen wird. |
AW: Klassendesign: Blockierender Constructor?
Okay, dann sind wir uns bezüglich des Constructors ja schonmal einig :thumb: Testen der Klasse ohne Internet ist kein Problem. Dafür kann man das Socket Interface ja als Dummy implementieren.
Dann mal die Frage nach der besten Alternative. Momentan steht zu Auswahl:
Delphi-Quellcode:
Ansatz)?
HandleData
|
AW: Klassendesign: Blockierender Constructor?
Wie wäre es denn mit einem Event-gesteuerten Ansatz in der Art
Delphi-Quellcode:
?
Socket.OnDataAvailable := MyHandleDataEvent
|
AW: Klassendesign: Blockierender Constructor?
Zitat:
Delphi-Quellcode:
Ansatz. Da aber ja nicht jede Socket Komponente so ein Event besitzt (vor allem nicht mit der selben Parameter-Definition), sollte der Benutzer damals selbst auf ein
HandleData
Delphi-Quellcode:
Event reagieren und darin dann
OnDataAvailable
Delphi-Quellcode:
vom IO-Handler aufrufen. Bzw. im Fall von blocking Sockets hat der Benutzer entsprechend nach neuen Daten gepollt und bei Empfang dann
HandleData
Delphi-Quellcode:
aufgerufen.
HandleData
Ist natürlich ohne Frage der Ansatz, der dem Benutzer die größte Freiheit lässt. Was mich einfach stört ist, dass das Senden dann dem IO-Handler obliegt, das Empfangen aber in der Hand des Benutzers ist. Das
Delphi-Quellcode:
im Socket Interface wäre damit auf jeden Fall obsolet.
Receive
|
AW: Klassendesign: Blockierender Constructor?
Was soll der IOHandler denn genau machen? Sorry, wenn es eine dumme Frage ist, ich kenne den Begriff "IOHandler" von Indy, aber habe Indy das letzte mal vor wahrscheinlich 10 Jahren angerührt, und mich damals auch nicht wirklich mit der Klassenstruktur auseinandergesetzt.
Also eigentlich ist das Paradigma bei blockierenden Sockets ja, dass man immer abwechselnd liest und schreibt. Deswegen ist mir nicht so ganz klar, warum du im Konstruktor bereits anfängst zu lesen (und einen extra Thread für das Senden erzeugst). Denn es hängt ja vom konkreten Protokoll ab, ob zuerst gelesen oder zuerst geschrieben werden muss. Soll IOHandler ein Platzhalter für die Implementierung des Protokolls sein, oder ist es eine Zwischenschicht zwischen Socket und Protokollimplementierung? Ich entwickle "derzeit" (in Anführungszeichen, weil ich seit Monaten nicht dazu gekommen bin, daran zu weiterarbeiten) auch eine Socket-Lib und bin inzwischen zu dem Ergebnis gekommen, dass blockierend und eventgetrieben (also sowas wie OnDataAvailable) eine grundlegend murxige Kombination ist. Meiner Meinung gibt es zwei lokale Maxima im Raum der möglichen Designs für eine Socket-Lib: 1. Blockierend, imperativ (also eine Receive- und eine Send-Methode, so wie bei dir in ISocket) 2. Nicht-Blockierund und eventgetrieben (Protokollimplementierung als State Machine) - darauf konzentriert sich meine Lib [OT]Übrigens, weil ich gerade sehe, dass du auch Interfaces für die Sockets verwendest: Es ist schockierend, wie viel Overhead die Referenzzählung bei Interfaces verursacht, zumindest mit dem Code, den Freepascal generiert. Ich hatte mal einen ganz kleinen Test-HTTP-Server, der nur "Hello World" ausgibt, gegen nginx gebenchmarkt, und nginx war selbst mit nur einem Kern schneller als mein Server mit allen vier Kernen. Sogar Apache war schneller! Obwohl mein Server ja nicht mal den Funktionsumfang eines richt HTTP-Servers hat. Habe das dann mal mit Valgrind und Co. analysiert, weil ich dachte, dass ich irgendwas grundlegend falsch oder anders mache, aber es stellte sich raus, dass der einzige Unterschied anscheinend auf das Refcounting zurückzuführen ist, das einen riesen Rattenschwanz erzeugt (Exception-Stack, RelocateThreadVars...). Ansonsten sieht die Aufrufstatistik fast identisch aus wie bei nginx. Ich werde wahrscheinlich deshalb, wenn ich mal wieder dazu komme, bei mir alle Interfaces rausschmeißen.[/OT] Zur konkreten Frage: Ich würde auch auf jeden Fall im Constructor keine Reads, Writes oder sonstwas ausführen. Dann lieber eine extra Routine wie
Delphi-Quellcode:
, wenn es denn sein muss.
Start()
|
AW: Klassendesign: Blockierender Constructor?
Zitat:
Falls du bessere Ideen zur Umsetzung hast, gerne immer her damit :stupid: Konkret möchte ich halt mehrere simultane (unterschiedlich priorisierte) Übertragungen über ein einzelnes Socket realisieren, wobei sowohl Client, als auch Server auf Benutzerinteraktion hin eigenständig Datenübertragungen beginnen können. Die Kummunikation folgt also nicht wirklich einem fest definierten linearen Verlauf. Zitat:
|
AW: Klassendesign: Blockierender Constructor?
Overhead?
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 09:58 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz