Problem bei Dateidownload ueber simplen HTTP GET Request



  • DaRe schrieb:

    Hi,
    ist das nicht eher was fürs c++ Forum?

    Das mag sein,... ich dachte da dass Forum cpp Forum heisst und mein Thema mit "Webzeugs" zu tun hat passt das hier ganz gut 😉

    DaRe schrieb:

    Gibt das einen 200 OK, wenn du nach der Binary verlangst?

    Ja ich bekomme ein 200 OK zurueck, auch die Content-Length die ich zuueck bekomme stimmt mit der echte Dateigroesse ueberein.

    DaRe schrieb:

    Warum
    readCounter=1024?

    Da hast du natuerlich recht, das sollte natuerlich 0 sein... war noch ein Ueberbleibsel vom rumprobieren.

    DaRe schrieb:

    BTW. calloc dachte ich initialisiert den neuangelegten Speicher gleich mit.

    Eigentlich ja, aber zur Sicherheit null ich einfach mal alles am Anfang.

    DaRe schrieb:

    memcpy(response+readCounter, buffer, readBytes);
    

    Hier fängst du ja dann erst bei dem 1025. Byte an. Was ist mit den ersten 1024 Byte?

    siehe oben...

    DaRe schrieb:

    Sleep(5);
    
    memset(buffer, 0, 512);
    

    Für was die beiden Funktionen? Denke die sind überflüßig.

    Sleep hab ich auch einfach aus Testzwecken eingebaut, mit memset null ich den Buffer wieder komplett.

    Wär super wenn mir da jmd weiter helfen könnte...
    (hier hat uebrigens jmd das selbe Problem nur leider endet der Thread mitten drin: http://www.cplusplus.com/forum/unices/2356/2/)

    DaRe schrieb:

    Hmm, also bei mir gehts noch hier weiter: http://www.cplusplus.com/forum/unices/2356/3/

    Aber es endet irgendwie in keiner Lösung oder ueberles ich da was?!

    DaRe schrieb:

    Versuch doch mal readCounter iwie bei 0 starten zu lassen, denke mal das ist durchaus ne Sache, die nicht so gewollt ist, oder?

    Wie gesagt, hatte ich auch normalerweise aber leider krieg ich trotzdem nur den Header + ein paar Zeichen der Exe...

    Ausgaben sind dann uebrigens:

    /*faengt an mit */
    copy data... 512 Bytes copied... 512 bytes read
    
    /*und endet mit*/
    copy data... 475624 Bytes copied... 371bytes read
    copy data... 475136 Bytes copied... 371bytes read
    copy data... 475507 Bytes copied... 371bytes read
    

    Und die Ausgabedatei (bis jetzt noch) der komplette Response geschrieben wird sieht folgendermaßen aus:

    HTTP/1.1 200 OK
    ETag: "b934806-7401e-4ae13eb35c670"
    Accept-Ranges: bytes
    Content-Length: 475166
    Date: Fri, 30 Sep 2011 09:02:26 GMT
    Connection: close
    Last-Modified: Thu, 29 Sep 2011 12:48:00 GMT
    Server: Apache/2.2.16 (Debian) mod_perl/2.0.4 Perl/v5.10.1
    Content-Type: application/x-msdos-program
    Keep-Alive: timeout=10, max=1000
    
    MZ
    

    Ich probier da jetzt seit zwei Tagen rum und solangsam frustet mich das Ganze, wär also Tipps hat... immer her damit!!
    Soll ich das ganze jetzt eigentlich noch ins C++ Forum schreiben oder kann einer der Moderatoren hin dort hin verschieben?!?!

    Gruß bob



  • hi,
    mit deinem recv aufruf erhälst du maximal 512 bytes pro while-durchlauf. deinen puffer adressierst du jedoch mit einem offset von 1024 bytes, damit bekommst du lücken in deinen daten, die mit nicht-initialisiertem datenmüll bedingt durch malloc gefüllt sind. wenn du alles in einen ram-puffer schreiben willst, musst du deinen puffer-offset also immer um die erhaltene anzahl bytes inkrementieren, die nicht immer 512 sein müssen.

    den puffer viel größer zu initialisieren wird bei größeren dateien nicht klappen, weil die tcp datenpakete eine begrenzte größe haben. portionsweise einlesen ist also der richtige weg. die vllt. lästige pufferung kannst du umgehen, indem du die erhaltenen daten direkt in eine datei schreibst.

    ansonsten kannst du den puffer auch in der passenden größe bereitstellen, indem du das mit der antwort erhaltene conent-length feld des response headers auswertest.

    die daten einer 'binärdatei' mit cout anzeigen zu lassen, dürfte spätestens bei der ersten enthaltenen 0 enden.
    was sollen die sleeps da mitten drin? schmeiss das raus.

    gruß,
    B.B.



  • da fällt mir noch auf: bevor du daten irgendwo hin kopierst, solltest du den rückgabewert von recv prüfen, der kann und wird nämlich irgendwann 0 oder -1 werden, im letzteren fall zerfetzt dir memcpy den heap weil size_t unsigned ist.



  • mit deinem recv aufruf erhälst du maximal 512 bytes pro while-durchlauf. deinen puffer adressierst du jedoch mit einem offset von 1024 bytes, damit bekommst du lücken in deinen daten, die mit nicht-initialisiertem datenmüll bedingt durch malloc gefüllt sind. wenn du alles in einen ram-puffer schreiben willst, musst du deinen puffer-offset also immer um die erhaltene anzahl bytes inkrementieren, die nicht immer 512 sein müssen.

    das hab ich ja in meinem letzte Post schon gesagt dass das nur zu Testzwecken war, aber auch mit der Initialisierung =0 funktioniert es nicht.

    lästige pufferung kannst du umgehen, indem du die erhaltenen daten direkt in eine datei schreibst.

    das is allerdings eine idee...

    die daten einer 'binärdatei' mit cout anzeigen zu lassen, dürfte spätestens bei der ersten enthaltenen 0 enden.

    das heisst ich soll das file einfach ohne "fstream::binary" oeffnen?!

    bevor du daten irgendwo hin kopierst, solltest du den rückgabewert von recv prüfen, der kann und wird nämlich irgendwann 0 oder -1 werden, im letzteren fall zerfetzt dir memcpy den heap weil size_t unsigned ist.

    ich ueberpruefe den Wert doch in der while schleife

    while (readBytes = recv(sDownload, buffer, 512, 0))
        {
    

    sobald recv da 0 oder -1 zureuck gibt verlaesst er die while schleife oder???

    also ich hab das ganze jetzt mal angepasst:

    //read the response
        char *response = (char*)malloc(1024*1024);
        char buffer[512];
        size_t readBytes = 0;
        size_t readCounter = 0;
        memset(response, 0, 1024*1024);    
        memset(buffer, 0, sizeof(buffer));
    
        ofstream downloadedFile;
        char fileOnDisk[512] = "D:\\";
        strcat(fileOnDisk, "test.txt");
        downloadedFile.open(fileOnDisk, fstream::app);
    
        while (readBytes = recv(sDownload, buffer, 512, 0))
        {
              memcpy(response+readCounter, buffer, readBytes);
              downloadedFile << buffer;
              readCounter += readBytes;
              memset(buffer, 0, 512);
              cout << "copy data... " << readCounter << " Bytes copied... " << readBytes << " bytes read\n";
        }
    
        downloadedFile.close();
    

    ergebniss ist eine 20kb große datei... die exe die gezogen wird ist allerdings 476kb

    😕 😕 😕



  • sobald recv da 0 oder -1 zureuck gibt verlaesst er die while schleife oder???

    Nein, nicht bei -1, nur bei 0.



  • Hmm, stimmt... hätte mir auch auffallen sollen ^^

    Eine kleine Gegenfrage, die '\0' Terminierung gilt auch in C++? Woher weiß denn dein:

    downloadedFile << response;
    

    wielange response eingelesen werden darf?

    Müsste dazu nicht iwie auch ein Ende ersichtlich sein. Vielleicht auch ein Ende wie '\0' sein? Und in Binaries kommen ja 00 Hex-Folgen gerne mal vor.

    Wäre das nicht sinnvoller mit so einem read(datei, puffer, length) (<--Pseudofunktion) zu machen?



  • DaRe schrieb:

    Hmm, stimmt... hätte mir auch auffallen sollen ^^

    Eine kleine Gegenfrage, die '\0' Terminierung gilt auch in C++? Woher weiß denn dein:

    downloadedFile << response;
    

    wielange response eingelesen werden darf?

    Müsste dazu nicht iwie auch ein Ende ersichtlich sein. Vielleicht auch ein Ende wie '\0' sein? Und in Binaries kommen ja 00 Hex-Folgen gerne mal vor.

    Wäre das nicht sinnvoller mit so einem read(datei, puffer, length) (<--Pseudofunktion) zu machen?

    Da hast du recht.

    OP müsste

    size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
    

    benutzen



  • bobbobbinson schrieb:

    die daten einer 'binärdatei' mit cout anzeigen zu lassen, dürfte spätestens bei der ersten enthaltenen 0 enden.

    das heisst ich soll das file einfach ohne "fstream::binary" oeffnen?!

    neineinein, binär in eine datei schreiben ist völlig richtig, die aussage bezog sich auf das anzeigen in der console. ob dafür allerdings der << operator geegnet ist, dafür sind meine c++ kentnisse schon zu verstaubt.

    bobbobbinson schrieb:

    ... was mir zu schaffen macht ist dass wenn ich eine binary Datei runterladen will ich nur den Header + ein paar Zeichen bekommen, wenn ich aber ein 4kb großes Textfile runterlade gibt es keine Probleme.

    du weißt, das alles was nach dem header folgt, also alles nach \r\n\r\n nutzdaten sind?
    das erklärt allerdings immer noch nicht den größenunterschied von rund 460kb, ich tippe mal auf den << operator - woher soll der wissen, wieviele bytes zu schreiben sind? die while bedingung musst du jedenfalls noch ändern.



  • das memset(buffer, 0, 512); kannst du auch rausschmeißen, daber darauf wurdest du ja schon hingewiesen.



  • Danke erst mal fuer die zahlreichen Posts...

    Hab soweit mal alles angepasst, whileschleifen-unterbrechung, in file schreiben mit fwrite...

    FILE *downloadedFile;
        char fileOnDisk[512] = "D:\\";
        strcat(fileOnDisk, "test.txt");
        remove(fileOnDisk);
        downloadedFile = fopen(fileOnDisk, "ab+");
    
        while (readBytes = recv(sDownload, buffer, sizeof(buffer), 0))
        {
              if (readBytes == -1)
                 break;
    
              fwrite(buffer, 1, readBytes, downloadedFile);
        }
    

    trotzdem gibts noch Probleme... wenn ich jetzt den Header aus dem File per Hand rausloesche stimmt die Dateigroesse auch perfekt, wenn ich das ganze dann aber ausfuehren will kommt nur kurz ein Konsolenfenster welches sich nach einer geschaetzen halben Sekunde wieder schliesst. In dem Fenster steht "Program too big to fit in memory".

    Irgendwelche Ideen? ^^

    (edit: hab gerade die exe mit dem texteditor geoeffnet, alles kopiert, in ein neues textfile eingefuegt, zu .exe umbenannt und siehe da.. der gleiche Fehler.. also reicht das bloße Kopieren des Inhalt nicht aus. Weiss jmd mehr?!)



  • Hab mich selber gerade mal ein wenig schlau auf Google gemacht, aber so eindeutig scheint der Fehler nicht zu sein.

    Das was ich jetzt rausbekommen habe könnte iwie Problem
    * bei 32 / 64 Bit Architektur sein.
    * mit physikalischen Speicher oder sowas

    Meine Frage, die exe ist für dein System korrekt kompiliert... oder ist das Programm auf einem "64 Bit Rechner" erstellt/compiliert worden und wird nun auf einem "32 Bit Rechner" ausgeführt?

    Ansonsten kann ich da so spontan nicht weiter helfen. Unter Ubuntu hat ich das Problem eben noch nicht 😉

    edit: MOOOOOOOOOOMENT
    Hab gerade nochmal deinen Code überflogen, was macht da:

    remove(fileOnDisk);
    

    Ist das zum freigeben von Speicher? Das sollte dir aber vermutlich auch einen Fehler ausgeben, da der Speicher nicht allokiert wurde und dementsprechend nicht in ieiner "Liste" für dynamische Speicherverwaltung auftaucht zum möglichen freigeben auftaucht(Ist jetzt ein wenig flappsig formuliert).
    Kann mich jetzt auch irren, da ich remove() spontan mit free() verknüpft habe.

    edit edit: Zumal es bei einem freigeben an der Stelle auch ein logischer Fehler wäre, da du eine Zeile später noch auf "befreite" Variable zugreifen möchtest.

    Wenn das zur Speicherfreigabe dient, dann entferne die ganze Zeile! 😉

    (edit)^3 : Weil ich gerade so unkonzentriert bin:

    hier noch ein Tipp

    while (readBytes = recv(sDownload, buffer, sizeof(buffer), 0))
        {
              if (readBytes == -1)
                 break;
    
              fwrite(buffer, 1, readBytes, downloadedFile);
        }
    

    Sieht so besser aus:

    while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0))>0)
              fwrite(buffer, 1, readBytes, downloadedFile);
    

    BTW: Nicht auf die Idee kommen readBytes "unsigned" zu machen 😉



  • Meine Frage, die exe ist für dein System korrekt kompiliert... oder ist das Programm auf einem "64 Bit Rechner" erstellt/compiliert worden und wird nun auf einem "32 Bit Rechner" ausgeführt?

    Nein die Exe wurde auf meinem Rechner (32bit) kompeliert, ist ein stink normales hello world programm. Mehr macht das nicht, wenn ich das File aber auf den Server hochlade, dann mit meinem Programm es runterlade und den Header per Hand entferne so dass auf das byte genau das selbe drin steht (zumindest wenn ich es grob miteinandern vergleiche (anfang, ende)) dann laesst sich die exe nicht ausführen. Wie gesagt selbst wenn ich meine ausführbare Exe in ein Textfile ändere, den Inhalt in ein neues Textfile kopiere und sie in eine Exe umbenenne kommt der gleiche Fehler.

    Ist das zum freigeben von Speicher? Das sollte dir aber vermutlich auch einen Fehler ausgeben, da der Speicher nicht allokiert wurde und dementsprechend nicht in ieiner "Liste" für dynamische Speicherverwaltung auftaucht zum möglichen freigeben auftaucht(Ist jetzt ein wenig flappsig formuliert).

    Nein dass loescht nur das File auf meiner Festplatte, damit ich sicher sein kann dass das neue File das da hingespeichert wird "neu" ist... falls das File nicht vorhanden ist, passiert eben gar nichts.
    http://www.cplusplus.com/reference/clibrary/cstdio/remove/

    Solangsam weiss ich nicht mehr weiter, auch meine Googlesuchanfrage-Möglichkeiten gehen zur Neige...

    edit:
    was mir gerade noch aufgefallen ist ist dass selbst wenn ich das funktionierende exe-File mit dem notepad öffne, ganz am schluss ein leerzeichen einfüge es wieder lösche und speichere (das file ist im grunde wie vorher) dann kommt der selbe fehler "program is too big to fit in memory"... crazyswayze!!



  • bobbobbinson schrieb:

    (edit: hab gerade die exe mit dem texteditor geoeffnet, alles kopiert, in ein neues textfile eingefuegt, zu .exe umbenannt und siehe da.. der gleiche Fehler.. also reicht das bloße Kopieren des Inhalt nicht aus. Weiss jmd mehr?!)

    Hä?
    Dein Text-Editor ist halt nicht binär-safe. D.h. du machst die .exe damit kaputt. Deswegen geht sie dann nimmer. Alles logisch, welcher Teil ist jetzt nicht klar?
    Schreib dein Programm so um, dass es die Header nicht mit abspeichert (oder von mir aus in ein anderes File speichert).



  • File so öffnen:

    downloadedFile = fopen(fileOnDisk, "wb");
    

    Dann wird ein evtl. schon existierendes zuerst geleert.

    ---

    Wenn du eine Binärdatei mit irgendeinem Texteditor öffnest und änderst kannst du nicht sicher sein, daß es danach noch ein funktionierende .exe ist.
    Nimm einen Hexeditor.

    Korrekterweise müsstest du zuerst das Headerende suchen und erst dann anfangen zu schreiben. Das ist aber auch nicht so einfach.
    Wenn der Header z.B. 514 bytes (mit "\r\n\r\n") groß ist, findest du das wirkliche Ende nie in deinem buffer. Du müsstest also den ganzen buffer solange an einen anderen anhängen bis du dort das Ende findest, usw. usf.



  • Wenn du eine Binärdatei mit irgendeinem Texteditor öffnest und änderst kannst du nicht sicher sein, daß es danach noch ein funktionierende .exe ist.
    Nimm einen Hexeditor.

    Ok, klingt logisch, wusst ich nicht...

    Also mal fix den Quellcode geaendert, so dass der Header erst gar nicht mit in das File rueber kopiert wird. (zwar nicht so safe dass, falls das Ende des Headers direkt zwischen zwei recieves liegt, aber habe aus sicherheit mal den buffer auf 1024 byte erhöht, aber speziell bei diesem file weiss ich dass die nutzdaten im response bei ungefähr 505 Bytes anfangen)

    Problem ist beim ersten Auslesen, also da wo der Header noch mit drin ist, stehen immer nur 3 byte Nutzdaten am Ende, egal wie groß ich den Buffer in den ausgelesen wird mache. Wenn ich alles (inklusive Header) in das File kopiere sind die Nutzdaten korrekt, will ich aber dass der header unbeachtet bleibt, schreibt er zwar die ersten Nutzdatenbytes in das File aber ein paar danach stehen nicht mit drin, eben dieses "can not run in DOS mode".

    Wär super wenn mir jmd meinen hoffentlich letzten Fehler erklaeren könnte 😉

    //read the response
        char buffer[1024];
        size_t readBytes = 0;   
        memset(buffer, 0, sizeof(buffer));
    
        FILE *downloadedFile;
        char fileOnDisk[512] = "D:\\";
        strcat(fileOnDisk, "test.exe"); // hier muss spaeter filerequest benutzt werden = helloworld.exe 
        remove(fileOnDisk);
        downloadedFile = fopen(fileOnDisk, "wb");
        bool isBody = false;
    
        while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0)) > 0)
        {          
              if (isBody)
              {
                 fwrite(buffer, 1, readBytes, downloadedFile);
              }
    
              if (strstr(buffer, "\r\n\r\n") && !isBody)
              {
                 char *tmp = strstr(buffer, "\r\n\r\n");
                 isBody = true;
                 fwrite(tmp+4, 1, strlen(tmp+4), downloadedFile);
              }
        }
    

    Gruß bob



  • fwrite(tmp+4, 1, strlen(tmp+4), downloadedFile);
    

    recv liefert keinen '\0' am Ende des Strings... strlen sucht aber nach '\0':

    while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0)) > 0)
        {          
              buffer[readBytes]='\0'; // <-----Notwendig
    
              if (isBody)
              {
                 fwrite(buffer, 1, readBytes, downloadedFile);
              }
    
              if (strstr(buffer, "\r\n\r\n") && !isBody)
              {
                 char *tmp = strstr(buffer, "\r\n\r\n");
                 isBody = true;
                 fwrite(tmp+4, 1, strlen(tmp+4), downloadedFile);
              }
        }
    

    Auch möglich durch subtrahieren von Adressen:

    while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0)) > 0)
        {          
              if (isBody)
              {
                 fwrite(buffer, 1, readBytes, downloadedFile);
              }
    
              if (strstr(buffer, "\r\n\r\n") && !isBody)
              {
                 char *tmp = strstr(buffer, "\r\n\r\n");
                 isBody = true;
    
                 // 3. Parameter angepasst 
                 fwrite(tmp+4, 1, readBytes-((tmp+4)-buffer), downloadedFile);
              }
        }
    

    Ich vermute letzteres ist etwas performanter.



  • DaRe schrieb:

    fwrite(tmp+4, 1, strlen(tmp+4), downloadedFile);
    

    recv liefert keinen '\0' am Ende des Strings... strlen sucht aber nach '\0':

    while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0)) > 0)
        {          
              buffer[readBytes]='\0'; // <-----Notwendig
    
              if (isBody)
              {
                 fwrite(buffer, 1, readBytes, downloadedFile);
              }
             
              if (strstr(buffer, "\r\n\r\n") && !isBody)
              {
                 char *tmp = strstr(buffer, "\r\n\r\n");
                 isBody = true;
                 fwrite(tmp+4, 1, strlen(tmp+4), downloadedFile);
              }
        }
    

    Auch möglich durch subtrahieren von Adressen:

    while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0)) > 0)
        {          
              if (isBody)
              {
                 fwrite(buffer, 1, readBytes, downloadedFile);
              }
             
              if (strstr(buffer, "\r\n\r\n") && !isBody)
              {
                 char *tmp = strstr(buffer, "\r\n\r\n");
                 isBody = true;
    
                 // 3. Parameter angepasst 
                 fwrite(tmp+4, 1, readBytes-((tmp+4)-buffer), downloadedFile);
              }
        }
    

    Ich vermute letzteres ist etwas performanter.

    WAAAAAH GEIL ES TUT!!! :-))) vielen Dank an die mitgeholfen haben 😉



  • DaRe schrieb:

    fwrite(tmp+4, 1, strlen(tmp+4), downloadedFile);
    

    recv liefert keinen '\0' am Ende des Strings... strlen sucht aber nach '\0':

    while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0)) > 0)
        {          
              buffer[readBytes]='\0'; // <-----Notwendig
    
              if (isBody)
              {
                 fwrite(buffer, 1, readBytes, downloadedFile);
              }
             
              if (strstr(buffer, "\r\n\r\n") && !isBody)
              {
                 char *tmp = strstr(buffer, "\r\n\r\n");
                 isBody = true;
                 fwrite(tmp+4, 1, strlen(tmp+4), downloadedFile);
              }
        }
    

    Auch möglich durch subtrahieren von Adressen:

    while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0)) > 0)
        {          
              if (isBody)
              {
                 fwrite(buffer, 1, readBytes, downloadedFile);
              }
             
              if (strstr(buffer, "\r\n\r\n") && !isBody)
              {
                 char *tmp = strstr(buffer, "\r\n\r\n");
                 isBody = true;
    
                 // 3. Parameter angepasst 
                 fwrite(tmp+4, 1, readBytes-((tmp+4)-buffer), downloadedFile);
              }
        }
    

    Ich vermute letzteres ist etwas performanter.

    Methode 1 funktioniert nicht für Binärdateien.
    Methode 2 schon.

    Etwas optimiert:

    while ((readBytes = recv(sDownload, buffer, sizeof(buffer), 0)) > 0)
    {          
        if (isBody)
        {
            fwrite(buffer,  1, readBytes, downloadedFile);
        }
        else
        {
            char *tmp = strstr(buffer,  "\r\n\r\n");
            if( tmp )
            {
                isBody  = true;
    
                // 3. Parameter angepasst
                fwrite(tmp+4, 1, readBytes-((tmp+4)-buffer), downloadedFile);
            }
        }
    }
    

    Ich hab übrigens auch schon header gehabt bei denen das Cookie alleine schon über 2000 bytes lang war.



  • DaRe schrieb:

    recv liefert keinen '\0' am Ende des Strings... strlen sucht aber nach '\0':

    wenn ich ne http response mit stringfunktionen bearbeite, sorge ich dafür dass der empfangspuffer stets nullterminiert ist:

    char buf [ 1024 ] = { 0 };
    int bytes;
    bytes = recv ( sock, buf, sizeof ( buf ) -1, 0 ); // -1: terminierung ist sichergestellt
    ...
    

    denn sonst kann es passieren, dass strstr und co. bis zum abwinken suchen, wenn sie die 0 nicht finden.

    gruß,
    B.B.



  • Oh ja, langsam lern ich dieses '\0' hassen.

    Wenn sein Header, wie er sagt nicht wirklich größer wird als 505 Byte oder sowas, dann ist die NULL-Prüfung auf strstr eigentlich nicht nötig... aber ich weiß nicht, was er da wirklich für nen Server am Start hat ^^

    Aber na gut, die erste Variante sollte nicht verwendet werden.


Anmelden zum Antworten