![]() |
schnelle Server Client Verbindung ohne Verluste
Guten Morgen zusammen,
basierend auf diesem Thread, habe ich eine Indy Server und Client Anwendung: ![]() Dabei ist mir allerdings folgendes aufgefallen: 1) erstmal die Aufgabenstellung: Ich muss vom Server viele Daten zum Client senden und der Client auch genau in der Reihenfolge abarbeiten/verarbeiten. "Viele Daten" können zwischen 20 und 80 Datensätze (alle unterschiedlich!) pro Sekunde sein. Es dürfen sich keine Datensätze überholen bzw ausfallen. Die Telegramme können zwischen 13 Byte und max 61000 Byte lang sein! Der Ausfall wird über Connect/Disconnect erkannt. Dafür wollte ich Indy TCPIP nehmen. Verbindungsaufbau und -abbau klappt soweit, kein Problem. 2) Beobachtungen im Client: Datensätze werden im Client im einem Thread eingelesen und zwar so:
Delphi-Quellcode:
Dabei ist mir aufgefallen, dass der Client die Datensätze nicht in der Geschwindigkeit abholen kann, wie der Server sie mit WriteLn schickt. Auch mit dem Timeout (100) habe ich schon rumgespielt (erhöht, verringert), keine Besserung. Der Client hängt merklich hinterher. Wenn ich im Server das Senden unterdrücke, kann man beim Client richtig zugucken, wie die ReadLn Befehle weiter arbeiten und noch Datensätze abgeholt werden obwohl der Server ja eigentlich nicht mehr schickt (die sind also gepuffert). Das mit dem Puffer ist ja erstmal nicht schlecht, aber der Client kann den Server nie wieder "einholen" wenn der Server aktiv weiterschicken würde. Im Gegenteil, das "hinterherhängen" wird immer schlimmer.
IdTCPClient1.IOHandler.CheckForDataOnSource(10);
if not IdTCPClient1.IOHandler.InputBufferIsEmpty then begin InData := IdTCPClient1.IOHandler.ReadLn('#~#*' + EOL, 100, -1, IndyTextEncoding_UTF8); if InData <> '' then begin //mach irgendwas damit end; end; Zur Info, der Server schickt mit WriteLn und Terminator. 3) Frage: Ist TCPIP dafür überhaupt der richtige Ansatz für meine Aufgabenstellung? Gibt es eine andere Möglichkeit, Daten von einem Server an einem Client in dieser Geschwindigkeit zu schicken und der Client kann diese auch entsprechend verarbeiten? Also wenn der Client mal "bisschen" hinterher hängt, ist ja nicht schlimm aber es darf nicht immer mehr werden. Mit UDP habe ich mal versucht, dass ist natürlich super schnell aber da überholen sich Telegramme und es fallen auch welche aus (ich weiß warum, deswegen fällt UDP raus). Wie würdet ihr das versuchen zu lösen? |
AW: schnelle Server Client Verbindung ohne Verluste
Die Art der Übertragung ist mit Sicherheit nicht das Problem.
Deine Flaschenhälse sind an anderen Stellen. Zum einen würde ich mir überlegen die Datenpakete zu indexieren damit du rausbekomst ob dazwischen was fehlt. Weiter musst Du die Verarbeitung vom Empfang trennen. Die Datenpakete müssen also beim Empfang einfach weggespeichert werden und andere Threads kümmern sich ums auswerten. Wenn das Projekt kommerziell ist, legt dir andere Komponenten für die Übertragung zu. Wir verenden seit vielen Jahren NSoftware. Du musst dich um nichts kümmern, es funktioniert einfach. Was sind denn eigentlich viele Daten? Bytes pro Sekunde? MB? GB? |
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
Ja, die Software ist kommerziell. NSoftware kenne ich nicht, was ist da "anders/besser"? Welches Produkt von NSoftware meinst du denn genau? Die Geschwindigkeitsmessung habe ich auch direkt am WriteLn und ReadLn gemacht. Die Verarbeitung habe ich noch gar nicht geprüft. Das heißt, die Zeit geht ja da verloren bzw man sieht, dass das ReadLn hinterher hängt. Denke nicht, dass es mit der Software drum herum zusammen hängt. |
AW: schnelle Server Client Verbindung ohne Verluste
Ich habe mit Indy das letzte mal vor mehr als 10 Jahren gearbeitet. Keine Ahnung ob die jetzt Event basierten Datenempfang haben. (NSoftware macht das sehr schön)
Ich hatte mir das selber geschrieben. Also einen Thread der nichts anderes gemacht hat als immer wieder zu fragen ob was im Eingangspuffer ist, das in einen Zwischenpuffer geschrieben und den immer wieder untersucht bis darin ein vollständiger Datenblock gefunden wurde. Der Datenblock wurde dann extrahiert und ein Flag gesetzt an dem ein anderer Thread erkannt hat das es was zu tuen gibt. Kam da mal Müll dazwischen, also ein Datenblock kam nicht vollständig durch, musste der später nochmal angefragt werden. Wobei ich das mit IP Works jetzt nicht wirklich anders mache. Es läuft nur einfach schön flüssig. Aber eigentlich sollte es auch mit ReadLn gehen. Es sei denn beim Sender geht das "Ln" nicht raus. Dann wartest du bis zum Timeout für das aktuelle Paket und die dahinter türmen sich bis der interne Puffer voll ist. Zeig mal deine komplette Config für das Init vom Client. Nicht das nur in der Config was nicht zu dir passt. NSoftware : IP Works / IP Works SSL. |
AW: schnelle Server Client Verbindung ohne Verluste
Von IPWorks gibts auch eine kostenlos nutzbare (leider auch etwas eingeschränkte) Delphi-Edition über GetIt.
|
AW: schnelle Server Client Verbindung ohne Verluste
NSoftware : IP Works / IP Works SSL muss ich mir mal angucken, auch was es da über GetIt gibt.
Aktuell sieht es so aus: Server:
Delphi-Quellcode:
Im IdTCPServerConnected, IdTCPServerDisconnected und IdTCPServerException sind nur Protokollierungen. Die sind aber auch nicht zu sehen, kommen also nicht (außer natürlich das 1x Connect, da ein Client sich verbindet).
IdTCPServer.DefaultPort := PipePort;
IdTCPServer.OnConnect := IdTCPServerConnected; IdTCPServer.OnDisconnect := IdTCPServerDisconnected; IdTCPServer.OnException := IdTCPServerException; IdTCPServer.OnExecute := IdTCPServerExecute; IdTCPServer.Active := True; procedure IdTCPServerExecute(AContext: TIdContext); begin if IdTCPServer.Active then begin AContext.Connection.IOHandler.CheckForDataOnSource(10); if not AContext.Connection.IOHandler.InputBufferIsEmpty then begin InData := AContext.Connection.IOHandler.ReadLn('#~#*' + EOL, 100, -1, IndyTextEncoding_UTF8); if InData <> '' then begin //mach irgendwas end; end; end; Client:
Delphi-Quellcode:
Im IdTCPClientConnected, IdTCPClientDisconnected und IdTCPClientStatus sind nur Protokollierungen. Die sind aber auch nicht zu sehen, kommen also nicht (außer natürlich das 1x Connect, da der Client sich verbindet).
IdTCPClient1.OnConnected := IdTCPClientConnected;
IdTCPClient1.OnDisconnected := IdTCPClientDisconnected; IdTCPClient1.OnStatus := IdTCPClientStatus; IdTCPClient1.Host := aServerIP; IdTCPClient1.Port := aPort; procedure TMyThread.Execute; begin while not Terminated do begin IdTCPClient1.IOHandler.CheckForDataOnSource(10); if not IdTCPClient1.IOHandler.InputBufferIsEmpty then begin InData := IdTCPClient1.IOHandler.ReadLn('#~#*' + EOL, 100, -1, IndyTextEncoding_UTF8); if InData <> '' then begin //mach irgendwas end; end; end; Natürlich ist noch mehr Quelltext drum herum aber das ist die Implementierung von Server und Client. Wie gesagt, der Server schickt im Durchschnitt 60 Datensätze (unterschiedlicher Inhalt) pro Sekunde mit einer Länge von 13 Byte bis max 61000 Byte |
AW: schnelle Server Client Verbindung ohne Verluste
Stefan meinte wohl das hier im .Execute
Delphi-Quellcode:
Hier sollten die Daten nur weggespeichert und ggf. durch einen anderen Thread verarbeitet werden. Sonst kann hier ein Flaschenhals entstehen.
if InData <> '' then
begin //mach irgendwas end; |
AW: schnelle Server Client Verbindung ohne Verluste
Darf ich dazu mal eine "blöde" Frage stellen?
Was für Daten möchtest du denn Übertragen? Was bedeutet denn Datensätze? Ich würde es mir an deiner Stelle nicht so kompliziert machen. Um Daten von einem Server zu einem Client zu Übertragen schlägt man sich normalerweise nicht mehr mit TCP und dem ganzen geraffel herum. Best-Practice wäre aus meiner Sicht folgendes: 1.) Auf der Server Seite setzt du dir einen kleinen Webserver auf. Minimale Lösung z.B. ![]() 2.) Deine Datensätze Packst du in Klassen. Wenn du mehrere Datensätze auf einmal Schicken willst packste dir die Klassen in ein Tarray<MyClass> und lässt es danach mit TJSON.ObjectToJSONStr serialisieren. (oder du nimmst gleich irgend ein ORM, oder, oder, oder) Vorteile: Aus meiner Sicht kommst du damit locker an die von dir angesprochenen ca. 3,5 MB pro Sekunde (61000Byte x 60(Pakete/s) / 1024 / 1024) hin. Du brauchst dich nicht mehr um die Reihenfolge kümmern. Du brauchst dich nicht mehr um das ganzen TCP/IP Connection Zeug kümmern. Nachteil: Overhead des HTTP layers, aber wie gesagt, deine Datenrate solltest du damit dicke schaffen. Ansonsten kannst du die auch mal Websocket zu diesem Thema anschauen. Das ist aber eine ganze Ecke komplizierter. vG PJM |
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
Wenn man sowas "selbst in die Hand nimmt", dann würde man auf UDP setzen. Da gibt es das nicht und muss von der eigenen Anwendung geprüft werden. Vorteil wäre das man damit das dann auf Geschwindigkeit optimieren kann, wo TCP systembedingt langsamer sein muss. |
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
Ein Versuch wäre noch, beides mal wirklich in kleine separate Threads auszulagern, jetzt ist noch ein wenig Overhead drum herum was vielleicht ein Problem sein könnte. Ich werde dazu berichten. Zitat:
Zitat:
Zitat:
|
AW: schnelle Server Client Verbindung ohne Verluste
Nen extra Server aufsetzen ist wie Kanonen und Spatzen. Direkt TCPIP is schon die smarteste Lösung für so kleine Sachen.
Zitat:
Du nimmst die default Einstellungen der Komponente? Solchen Problemen ist immer schwer beizukommen. Indy Sourcen sind aber mit dabei. Da mal versucht duchzusteppen?
Delphi-Quellcode:
Ich habe jetzt die Quelltexte nicht da um zu prüfen was ReadLn macht, aber eventuell ist die Kombination aus "CheckForDataOnSource" und "ReadLn" das Problem. Was passiert wenn du "CheckForDataOnSource" weglässt?
IdTCPClient1.IOHandler.CheckForDataOnSource(10);
<...> InData := IdTCPClient1.IOHandler.ReadLn('#~#*' + EOL, 100, -1, IndyTextEncoding_UTF8); <...> |
AW: schnelle Server Client Verbindung ohne Verluste
Liste der Anhänge anzeigen (Anzahl: 3)
Zitat:
Die Einstellungen der Komponenten sind im Anhang. Zitat:
Beim Server wurde es im "IdTCPServerExecute" gebraucht, da sonst die CPU Last auf knapp 6% hoch ging. Der Tipp kam von Jaenicke hier ![]() |
AW: schnelle Server Client Verbindung ohne Verluste
Mich wundert, dass hier so auf den Indys rumgehackt wird. Ich benutze die seit Jahren ohne Probleme. Wobei ich zugeben muss, dass ich keine großen Datenmengen übertrage und auch nie die Übertragungszeit gemessen habe.
Die Verarbeitung läuft jeweils in Hintergrund-Threads. Früher war es die Version, die mit Delphi 2007 installiert wurde, inzwischen Indy10 aus dem Github-Repository. Ah ja: Die beteiligten Programme sind in Delphi 2007 und Delphi 10.2 geschrieben. |
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
Der Server muss dann mehrere Packete auf die Reihe schicken und wenn ein Packet nach Zeit x nicht bestätigt wird nochmal senden. Oder auch der Client bestätigt die Packete "im Block", oder.... |
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
Ich habe nur gerade keine Zeit mir mal einen Test zusammenzuschreiben. Wir betreiben seit Jahren verschiedene Dienste im Firmennetzwerk die via IP Works Komponenten arbeiten. Daher kenne ich die eben am besten. Um CPU Auslastung zu reduzieren verwende ich
Delphi-Quellcode:
. Das ist besser als in irgend einem Teil des Codes Zeit zu verschwenden über den du keine Kontrolle hast.
Sleep(0)
|
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
Dieses Kommunikationsmuster habe ich mit Indy mal in in einem kleinen Demoprogramm als 'umgekehrte request-response' (Anfrage/Antwort) umgesetzt. ![]() ![]() Die Oberfläche erlaubt wahlweise, dass der Client eine Anfrage an den Server sendet, oder umgekehrt, der Server eine Anfrage an den Client sendet. Es fehlt zwar etwas Fehlerbehandlung - zum Beispiel Prüfung auf ReadLnTimeout nach Readln - aber es zeigt, dass auch der Server die erste(n) Nachricht(en) senden kann, nachdem die Verbindung hergestellt ist. Der clientseitige Code läuft in einem Thread und sein Betriebsmodus wird durch ein Flag gesteuert. Entweder wird ein Request gesendet und dann auf die Antwort gewartet, oder auf einen Request gewartet und eine Antwort gesendet.
Delphi-Quellcode:
An diesem Beispiel fällt die Abwesenheit von CheckForDataOnSource auf. Dieses wird nicht benötigt, da ReadLn solange wartet, bis eine Zeile aus dem Socket gelesen wurde, oder es zu einem Timeout kommt.
while not Terminated do
begin if Send then begin // send request to server and receive response Send := False; Request := 'REQ:' + FClientRequestHolder.Text; TCPClient.IOHandler.WriteLn(Request); repeat Response := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8); until Response <> ''; LogInMainThread(Request, Response); end else begin // receive request from server and send response Request := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8); if StartsStr('REQ:', Request) then begin Response := 'from client ' + ReverseString(Request); TCPClient.IOHandler.WriteLn(Response); end; end; end; |
AW: schnelle Server Client Verbindung ohne Verluste
Hi,
@AJ_Oldendorf, there is so much can be written about this, yet don't know where to start, so i will start with what thoughts come to mind (logically ordered or not) 1) You said a lot of data between 20 and 80 packet, ( assuming packet and telegram are the same with in the translation !), see i wrote many servers and they are tested and running in real production, working with more than 400k packet per second saturating almost %50 of the 2.5Gbps connection, yet the CPU never goes above %6, some of traffic is done on MySQL, simple insert and delete but mostly update, so can TCP solve you need ?, absolutely yes. 2) In older thread you tried to get the MTU and its 1500b effect on the speed, before you go that way, i suggest lets fix you problem by understanding it, which i will try below, but going after low level is white rabbit hole, as yes it is 1500 most the time, yet it will never be like that as this is isolated by the network adapter/driver, and every OS has many option/setting that will affect this behavior, yet it will stay 1500, but you user mode software will handle it differently, i suggest to not care about these deep and low level for now, as they are not your problem, forget about tweaking low level stuff for now, don't waste your time while you problem is in different place. 3) Indy is not slow or slower of any other, all other are exactly like that, the same, all of them will serve you at the same speed, the difference will manifested in aspects you will not need, not in this case/scenario, so no matter what you will use it will serve you well. 4) Once i saw ReadLn and WriteLn, i know for sure this can't perform s***, the server will send line by line and the client will read line by line, this is a huge bottleneck in the code. 5) I see this code
Delphi-Quellcode:
and i know for sure this can't be perform (like 4), mixing lines with process in place, you can do this only if you send data very rarely, here you combined most worse approaches in one loop.
IdTCPClient1.IOHandler.CheckForDataOnSource(10);
if not IdTCPClient1.IOHandler.InputBufferIsEmpty then begin InData := IdTCPClient1.IOHandler.ReadLn(' #~#* ' + EOL, 100, -1, IndyTextEncoding_UTF8); if InData <> '' then begin //do something with it end ; end ; What is happening here ? let me give you an example how these act as sticks in wheel: 1) Server send line after line, here Nagle (if enabled) will matter, but in not much as will see below. 2) Client notified to receive 3) Client Read One Single Line, in most and slowest possible way, that handle char by char. 4) Client proceed to process One Single Line. 5) When client extracted one line from the received buffer .. well this part is important, the extraction happened in a buffer in user space owned by you own process because you received it, it could be one line or more than one, it could be one or more MTU splitting many lines, well this fact is not important as the important part is what is happening on the network at this exact moment, when you application issued read line for the first time, read all what it could from the network using API like recv, Indy with ReadLn will only process and grab from the network new recv if there is no new lines in the already received and memory loaded buffers, so in other words and shorter way... When you are handling line by line the network buffers could be filled from the peer (server here) and this will trigger stop sending ACK, so the server will stop sending lines/buffers, your application handle more and more lines and then request one buffer no matter how much in size, it will read from the network driver/provider..., triggering sending an ACK to server server will send one and wait new ACK, and you client server now locked in very slow recv-process-request, extremely slow. Suggestions: 1) start with removing WriteLn/ReadLn with something useful and performant, in fact, any other method is faster. 2) Send the data in bulk as much as you can. 3) Read as much as you can on each and every read from the socket, provide a buffer lets say 16kb buffer perform read for 16kb or 64kb, even 8kb will be fast enough as it is default for most applications, the point is when read is needed read what you can and as much as you can, not as much you need. 4) Try ICS may be you will like it, there is an obsession with old and outdated practices like this case with readln/writeln, heck.. i was shocked that TFile is still used by many, it is slow as turtle, use modern practices. 5) I saw another code above like this one by mjustin
Delphi-Quellcode:
Will comment on that one line "LogInMainThread(Request, Response);" what do you think the network stream behavior will be while the application is synchronizing with main thread, i explained above, it trigger full stop send on server, depleted socket window for this socket and many others, lost the sending momentum that build up with TCP between peers(unlike UDP which start with maximum), this behavior you see when downloading huge files, the first few seconds the speed is low then gradually build up to saturate the allowed bandwidth, as the server start to send more and more without waiting for ACK.
while not Terminated do
begin if Send then begin // send request to server and receive response Send := False; Request := ' REQ: ' + FClientRequestHolder.Text; TCPClient.IOHandler.WriteLn(Request); repeat Response := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8); until Response <> ''; LogInMainThread(Request, Response); end else begin // receive request from server and send response Request := TCPClient.IOHandler.ReadLn(IndyTextEncoding_UTF8); if StartsStr(' REQ: ', Request) then begin Response := ' from client ' + ReverseString(Request); TCPClient.IOHandler.WriteLn(Response); end ; end ; end ; Hope that helps. |
AW: schnelle Server Client Verbindung ohne Verluste
Wenn es so schnell sein muss, sollten die Daten sofort an einen anderen Thread zur Verarbeitung gegeben werden und dort erst die Pakete extrahiert werden. So bekomme ich eine sehr geringe Latenz. Client:
Delphi-Quellcode:
Server:
program Project204;
uses System.Classes, System.SysUtils, IdTCPClient, IdGlobal; type TMyTCPClient = class private FClient: TIdTCPClient; public constructor Create; destructor Destroy; override; procedure Connect(const AHost: string; APort: Integer); procedure Disconnect; procedure SendData(const Data: TIdBytes); end; { TMyTCPClient } constructor TMyTCPClient.Create; begin FClient := TIdTCPClient.Create(nil); end; destructor TMyTCPClient.Destroy; begin Disconnect; FreeAndNil(FClient); inherited; end; procedure TMyTCPClient.Connect(const AHost: string; APort: Integer); begin FClient.Host := AHost; FClient.Port := APort; FClient.ConnectTimeout := 5000; // 5 Sekunden Timeout FClient.ReadTimeout := 5000; // 5 Sekunden Timeout für Lesevorgänge FClient.Connect; Writeln('Verbunden mit ', AHost, ':', APort); end; procedure TMyTCPClient.Disconnect; begin if FClient.Connected then begin FClient.Disconnect; Writeln('Verbindung getrennt.'); end; end; procedure TMyTCPClient.SendData(const Data: TIdBytes); begin if FClient.Connected then begin FClient.IOHandler.Write(Data); Writeln(Now, 'Gesendet: ', Length(Data), ' Bytes'); end else Writeln('Fehler: Nicht verbunden.'); end; var MyClient: TMyTCPClient; TestData: TIdBytes; begin try MyClient := TMyTCPClient.Create; try MyClient.Connect('127.0.0.1', 5000); SetLength(TestData, 1024); FillChar(TestData[0], Length(TestData), 65); for var i := 1 to 1000000 do MyClient.SendData(TestData); Readln; MyClient.Disconnect; finally FreeAndNil(MyClient); end; except on E: Exception do Writeln('Fehler: ', E.Message); end; end.
Delphi-Quellcode:
program Project203;
uses System.Classes, System.SysUtils, System.SyncObjs, IdTCPServer, IdContext, IdGlobal, System.Generics.Collections; type TDataQueue = class private FQueue: TQueue<TIdBytes>; FLock: TCriticalSection; public constructor Create; destructor Destroy; override; procedure Enqueue(const Data: TIdBytes); function Dequeue: TIdBytes; end; TProcessingThread = class(TThread) private FDataQueue: TDataQueue; protected procedure Execute; override; public constructor Create(ADataQueue: TDataQueue); end; TMyTCPServer = class private FServer: TIdTCPServer; FDataQueue: TDataQueue; FProcessingThread: TProcessingThread; procedure OnExecuteHandler(AContext: TIdContext); public constructor Create; destructor Destroy; override; procedure Start; procedure Stop; end; { TDataQueue } constructor TDataQueue.Create; begin FQueue := TQueue<TIdBytes>.Create; FLock := TCriticalSection.Create; end; destructor TDataQueue.Destroy; begin FQueue.Free; FLock.Free; inherited; end; procedure TDataQueue.Enqueue(const Data: TIdBytes); begin FLock.Acquire; try FQueue.Enqueue(Data); finally FLock.Release; end; end; function TDataQueue.Dequeue: TIdBytes; begin FLock.Acquire; try if FQueue.Count > 0 then Result := FQueue.Dequeue else SetLength(Result, 0); finally FLock.Release; end; end; { TProcessingThread } constructor TProcessingThread.Create(ADataQueue: TDataQueue); begin FDataQueue := ADataQueue; inherited Create(False); end; procedure TProcessingThread.Execute; var Data: TIdBytes; begin while not Terminated do begin Data := FDataQueue.Dequeue; if Length(Data) > 0 then begin Writeln('Empfangen: ', Length(Data), ' Bytes'); end else Sleep(1); end; end; { TMyTCPServer } constructor TMyTCPServer.Create; begin FDataQueue := TDataQueue.Create; FProcessingThread := TProcessingThread.Create(FDataQueue); FServer := TIdTCPServer.Create(nil); FServer.DefaultPort := 5000; FServer.OnExecute := OnExecuteHandler; end; destructor TMyTCPServer.Destroy; begin Stop; FreeAndNil(FServer); FreeAndNil(FProcessingThread); FreeAndNil(FDataQueue); inherited; end; procedure TMyTCPServer.OnExecuteHandler(AContext: TIdContext); var Buffer: TIdBytes; begin SetLength(Buffer, 8192); while AContext.Connection.IOHandler.InputBuffer.Size > 0 do begin AContext.Connection.IOHandler.ReadBytes(Buffer, Length(Buffer), False); FDataQueue.Enqueue(Buffer); end; end; procedure TMyTCPServer.Start; begin FServer.Active := True; end; procedure TMyTCPServer.Stop; begin FServer.Active := False; end; var MyServer: TMyTCPServer; begin try MyServer := TMyTCPServer.Create; MyServer.Start; Writeln('Server läuft auf Port 5000. Drücke Enter zum Beenden.'); Readln; MyServer.Stop; FreeAndNil(MyServer); except on E: Exception do Writeln('Fehler: ', E.Message); end; end. |
AW: schnelle Server Client Verbindung ohne Verluste
Danke jaenicke und danke an Kas Ob.
Ich habe verstanden, dass TCPIP und Indy mein Grundprinzip locker abdecken können. Ich darf nur nicht mit ReadLn und WriteLn arbeiten. Ich werde versuchen, meine Software umzugestalten (auf getrenntes Senden und Empfangen in Threads) sowie ohne Terminator und TIdBytes sowie immer Maximum lesen. Ich werde berichten... |
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
|
AW: schnelle Server Client Verbindung ohne Verluste
Bei mir läuft das so 1:1 durch. Wenn das bei dir hängt, ist die Frage, wo der Server dann hängt. Da es bei mir nicht passiert, tappe ich da im Dunkeln, da müsstest du mal im Debugger schauen...
|
AW: schnelle Server Client Verbindung ohne Verluste
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Anbei mal der Stack (kann man den aus der IDE auch direkt als Text speichern ???). Im OnExecuteHandler werden im ersten Durchlauf 8192 von den vorliegenden 10240 Bytes abgeholt, der zweite Durchlauf für die restlichen 2048 Bytes führt dann zum gezeigten Stack so weit ich ihn verfolgt habe. Leider führt der Befehl "Pause" für einen hängenden Prozess ja nur zu einem leeren Stack. Der etwas irritierende Parameterwert "-2" im ReadFromSource() ist nicht der Original-Aufrufparameter sondern der vom Code dann auf "IdTimeoutInfinite" geänderte. |
AW: schnelle Server Client Verbindung ohne Verluste
Ach so, ja, das ist Absicht. Es ging ja um Geschwindigkeitstest, da habe ich den Fall, dass ein Paket oder eine Gruppe von Paketen (8192 Byte) nicht vollständig ankommt, nicht behandelt.
|
AW: schnelle Server Client Verbindung ohne Verluste
Ich habe früher viel RealThinClient genutzt, auch mittlerweile OpenSource. Ist sehr stabil, schnell und einfach zu implementieren
|
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
I either do low level using API for extra tweaking or RTC for anything else, also notice with RTC, you don't need to use its components and classes from the palate, but can use the RAW ones for TCP and UDP, they are powerful and comes with threading (pool and sync... etc) out-of-the-box that can be switched from Multithreaded (asynchronous) to Single-thread (synchronous) by flipping a bool. |
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
Indy verwendet einen 32K (default) großen Buffer für das Empfangen von Daten. Man kann die Buffergröße bei Bedarf auch noch erhöhen (siehe TIdIOHandler.SendBufferSize property). Auch beim Senden wird ein 32k (default) großer Buffer verwendet. Dass IOHandler.ReadLn / WriteLn schlechtere Performance hat, wäre schon vor Jahren Thema in den einschlägigen Delphi-Foren gewesen, wenn es denn wahr wäre. |
AW: schnelle Server Client Verbindung ohne Verluste
@jaenicke
Ich habe dein Code mal minimal angepasst gemäß deinem Beitrag hier: ![]() Server:
Delphi-Quellcode:
Client:
program Server;
uses System.Classes, System.SysUtils, System.SyncObjs, IdTCPServer, IdContext, IdGlobal, System.Generics.Collections, System.Diagnostics; type TDataQueue = class private FQueue: TQueue<TIdBytes>; FLock: TCriticalSection; public constructor Create; destructor Destroy; override; procedure Enqueue(const Data: TIdBytes); function Dequeue: TIdBytes; end; TProcessingThread = class(TThread) private FDataQueue: TDataQueue; Anz : Integer; protected procedure Execute; override; public constructor Create(ADataQueue: TDataQueue); end; TMyTCPServer = class private FServer: TIdTCPServer; FDataQueue: TDataQueue; FProcessingThread: TProcessingThread; procedure OnExecuteHandler(AContext: TIdContext); public constructor Create; destructor Destroy; override; procedure Start; procedure Stop; end; { TDataQueue } constructor TDataQueue.Create; begin FQueue := TQueue<TIdBytes>.Create; FLock := TCriticalSection.Create; end; destructor TDataQueue.Destroy; begin FQueue.Free; FLock.Free; inherited; end; procedure TDataQueue.Enqueue(const Data: TIdBytes); begin FLock.Acquire; try FQueue.Enqueue(Data); finally FLock.Release; end; end; function TDataQueue.Dequeue: TIdBytes; begin FLock.Acquire; try if FQueue.Count > 0 then Result := FQueue.Dequeue else SetLength(Result, 0); finally FLock.Release; end; end; { TProcessingThread } constructor TProcessingThread.Create(ADataQueue: TDataQueue); begin FDataQueue := ADataQueue; Anz := 0; inherited Create(False); end; procedure TProcessingThread.Execute; var Data: TIdBytes; sw3 : TStopwatch; t3 : Int64; begin while not Terminated do begin Data := FDataQueue.Dequeue; if Length(Data) > 0 then begin Inc(Anz, Length(Data)); Writeln('Empfangen: ', Length(Data), ' Bytes' + '- Anz: ' + Anz.ToString); Writeln('Gesamtlänge: ' + Anz.ToString + ' Bytes'); end else Sleep(1); end; end; { TMyTCPServer } constructor TMyTCPServer.Create; begin FDataQueue := TDataQueue.Create; FProcessingThread := TProcessingThread.Create(FDataQueue); FServer := TIdTCPServer.Create(nil); FServer.DefaultPort := 5000; FServer.OnExecute := OnExecuteHandler; end; destructor TMyTCPServer.Destroy; begin Stop; FreeAndNil(FServer); FreeAndNil(FProcessingThread); FreeAndNil(FDataQueue); inherited; end; procedure TMyTCPServer.OnExecuteHandler(AContext: TIdContext); var Buffer: TIdBytes; begin //SetLength(Buffer, 61000); //<- nicht feste größe einlesen while AContext.Connection.IOHandler.InputBuffer.Size > 0 do begin SetLength(Buffer, AContext.Connection.IOHandler.InputBuffer.Size); //<- so viel einlesen wie im Buffer enthalten ist AContext.Connection.IOHandler.ReadBytes(Buffer, Length(Buffer), False); FDataQueue.Enqueue(Buffer); end; end; procedure TMyTCPServer.Start; begin FServer.Active := True; end; procedure TMyTCPServer.Stop; begin FServer.Active := False; end; var MyServer: TMyTCPServer; begin try MyServer := TMyTCPServer.Create; MyServer.Start; Writeln('Server läuft auf Port 5000. Drücke Enter zum Beenden.'); Readln; MyServer.Stop; FreeAndNil(MyServer); except on E: Exception do Writeln('Fehler: ', E.Message); end; end.
Delphi-Quellcode:
Folgendes Erscheinungsbild:
program Client;
uses System.Classes, System.SysUtils, IdTCPClient, IdGlobal, System.Diagnostics; type TMyTCPClient = class private FClient: TIdTCPClient; public constructor Create; destructor Destroy; override; procedure Connect(const AHost: string; APort: Integer); procedure Disconnect; procedure SendData(const Data: TIdBytes); end; { TMyTCPClient } constructor TMyTCPClient.Create; begin FClient := TIdTCPClient.Create(nil); end; destructor TMyTCPClient.Destroy; begin Disconnect; FreeAndNil(FClient); inherited; end; procedure TMyTCPClient.Connect(const AHost: string; APort: Integer); begin FClient.Host := AHost; FClient.Port := APort; FClient.ConnectTimeout := 5000; // 5 Sekunden Timeout FClient.ReadTimeout := 5000; // 5 Sekunden Timeout für Lesevorgänge FClient.Connect; //FClient.IOHandler.RecvBufferSize := 32768; //FClient.IOHandler.SendBufferSize := 32768; //FClient.IOHandler.RecvBufferSize := 61440; //FClient.IOHandler.SendBufferSize := 61440; Writeln('Verbunden mit ', AHost, ':', APort); end; procedure TMyTCPClient.Disconnect; begin if FClient.Connected then begin FClient.Disconnect; Writeln('Verbindung getrennt.'); end; end; procedure TMyTCPClient.SendData(const Data: TIdBytes); begin if FClient.Connected then begin FClient.IOHandler.Write(Data); //Writeln(Now, ' Gesendet: ', Length(Data), ' Bytes'); end else begin Writeln('Fehler: Nicht verbunden.'); end; end; var MyClient: TMyTCPClient; TestData: TIdBytes; Anz : LongWord; begin try MyClient := TMyTCPClient.Create; try MyClient.Connect('127.0.0.1', 5000); var sw3 := TStopwatch.StartNew; var t3 : Int64; SetLength(TestData, 61000); //1024 FillChar(TestData[0], Length(TestData), 65); Anz := 0; for var i := 1 to 20 do begin Inc(Anz, Length(TestData)); MyClient.SendData(TestData); end; t3 := sw3.ElapsedMilliseconds; //Zeitmessung stoppen Writeln('Zeitdauer: ' + t3.ToString + ' ms'); Writeln('Gesamtlänge: ' + Anz.ToString + ' Bytes'); Readln; MyClient.Disconnect; finally FreeAndNil(MyClient); end; except on E: Exception do Writeln('Fehler: ', E.Message); end; end. 1) Client sendet mit 20 Telegrammen in Summe 1220000 Bytes, wird auch angezeigt 2) Server empfängt in 38 Telegrammen die 1220000 Bytes, wird auch angezeigt 3) CPU Auslastung vom Server steigt danach auf 5-7% ohne das weitere Daten empfangen werden oder vom Client geschickt werden Beim OnExecuteHandler vom Server habe ich eine kleine Anpassung gemacht (siehe Kommentar). Frage: Woran liegt das mit der CPU Auslastung und wie bekommt man das gelöst? Müsste der Server nicht auch 20 Telegramme á 61000 Bytes empfangen, anstatt 38 Stück? |
AW: schnelle Server Client Verbindung ohne Verluste
@jaenicke
Ich push einfach mal. Hast du dazu eine Lösung/Idee? Es ist ja so, dass mein Client immer unterschiedlich lange Telegramme schickt. Die können von 13 Byte bis 61000 Byte lang sein. Damit muss der Server klar kommen und ich dachte, ich kann beim Server dann entsprechend so viel Byte einlesen, wie der InputBuffer lang ist. |
AW: schnelle Server Client Verbindung ohne Verluste
@AJ_Oldendorf i am no expert in Indy at all, as i hate the s*** out of it due to exception raising policy, but while waiting for someone to resolve this for you, let me put few thoughts
1) Make sure you are blocking on the socket means as long you existing the loop then it will return and this will raise the CPU usage up to full Thread/Core. 2) ReadLn is blocking capable and that why it used everywhere. 3) Guess what ?! ReadBytes also blocking capable too. so try this
Delphi-Quellcode:
On side note this blocking might need timeout adjustment, so adjust AContext.Connection.IOHandler.ReadTimeout to something short, it could be 30 second or even shorter will be fine, this might affect how much concurrent connection you expect, if few then put it 1 second (timeout = 1000) and it will be find.
procedure TMyTCPServer.OnExecuteHandler(AContext: TIdContext);
var Buffer: TIdBytes; begin if not AContext.Connection.Connected then Exit; AContext.Connection.IOHandler.ReadBytes(Buffer, -1, True); // block and wait ! , while True for append (best practice) in case there is leftover if Length(Buffer) > 0 then FDataQueue.Enqueue(Buffer); { //SetLength(Buffer, 61000); //<- nicht feste größe einlesen while not AContext.Connection.IOHandler.InputBufferIsEmpty do begin SetLength(Buffer, AContext.Connection.IOHandler.InputBuffer.Size); //<- so viel einlesen wie im Buffer enthalten ist AContext.Connection.IOHandler.ReadBytes(Buffer, Length(Buffer), False); FDataQueue.Enqueue(Buffer); end;} end; about the hate for Indy, it comes form where an exception will surprise you, well from almost every line/operation, so i think some try..except is due somewhere in your client and server, but again you need someone else to help with what i wrote (which could be not the optimal) and exception handling..... or Try somethin else ICS has plenty of examples/samples and it is way more friendly with its events, Indy still valuable and will stay the most protocol stuffed library in pascal. |
AW: schnelle Server Client Verbindung ohne Verluste
ok, ich glaube es verstanden zu haben
Delphi-Quellcode:
Durch das Sleep(1) funktioniert es auch (dass die CPU Auslastung nicht ansteigt), wenn kein Empfang mehr stattfindet.
while AContext.Connection.IOHandler.InputBuffer.Size > 0 do
begin SetLength(Buffer, AContext.Connection.IOHandler.InputBuffer.Size); AContext.Connection.IOHandler.ReadBytes(Buffer, Length(Buffer), False); FDataQueue.Enqueue(Buffer); end; Sleep(1); Nur ist mir nicht klar, warum der Client 20 Telegramme á 61000 Byte abschickt und der Server 38 Stück empfängt wobei die meisten 32768 Byte lang sind und am Ende ein kürzeres. Warum nicht auch 20 Telegramme? |
AW: schnelle Server Client Verbindung ohne Verluste
Evtl. 32k Buffersize?
|
AW: schnelle Server Client Verbindung ohne Verluste
Zitat:
Delphi-Quellcode:
procedure TMyTCPServer.OnExecuteHandler(AContext: TIdContext);
var Buffer: TIdBytes; begin if AContext.Connection.IOHandler.InputBuffer.Size > 0 then begin while AContext.Connection.IOHandler.InputBuffer.Size > 0 do begin SetLength(Buffer, AContext.Connection.IOHandler.InputBuffer.Size); //<- so viel einlesen wie im Buffer enthalten ist AContext.Connection.IOHandler.ReadBytes(Buffer, Length(Buffer), False); FDataQueue.Enqueue(Buffer); end; end else Sleep(1); end; Zitat:
|
AW: schnelle Server Client Verbindung ohne Verluste
@jaenicke: Danke für den Hinweis mit dem Sleep.
Beim Empfang muss ich sowieso die Telegramme wieder entsprechend zusammensetzen. Wollte es nur verstanden haben. EDIT: Das mit dem Sleep in deiner Variante funktioniert leider nicht. Da geht die CPU Auslastung auch auf 7% hoch nachdem der Empfang fertig ist |
AW: schnelle Server Client Verbindung ohne Verluste
Ach, Entschuldigung, das ist ja auch verkehrt herum. Ich habe es korrigiert. Die Beschreibung war korrekt, der Quelltext nicht. Das Sleep muss ins Else.
|
AW: schnelle Server Client Verbindung ohne Verluste
Ich danke dir :-)
|
AW: schnelle Server Client Verbindung ohne Verluste
@jaenicke:
Ich muss doch nochmal fragen, wie würde in deinem Beispiel denn die Implementierung aussehen, wenn der Client auch empfangen kann auf Telegramme vom Server? Da gibt es die
Delphi-Quellcode:
ja nicht also müsste man zyklisch das pollen über einen Thread machen oder?
procedure OnExecuteHandler
|
AW: schnelle Server Client Verbindung ohne Verluste
Ja, das kannst du mit einem Thread machen, der pollt. Aber du musst dann auch sicherstellen, dass nicht gleichzeitig gelesen und geschrieben wird, sprich einen entsprechenden Lock verwenden.
Ich finde diese Architektur nicht schön, aber es funktioniert. |
AW: schnelle Server Client Verbindung ohne Verluste
@jaenicke:
Hm, ein Thread, der zyklisch liest (wie in deinem Beispiel) und ein Thread, der zyklisch schreibt, geht nicht? Ich muss die beiden untereinander wieder synchronisieren? Irgendwie habe ich erwartet, dass das die Indy intern machen :-( |
AW: schnelle Server Client Verbindung ohne Verluste
Du kannst z.B. das reine Senden und Empfangen mit TMonitor absichern:
Delphi-Quellcode:
TMonitor.Enter(AContext.Connection.IOHandler);
try AContext.Connection.IOHandler.ReadBytes... finally TMonitor.Exit(AContext.Connection.IOHandler); end; |
AW: schnelle Server Client Verbindung ohne Verluste
Also beim Server, habe ich das Senden in einem Thread jetzt so ausgelagert:
Delphi-Quellcode:
Zeitmessung 02 schlägt regelmäßig zu, 300ms ist normal (pro Aufruf), manchmal sogar länger.
var
SendMsg : PAnsiString; Daten : AnsiString; outMsg : TIdBytes; ... if ClientContext.Connection.Connected then begin while (Sendeliste.Count > 0) do begin var sw4 := TStopwatch.StartNew; var t4 : Int64; SendMsg := Sendeliste.Items[0]; Daten := SendMsg^; SetLength(outMsg, Length(Daten)); Move(Daten[1], outMsg[0], Length(Daten)); t4 := sw4.ElapsedMilliseconds; //Zeitmessung stoppen //Zeitmessung 01 auswerten sw4 := TStopwatch.StartNew; //Zeitmessung starten ClientContext.Connection.IOHandler.Write(outMsg); t4 := sw4.ElapsedMilliseconds; //Zeitmessung stoppen //Zeitmessung 02 auswerten sw4 := TStopwatch.StartNew; //Zeitmessung starten Dispose(SendMsg); Sendeliste.Delete(0); t4 := sw4.ElapsedMilliseconds; //Zeitmessung stoppen //Zeitmessung 03 auswerten end; end Hast du eine Idee dazu? Das ReadEvent habe ich über ein Merker geblockt, sodass in dem Moment kein Schreiben möglich ist. Da findet also in dem Moment nichts statt. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 03:55 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