Rat bei TCP-Versendung von Strukturen benötigt



  • Hallo, mein derzeitiges Problem habe ich schon in anderen Threads gepostet, aber nie ganz vollständig, daher nicht wundern über einen neuen Thread, hab nur derzeit keine Ideen mehr wo ich einen Fehler mache:

    Aufgabe die ich lösen möchte:Aufnahme von Gelenkdaten eines Roboters und Versendung dieser an einen nebenstehenden PC via TCP.

    Die Gelenkdaten bestehen aus 8 NutzDatenbyten und speichere ich zunächst in einem Array einer passenden Struktur ab, nämlich dieser:

    typedef struct Daten
    {
        unsigned int timestamp;
        unsigned short type;//type definiert, was in den Werten steht
        unsigned char ID;
        tCanData daten;//Wird von 0 aufwärts geladen, falls nötig(bei z.B. 4 byte länge(0-3 belegt) sind bytes 5-7 nicht definiert)
        //Id des Gelenkes
    }Daten;
    

    tCanData ist eine union, welche zur einfachen Konversion der Nutzdaten auf dem Roboter selber verwendet wird(die Union ist nicht von mir):

    typedef union tCanData {
        signed    long      slong[2];
        unsigned  long      ulong[2];
        unsigned int        uint[2];
        unsigned short      ushort[4];
        signed   short      sshort[4];
        unsigned char       uchar[8];
        float               fl[2];
    } tCanData;
    

    Die Daten füge ich mit folgender Methode ein:

    //Kurze Zusammenstellung der Variablentypen
    unsigned int SpeicherPointer = 0;
    #define SIZE_MSGSPEICHER 100000
    Daten MsgSpeicher[SIZE_MSGSPEICHER];
    
    void insert(signed short* data,unsigned short type,char ID)
    {
                if(SpeicherPointer < SIZE_MSGSPEICHER)
                {
                    int i;
                    MsgSpeicher[SpeicherPointer].type = type;
                    MsgSpeicher[SpeicherPointer].timestamp = rt_now_msecs();
                    for(i = 0;i<4;i++)
                    {
                        MsgSpeicher[SpeicherPointer].daten.sshort[i] = data[i];
                    }
                    MsgSpeicher[SpeicherPointer].ID = ID;
                    SpeicherPointer++;
                }
                else if(SpeicherPointer == SIZE_MSGSPEICHER)
                {
                    syslogf(LOG_WARNING,"MsgSpeicher voll");
                    SpeicherPointer++;//Wir zeigen die Nachricht also nur einmal
                }
                if(SpeicherPointer%1000 == 0 )//hatte ich nur mal als Sicherheit //eingefügt
                {
                    trelea(&Msg_sema);//Löst Semaphore "aus" wodurch Loggertask //aktivert wird
                }
    }
    

    data sind die Nutzdaten des Gelenkes(gibt mehrere, aber via Can senden die unabhängig jeweils ihre eigenen Daten an den Leitrechner des Roboters).
    In type versteckt sich die Art der Daten, etwa Istwerte eines Gelenkes oder ä..
    Id ist dann natürlich die Nummer des Gelenks, wodurch ich sagen kann, welches Gelenk da überhaupt Daten sendet.

    Das eigentliche Versenden der Daten geschieht durch meine Sendetask:

    [c++]
    unsize int size = (sizeof(unsigned int)+sizeof(short)+sizeof(char)+sizeof(short)*4);
    //size ist so groß wie die Summe der Größen der "Datenstruktur" weiter oben(15 //bytes ohne padding)
    #define PACKETSIZE 50
    void DatenLogger(void)
    {
    struct sockaddr_in inet;
    int mysocket;
    short Port = PORT_PC;//==8000
    int len = 0;

    inet.sin_family = AF_INET;
    inet.sin_addr.s_addr = inet_addr(IP_PC);// PC IP
    inet.sin_port = htons(Port);
    memset(&inet.sin_zero[0],0,8);
    mysocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(connect(mysocket,(struct sockaddr*)&inet,sizeof(inet)) == -1 && logactive == true)//==error
    {
    syslogf(LOG_WARN,"Fehler beim connecten");
    logactive = false;
    }
    else
    {

    while((logactive==true) && (len != -1))
    {
    len = 0;
    trequ(&Msg_sema);//warte auf semaphore, siehe insert()
    if((SpeicherPointer-Lesepointer)>=PACKETSIZE)//wenn genug zum //versenden vorhadne ist
    {
    unsigned char packet[PACKETSIZEsize];//groß genug für PACKETSIZE packete
    Dtoc(&packet[0],&MsgSpeicher[Lesepointer],PACKETSIZE);
    //!Die untenstehende Sendegröße ist die Nutzdatengröße der //Struktur "Daten"
    len = send(mysocket,&packet[0],size
    PACKETSIZE,0);
    //len = send(mysocket,&MsgSpeicher[Lesepointer],sizeof(Daten)PACKETSIZE,0);
    }
    /

    else if(((SpeicherPointer-Lesepointer)<PACKETSIZE) && (SpeicherPointer-Lesepointer>0))
    {
    if(SpeicherPointer-Lesepointer < 0)
    Dtoc(&packet[0],&MsgSpeicher[Lesepointer],SpeicherPointer-Lesepointer);
    len = send(mysocket,&packet[0],size*(SpeicherPointer-Lesepointer),0);
    //len = send(mysocket,&MsgSpeicher[Lesepointer],sizeof(Daten)*(SpeicherPointer-Lesepointer),0);
    }
    */
    syslogf(LOG_INFO,"len:%d",len);
    if(len == -1 || mysocket == -1)
    {
    syslogf(LOG_WARNING,"Verbindung abgebrochen");
    logactive = false;
    }
    //else if(len>0)
    //Lesepointer+=len/size;

    }
    syslogf(LOG_INFO,"Task Datenlogger fertig - deaktiviere");
    logactive = false;
    }
    close(mysocket);
    }
    [/cpp]

    Die Funktion Dtoc sieht wie folgt aus:

    /*
    Die Funktion kopiert count Pakete in das chararray, was exakt size*PACKETSIZE groß ist, also genug speicher besitzt*/
    /* habe mal getestet ob hier schon fehler auftreten und augenscheinlich ist dem nicht so(hab einfach type zurückkonvertiert, d.h. ausm char array wieder in eine short variable kopiert und geschaut ob der wert sinnvoll ist, dem war auch so). Dazu aber später noch was*/
    void Dtoc(unsigned char* target,Daten* source, int count)
    {
        int i;
    
        for(i = 0;i < count;i++)
        {
            memcpy((void*)&target[0+i*size],(void*)&source[i].timestamp,sizeof(source[i].timestamp));        memcpy((void*)&target[4+i*size],(void*)&source[i].type,sizeof(source[i].type));        memcpy((void*)&target[6+i*size],(void*)&source[i].ID,sizeof(source[i].ID));
            memcpy((void*)&target[7+i*size],(void*)&source[i].daten.ushort[0],sizeof(source[i].daten.ushort[0]));
            memcpy((void*)&target[9+i*size],(void*)&source[i].daten.ushort[1],sizeof(source[i].daten.ushort[1]));
            memcpy((void*)&target[11+i*size],(void*)&source[i].daten.ushort[2],sizeof(source[i].daten.ushort[2]));
            memcpy((void*)&target[13+i*size],(void*)&source[i].daten.ushort[3],sizeof(source[i].daten.ushort[3]));
        }
    
    }
    

    Nun der Empfangsteil auf dem PC(ich habe Fehlerbehandlung beim starten des TCP clienten rausgelöscht, damit es kompakter ist, dies waren aber nur sachen ala "rc == -1?DO FEHLERMELDEN PROGRAMM BEENDEN", Fehler beim erstellen des Sockets oder beim verbinden der Sockets geschehen nicht):

    #include <windows.h>
    #include <winsock2.h>
    #include <stdio.h>
    #define PACKETSIZE 50
    #define LENGTH 1000
    
    unsigned int counter = 1;
    //identisch zur obigen tCanData struktur
    typedef union tCanData {
        signed    long      slong[2];
        unsigned  long      ulong[2];
        unsigned int        uint[2];
        unsigned short      ushort[4];
        signed   short      sshort[4];
        unsigned char       uchar[8];
        float               fl[2];
    } tCanData;
    
    typedef struct Daten
    {
        unsigned int timestamp;
        unsigned short type;//type definiert, was in den Werten steht
        unsigned char ID;//Id des Gelenkes
        tCanData daten;
    
    }Daten;
    
    //Liste zum einfachen Abspeichern der Daten
    //Prototypen
    typedef struct Datenliste
    {
       Daten Information;
       struct Datenliste* next;
    }Datenliste;
    int startWinsock(void);
    
    Datenliste Anfang;
    Datenliste* Target = &Anfang;
    
    //identisch zum size des Roboters, auch 15 Byte groß bzw size == 15
    int size = (sizeof(unsigned int)+sizeof(short)+sizeof(char)+sizeof(short)*4);
    
    void ctoD(Daten* source,unsigned char* target, int count)
    {
        int i;
        for(i = 0;i < count;i++)
        {
            memcpy((void*)&source[i].timestamp,(void*)&target[0+i*size],sizeof(source[i].timestamp));
         memcpy((void*)&source[i].type,(void*)&target[4+i*size],sizeof(source[i].type));
            memcpy((void*)&source[i].ID,(void*)&target[6+i*size],sizeof(source[i].ID));
            memcpy((void*)&source[i].daten.sshort[0],(void*)&target[7+i*size],sizeof(source[i].daten.sshort[0]));
            memcpy((void*)&source[i].daten.sshort[1],(void*)&target[9+i*size],sizeof(source[i].daten.sshort[1]));
            memcpy((void*)&source[i].daten.sshort[2],(void*)&target[11+i*size],sizeof(source[i].daten.sshort[2]));
            memcpy((void*)&source[i].daten.sshort[3],(void*)&target[13+i*size],sizeof(source[i].daten.sshort[3]));
    
    //der Roboter arbeitet mit big endian, und der PC mit little endian, daher /nochmal die byteorder drehen
            source[i].timestamp = ntohl(source[i].timestamp);
            source[i].type = ntohs(source[i].type);
            source[i].daten.sshort[0] = ntohs(source[i].daten.sshort[0]);
            source[i].daten.sshort[1] = ntohs(source[i].daten.sshort[1]);
            source[i].daten.sshort[2] = ntohs(source[i].daten.sshort[2]);
            source[i].daten.sshort[3] = ntohs(source[i].daten.sshort[3]);
        }
    
    }
    
    int main()
    {
      long rc;
    
      SOCKET acceptSocket;
      SOCKET connectedSocket;
      SOCKADDR_IN addr;
      // Winsock starten
      rc=startWinsock();
      // Socket erstellen
      acceptSocket=socket(AF_INET,SOCK_STREAM,0);
      if(acceptSocket==INVALID_SOCKET)
      {
        return 1;
      }
      else
      {
        printf("Socket erstellt!\n");
      }
    memset(&addr,0,sizeof(SOCKADDR_IN));
    addr.sin_family=AF_INET;
    addr.sin_port=htons(8000);
    addr.sin_addr.s_addr=ADDR_ANY;
    rc=bind(acceptSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR_IN));
    rc=listen(acceptSocket,10);
    
    connectedSocket=accept(acceptSocket,NULL,NULL);
    
    unsigned int l = 0;
    while(rc != -1 && l < LENGTH)
    {
        unsigned char buf[PACKETSIZE*size];
        Daten datenbuf[PACKETSIZE];
        rc=recv(connectedSocket,&buf[0],size*PACKETSIZE,0);
        //printf("%i Bytes empfangen\n",rc);
        ctoD(&datenbuf[0],&buf[0],rc/size);
        if(rc != -1)
        {
            int i;
            for(i = 0; i < rc/size;i++)
            {
                Target->Information = datenbuf[i];
                Target->next = (Datenliste*)malloc(sizeof(Datenliste));
                Target->next->next = NULL;
                Target = Target->next;
                l++;
            }
        }
    }
    closesocket(connectedSocket);
    printf("Übertragung fertig oder abgebrochen, speichere Daten\n");
    Target = &Anfang;
    FILE *fp = NULL;
    fp = fopen("Datenlog.txt","a+");
    //speichert die Liste in Datei ab bis Ende der Liste erreicht ist
    if(fp != NULL)
    {
        fprintf(fp,"BEGIN\n");
        for(Target = &Anfang;Target->next != NULL;Target = Target->next,counter++)
    fprintf(fp,"%u:%u:%u:%d:%d:%d:%d::%d\n",Target->Information.type,Target->Information.ID,Target->Information.timestamp
                                         ,Target->Information.daten.sshort[0],Target->Information.daten.sshort[1],Target->Information.daten.sshort[2]
                                         ,Target->Information.daten.sshort[3],counter);
    }
    fprintf(fp,"END\n\n\n\n");
    printf("Daten gespeichert, schließe Datei\n");
    fclose(fp);
    printf("Enter drücken zum schließen");
    getchar();
      return 0;
    }
    
    int startWinsock(void)
    {
      WSADATA wsa;
      return WSAStartup(MAKEWORD(2,0),&wsa);
    }
    

    Sooo, folgendes tritt auf(screenshot):

    http://up.picr.de/11673503lf.jpg
    Das Speicherschema lautet:

    Type:ID:Timestamp(in ms seit Roboterstart):Daten0:Daten1:Daten2:Daten3

    Der Ausschnitt ist aus der Mitte der Daten genommen, folgendes ist mir wiederholt aufgefallen(hier nicht, aber vielleicht lässt sich trotzdem ein Grund ableiten):

    Zunächst funktioniert die Übertragung scheinbar ohne Fehler, die ersten Einträge sind immer sinnvoll(korrekte IDs der Gelenke und timestamp sowie Typ der Daten passt, über die Nutzdaten kann ich noch nichts sagen, müsste ich testen).
    Danach kommt kurze Zeit unsinn, dann kommt das Interessante:
    für kurze Zeit rückt die timestamp auf den Platz des Types(wird auch korrekt weitergezählt) und nach einigen solcher einträgen, rückt die timestamp wieder auf ihren richtigen Platz(also den 3. nach obigem Schema) und die Daten werden wieder korrekt gespeichert.
    Praktisch IMMER erfolgt der 1. Fehler nach 71 Einträgen(kam immer und immer wieder vor), es scheint eine gewisse Regelmäßigkeit in meinem problem vorzuherrschen.

    Untersuchungen die ich unternommen habe zum Analysieren des Fehlers:

    Auf der Roboterseite:
    Ich habe alle abgespeicherten Datenpakete auf sinnhaftigkeit des "types" untersucht. Dort gab es KEINEN einzigen Fehler, d.h. abgespeichert werden die Daten korrekt.
    NAchdem ich die Daten in das Chararray gepackt habe(kurz vorm versenden) existiert auch kein Fehler in den Daten. Dies habe ich durch erneutes kopieren in variablen(also nochmaliges memcpy des chararrays) kontrolliert, bei dem erneut nur korrekte Werte rauskamen.

    Auf der PC-Seite:
    Hier stelle ich nun Fehler fest:
    Direkt nach dem Auslesen aus dem empfangenen Chararray(also direkt nachdem ich memcpy verwendet habe) sind die Daten falsch.

    Entweder werden die Daten also auf dem Roboter durch die send()-Funktion falsch versendet, die Empfangsfunktion auf der PC-Seite liest die Daten falsch aus ODER das kopieren der empfangenen Chararraybytes läuft falsch ab.

    Ich hoffe jemand liest sich das alles durch und könnte mir einen Rat geben.
    Vielen, vielen Dank schonmal.


  • Mod

    Ich wage mal zu behaupten, dass deine Problembeschreibung zu komplex für die Lösung im Rahmen eines Internetforums ist. Ein paar Tipps:

    - Um dumme Fehler zu vermeiden: Vergleiche mal, ob der Sendepuffer und der Empfangspuffer auch identisch sind.
    - Mach mal die ganzen Unsinnscasts raus (In C sind Zeiger implizit in void* konvertierbar), die verbergen nur mögliche Fehler. Compilier mit vollen Warnstufen, behandle alle Warnungen wie Fehler. Machst du hoffentlich sowieso schon so, aber du glaubst nicht wie oft hier Fragen kommen, die man direkt durch Angabe der Compilerwarnungen beantworten kann.
    - Mein Gefühl sagt mir, der Fehler wird in der Funktion ctoD stecken. Falls der obige Punkt nichts ergeben hat, dann geh die Funktion Zeile für Zeile im Debugger durch. memcpy, Unions und Bytegeschiebe sind die erste Adresse, wenn Daten nicht mehr so aussehen, wie erwartet.



  • Vom Prinzip möchte ich SeppJ zustimmen, aber evtl. ist hier Fehlerpotenzial:
    Wenn Dein recv mal nicht die erwarteten size*PACKETSIZE Daten liest, sondern vielleicht mal
    size * PACKETSIZE - PACEKTSIZE / 2
    einfach mal um eine Menge zu benennen. Dann passiert doch hier folgendes:
    Es werden PACKETSIZE - 1 Durchläufe durch die for-Schleife gemacht und es bleiben unverarbeitete Daten im Buffer, beim nächsten recv steht dann zu Beginn des Buffers der Rest des unvollständigen Paketes vom vorherigen Lesen und Deine Datenstruktur stimmt nicht mehr ...
    Wenn ich das richtig verstehe, willst Du eine Anzahl vollständiger Strukturen lesen, prüfst aber nicht, ob die auch vollständig gelesen wurden.

    while(rc != -1 && l < LENGTH)
    {
        unsigned char buf[PACKETSIZE*size];
        Daten datenbuf[PACKETSIZE];
        rc=recv(connectedSocket,&buf[0],size*PACKETSIZE,0);
        //printf("%i Bytes empfangen\n",rc);
        ctoD(&datenbuf[0],&buf[0],rc/size);
        if(rc != -1)
        {
            int i;
            for(i = 0; i < rc/size;i++)
            {
                Target->Information = datenbuf[i];
                Target->next = (Datenliste*)malloc(sizeof(Datenliste));
                Target->next->next = NULL;
                Target = Target->next;
                l++;
            }
        }
    }
    


  • Hi, danke für eure Hilfe. Ich hab schonmal geprüft(länger her) ob auch eine sinnvolle Menge(d.h. vollständige Strukturen) übermittelt wird.
    Ich schmeiß morgen mal den Debugger an, Roboterseitig kann ich das leider nicht tun 😞



  • Namenloser324 schrieb:

    Hi, danke für eure Hilfe. Ich hab schonmal geprüft(länger her) ob auch eine sinnvolle Menge(d.h. vollständige Strukturen) übermittelt wird.

    Das musst Du bei jeder Übertragung tun. Wenn Du mit einem recv x Byte lesen willst, und bei 10 aufeinanderfolgenden recv - Aufrufen kommen auch x Byte an, dann heißt das (selbstverständlich) nicht, dass auch beim elften Aufruf x Byte gelesen werden ...



  • Ich verstehe nicht ganz was du meinst:

    ich habe es sogetestet, dass ich mir einfach die zurückgegebene Bytezahl der recv funktion ausgeben hab lassen. Das ergab für zig testläufe immer ein vielfaches der strukturgröße(ohne padding ;))



  • Ich meine, Du kannst so oft Du willst recv(..., ..., x, ...) aufrufen und x Byte erhalten. Das gibt Dir keine Garantie, dass Du auch beim nächsten Aufruf x Byte erhältst - es sei denn, x ist 1 (eins).
    Und wenn nun bei einem Leseaufruf am Ende des Buffers nur ein halbes Paket gelesen wird, dann steht nach dem nächsten Leseaufruf erst mal die zweite Hälfte am Beginn des Buffers, und die folgenden Pakete sind entsprechend verschoben. Aber ich glaube, das habe ich schon gesagt ...
    Und wenn ich Deinen Code richtig verstehe, dann kann er ein solches Szenario nicht korrekt verarbeiten.
    Du kannst ja mal in Dich gehen und überlegen, ob die auftretenden Fehler zu solch einem Szenario passen würden ...



  • Hey, danke, das mit dem Bufferauslesen war mir nicht klar. Schau ich morgen, allerdings meine ich den Fehler auch dann gehabt zu haben, wenn ich immer genau 50 Packete empfangen hab.



  • rc ist uninitialisiert bei der ersten Verwendung glaube ich, und schmeiße die oberhässliche verkettete Liste raus und nehme ein normales Array.



  • Belli schrieb:

    Ich meine, Du kannst so oft Du willst recv(..., ..., x, ...) aufrufen und x Byte erhalten. Das gibt Dir keine Garantie, dass Du auch beim nächsten Aufruf x Byte erhältst - es sei denn, x ist 1 (eins).
    Und wenn nun bei einem Leseaufruf am Ende des Buffers nur ein halbes Paket gelesen wird, dann steht nach dem nächsten Leseaufruf erst mal die zweite Hälfte am Beginn des Buffers, und die folgenden Pakete sind entsprechend verschoben. Aber ich glaube, das habe ich schon gesagt ...
    Und wenn ich Deinen Code richtig verstehe, dann kann er ein solches Szenario nicht korrekt verarbeiten.
    Du kannst ja mal in Dich gehen und überlegen, ob die auftretenden Fehler zu solch einem Szenario passen würden ...

    hallo, also ich glaube du hattest recht. Hab mir das mal anzeigen lassen und es werden tatsächlich datenmengen empfangen die nicht ein ganzzahliges vielfaches der strukturgröße sind(also abgeschnitten sind).
    Daran wird es wohl liegen.

    Na gott sei dank, ennnndlich 😃 hab da jetzt mehr als ne Woche drangesessen, was auch daran lag das der roboter vllt in 50% der fälle hochfährt und sonst irgendwelche fehler hat(weiß niemand wieso). Naja, viiielen dank 😃 denke das wird es sein, denn wenn man die abgeschnittenen "reste"(er hört ja mittendrin von strukturen auf) aneinanderlegt erhält man am ende tatsächlich ein ganzzahliges vielfaches der strukturgröße was dazu führt, dass es irgendwann wieder normal funktioniert.

    Also vielen, vielen Dank an alle 🙂 Hab ich wieder was gelernt^^



  • Achso, falls jemand mal ein ähnliches Problem haben sollte:
    hab jetzt einfach die Rohdaten zunächst komplett abgespeichert(d.h. in einer whileschleife solange immer wieder die recv() funktion benutzt bis die gewünschte datenmenge in summe empfangen worden ist) und danach die Daten in einem Array von Strukturen gespeichert.


Anmelden zum Antworten