TCP-Server Programmierung
Moderatoren: Andreas Damm, Jens Haupert
-
- Beiträge: 4718
- Registriert: 28.04.2002 12:56:00
- Kontaktdaten:
Re: TCP-Server Programmierung
Ich hänge hier gerade ein bisschen.
Inzwischen habe ich meinen Empfänger so umgerüstet, dass die Daten über einen zweiten Thread empfangen werden. Dieser läuft in einer Endlosschleife und ruft ständig Socket.Receive() auf, eben so wie Roland es erklärt hat. Die Daten, die Receive ausspuckt, werden in einem byte-Array zwischengelagert und sollen von dort an einen MemoryStream angehängt werden. Die Funktion, die die empfangenen Daten im Sinne des Protokolls auswertet, soll dann die jeweils benötigte Menge Bytes aus dem Stream wieder auslesen und interpretieren.
Ursprünglich habe ich hier eine generische Queue verwendet, allerdings erschien mir die wie ein Holzhammer für die fünf Bytes, die ich herumschieben will.
Mein Problem jetzt: Wenn ich Daten aus dem Stream lese, kommen nur Nullen raus. Sehe ich das richtig, dass MemoryStream für Lese- wie für Schreibvorgänge den selben Positionszeiger verwendet?
In dem Falle bitte ich um Alternativvorschläge. Bittedanke. :-)
P.S:
C# mit .NET unter MSVS'08 ist die absolut genialste Programmierumgebung, in der ich je gearbeitet habe. So produktiv war ich noch nie...
Der Soundthesizer-Code in Delphi bestand gefühlt zur Hälfte aus Wrapperklassen für Listen, um für jede Klasse einen passenden Listentyp zu haben. Dank Generika schnurrt das jetzt einfach mit einem Schlag auf eine Zeile pro Typ zusammen. Yay! Momentan entdecke ich praktisch täglich spannende neue Sachen, die einem das Leben leichter machen.
Gibt es eigentlich schon irgendwo verlässliche Benchmarks, mit denen man den "C# stinkt weil Interpreter !!!111einseinself"-Ketzern den Wind aus den Segeln nehmen kann? Oder ist es wirklich so lahm und ich hab es nur noch nicht bemerkt? :-)
Servus
Andi
Inzwischen habe ich meinen Empfänger so umgerüstet, dass die Daten über einen zweiten Thread empfangen werden. Dieser läuft in einer Endlosschleife und ruft ständig Socket.Receive() auf, eben so wie Roland es erklärt hat. Die Daten, die Receive ausspuckt, werden in einem byte-Array zwischengelagert und sollen von dort an einen MemoryStream angehängt werden. Die Funktion, die die empfangenen Daten im Sinne des Protokolls auswertet, soll dann die jeweils benötigte Menge Bytes aus dem Stream wieder auslesen und interpretieren.
Ursprünglich habe ich hier eine generische Queue verwendet, allerdings erschien mir die wie ein Holzhammer für die fünf Bytes, die ich herumschieben will.
Mein Problem jetzt: Wenn ich Daten aus dem Stream lese, kommen nur Nullen raus. Sehe ich das richtig, dass MemoryStream für Lese- wie für Schreibvorgänge den selben Positionszeiger verwendet?
In dem Falle bitte ich um Alternativvorschläge. Bittedanke. :-)
P.S:
C# mit .NET unter MSVS'08 ist die absolut genialste Programmierumgebung, in der ich je gearbeitet habe. So produktiv war ich noch nie...
Der Soundthesizer-Code in Delphi bestand gefühlt zur Hälfte aus Wrapperklassen für Listen, um für jede Klasse einen passenden Listentyp zu haben. Dank Generika schnurrt das jetzt einfach mit einem Schlag auf eine Zeile pro Typ zusammen. Yay! Momentan entdecke ich praktisch täglich spannende neue Sachen, die einem das Leben leichter machen.
Gibt es eigentlich schon irgendwo verlässliche Benchmarks, mit denen man den "C# stinkt weil Interpreter !!!111einseinself"-Ketzern den Wind aus den Segeln nehmen kann? Oder ist es wirklich so lahm und ich hab es nur noch nicht bemerkt? :-)
Servus
Andi
- Roland Ziegler
- Beiträge: 5522
- Registriert: 04.11.2001 22:09:26
- Wohnort: 32U 0294406 5629020
- Kontaktdaten:
Re: TCP-Server Programmierung
Irgendeine Art Rollspeicher (oder Queue) ist wohl auch gar nicht erforderlich. Ein simples Byte-Array, aus dem dann mit BinaryReader via MemoryStream typsicher herausgelesen wird, reicht völlig aus. Das geschieht, sowie das Telegramm komplett empfangen wurde. Nennt man auch Deserialisierung. Dann hast Du eine interne Struktur. Die kann so aussehen, wie Du es jetzt hast, als Tagged Values. Ein enum, der den Wert-Typ beschreibt als Schlüssel und den Wert selbst, in der Ausbildung float oder int oder bool usw.Andreas Karg hat geschrieben:Ich hänge hier gerade ein bisschen.
Inzwischen habe ich meinen Empfänger so umgerüstet, dass die Daten über einen zweiten Thread empfangen werden. Dieser läuft in einer Endlosschleife und ruft ständig Socket.Receive() auf, eben so wie Roland es erklärt hat.
Jetzt kannst Du wählen, ob Callback oder Delegate/Event. Bei Callback müsstest bei der Konstruktion eines Objektes Deiner Socket-Wrapper-Klasse ein Interface übergeben, das als Methoden die Weitergabe Deiner Tagged Values erlaubt. Liegt ein zu empfangenes Telegramm fertig vor und hast Du die Tagged Values extrahiert, rufst Du damit schlicht die Methoden des Callback-Interfaces auf.
Die Anwendungsklasse, die Deinen Socket-Wrpper erzeugt, implementiert besagtes Callback-Interface und bekommt dann die Daten ereignismäßig und automatisch nach Eintreffen im Socket zugestellt.
Delegate/Event ist nicht so viel anders. Delegates sind ja eigentlich nur typsichere Function-Pointer, mit denen man Callbacks auch ohne Interface-Verpackung realisieren kann (und die mir besser gefallen als anonyme Klassen in Java, die demselben Zweck dienen, und die man inzwischen auch in C# nutzen kann, vielleicht für Java-Umsteiger). Events sind Delegates mit mehreren Abnehmern.
Ein Vorteil von Delegates/Events wäre die erwähnte mögliche Single-Task-Umgebung für den Anwender. Beim klassischen Weg über Callback erfolgt der Aufruf beim Anwender ja aus Deinem Socket-Receive-Thread heraus. Das ist insbesondere für Einsteiger in die Softwareentwicklung höchst gefährlich, wird man doch Verriegelung und Synchronisierung von Objekten nicht als erstes lernen.
Implementiert die zentrale Klasse des Anwenders ein GUI, dann bekommt man einen Windows-Event-Loop mit dazu. Den kann man nutzen, um ihn von außen mit Daten zu füttern. Der Event-Loop hat seinen eigenen Thread und über diesen Einheits-Thread verteilt der Event-Loop auch sämtliche extern veranlasste Events. (Das ist nicht .Net-spezifisch, sondern gilt für alle GUI-Systeme, ob Win32 oder X11). Die Schnittstelle in .Net dazu heißt ISynchronizeInvoke. Wenn man sie selber ausfüllen wollte, müsste man so etwas wie ein "Active Object" realisieren (siehe dazu den erwähnten Herrn Schmidt). Irgendeine der GUI-Basisklassen, die mit dem Event-Loop, implementiert aber besagtes ISynchronizeInvoke. Natürlich ist der damit verbundene Taskwechsel langsam. Faktor 35 habe ich mal gemessen. Der Vorteil für die Nutzung aber erheblich.
Da ist sicher was dran. Produktivität ist immer auch das Stichwort, das als erstes fällt, wenn es um für und wider von .Net geht.C# mit .NET unter MSVS'08 ist die absolut genialste Programmierumgebung, in der ich je gearbeitet habe. So produktiv war ich noch nie...
Das habe ich allerdings bei Delphi nie verstanden. Collections oder in C++ Container sind mit das nützlichste, was uns die Sprachentwickler in den letzten 15 Jahren beschert haben. Deren Verwendung predige ich seit Jahren. Warum sich bei Delphi nichts adäquates etabliert hat, kann ich nicht beantworten. Ich denke, es hat mit dem Hintergrund vieler Entwickler zu tun. Das Entwicklervolk ist ja auch der entscheidende Unterschied zwischen C# und Visual Basic. Die Sprachen sind vom Ergebnis her gleichwertig, die Leute, die sie anwenden, im Schnitt doch sehr verschieden. Java hatte gescheite Collections recht früh. Auch .Net hatte ja schon welche, wenn auch noch recht mäßige in 1.0 und 1.1, beide Java und .Net damals noch typunsicher auf Object bezogen, weil es noch keine Generik gab. Von C++-Seite ist aber offensichtlich viel und erfolgreich Druck gemacht worden. Java hat dann nachgezogen, wenn auch in vereinfachter Form, um binärkompatibel zu bleiben. Generik war der erste Schritt in Richtung funktionaler Sprachelemente. LINQ, wie immer man so so etwas in einer OO-Sprache stehen mag, wäre ohne Generik nicht denkbar.Der Soundthesizer-Code in Delphi bestand gefühlt zur Hälfte aus Wrapperklassen für Listen, um für jede Klasse einen passenden Listentyp zu haben. Dank Generika schnurrt das jetzt einfach mit einem Schlag auf eine Zeile pro Typ zusammen.
Die Mächtigkeit ist zugleich Risiko. Insbesondere die ganze Reflection-Geschichte verleitet mich immer wieder dazu, an Stellen tiefer einzusteigen als ich eigentlich wollte, ob das beispielsweise nun XML-Serialisierung oder der PropertyGrid mit seinen TypeConverters ist.Momentan entdecke ich praktisch täglich spannende neue Sachen, die einem das Leben leichter machen.
Schwer zu sagen. Ich würde weiterhin keine Hochlastaufgaben mit .Net lösen. JIT-Compiling und GC finden statt und brauchen Zeit. Bei Java wurde viel diskutiert und optimiert. .Net hat die Ergebnisse mit der Generationsverwaltung übernommen. Der Entwickler hat aber auch in .Net wesentlichen Einfluss auf den Durchsatz. Objekte mit sehr kurzer Lebensdauer sind harmlos, selbst wenn es viele sind bei hoher Dynamik. Teuer sind diese mittleren Dinger, die quer referenzieren und hin und her wandern, dann irgendwann in Vergessenheit geraten. Die schlagen beim Aufräumen voll zu.Gibt es eigentlich schon irgendwo verlässliche Benchmarks, mit denen man den "C# stinkt weil Interpreter !!!111einseinself"-Ketzern den Wind aus den Segeln nehmen kann? Oder ist es wirklich so lahm und ich hab es nur noch nicht bemerkt? :-)
OpenBVE ist eine Entwicklung in C#, allerdings sehr unorthodox in der Weise, dass C# als prozedurale Sprache genutzt wird. Die Klassen sind nur Module für die Verpackung lauter statischer Methoden. Ob sich der Entwickler davon erhofft, um GC drumherum zu kommen? Für die Grafik wird dort ein .Net-Wrapper zu OpenGL verwendet.
Seit ein paar Wochen ist dieses OpenRails-Projekt öffentlich, das einen MSTS-kompatiblen Neubau schaffen will. Dort kommt auch C# zum Einsatz, Grafik basierend auf XNA.
MS selbst sortiert XNA und Spieleentwicklung in .Net noch als "casual", "home" oder "academic" ein. Bei "professional" schlagen sie weiterhin unmittelbar DirectX und C++ vor.
Wenn es aber nicht um maximale Bildfrequenz oder sonstwie hohe Rechenleistung geht, kann ich nicht erkennen, warum man für neue Projekte .Net nicht nutzen sollte.
Zuletzt geändert von Roland Ziegler am 10.12.2009 14:12:27, insgesamt 1-mal geändert.
- Jens Haupert
- Beiträge: 4989
- Registriert: 23.03.2004 14:44:34
- Aktuelle Projekte: http://www.zusidisplay.de
- Wohnort: Berlin
- Kontaktdaten:
Re: TCP-Server Programmierung
Hallo,Andreas Karg hat geschrieben:Inzwischen habe ich meinen Empfänger so umgerüstet, dass die Daten über einen zweiten Thread empfangen werden. Dieser läuft in einer Endlosschleife und ruft ständig Socket.Receive() auf, eben so wie Roland es erklärt hat. Die Daten, die Receive ausspuckt, werden in einem byte-Array zwischengelagert (...)
genau so macht es das ZusiDisplay auch. Allerdings werden dort die Daten dann von einer Methode direkt aus dem byte-Array entnommen, solange wie komplette Pakete drin stehen. Klappt prima, mein Code ist allerdings nicht gerade "präsentationtauglich". Daher kann ich auch zum MemoryStream keine Tipps geben.
Zum VisualStudio kann ich auch nichts gravierend negatives Berichten, außer dass es selbst im 2010er immer noch nicht möglich ist mit Strg+Klick auf Methodenaufruf/Klassenname direkt zu deren Implementierung zu springen. C# bietet z.Z. eindeutig den besten Tradeoff zwischen Einsteigerfreundlichkeit und Mächtigkeit/Komplexität (4.0 legt ja in diesem Punkt nochmal eine Schippe drauf); Java ist im Vergleich dazu einfach nur schlecht. Vieles ist dort deutlich unausgegorener und nicht zu Ende gedacht.
@Roland: Ich schreib zu diesen Themen am besten nichts mehr, von dir kommt zeitnah sowieso immer eine viel bessere und ausführlichere Antwort.
MfG Jens
-
- Beiträge: 4718
- Registriert: 28.04.2002 12:56:00
- Kontaktdaten:
Re: TCP-Server Programmierung
Hmm, Roland. Mit Serialisierung hab ich schon vor einiger Zeit ein wenig herumgespielt. Wenn ich dich richtig verstehe, müsste ich also hergehen und mir eine Struct bauen, die die Abfolge der Daten im Protokoll nachvollzieht. Die gebe ich dann an den BinaryReader und der schafft es dann irgendwie, das Zusi-Protokoll so zu interpretieren, dass alle Werte an der richtigen Stelle liegen und die eigentliche Nachricht (z.B. ein String) mit der korrekten Länge eingelesen wird? Oder bezog sich das schon auf die Übergabe der Werte an den Anwender?
Die würde ich entsprechend deinem Vorschlag mit einem Ereignis lösen. Allerdings würde ich statt dem Enum für den Werttyp drei Extratypen von Structs (für String, Float & Int) definieren, die von einem Grundtyp (Byte-Array) erben. Sinn ist der, dass sich der Anwender, so er denn wenig Ahnung hat, nicht damit beschäftigen muss, wie er aus einem vierelementigen Byte-Array seinen Float-Wert rauskriegt. Wer's drauf anlegt, hat gleichzeitig Zugriff auf das zugrundeliegende Roh-Array.
Da Zusi nur diese drei verschiedenen Typen plus einen obskuren TDateTime-Wert verschickt, halte ich das für eine durchaus brauchbare Lösung. Welche Messgröße welchen Datentyp besitzt, ergibt sich aus der commands.ini vom TCP-Server, die ich in Form eines serialisierten generischen Wörterbuchs in einer Ressourcendatei mitliefere.
Inzwischen habe ich mir übrigens meine Byte-Queue schon selber gebaut. Die basiert nicht auf dem generischen Queue-Typ sondern ist eine Eigenkonstruktion mit einem Byte-Array, das nach Bedarf automatisch vergrößert wird und gegebenenfalls auch verkleinert werden kann.
Noch was: Der von dir angesprochene Task-Wechsel über die Event-Loop - bezieht sich der Faktor 35 auf die benötigten Ressourcen (Rechenzeit & Speicher) oder schlicht auf den Zeitraum, der zwischen Auslösung und Ausführung des Ereignisses vergeht?
Die würde ich entsprechend deinem Vorschlag mit einem Ereignis lösen. Allerdings würde ich statt dem Enum für den Werttyp drei Extratypen von Structs (für String, Float & Int) definieren, die von einem Grundtyp (Byte-Array) erben. Sinn ist der, dass sich der Anwender, so er denn wenig Ahnung hat, nicht damit beschäftigen muss, wie er aus einem vierelementigen Byte-Array seinen Float-Wert rauskriegt. Wer's drauf anlegt, hat gleichzeitig Zugriff auf das zugrundeliegende Roh-Array.
Da Zusi nur diese drei verschiedenen Typen plus einen obskuren TDateTime-Wert verschickt, halte ich das für eine durchaus brauchbare Lösung. Welche Messgröße welchen Datentyp besitzt, ergibt sich aus der commands.ini vom TCP-Server, die ich in Form eines serialisierten generischen Wörterbuchs in einer Ressourcendatei mitliefere.
Inzwischen habe ich mir übrigens meine Byte-Queue schon selber gebaut. Die basiert nicht auf dem generischen Queue-Typ sondern ist eine Eigenkonstruktion mit einem Byte-Array, das nach Bedarf automatisch vergrößert wird und gegebenenfalls auch verkleinert werden kann.
Noch was: Der von dir angesprochene Task-Wechsel über die Event-Loop - bezieht sich der Faktor 35 auf die benötigten Ressourcen (Rechenzeit & Speicher) oder schlicht auf den Zeitraum, der zwischen Auslösung und Ausführung des Ereignisses vergeht?
- Roland Ziegler
- Beiträge: 5522
- Registriert: 04.11.2001 22:09:26
- Wohnort: 32U 0294406 5629020
- Kontaktdaten:
Re: TCP-Server Programmierung
Wieso, Du bist doch gerade der Pionier beim Einsatz von .Net im Zusi-Umfeld?Jens Haupert hat geschrieben: @Roland: Ich schreib zu diesen Themen am besten nichts mehr, von dir kommt zeitnah sowieso immer eine viel bessere und ausführlichere Antwort.
Wir sollten hier eh mal wieder ein wenig IT-intensiver werden.
Visual Studio ist in mancher Hinsicht noch etwas bieder. Sowohl Eclipse als auch NetBeans bieten mehr dynamische Information im Texteditor. So finde ich die Hervorhebung von statischen und von Member-Elementen schon recht nützlich. Auch die Randmarkierungen für alle möglichen Zwecke sehe ich als hilfreich. Bringt VS2010 hier was neues?
Wird C# 4.0 die verbliebene Lücke zu C++ Templates schließen? Kann ich in Zukunft eine arithmetische Operation auf einen skalaren generischen Typen machen, also z.B. wahlweise double oder int? In C++ habe ich z.B. für Klassen, die mit Punkten arbeiten, nur eine Implementierung. In C# musste ich bislang alles für int und double zweifach schreiben. Löst "dynamic" das? Wenn "dynamic" aber erst zur Laufzeit ausgewertet wird, sieht das ja nach Reflection aus und würde dann doch wohl recht langsam werden?
Der Erweiterung von Ko- und Kontravarianz (bisher nur implizit für Delegates) kann ich sofort zustimmen, sehr hilfreich.
- Michael_Poschmann
- Beiträge: 19923
- Registriert: 05.11.2001 15:11:18
- Aktuelle Projekte: Modul Menden (Sauerland)
- Wohnort: Str.Km "1,6" der Oberen Ruhrtalbahn (DB-Str. 2550)
Re: TCP-Server Programmierung
Vielleicht täten ein paar Zeilen Beispiel-Code aus dem Stellwerksprojekt motivieren tun...Roland Ziegler hat geschrieben:Wir sollten hier eh mal wieder ein wenig IT-intensiver werden.
Und husch wieder weg
Michael
- Roland Ziegler
- Beiträge: 5522
- Registriert: 04.11.2001 22:09:26
- Wohnort: 32U 0294406 5629020
- Kontaktdaten:
Re: TCP-Server Programmierung
Socket.Receive() liest Bytes in ein Array. Mit dem gefüllten Array initialisierst Du einen MemoryStream. Auf dem setzt Du einen BinaryReader auf. Dann gehst Du schrittweise vor. Das Telegramm verrät Dir ja seinen Aufbau, wenn Du es sequentiell ausliest. Für Multibyte-Strukturen gilt ja wohl little-endian, also muss man auch keine Bytes tauschen. Wenn Du weißt, Du musst jetzt als nächstes ein float einlesen, dann machst Du das mit dem BinaryReader. Der liefert Dir das float und inkrementiert den Read-Pointer im Stream und damit im zugrunde liegenden Byte-Array um 4.Andreas Karg hat geschrieben:Hmm, Roland. Mit Serialisierung hab ich schon vor einiger Zeit ein wenig herumgespielt. Wenn ich dich richtig verstehe, müsste ich also hergehen und mir eine Struct bauen, die die Abfolge der Daten im Protokoll nachvollzieht. Die gebe ich dann an den BinaryReader und der schafft es dann irgendwie, das Zusi-Protokoll so zu interpretieren, dass alle Werte an der richtigen Stelle liegen und die eigentliche Nachricht (z.B. ein String) mit der korrekten Länge eingelesen wird? Oder bezog sich das schon auf die Übergabe der Werte an den Anwender?
Hast Du ein Telegramm durch, gehst Du idealerweise mit Seek im MemoryStream zurück auf Los oder stellst durch Close() BinaryReader und MemoryStream der Entsorgung anheim. Auch die zweite Variante würde so teuer nicht werden, weil die Stream-Objekte wohl in Generation 0 blieben. Bei nächsten Receive schreibst Du in Dein vorhandenes Byte-Array wieder von Anfang an. Das Byte-Array darf dann eine durch und durch statische Länge haben.
Dem Anwender würde ich dann die extrahierten und fertigen Wertepaare, also die Tagged Values übergeben. Du hast doch schon eine generische Struktur dafür. Der Anwender will in der Tat gar nicht wissen, dass das float ursprünglich aus 4 byte zusammengesetzt ist. Noch weniger will er es selber umwandeln müssen.
Structs sind Value-Typen, da geht Vererbung nicht. Deine Struct ist ein Wertepaar, jener immer wiederkehrende Tagged Value, in sich weitgehend semantikfrei, aber für den Nutzer einfach zu verarbeiten. Der Tag, also der Schlüssel, sollte ruhig ein enum sein, es sei denn, Dur willst für jeden Wertetyp eine eigene Klasse definieren, was ich für zuviel Aufwand halte.Die würde ich entsprechend deinem Vorschlag mit einem Ereignis lösen. Allerdings würde ich statt dem Enum für den Werttyp drei Extratypen von Structs (für String, Float & Int) definieren, die von einem Grundtyp (Byte-Array) erben.
Angenommen Geschwindigkeit sei ein float, dann würde Deine Struktur als generischen Value-Parameter float haben, und in der Instanz als Schlüssel sowas wie ETypen.Geschw
Mit ein bisschen Trickserei kannst Du die Tagged Value-Strukturen generisch erzeugen und in der Event-Senke trotzdem konkret verarbeiten.
Mein Massentest hatte zwei Varianten bei Aufruf der ISynchronizedInvoke-Methode. War eine eigene Implementierung, die nur das Invoke testete. Die Varianten waren die mit und ohne Taskwechsel. Edit: Faktor 35 war Elapsed Time.Noch was: Der von dir angesprochene Task-Wechsel über die Event-Loop - bezieht sich der Faktor 35 auf die benötigten Ressourcen (Rechenzeit & Speicher) oder schlicht auf den Zeitraum, der zwischen Auslösung und Ausführung des Ereignisses vergeht?
@Michael: Stellwerk setzte bekanntlich auf Remoting und zukünftig auf WCF. Da entfällt jedwede benutzerspezifische Serialisierung.
Das aktuelle Stellwerk von Richard Plokhaar ist übrigens auch in C# geschrieben.
Zuletzt geändert von Roland Ziegler am 10.12.2009 15:35:13, insgesamt 2-mal geändert.
-
- Beiträge: 4718
- Registriert: 28.04.2002 12:56:00
- Kontaktdaten:
Re: TCP-Server Programmierung
Äh, vergiss', was ich da geschrieben habe. Im Grunde hab ich genau das vorgehabt, was du auch vorgeschlagen hast. Nur mit Bärenhunger und Hirn kaputt. Ich hätte also erst mal Mist geschrieben und dann gespannt, dass es auch viel einfacher (deine Lösung) geht.Roland Ziegler hat geschrieben:Der Tag, also der Schlüssel, sollte ruhig ein enum sein, es sei denn, Dur willst für jeden Wertetyp eine eigene Klasse definieren, was ich für zuviel Aufwand halte.
Was mich grad noch ein wenig stutzig macht:
Wie stelle ich denn absolut sicher, dass ich mit genau einem Receive-Aufruf am Socket jeweils genau ein komplettes Telegramm (oder zumindest nur vollständige Telegramme) bekomme? Das ist der eigentliche Grund, weswegen ich meine Queue gebaut habe: Erstmal wird da alles, was kommt, reingefüllt und geguckt, ob sich schon mindestens ein vollständiges Telegramm angesammelt hat. Das wird dann interpretiert.
- Roland Ziegler
- Beiträge: 5522
- Registriert: 04.11.2001 22:09:26
- Wohnort: 32U 0294406 5629020
- Kontaktdaten:
Re: TCP-Server Programmierung
Du kannst in einem Receive()-Aufruf nicht sicherstellen, dass Du genau ein Telegramm bekommst, nicht bei variabler Länge. Darauf hat Christopher auch schon hingewiesen. Blöcke können beliebig getrennt sein und ggf muss man einen Moment warten, bis der Peer den Rest hinterherschickt. Anzustreben ist aber, mit zwei Receive() auszukommen. Das erste Receive mit konstanter Länge, nämlich alles, was den Telegrammkopf ausmacht. Dort sollte dann auch die variable Länge des Datenteils zu finden sein. Kennt man die, folgt ein zweites Receive, diesmal mit der zuvor extrahierten tatsächlichen Länge des variablen Datenteils. Zu diesem Zweck ist die Receive-Methode überladen und bietet eine Variante mit einem zusätzlichen Offset-Parameter in das Byte-Array. (Die Jungs haben mitgedacht . - Nö, ist so üblich, funktioniert auch in Java so.)Roland Ziegler hat geschrieben:Wie stelle ich denn absolut sicher, dass ich mit genau einem Receive-Aufruf am Socket jeweils genau ein komplettes Telegramm (oder zumindest nur vollständige Telegramme) bekomme? Das ist der eigentliche Grund, weswegen ich meine Queue gebaut habe: Erstmal wird da alles, was kommt, reingefüllt und geguckt, ob sich schon mindestens ein vollständiges Telegramm angesammelt hat. Das wird dann interpretiert.
- Jens Haupert
- Beiträge: 4989
- Registriert: 23.03.2004 14:44:34
- Aktuelle Projekte: http://www.zusidisplay.de
- Wohnort: Berlin
- Kontaktdaten:
Re: TCP-Server Programmierung
Hallo,Roland Ziegler hat geschrieben:Du kannst in einem Receive()-Aufruf nicht sicherstellen, dass Du genau ein Telegramm bekommst, nicht bei variabler Länge.
zu diesem Punkt noch die Anmerkung von mir, dass es nicht nur nicht sichergestellt ist, dass komplette Pakete ankommen; dies ist unter WinXP-7 praktisch nie der Fall. Ich hatte da zu Anfangszeiten diverse Versuche unternommen. Vor allem bei einer Übertragung über WLAN, sammelt der Netzwerk-Stack erst mal ganz schön Daten, bis diese übertragen werden. Das führt sogar dazu, dass ein Paketteil sehr lange in der Luft hängt bis der Rest ankommt. Sieht man sehr schön, wenn mit F9 gefahren wird und die Übertragung über WLAN läuft; dann ruckelt der Geschwindigkeitsanzeiger doch sehr deutlich. Bei LAN ist es nicht so auffällig. (Frage an Roland: Lässt sich das via Socket auf Clientseite irgendwie konfigurieren?)
MfG Jens
Zuletzt geändert von Jens Haupert am 10.12.2009 17:36:33, insgesamt 1-mal geändert.
-
- Beiträge: 775
- Registriert: 26.01.2005 16:10:18
- Wohnort: Darmstadt
Re: TCP-Server Programmierung
Hallo Roland,
Gruß
- Christopher
ist es nicht so, dass man sich strenggenommen nicht einmal darauf verlassen kann? Receive() blockiert ja nicht, bis die gewünschte Anzahl Oktetts eingetroffen ist, sondern gibt die tatsächlich empfangene Datenmenge zurück. Defensive Programmierung heißt hier also, darauf vorbereitet zu sein, ein Datentelegramm in beliebig vielen Schnipseln zu erhalten. So habe ich es zumindest gelöst.Roland Ziegler hat geschrieben:Du kannst in einem Receive()-Aufruf nicht sicherstellen, dass Du genau ein Telegramm bekommst [...] Anzustreben ist aber, mit zwei Receive() auszukommen. Das erste Receive mit konstanter Länge, nämlich alles, was den Telegrammkopf ausmacht. Dort sollte dann auch die variable Länge des Datenteils zu finden sein. Kennt man die, folgt ein zweites Receive, diesmal mit der zuvor extrahierten tatsächlichen Länge des variablen Datenteils.
Gruß
- Christopher
-
- Beiträge: 4718
- Registriert: 28.04.2002 12:56:00
- Kontaktdaten:
Re: TCP-Server Programmierung
Gilt das auch für die überladene Version Receive(byte[] Buffer, int size, SocketFlags socketFlags)? Bei der müsste das nämlich gehen, wenn ich die Doku richtig verstanden habe.
- Roland Ziegler
- Beiträge: 5522
- Registriert: 04.11.2001 22:09:26
- Wohnort: 32U 0294406 5629020
- Kontaktdaten:
Re: TCP-Server Programmierung
Auf Empfangsseite kann man nur ReadTimeout beeinflussen, auf Sendeseite kann man ggf. den Nagle-Algorithmus ausschalten, was sich aber nicht tun würde. Irgendwo stand, maximale Wartezeit bis zum Versand unvollständiger Blöcke sei 200ms. Die maximale Paketlänge MTU kann man aus .Net nicht beeinflussen, denke ich mal, denn die betrifft das HW-Interface. Wenn überhaupt möglich, wäre das ja auch ein systemadministrativer Eingriff.Jens Haupert hat geschrieben:Frage an Roland: Lässt sich das via Socket auf Clientseite irgendwie konfigurieren?
Was die tatsächliche Laufzeit und Verzögerung angeht, da gibt es ja noch andere Einflussfaktoren. Zwischen WiFi und LAN findet ein Medienwechsel statt, der vermutlich noch mit Level-2-Switching abgehandelt wird (habe mich nicht näher damit beschäftigt). Switching verändert keine Pakete, kann sie aber sehr wohl puffern. Anders sieht es mit WAN mit Level-3-Routing aus (IP V4). Da werden Pakete durchaus neu zusammengesetzt. (Wenn noch GPRS ins Spiel kommt, erlebt man tollste Sachen, durchaus auch Pakete der Größe 1, welch eine Verschwendung von Bandbreite.)
Im Prinzip richtig. Das kann man aber über das Timeout beim BlockingSocket beeinflussen. War bei meinen Implementierungen häufig ein konfigurierbarer Parameter. Vielleicht möchte man sich bei einem Timeout auch entscheiden, den Socket zuzumachen, wegen Nicht-Reaktion der Gegenseite. Im WAN bleibt so ein TCP-Socket nämlich rein technisch beliebig lange offen. Genau so kann man bei einem Timeout entscheiden, ob man einen weiteren Versuch wagt, die restlichen Bytes noch zu lesen. Man braucht bei variabler Telegrammlänge immer mindestens zwei Receive-Aufrufe, bei Timeout ggf auch mehr.Christopher Spies hat geschrieben:ist es nicht so, dass man sich strenggenommen nicht einmal darauf verlassen kann? Receive() blockiert ja nicht, bis die gewünschte Anzahl Oktetts eingetroffen ist, sondern gibt die tatsächlich empfangene Datenmenge zurück. Defensive Programmierung heißt hier also, darauf vorbereitet zu sein, ein Datentelegramm in beliebig vielen Schnipseln zu erhalten. So habe ich es zumindest gelöst
@BR472: Ich befürchte, wir haben Dich jetzt total verschreckt. War nicht die Absicht. Die von Andi bislang angebotene Lib wird die Aufgabe durchaus lösen. Sein Ansatz ist "straightforward" und die Nutzung zudem einfach. Wenn Du im Moment noch Schwierigkeiten hast, dieses Teil bei Dir funktionstüchtig einzubinden, könnte es auch noch daran liegen, dass Dir noch einige Grundlagen objektorientierter Programmierung nicht geläufig sind. Gerade VB ist dafür berüchtigt, den Nutzer mit solchen Grundlagen "zu verschonen", ein durchaus zweifelhaftes Vergnügen. Ein gutes Buch zum Thema Einführung in die Objektorientierung sollte aber hier schon nach wenigen Kapiteln Erleuchtung schaffen.
-
- Beiträge: 4718
- Registriert: 28.04.2002 12:56:00
- Kontaktdaten:
Re: TCP-Server Programmierung
BR472:
Roland hat Recht. Die Klasse ist so, wie ich sie oben zum Download angeboten habe, durchaus verwendbar und zuverlässig. Irgendwelche Performanceprobleme, die durch das von mir benutzte Verfahren entstehen könnten, halte ich für eher unwahrscheinlich angesichts der geringen Datenmengen, die hier herumgeschaufelt werden.
Inzwischen habe ich alle seine Vorschläge umgesetzt. Ich selber bin sehr zufrieden mit dem Ergebnis, weil es meinen Code sehr verschlankt. Andererseits erfordert es vom Benutzer ein wenig mehr Erfahrung als die Urversion, dafür allerdings weniger Arbeit, wenn alles klappt.
Falls es interessierte Tester gibt, kann ich die neue Version gern auch hochladen. Man möge sich dann melden.
Roland hat Recht. Die Klasse ist so, wie ich sie oben zum Download angeboten habe, durchaus verwendbar und zuverlässig. Irgendwelche Performanceprobleme, die durch das von mir benutzte Verfahren entstehen könnten, halte ich für eher unwahrscheinlich angesichts der geringen Datenmengen, die hier herumgeschaufelt werden.
Inzwischen habe ich alle seine Vorschläge umgesetzt. Ich selber bin sehr zufrieden mit dem Ergebnis, weil es meinen Code sehr verschlankt. Andererseits erfordert es vom Benutzer ein wenig mehr Erfahrung als die Urversion, dafür allerdings weniger Arbeit, wenn alles klappt.
Falls es interessierte Tester gibt, kann ich die neue Version gern auch hochladen. Man möge sich dann melden.
Re: TCP-Server Programmierung
Hallo,
ich habt mich nicht verschreckt . Ich habe nur ein bisschen mich selbst an die Arbeit gemacht und nun habe ich geschafft, was ich wollte und auch noch mehr, sofern man ein kleines Problemchen löst, klappt alles wunderbar. Ich hätte bloß eine Klasse anlegen brauchen, und den Inhalt der Quelldatei hineinkopieren müssen, da VB andere Dateien nicht einfach einem Projekt hinzufügt durch einen reinen Doppelklick.
Liebe Grüße Tobi
ich habt mich nicht verschreckt . Ich habe nur ein bisschen mich selbst an die Arbeit gemacht und nun habe ich geschafft, was ich wollte und auch noch mehr, sofern man ein kleines Problemchen löst, klappt alles wunderbar. Ich hätte bloß eine Klasse anlegen brauchen, und den Inhalt der Quelldatei hineinkopieren müssen, da VB andere Dateien nicht einfach einem Projekt hinzufügt durch einen reinen Doppelklick.
Liebe Grüße Tobi