C++ Server Client anwendung Verständnis Fragen

  • Hallo
    Ich Arbeite gerade an meinem ersten C++ Programm, einer Client Server Anwendung um Messdaten eines Solarladereglers auszulesen und speichern und an einen Server und oder Display weiterzuleiten (Modbus Protokoll).
    Gewisse Sachen verstehe ich aber nicht und bräuchte dafür bitte eure Hilfe.


    1. Ich Lese vom SolarRegler Die Werte als Register (Modbus) in einen Buffer *uint8_t und Speicher sie in einem Array uint16_t. Ein Register hat ja 2 Byte.
    zum Umwandeln der Werte von x bit in y bit verwnde ich diese Funktionen aus der LibModbus:



    zum versenden übers Netzwerk würde ich sie auch mit hilfe der Funktionen wieder in einen uint8_t bzw. char Buffer Legen.
    die gegenseite sollte sie dann analog zum Client wieder in uint16_t umwandeln und Speichern.


    Muss ich da wegen Endianess etwas beachten?


    Ich bin der meineung dass das nur wichtig ist bei Werten größer ein Byte damit sie nicht in einem int16_t buffer "falsch" abgelegt wird.



    2. Ich würde zum versenden über Netzwerk gern mein eigenes Protokoll entwerfen. Da die Werte aber alle Binär vorhanden sind will ich sie auch so versenden wie oben beschrieben.
    Sollte ich dann im Header zum beschreiben was zu machen ist bzw. was in der Payload ist Text verwenden oder sollte besser alles Binär gesendet werden?


    zb.: 8bitt -> Action 16bit -> Message lenght 16bit -> Payload Type 16Bit -> Register StartAddress 16bitt -> Register Count...


    Sollte ich auch bevor das Protokoll ausgewertet wird Message Lenght überprüfen ob alles empfangen wurde und evt. mit einem weiteren recv den fehlenten Rest and den Buffer anhängen?
    wie lange bleiben eigentlich die Daten die empfangen werden in dem Socket.


    3. Wie sieh es mit Thresds aus? Sollte ich den Server in einem eigenen Thread laufen lassen. oder kann er auch als Teil der Hauptschleife laufen, in der dann auch noch evt. Daten wieder an das Display gesendet werden.
    Für den Client wollte ich eigentlich keine Threads verwenden da er zuerst sowieso die Daten vom Solar Lade Regler liest und Speichert und dann über Netzwerk an den Server sendet. Oder sollte ich da auch Threads verwenden.
    wenn Ja welche sollte ich verwenden POSIX oder die Thread Class aus c++?


    4. Was muss beim Multi Threading beachtet werden?
    Ich habe eine Klasse memory die die ganzen Register in Arrays speichert und die natürlich zum versenden auch wieder ausgelesen werden müssen. Muss ich da mit mutex Arbeiten?
    Nur beim Schreiben oder auch beim Lesen?


    So das wars jetzt erst mal. Ist nicht leicht als Neuling alle Infos richtig zu interpretieren.
    Hoffe mir kann einer von den Profis die ein oder andere Frage beantworten.
    mfg Thomas

    VDR:
    Hardware: Thermaltake DH102, Zotac ION ITX-F-E, 2Gig Ram, TechnoTrend
    dual DVB-S2 6400, TechnoTrend Connect CT-3650,


    Software: EasyVDR 1.0

  • Bin zwar eher unerfahren was Programmieren angeht, aber schau dir mal libevent und protobuf an. Evtl. wäre das was für dein Projekt.

  • Welche Umgebung (HW, SW, OS?) liegt denn jeweils bei deinem Server und Client vor? Das kommt aus deiner Frage nicht heraus.
    Ist der SolarRegler irgendein Microcontroller Board? Ich kenne Modbus jetzt nicht, aber das ist doch schon ein Kommunikationsprotokoll was auch TCP kann, oder?


    Dann ist da z.B. wichtig, welche C++ Compiler für die einzelnen Plattformen existieren (wie aktuell der ist, welchen Standard er unterstützt usw.). Der Client ist dann ein normaler PC?


    Für alle deine Fragen gibt es mehrere Lösungen; erst wenn die "Umgebungsfrage" geklärt ist, kann man genauer entscheiden, was man macht.

  • Hallo Thomas,


    hier ein paar Hinweise:


    1) Endianess


    Wenn du auf beiden Seiten dieselbe Endianess hast und sicherstellen kannst, dass sich das niemals ändert, kannst du hier direkt eine Abkürzung nehmen. Versende die Daten aus Binär-Paket und lese die Daten auf der anderen Seite einfach wieder ein (cast auf den jeweiligen Datentype). Bei einer Spezialanwendung durchaus OK.
    Wenn du es etwas generischer haben willst oder nicht dieselbe Endianess vorliegt, muss dein Netzwerk-Protokoll explizit eine Endianess definieren. Dann musst du beim Senden oder Empfangen, die Daten entsprechend korrigieren.


    2) Multithreading


    Laut deiner Beschreibung wahrscheinlich mit Kanonen auf Spatzen schießen. Es hängt davon ab, was die Server-Applikation alles zu tun hat. Ein TCP-Server braucht nicht zwingend multithreading. Es macht sowieso nur Sinn, wenn du mehr als eine Verbindung gleichzeitig am Server benötigst. Aber auch da geht es ohne.
    Meine Empfehlung: Erstmal einfach halten und ohne Multithreading probieren.


    3) Protokoll


    Hier würde ich ein Binärprotokoll verwenden a la:


    <STX> <KENNZEICHNUNG> <PAYLOAD_LENGTH> <PAYLOAD> <ETX>


    Alles was zwischen STX und ETX ist, muss escaped werden, d.h. das STX und ETX byte darf außer am Anfang und Ende nirgends auftauchen.

  • Danke für die Antworten.
    Zum System:
    Der client im Garten ist ein Raspberry und als Server werde ich vmtl. Mein Qnap Nas verwenden.
    Also alles Linux gcc.
    Beim Modbus protokoll werden die einzelnen Register als Big Endian versendet und so würde ich sie auch weiter versenden.
    Finde ich auch Logischer.
    Das Display ist übrigens auch wieder mit Modbus angeschlossen. Aber da ich die Daten auch aus der Ferne abfragen möchte, muss ich im prinzip eine Art Proxy bauen.
    Da ich nicht Display(Master) und Computer(Master) gleichzeitig betreiben kann.
    Würde Später dann auch die Gelegenheit Nützen um ein bar Werte, die am Solarladeregler nicht so aussagekräftig sind, durch die des Batterie Monitors zu ersetzen.
    Aber evt. Werde ich den Code später auch mal veröffentlichen also sollte das auch auf unterschiedlichen Endianess funktionieren.


    Angenommen ich mach das alles im Hauptthread dann muss ich bei jeden Schleifendurchgang mit Select den Socket überwachen, und dann mit accept() die verbindung annehmen, und der Client wartet beim connect bis der Server wieder beim accept() angekommen ist?


    Zum Protokoll:
    Den Anfang und das Ende mit einem Byte zumarkieren wird schwierig da im Prinzip von 0x00 bis 0xFF alles gesendet werden kann


    Muss ich in der Hauptschleife eigentlich auch ein sleep ö. ä. Einbauen damit der Prozessor nicht komplett ausgelastet ist?


    Mfg Thomas

    VDR:
    Hardware: Thermaltake DH102, Zotac ION ITX-F-E, 2Gig Ram, TechnoTrend
    dual DVB-S2 6400, TechnoTrend Connect CT-3650,


    Software: EasyVDR 1.0

  • Protokolle hin oder her aber HTTP ist bewährt und es gibt schon Server dafür. Ich würde am Server einen HTTP-Server (Lighthttp, Apache, ...) installieren. Das Programm, das die Daten holt, dann einfach als CGI-Programm ins Webroot. Im CGI-Programm musst du die Daten dann lediglich auf STDOUT rauswerfen.

  • Zum Protokoll:
    Den Anfang und das Ende mit einem Byte zumarkieren wird schwierig da im Prinzip von 0x00 bis 0xFF alles gesendet werden kann


    Hallo,


    deswegen beim Senden escapen und empfangen de-escapen. Du definierst dir ein escape byte (DLE = data-link-escape) und veränderst damit die Bedeutung des nachfolgenden bytes. Zum Beispiel:


    STX = 0x02
    ETX = 0x03
    DLE = 0x10


    Wenn in deinen Nutzdaten nun ein STX, ETX oder auch DLE byte vorkommt, muss das escaped werden. Das macht man dann indem man vor dem betreffenden byte ein DLE einfügt und auf das byte DLE (also 0x10) addiert.


    Beispiel
    Du möchtest folgende Nutzdaten in ein Paket einbauen: 0x01 0x02
    Das byte 0x02 muss escaped werden, da es einem STX entspricht. Das sieht dann so aus: 0x01 0x10 0x12 und kollidiert nicht mehr mit STX.
    Beim empfangen wird das dann wieder rückgängig gemacht.

  • Der Solarregler hat schon eine ModbusTCP Schnittstelle?
    Warum das dann krampfhaft neu erfinden?
    Da gibt es doch fertige Libs für. Oder Mann nimmt FHEM dafür. Oder nodejs, bzw node-red auf dem Raspi, da war in der letzten MAKE (doe IoT Sonderausgabe) was nettes drüber drin.

  • semi
    Ok das muss ich mir merken klingt plausibel wenn auch im ersten Moment etwas kompliziert.


    mini73
    Diese Funktion kenne ich schon mir ist nur nicht ganz klar wann es nötig ist auch Endianess zu achten.
    Dazu ein beispiel:
    Ich will diesen Wert: uint16_t = 0x0110 versenden.
    lege den jetzt in einen Buffer:
    char Buffer[2] = {0x01, 0x10] dazu verwende ich dieses Makro:

    Code
    1. #define MODBUS_SET_INT16_TO_INT8(tab_int8, index, value) \
    2. do { \
    3. tab_int8[(index)] = (value) >> 8; \
    4. tab_int8[(index) + 1] = (value) & 0xFF; \
    5. } while (0)


    Beim empfänger liegt dieser Wert genauso im Buffer und wir dann mit diesem Makro:

    Code
    1. #define MODBUS_GET_INT16_FROM_INT8(tab_int8, index) ((tab_int8[(index)] << 8) + tab_int8[(index) + 1])


    Dann liegt der Wert doch beim empfänger genauso vor:
    uint16_t = 0x0110


    wie der Wert doch jetzt im Speicher des jeweiligen Systems liegt ist doch in diesem Fall egal die binär Minipulationen müssen doch gleich Funktionieren. Natürlich setzt das Vorraus dass mein Protokoll die Daten in einem bestimmten Format (in diesem Fall Big Endian) versendet.
    Oder habe ich da was falsch verstanden?


    mfg Thomas

    VDR:
    Hardware: Thermaltake DH102, Zotac ION ITX-F-E, 2Gig Ram, TechnoTrend
    dual DVB-S2 6400, TechnoTrend Connect CT-3650,


    Software: EasyVDR 1.0

  • Die Bytes, die du an einem Ende reingibst, kommen genauso am anderen Ende raus, aber die htons bzw. nstoh Funktionen musst du benutzen. Wenn du das niederste Byte zuerst schickst, wird es am anderen Ende auch als erstes ankommen. Da du damit ein Binärprotokoll baust, muss deine Gegenstelle natürlich wissen, dass das erste Byte das niederste ist.


    Sonst nicht lange fragen, sondern einfach ausprobieren.


    Lars

  • Quote

    Diese Funktion kenne ich schon mir ist nur nicht ganz klar wann es nötig ist auch Endianess zu achten.


    Immer, wenn du Integer zwischen zwei Computern austauschst und nicht dafür garantieren kannst,
    dass beide Computer die gleiche Endianess haben. In dem Fall benutzt du auf der Sendeseite hton[sl] (host-to-netzwork) und
    auf der Empfangsseite ntoh[sl] (network-to-host). Zwischen den Computern auf dem Netzwerk wird dann BigEndian benutzt.