![]() |
Spielwiese - SocketTest
Liste der Anhänge anzeigen (Anzahl: 3)
Hi,
im Bereich "Tutorial" wäre es nicht korrekt aufgehoben, daher mal hier... Ich habe ein Testprojekt (XE3) mit ServerSocket und ClientSocket erstellt. Es gibt einen "ClientManager", der die Mausereignisse erhält und mit dem Server kommuniziert und als Ergebnis ein Bitmap erstellt, das im Formular dargestellt wird. Die Clientlogik läuft also in dem Manager und das Formular dient nur als grafische und Ereignisschnittstelle. Im Servermanager auf der anderen Seite läuft eine eigenständige "Businesslogik" in einem eigenen Thread. Änderungen in der Datenschicht (hier testweise alle paar Millisekunden) werden an die Clients geschickt, die die Daten empfangen und ein neues Bitmap erstellen. So hält der Server einen kompletten Datenbestand und die Clients zeichnen sich entsprechend der übertragenen Daten. Ein wenig könnte man das vielleicht mit einem simplen Spiel vergleichen. Der Anwendungsfall macht natürlich wenig Sinn, aber - ich habe zwei konkrete Fragen, die Ihr vielleicht beantworten könnt - vielleicht können wir die Struktur der Anwendung verbessern und die mit Sicherheit vorhandenen problematischen Teile optimieren. - vielleicht ist das ja für andere mal von Interesse - evtl. könnte man auch ein etwas ernsthafteres Projekt daraus weiterentwickeln (vielleicht ein kleines Netzwerkspiel für die DP oder das von Mavarik angedachte Ameisenprojekt?) um daran weiter zu lernen - nur so eine Idee... Meine Fragen: 1) Bei mir arbeitet der Server oft deutlich (10-30 mal) langsamer als normal, wenn die Delphi-IDE geschlossen ist. Starte ich die IDE, läuft er wieder normal, schließe ich sie, ist er wieder langsamer. Das ist jedoch nicht immer so (im verlinkten Video z.B. nicht). Kann das jemand einordnen? 2) Ich erhalte einen "asnchronen Fehler" vom ServerSocket, wenn ein Client geschlossen wird. Das Problem liegt darin, dass die in einem eigenen Thread laufende "Businesslogik" ständig (alle paar ms) Änderungsmeldungen an alle Clients verschickt. Ich habe zwar versucht, diese mit dem Mainthread zu synchronisieren, aber das reicht nicht. Der zu schließende Socket hat wohl schon die Kill-Nachricht vom Server, befindet sich aber noch einen Moment in der Socketliste und wird somit vom BL-Thread noch mit beim Nachrichtenversand berücksichtigt. Das führt dann zu einem Fehler. Ich finde keine Möglichkeit, die Kill-Nachricht rechtzeitig zu erkennen und somit keine Rundumnachricht mehr an den Kill-Socket zu senden. Kann da jemand helfen? Anbei das XE3-Projekt. Die Bibliothekspfade müsste man bei Bedarf einrichten, so dass die Nachnutzung leider etwas aufwendiger wird (ggf. kann ich dazu noch eine Übersicht erstellen, was erforderlich wäre). Die Exen sind in den Release-Ordnern. In den Ini´s könnte man andere Ports und für die Clients andere IPs eintragen (habe ich aber noch nicht getestet). Hier ein Link zu einem Video: ![]() (Sind aber 18 min, also lohnt sich nur, wenn Ihr direktes Interesse am Thema habt.) Bin gespannt, ob das für irgendwen von Interesse ist... |
AW: Spielwiese - SocketTest
Liste der Anhänge anzeigen (Anzahl: 2)
Mal eine neuere Version.
Die IP und Port in der Ini wird jetzt auch berücksichtigt. :oops: (Ich war wohl doch zu übermüdet.) Die Exen werden jetzt zentral unter D:\SocketTest erzeugt. Das ist m.E. übersichtlicher. Ich habe das jetzt auch über WLAN und Internet getestet. Der Server schickt 4*360 Änderungen pro Sekunde an die Clients. Lokal sieht das flüssig aus. Über Netzwerk hakelt es natürlich. Natürlich ist das nicht optimiert, aber es soll ja auch mal ein Stresstest sein. Als nächstes will ich mal protokollieren, ob alle Daten bei den Clients ankommen oder ob welche verloren gehen. Kann ja ein einfacher Integer sein, der laufend erhöht wird. Werde ich nächste Woche mal tun. Dann will ich auch prüfen, wie die Sockets sich im blockierenden Modus verhalten. |
AW: Spielwiese - SocketTest
Zitat:
|
AW: Spielwiese - SocketTest
... oder was es für Probleme geben kann, wenn der Server massenweise Nachrichten verschickt und die Clients nicht hinterherkommen würden.
Wie gesagt, ich kann diese ganzen Grenzfälle nicht wirklich einordnen und werde das daher mal "am eigenen Leib" versuchen... ;-) |
AW: Spielwiese - SocketTest
Hallo stahli
Wenn du dich nur auf eigene Mittel verlassen willst, dann lass doch den Server in gewissen Abständen (zum Beispiel abhängig von der Zeit oder von der Menge der gesendeten Daten) eine Nachricht [Inhalt: Sendezeit] an den Client senden, welche der Client rausfischt und an den Server zurücksendet. Dann weisst du relativ genau, wie schnell/gut deine Leitung ist. Gruss M |
AW: Spielwiese - SocketTest
Liste der Anhänge anzeigen (Anzahl: 2)
Ich bin hin und her gerissen ... aber multidimensional! :freak:
Erst mal zu einem festgestellten Problem: Nicht blockierende Sockets unterhalten sich ungeordnet. Aus drei gesendeten Texten Text1 Text2 Text3 kann auf der Empfängerseite Text1Text2Text3 werden. Im Screenshot 1 sieht man, dass die eigentlich einzeln gesendeten Stringlisten auf der Gegenseite verbunden ankommen. Möglicherweise werden sie u.U. auch mittendrin geteilt, das habe ich nicht untersucht. Man muss somit klare Trennzeichen definieren: Text1* Text2* Text3* würde dann Text1*Text2*Text3* und könnte wieder zerlegt werden oder man könnte bei übertragenen Stringlisten festlegen, wie viele der nächsten Zeilen zu einem command gehören: 3 SetXY 10 10 2 SetName stahli Also man braucht genaue Regeln, damit man sie wieder ordentlich trennen bzw. zusammenfügen kann, ehe man sie verarbeitet. Was aber gravierender ist, ist dass die Reihenfolge bzw. der Zeitpunkt der Textsendungen nicht klar vorherzusehen ist (siehe Screenshot2). Es ist insofern nachvollziehbar, da ja für den Mainthread Messages erzeugt werden, die dann nach und nach abgearbeitet werden. Wenn ich also in meiner Demo vielfach pro Sekunde automatisch Daten vom Server verschicke (und vielleicht von Clients aus zwischendurch auch noch Rückfragen starte), werden die Clients mit Messages bombardiert und sind voll beschäftigt, diese abzuarbeiten. Von 1000 Zwischenständen werden somit 999 gar nicht sinnvoll berücksichtigt oder es werden 100 Rückfragen in Form von Messages "gesammelt", obwohl dies natürlich nur einmal sinnvoll wäre. Entsprechend muss man entweder die Frequenz reduzieren oder auf Clientseite feststellen, dass gerade nur der letzte erhaltene Zwischenstand relevant ist - oder beides. Eine Rückfrage bezüglich einer bestimmten Information dürfte nicht nochmal rausgehen, sofern schon eine gleichartige erzeugt wurde und noch nicht beantwortet ist. Das müsste somit extra verwaltet werden, da man schlecht die WindowsMessageQueue durchsuchen kann (oder diese vielleicht gerade schon unterwegs zum Server ist). Ein Chatbeispiel ist für non blocking Sockets sicherlich gut geeignet, nicht aber unbedingt ein Projekt, das auf Serverseite ständig neue Änderungen erzeugt oder erzeugen kann und viele Clients darüber informieren will. Wenn man dies mit non blocking Sockets realisieren will muss der Server am besten für jeden Client verwalten, wann dieser zuletzt über welche Änderungen informiert wurde und Änderungsmitteilungen etwas verzögert und optimiert versenden. Der Client dürfte seinerseits nicht sofort neue Daten abrufen sondern müsste auch schauen, wann ein guter Zeitpunkt dafür ist und den Status aller Anfragen intern selbst verwalten - obwohl die Antwort über Windows-Nachrichten kommt. Also so etwas ist sicherlich möglich, aber kann sicher sehr schnell sehr aufwendig werden. Jetzt könnte man natürlich auf den blockierenden Modus umstellen, aber dann kann ich ja auch bei den Indys bleiben, mit denen ich eine funktionierende Kommunikation schon im Griff hatte. Ich hatte mir nur gedacht, dass ich mit den nicht blockierenden Sockets performanter bin und halt ohne pulls auskomme. Schicken die non blocket sockets eigentlich interne Pulls und erzeugen letztlich auch eine Last auf dem Netzwerk und der CPU? Oder sind Pulls aller 1/2 Sekunde von den Clients schon teurer? Long Pulls über Threads wären natürlich auch denkbar, aber da würde ich doch regelmäßige kurze Pulls bevorzugen, in denen Zeitstempel abgerufen werden. Wenn man ein Projekt jetzt doch mit den non blocket sockets aufbaut, könnte man das Projekt dann grundsätzlich irgendwie auf andere Plattformen übertragen? Sicherlich doch nicht, da man dann ja an die Windows Messages gebunden ist - oder? Mein Zwischenfazit ist derzeit, dass die Indys wohl doch keine so schlechte Lösung sind und man eben mit zyklischen Pulls oder LongPulls leben muss. Weiteres Fazit ist, dass es scheinbar kein vernünftiges Tutorial gibt, das diese ganzen Zusammenhänge mal beleuchtet... :roll: |
AW: Spielwiese - SocketTest
Zitat:
|
AW: Spielwiese - SocketTest
Du beziehst Dich aber auf long polling, bei dem der Client einen extra Thread startet, der bis zur Antwort vom Server blockiert - richtig?
Dann muss aber auch der Server eine Connection pro Client offen halten - richtig? Das würde bedeuten, dass man nicht tausende sondern höchstens hunderte Clients verbinden kann - richtig? Wenn der Client keine Antwort vom Server erhält (was ja der Standardfall sein sollte) und seinerseits jetzt Änderungen veranlassen oder weitere Daten abrufen will, muss er dann eine weitere Verbindung aufbauen oder kann er den wartenden Thread abbrechen, seine Kommunikation abwickeln und wieder in den Wartemodus gehen? Indys hätten keine Nachteile gegenüber blockierenden Sockets, abgesehen von den erweiterten Möglichkeiten und unterstützen Protokollen? Also die Kommunikation und der Ablauf an sich wären quasi identisch - richtig? |
AW: Spielwiese - SocketTest
Zitat:
Zitat:
![]() Zitat:
![]() Zitat:
|
AW: Spielwiese - SocketTest
Zitat:
![]() Zitat:
|
AW: Spielwiese - SocketTest
Zitat:
Von zwei Millionen simultanen Connections auf einem einzigen Linux Server berichtete WhatsApp bereits im Jahr 2012: ![]() |
AW: Spielwiese - SocketTest
Vielleicht gefallen dir auch die ICS Komponenten von F.Piette:
![]() Ich habe damit ein Spiel programmiert, bei welchem nur bei der Anmeldung in den Onlinespielraum die Server eine wichtige Rolle spielen. Danach sind möglichst alle Spiele mit allen anderen via möglichst schnelle Wege via tcp verbunden [also jedes Spiel ist dann ein wenig Server und jedes ein wenig Client]. Es klappt prima :). Viel Spass bei deinem Projekt. |
AW: Spielwiese - SocketTest
Zitat:
Zitat:
Das wären dann aber beides asynchrone non blocking Lösungen. Zwischenzeitlich war ich der Meinung, dass die Indys vom Ansatz her sinnvoller wären. Sie arbeiten zwar blockierend aber die Kommunikation ist dafür klarer und nachvollziehbarer. Was mich hier stört, ist die schlechtere Scalierbarkeit. Es steht zwar aktuell nicht an, aber wenn es mal 1000 gleichzeitige Zugriffe auf einen Server gäbe, käme man mit den Indys halt (offenbar) in Probleme. Insofern dachte ich mir, jetzt doch die Kommunikation asynchron abzuwickeln und diese selbst zu optimieren (also pro Client nicht mehrfach die selbe Information zu schicken und somit nicht massenweise Events anzuhäufen). Eigentlich klingt das ganz gut (etwas mehr Aufwand aber auch mehr Leistung als bei den Indys) --- ABER --- ich würde die Events gern in eine eigene Warteschlange stellen und diese in einem eigenen Thread abarbeiten, also keine Windows-Messages erzeugen, sondern eigene in Form von Records oder Objekten. Diese sollten in einer threadsaven Liste abgelegt werden (wenn sie vom Manger als relevant eingestuft werden) und dann in einem folgenden Schritt verarbeitet werden. So könnte man z.B. folgendes lösen: Alle Paar ms liefert der Server mir zur Anzeige meinen aktuellen Kontostand. Da dieser gerade erwartungsgemäß ansteigt 5..34..2345..194322... gibt es ständige Updates. Da die Client-GUI gerade so schnell nicht hinterherkommt könnte der Manager die vorherige Konto-Info verwerfen und mit der neusten überschreiben. So hat die GUI dann nur eine Änderung darzustellen. Sofern natürlich alle Änderungen relevant sind, könnte der Manager diese chronologisch sammeln. Also das wäre meine bevorzugte Lösung -> non blocket sockets aber ohne an Windows(-Nachrichten) gebunden zu sein. Aber so etwas gibt es nicht - oder? ... ähh ... halt ... Ok, man könnte sich in der OnClientRead-Behandlung eine Liste von eigenen Nachrichten erzeugen... Dann wäre es nur Aufgabe des Mainthreads, die Nachrichten für den Manager aufzubereiten, wo diese dann verarbeitet werden. Aber damit wäre man immer noch auf Windows festgelegt und könnte nicht so einfach auf eine andere Plattform wechseln. Also steht die Entscheidung wohl zwischen 1) Indy als einfache Lösung mit der Option anderer Plattformen, aber eingeschränkter Client-Anzahl 2) non blocket Sockets (oder ICS) als performantere Lösung mit mehr eigenem Verwaltungsaufwand aber Festlegung auf Windows Ich bin weiterhin verwirrt und für Äußerungen dankbar. EDIT: Mein gewünschtes Modell wäre wohl so etwas wie ein IOCP-Server. Das schaue ich mir heute Abend nochmal genauer an. Aber wäre das auch auf anderen Plattformen umsetzbar? UDP ist für mich kein Thema. Nachrichten müssen sicher ankommen. |
AW: Spielwiese - SocketTest
Zitat:
Ein Serverprozess, der laufend Nachrichten erzeugt ---> (Sehr) viele Clients, die nur die Nachrichten erhalten sollen (via "Server Push") Das geht plattformübergerifend mit einem Message Broker wie RabbitMQ: Delphi Server ---> RabbitMQ --- Delphi Clients clientseitig: * alle Clients verbinden sich nur mit dem Message Broker. Das bedeutet je Client eine einzige Verbindung (z.B. Indy Sockets) zum RabbitMQ Server. * die Clients 'abonnieren' nach dem Verbindungsaufbau einen benannten logischen Nachrichtenkanal beim RabbitMQ Server, dessen Nachrichten sie erhalten möchten * der Client wartet auf Nachrichten serverseitig: * der Server baut eine einzige Verbindung zum Message Broker auf * der Server sendet Nachrichten an den logischen Nachrichtenkanal auf dem Message Broker, falls welche gesendet werden sollen * der Message Broker sendet diese Nachricht an alle Clients, die diesen Nachrichtenkanal abonniert haben So wird erreicht, dass der Delphi Server nie mehr als eine Socket Verbindung benötigt. Ob die Nachrichten an einem einzigen, tausend oder zehntausend Clients zugestellt werden, macht für den Delphi Prozess keinen Unterschied. Es laufen nicht für jeden Client separate Threads im Delphi Prozess. Und das Beste: auch wenn zehntausend Clients gleichzeitig Nachrichten an den Delphi-Server senden wollen: der Delphi benötigt nur eine einzige Socket-Verbindung, zum RabbitMQ Server. p.s. RabbitMQ ist natürlich nur ein Beispiel, es ginge ebenso mit einem ActiveMQ oder Artemis Message Broker (alle open source) |
AW: Spielwiese - SocketTest
Zitat:
![]() ![]() |
AW: Spielwiese - SocketTest
@mjustin
Mein realer Anwendungsfall sieht eher so aus, dass die Clients sich Daten gezielt beim Server abholen, und zwar danach, was sie gerade anzeigen sollen. Also der eine Client stellt die Kundenliste dar, und nach einem Pagewechsel jetzt den Fahrplan der DB. Ein anderer Client zeigt ein Kochrezept an. Sie sagen jeweils dem Server, was sie gerade brauchen, puffern das und stellen die Daten dar. Ein anderer Client Ändert jetzt irgend etwas in der Datenschicht. Z.B. wird ein Kunde hinzugefügt. Derzeit ändert (in meinem Realprojekt, nicht in der Demo hier) der Server einen Zeitstempel auf Now. Die Client fragen regelmäßig, den aktuellen Serverzeitstempel ab. Wenn der jetzt neuer ist als bei der letzten Abfrage holen sie sich die o.g. Daten (bzw. die, die sie gerade darstellen) neu vom Server ab und zeichnen die GUI neu. Das heißt, einen Moment werden noch die Daten aus dem Puffer angezeigt (oder wenn nicht vorhanden leere Controls oder Zellen) und nach der Aktualisierung umgehend die neuen Daten. Da das als lazy loading abläuft gibt es ein paar kurze Verzögerungen, die ich noch etwas optimieren wollte. Das heißt, dass die Clients sich im Regelfall schon ganz bestimmte Informationen abrufen müssen. Ein automatischer Rundruf vom Server könnte sich eigentlich auf ein "Es gibt neue Daten!" beschränken. Insofern war die Demo hier etwas missverständlich zu meiner echten Zielstellung. Ich wollte hier nur mal den Server testweise extrem beschäftigen ... und habe festgestellt, dass dies nicht so funktioniert wie ich erwartet hatte. So wie ich Message Broker inzwischen verstanden habe wären die für meine reale Zielstellung eher nicht hilfreich (da die Clients ja direkt bestimmte Daten nach ihren individuellen Bedürfnissen abfordern bzw. gezielte Änderungen an bestimmten Daten initiieren). @jaenicke Ok, danke! EDIT: Idee ... Oh :idea: Ich versuche da mal was. Kann ein paar Tage dauern... :coder: |
AW: Spielwiese - SocketTest
Zitat:
Die meisten Message Broker unterstützen auch Kommunikation in der Art eines Remote Procedure Calls (RPC). Dazu erzeugt jeder Client einen individuellen, nur für ihn gültigen Nachrichtenkanal auf dem Server (der beim Verbindungsende gelöscht wird). Dann sendet der Client seine spezielle Message an einen Nachrichtenkanal (zum Beispiel: "ich brauche das Rezept für Boeuf Stroganoff"). Und innerhalb dieser Nachricht gibt der Client an, dass er die Antwort auf seinem individuellen Kanal erwartet. Der Message Broker leitet die Nachricht an den Delphi Server weiter, schlägt das Rezept nach, und sendet es los. So kann ein Message Broker individuelle Anfragen der Clients an den Delphi Server weiterleiten, und die Antworten an den richtigen Client zurück senden. Dabei bleibt der große Vorteil des Message Brokers erhalten, dass der Delphi Server nur eine einzige Socket-Verbindung zu Message Broker aufbaut. Unabhängig von der Zahl der Clients, deren RPC Requests er beantwortet. |
AW: Spielwiese - SocketTest
Zitat:
Im Grunde gibt es mehrere Module (Interfaces), die miteinander verbunden - aber auch austauschbar - sind. Die Businesslogik und die GUI-Logik können IMessages für die Gegenseite erstellen und diese ihrem Handler übergeben. Der Handler entscheidet, wann er die Message wirklich versendet und übernimmt das gesamte Protokoll. Ebenso zerlegen die Handler beim Empfang von Nachrichten die (verbunden)Pakete in einzelne Häppchen, fordern sich von der Businesslogik bzw. GUI-Logik (bzw. von deren MessageResolvern) entsprechende MessageInterfaces ab und entscheiden wiederum selbstständig, wann sie diese Messages tatsächlich zur Verarbeitung weiter geben. Man kann Messages eine Priorität zuweisen oder den Status Singleton oder einen Timerwert. Messages mit Priotät werden in der Messagequeue vorn eingefügt. Singletons werden, falls sich schon eine gleichartige Message in der Queue befindet, durch die neue Message überschrieben (so lange die alte halt noch nicht verarbeitet wurde). Timerwerte bewirken, dass Nachrichten dieser Identität frühestens n ms nach dem letzten Versand verschickt bzw. verarbeitet werden. So kann man durch die Einstellung der IMessage-Eigenschaften den Handlern direkt vorgeben, wie sie den Versand optimieren sollen. In der Buinesslogik und GUI-Logik muss man nur noch die Messages erzeugen, aus erhaltenen StringListen wieder parsen und in der Logik verarbeiten. Die Handler nutzen ihrerseits Module für die tatsächliche Datenübertragung (aktuell bei mir mit non blocking sockets). Diese Module könnte man grundsätzlich auch austauschen, so dass man auch z.B. Indys nutzen könnte, ohne die Business- oder GUI-Logik ändern zu müssen. Besteht Interesse an der Lösung? Dann würde ich nochmal ein Video erstellen und das Projekt hier einstellen. Bis hierher sieht das gut aus. Ein paar Tage brauche ich dann aber noch für den Rest. |
AW: Spielwiese - SocketTest
Hallo Stahli,
natürlich besteht da Interesse. Dein Video war schon beeindruckend. Habe leider aktuel wenig Zeit, sonst hätte ich das mal selbst ausprobiert. |
AW: Spielwiese - SocketTest
Ja super, dann mache ich das mal die nächsten Tage fertig.
Das Problem an meiner ersten Lösung war, dass dort nur immer die erste von mehreren verbundenen Nachrichten verarbeitet wurde und die Geschäftslogik sich mit den Texten herumschlagen musste. Kamen beim Client z.B. erst command1 command2 command3 und dann command2 command1 command3 an, wurde jeweils nur die erste Nachricht geparst und der Rest ignoriert - bis ich dann diese Verkettung der Nachrichten erkannt habe. Das lösen jetzt meine Handler automatisch auf, wandeln die Textnachrichten in IMessages um, die auch die Geschäftslogik verarbeiten kann und geben diese kontrolliert zur Weiterverarbeitung raus. Die einzelnen Aufgaben werden in einzelnen Threads abgearbeitet und die Nachrichten werden in threadsicheren (hoffentlich) Queues gepuffert. So muss die Geschäfts- und GUI-Logik jetzt nur noch IMessages erstellen und verarbeiten und man muss sich um den Nachrichtenfluss nicht mehr selbst kümmern. |
AW: Spielwiese - SocketTest
Liste der Anhänge anzeigen (Anzahl: 2)
Ok, hier mal das Projekt (XE3 und Exen) und ein neues Video:
![]() Das Video dauert 40 min. Es ist also sicher nur interessant für diejenigen, die an asynchroner Kommunikation direktes Interesse haben. (Dies ist übrigens mein 4. Aufnahmeversuch gewesen, da ich bei vorherigen Versuchen diverse Probleme und Abbrüche bis hin zu einem BlueScreen hatte. Also bitte seht mir nach, wenn es nicht so ganz strukturiert ist. Der Nerv-Faktor war schon etwas erhöht. :? Notfalls kann ich ja nochmal nachliefern.) Ich denke, der Ansatz könnte ganz tauglich sein. Aber sicher sind noch einige Bugs und Optimierungsmöglichkeiten vorhanden. Auch das Problem mit dem gelegentlichen Fehler beim Abmelden eines Clients konnte ich noch nicht vollständig lösen. Da ich das Konzept jetzt mal in einem anderen Projekt umsetzen will würde mich mal interessieren, was Ihr dazu meint und wer daran auch selbst Interesse hat. Insbesondere ist natürlich interessant, wenn Ihr hier Probleme erkennt bzw. Lösungen für diese habt. Natürlich ist das Projekt recht komplex und umfangreich, aber wenn wir eine stabile Version erreichen könnte das m.E. ein sehr nützliches Package für eine allgemeine und einfache asynchrone Kommunikation werden. Es wäre also nett, wenn Ihr mal Rückmeldungen bezüglich Interesse und Verbesserungen gebt. Gruß Stahli |
AW: Spielwiese - SocketTest
Ich nutze das Paket jetzt etwas erweitert in einem realen Programm. Das macht auf meinem Win10-Rechner und Win10-Laptop auch schon einen sehr guten Eindruck.
Auf meinem dienstlichen Rechner (Win7 Prof, 32bit) werden im Client aber nicht alle Daten dargestellt. Teilweise funktioniert es, aber nicht vollständig oder nur kurze Zeit. Eine TCP-Verbindung besteht zumindest anfangs mal. Welche einfachste Möglichkeit gibt es, den TCP-Traffic von außen zu loggen (ohne selbst etwas einzubauen)? Wenn ich sehen würde, ob überhaupt noch Nachrichten ausgetauscht werden, könnte ich das Problem vielleicht schon mal etwas eingrenzen. Ich habe hier keine Adminrechte und kann daher nichts installieren. Gibt es dennoch eine Möglichkeit, den Traffic etwas einzusehen? |
AW: Spielwiese - SocketTest
Zitat:
![]() EDIT: Zitat:
Aber vielleicht gibt es ja eine Version die als Portable genutzt werden kann. :| EDIT 2: Ja auf der DownloadSeite gibt es eine ![]() |
AW: Spielwiese - SocketTest
Vielen Dank.
Ich habe mir jetzt aber doch ein eigenes provisorisches Logging eingebaut (aus Zeitgründen noch nicht fertig). Ich kann schon mal sehen, dass beim Client nicht alles ankommt. Wird irgendwas beim Server klemmen. Mal schauen... |
AW: Spielwiese - SocketTest
Bei den non-blocking Sockets musst du beim Senden aufpassen:
Zitat:
Zitat:
![]() Was zusätzlich immer passieren kann (blocking und non-blocking), dass die Daten eines einzigen send()-Aufrufs in mehreren recv()-Aufrufen ankommen. Irgendeine Form von Pakettrennung muss man also zwingend selbst implementieren (Längenbasiert oder anhand von Trennzeichen). Aber das machst du soweit ich mich erinnere ja indirekt mit deinen String-Listen. |
AW: Spielwiese - SocketTest
Ich habe den Übeltäter. ;-)
Längere Texte werden ggf. bei der Übertragung gesplittet. Mein Handler hatte hier noch einen Bug und hat die unvollständigen Teile schon verarbeitet. Das Problem trat auf meinen 2 Rechnern nicht auf, aber bei allen anderen getesteten. Das lag wohl daran, wie die einzelnen Threads jeweils zeitlich zum arbeiten kommen. Also falls das jemand nutzen will dann hier ein Bugfix:
Delphi-Quellcode:
function TsoConnectionStringList.GetNextSL: TStringList;
var C: Integer; begin fCS.Enter; try if (fIndex < fItems.Count) then begin C := StrToInt(fItems[fIndex]); fItems[fIndex] := ''; Inc(fIndex); Result := TStringList.Create; while (C > 0) do begin Result.Add(fItems[fIndex]); fItems[fIndex] := ''; Inc(fIndex); Inc(fTransferCounter); Dec(C); end; if (fIndex >= fItems.Count) then begin fItems.Clear; fIndex := 0; end else begin if (fIndex > 1000) then begin while (fItems.Count > 0) and (fItems[0] = '') do begin fItems.Delete(0); Dec(fIndex); end; end; end; end else Result := nil; finally fCS.Leave; end; end; |
AW: Spielwiese - SocketTest
Die letzte Änderung reicht doch nicht aus.
Also mein Konzept ist gut einsetzbar für kurze Stringlisten-Messages. Wem das ausreicht, der kann das gut einsetzen. Längere Stringlisten werden aber von den Sockets in 2 oder mehr Teilen übertragen und auf Grund der Zeilenumbrüche nicht immer korrekt zusammengesetzt. Wer also auch längere Stringlisten übertragen will braucht eine alternative Lösung. Ich bin daher dabei, die Stringlisten in Streams zu speichern und die Streams zu übertragen. Das hat auch den Vorteil, dass man die Streams bei Bedarf leichter komprimieren bzw. verschlüsseln kann. Orientiert habe ich mich an folgendem Beitrag: ![]() Jetzt habe ich das Problem, dass Read(...) manchmal feuert, Socket.ReceiveLength aber 0 ist und entsprechend keine Daten geladen werden können. Read(...) feuert aber später nicht noch einmal, so dass Daten verloren gehen. Wenn ein Stream vollständig angekommen ist wird er wieder in eine StringList geschrieben und in eine Queue gehängt zur weiteren Verarbeitung durch Threads. Kann sein, dass ich hier etwas verbockt habe, aber in der Nacht konnte ich da nichts mehr finden. Eigentlich sollten die Verarbeitungsthreads ja die Eriegnisbehandlung im Mainthread nicht stören. Solange ich Strings übertragen hatte war das auch nicht der Fall. (Blöderweise spinnt mein USB-Stick schon wieder, so dass ich hier gerade keinen Code dabei habe und ihn gerade nicht posten kann.) Hat jemand so pauschal eine Idee, wie das Problem Socket.ReceiveLength=0 zu lösen ist? |
AW: Spielwiese - SocketTest
Zitat:
Delphi-Quellcode:
Operationen des Buffers zu minimieren. Beispielsweise könntest du noch ein zustäzliches Feld
Realloc
Delphi-Quellcode:
pflegen, mit dem du dir diese Aktionen sparst (müssten dann nur einmalig nach der Schleife ausgeführt werden, aber nicht bei jedem Durchlauf). Falls deine Pakete nicht in anderen Threads abgearbeitet werden, kannst du dir auch das Auslesen der Paketdaten sparen und deinem Paket-Handler einfach einen Zeiger auf
BufferPos
Delphi-Quellcode:
zurückgeben, etc. Auch würde ich den Buffer nie komplett auf Null shrinken, sondern immer mindestens X-Byte alloziiert lassen, damit du bei kleineren Paketen im Optimalfall sogar komplett auf
@Buffer[BufferPos]
Delphi-Quellcode:
verzichten kannst.
Realloc
Edit: Achso und wie sieht deine Sende-Routine aus? Bei non-blocking Sockets kann ![]() ![]() |
AW: Spielwiese - SocketTest
Sind es blocking oder non-blocking Sockets?
Zitat:
Blockierender Ansatz wäre eigentlich so: Server (wie im Original):
Delphi-Quellcode:
Client:
var
lLen: Integer; lStream: TStream; begin lStream := TFileStream.Create('c:\testbild.bmp', fmOpenRead); lLen := lStream.Size; // grösse Stream ermitteln Socket.SendBuf(lLen, SizeOf(lLen)); // Grösse senden Socket.SendStream(lStream); // dann das Bild hinten dran... end;
Delphi-Quellcode:
Der nicht-blockierende Ansatz wäre so wie Zacherl beschrieben hat.
var
lLen: Integer; lStream: TStream; Buffer: Pointer; begin Socket.RecvBuf(lLen, SizeOf(lLen)); lStream := TFileStream.Create('c:\testbild.bmp', fmOpenWrite); // Im einfachsten Fall. Besser wäre es natürlich, einen Buffer fester Größe // zu erzeugen und mit einer Schleife zu arbeiten, damit der RAM nicht gesprengt wird. GetMem(Buffer, lLen); Socket.ReceiveBuf(Buffer^, lLen); lStream.Write(Buffer^, lLen); FreeMem(Buffer); lStream.free; end; Wieso genau der Fehler bei dir kommt, weiß ich nicht, aber ich hatte ja schon mal geschrieben, dass blockierend und eventgetrieben sich nicht gut verträgt. |
AW: Spielwiese - SocketTest
Liste der Anhänge anzeigen (Anzahl: 1)
Meine Anwendung ist nicht blockierend.
Ich habe gestern erst mal meinen bisherigen Ansatz in einer repeat-Schleife gepackt und den Ablauf etwas geloggt. Im Eingang in die Methode hat ReceiveLength z.B. 8 ausgegeben. Drei Zeilen weiter dann ggf. 100. Also verändert sich der Wert auch schon mal dynamisch. Until habe ich dann bei "ReceiveLength = 0" veranlasst. Im Ergebnis holt sich die Behandlung zunächst einen Integerwert für die Streamgröße sobald 4 Bytes verfügbar sind und den Stream selbst sobald dieser komplett verfügbar ist. So sollte das m.E. ja eigentlich funktionieren - hat es auch. Aber sobald ich die Logs raus genommen habe lief es wieder nicht mehr. Da muss wohl irgendwo anders noch ein (Thread-)Problem sein. In der Nacht um 3 habe ich dann erst mal aufgegeben. :-/ Hier mal der Quelltext (mit dem Logging), der funktionierte. PS: Ich schaue mir auch Zacherls Hinweis nochmal an, aber ich dachte, meine aktuelle Lösung würde quasi das Gleiche machen...
Delphi-Quellcode:
...Send
MS := TMemoryStream.Create; lSL.SaveToStream(MS); MS.Seek(0, soBeginning); MSSize := MS.Size; fServerSocket.Socket.Connections[I].SendBuf(MSSize, SizeOf(MSSize)); if fServerSocket.Socket.Connections[I].SendStream(MS) then LogSL('==>', '', lSL) else LogSL('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==>', '', lSL); //============================================== ...Read var iLen: Integer; Bfr: Pointer; SL: TStringList; lP: Int64; rl: Integer; iL: Integer; begin if fff then Codesite.Send('ffffffffffffffffffffffffffff'); fff := True; repeat rl := Socket.ReceiveLength; Codesite.Send('Read: ' + IntToStr(rl)); Codesite.Send('a: ' + IntToStr(Socket.ReceiveLength)); if not Assigned(fMS) then begin Codesite.Send('not Assigned(fMS)'); if Socket.ReceiveLength >= SizeOf(fMSSize) then begin Codesite.Send('Socket.ReceiveLength >= SizeOf(fMSSize)'); iL := Socket.ReceiveBuf(fMSSize, SizeOf(fMSSize)); Codesite.Send('reading=' + IntToStr(iL)); fMS := TMemoryStream.Create; end else Codesite.Send('NOT Socket.ReceiveLength >= SizeOf(fMSSize)'); end else Codesite.Send('NOT not Assigned(fMS)'); Codesite.Send('b: ' + IntToStr(Socket.ReceiveLength)); if Assigned(fMS) then begin Codesite.Send('Assigned(fMS)'); iLen := Socket.ReceiveLength; Codesite.Send('*** ' + IntToStr(iLen) + '/' + IntToStr(fMSSize)); iLen := Min(fMSSize, iLen); GetMem(Bfr, iLen); try iL := Socket.ReceiveBuf(Bfr^, iLen); Codesite.Send('reading=' + IntToStr(iL)); Dec(fMSSize, fMS.Write(Bfr^, iLen)); if Socket.ReceiveLength > 0 then begin Codesite.Send('!!!REST'); end; // lP := fMS.Position; // fMS.Position := 0; // SL := TStringList.Create; // SL.LoadFromStream(fMS); // SL.Insert(0, IntToStr(SL.Count)); // LogSL('<--', '', SL); // FreeAndNil(SL); // fMS.Position := lP; finally FreeMem(Bfr); end; Codesite.Send('c: ' + IntToStr(Socket.ReceiveLength)); if fMSSize = 0 then begin Codesite.Send('fMSSize = 0'); fMS.Position := 0; SL := TStringList.Create; SL.LoadFromStream(fMS); SL.Insert(0, IntToStr(SL.Count)); FreeAndNil(fMS); fMessageHandlerServer.RegisterInSL(SL, Socket); FreeAndNil(SL); end else Codesite.Send('NOT fMSSize = 0'); end else Codesite.Send('NOT Assigned(fMS)'); Codesite.Send('----: ' + IntToStr(Socket.ReceiveLength)); until (Socket.ReceiveLength = 0); Codesite.Send('----------------------: ' + IntToStr(Socket.ReceiveLength)); fff := false; end; |
AW: Spielwiese - SocketTest
Zitat:
Also ich muss vorweg sagen, ich habe mit TClientSocket/TServerSocket nie gearbeitet, wohl aber mit der dahinterliegenden BSD-Socket bzw. WinSock-API. Dort ist es so, dass die Funktion recv() einen Wert zurückliefert, der angibt, wieviele Bytes tatsächlich gelesen wurde (oder -1 falls keine Bytes gelesen werden konnten oder im Falle eines Fehlers). Ein Rückgabewert von 0 bedeutet, dass die Verbindung geschlossen wurde. Das könnte hier der Fall sein. Das kannst du aber nur herausfinden, indem du tatsächlich ein recv ausführst, ReceiveLength ist irreführend. Wenn du deinen Code nicht überall ändern willst, kannst du einfach 1 Byte lesen und als Flag MSG_PEEK übergeben. So wird das Byte nicht "konsumiert" und der restliche Programmablauf bleibt unbeeinflusst.
Delphi-Quellcode:
var
Dummy: Byte; begin if Socket.ReceiveBuf(Dummy, 1, MSG_PEEK) = 0 then // Verbindung geschlossen; |
AW: Spielwiese - SocketTest
Zitat:
Zitat:
Deine Send-Routine enthält auf jeden Fall das von mir beschriebene Problem, dass ![]() Zitat:
|
AW: Spielwiese - SocketTest
Zitat:
Ich versuche es noch mal in Tabellenform:
Code:
| Verbindung besteht | Verbindung geschlossen
Lesbare Bytes vorhanden | > 0 | -- Keine lesbaren Bytes vorhanden | -1 | 0 Fehler | -1 | -1 (Wie zur Hölle verwendet man den [TABLE]-BBCode? :duck: ) |
AW: Spielwiese - SocketTest
Zitat:
Mein echtes Projekt hatte Indy genutzt, also blocking. Das stelle ich derzeit entsprechend um. Zitat:
Die Datenmengen sind derzeit auch sehr klein, so dass diese kein Problem darstellen sollte. Ich gehe derzeit eher von einem Threading-Problem aus. Aber ich schaue mir das die nächsten Tage nochmal genauer an. Danke Euch! |
AW: Spielwiese - SocketTest
Also das Problem war hausgemacht durch die Umstellung auf Streams und einige kritische Zugriffe durch Threads.
Ich habe das jetzt bereinigt und es läuft augenscheinlich perfekt. Anbei die wesentlichen Auszüge, falls es jemanden hilft. Das komplette Framework werde ich hier nicht hochladen, da es scheinbar nicht sehr von Interesse ist. Falls doch, schreibt eine pm.
Delphi-Quellcode:
procedure ...SendSLTo...(aSL: TStringList);
var MS: TMemoryStream; MSSize: LongInt; begin MS := TMemoryStream.Create; aSL.SaveToStream(MS); MS.Seek(0, soBeginning); MSSize := MS.Size; fClientSocket.Socket.SendBuf(MSSize, SizeOf(MSSize)); fClientSocket.Socket.SendStream(MS); end; procedure ...SocketClientRead(Sender: TObject; Socket: TCustomWinSocket); const BufSize = 1024 * 10; var Len: Integer; Bfr: Pointer; begin GetMem(Bfr, BufSize); repeat Len := Socket.ReceiveBuf(Bfr^, BufSize); if (Len > 0) then fMessageHandlerServer.RegisterInBuffer(Bfr, Len, Socket); until (Len <= 0); FreeMem(Bfr); end; procedure ...RegisterInBuffer(const aBufr: Pointer; const aLen: Integer; const aConnection: TObject); var lMS: TMemoryStream; lSL: TStringList; BreakFlag: Boolean; begin fCS.Enter; try if (aLen > 0) then begin fMS.Seek(0, soEnd); fMS.Write(aBufr^, aLen); end; BreakFlag := False; repeat if (fBlockSize = 0) then begin if (fMSPos + SizeOf(fBlockSize) <= fMS.Size) then begin fMS.Position := fMSPos; fMS.Read(fBlockSize, SizeOf(fBlockSize)); fMSPos := fMS.Position; end; end; if (fBlockSize > 0) then begin if (fMSPos + fBlockSize <= fMS.Size) then begin fMS.Position := fMSPos; lMS := TMemoryStream.Create; lMS.CopyFrom(fMS, fBlockSize); fMSPos := fMS.Position; lSL := TStringList.Create; lMS.Position := 0; lSL.LoadFromStream(lMS); LogSL('<==', '', lSL); RegisterInSL(lSL, aConnection); FreeAndNil(lMS); fBlockSize := 0; end else BreakFlag := True; end else BreakFlag := True; until (BreakFlag); if (fBlockSize = 0) then begin fMS.Clear; fMSPos := 0; end; finally fCS.Leave; end; end; procedure ...DoRegisterInSL(out aDone: Boolean); var lSL: TStringList; lMessage: IsoMessage; lConnectionStringList: IsoConnectionStringList; lConnection: TObject; begin aDone := False; lConnectionStringList := fMessageStringList.GetNextConnectionStringList; if Assigned(lConnectionStringList) then begin lConnection := lConnectionStringList.Connection; lSL := lConnectionStringList.GetNextSL; if Assigned(lSL) then begin if Assigned(fMessageImporter) then begin fMessageImporter.ResolveSLToMessage(lSL, lConnection, lMessage); if Assigned(lMessage) then begin fInMessageList.Add(lMessage); aDone := True; end; end; FreeAndNil(lSL); end; end; end; |
AW: Spielwiese - SocketTest
Bei dem letzten veröffentlichten Stand gibt es noch Probleme bei umfangreichen gleichzeitigen Zugriffen durch mehrere Clients, da bis dahin nur ein Puffer für eingehende Streams verwendet wurde.
Das nur als Info, falls jemand bei Tests Probleme bekommt. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:30 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