problem mit http - recv



  • hi leute

    ich hab ein problem mit dem recv von einem http server ...

    ich schicke ein GET dann überprüfe ich mit recv ob was angekommen ist ... das ist der fall ... dann schicke ich ein zweites GET, und noch ein recv .... doch nun kommt nix mehr an ... wireshark bestätigt das .... bei follow tcp stream kommt immer nur get - die antwort - get und dann keine antwort ....

    ich nutze c++ und linux, hab mich an die anleitung ( http://www.c-plusplus.net/forum/viewtopic-var-t-is-169861.html ) gehalten und das programm leicht abgeändert

    #include <iostream>
    #include <fstream>
    #include <stdexcept> // runtime_error
    #include <sstream>
    #include <sys/socket.h> // socket(), connect()
    #include <arpa/inet.h> // sockaddr_in
    #include <netdb.h> // gethostbyname(), hostent
    #include <errno.h> // errno
    
    std::runtime_error CreateSocketError()
    {
        std::ostringstream temp;
        temp << "Socket-Fehler #" << errno << ": " ;//<< strerror(errno);
        return std::runtime_error(temp.str());
    }
    
    void SendAll(int socket, const char* const buf, const int size)
    {
        int bytesSent = 0; // Anzahl Bytes die wir bereits vom Buffer gesendet haben
        do
        {
            int result = send(socket, buf + bytesSent, size - bytesSent, 0);
            if(result < 0) // Wenn send einen Wert < 0 zurück gibt deutet dies auf einen Fehler hin.
            {
                throw CreateSocketError();
            }
            bytesSent += result;
        } while(bytesSent < size);
    }
    
    std::string globalBuffer; // Globale Variable, nicht nachmachen!
    
    // Liest eine Zeile des Sockets in einen stringstream
    void GetLine(int socket, std::stringstream& line)
    {
        char buf[1024];
        int recvSize;
        std::string::size_type pos;
        while((pos = globalBuffer.find('\n')) == std::string::npos)
        {
            if((recvSize = recv(socket, buf, sizeof(buf), 0)) <= 0)
            {
                throw CreateSocketError();
            }
            globalBuffer.append(buf, recvSize);
        }
        line << globalBuffer.substr(0, pos);
        globalBuffer.erase(0, pos + 1);
    }
    
    int Recv(int socket, char *buf, int len, unsigned int flags) // Eigene Recv-Funktion
    {
        if(!globalBuffer.empty())
        {
            int copySize = len <= globalBuffer.size() ? len : globalBuffer.size();
            globalBuffer.copy(reinterpret_cast<char*>(buf), copySize);
            globalBuffer.erase(0, copySize);
            return copySize;
        }
        else
        {
            return recv(socket, buf, len, flags);
        }
    }
    
    // Entfernt das http:// vor dem URL
    void RemoveHttp(std::string& URL)
    {
        size_t pos = URL.find("http://");
        if(pos != std::string::npos)
        {
            URL.erase(0, 7);
        }
    }
    
    // Gibt die Dateiendung im URL zurück
    std::string GetFileEnding(std::string& URL)
    {
        using namespace std;
        size_t pos = URL.rfind(".");
        if(pos == string::npos)
        {
            return "";
        }
        URL.erase(0, pos);
        string ending = ".";
        // Algorithmus um Sachen wie ?index=home nicht zuzulassen
        for(string::iterator it = URL.begin() + 1; it != URL.end(); ++it)
        {
            if(isalpha(*it))
            {
                ending += *it;
            }
            else
            {
                break;
            }
        }
        return ending;
    }
    
    // Gibt den Hostnamen zurück und entfernt ihn aus der URL, sodass nur noch der Pfad übrigbleibt
    std::string RemoveHostname(std::string& URL)
    {
        size_t pos = URL.find("/");
        if(pos == std::string::npos)
        {
            std::string temp = URL;
            URL = "/";
            return temp;
        }
        std::string temp = URL.substr(0, pos);
        URL.erase(0, pos);
        return temp;
    }
    
    int main()
    {
        using namespace std;
    
        cout << "URL: ";
        string URL="www.google.de";
        //cin >> URL; // User gibt URL der Datei ein, die herruntergeladen werden soll
    
        RemoveHttp(URL);
    
        string hostname = RemoveHostname(URL);
    
        hostent* phe = gethostbyname(hostname.c_str());
    
        if(phe == NULL)
        {
            cout << "Host konnte nicht aufgeloest werden!" << endl;
            return 1;
        }
    
        if(phe->h_addrtype != AF_INET)
        {
            cout << "Ungueltiger Adresstyp!" << endl;
            return 1;
        }
    
        if(phe->h_length != 4)
        {
            cout << "Ungueltiger IP-Typ!" << endl;
            return 1;
        }
    
        int Socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(Socket == -1)
        {
            cout << "Socket konnte nicht erstellt werden!" << endl;
            return 1;
        }
    
        sockaddr_in service;
        service.sin_family = AF_INET;
        service.sin_port = htons(80); // Das HTTP-Protokoll benutzt Port 80
    
        char** p = phe->h_addr_list; // p mit erstem Listenelement initialisieren
        int result; // Ergebnis von connect
        do
        {
            if(*p == NULL) // Ende der Liste
            {
                cout << "Verbindung fehlgschlagen!" << endl;
                return 1;
            }
    
            service.sin_addr.s_addr = *reinterpret_cast<unsigned long*>(*p);
            ++p;
            result = connect(Socket, reinterpret_cast<sockaddr*>(&service), sizeof(service));
        }
        while(result == -1);
    
        cout << "Verbindung erfolgreich!" << endl;
    
        string request = "GET ";
        request += URL;    // z.B. /faq/index.html
        request += " HTTP/1.1\n";
        request += "Host: " + hostname + "\nConnection: close\n\n";
    
        cout << request << endl;
    
        try
        {
            cout << "1.Send\n";
            SendAll(Socket, request.c_str(), request.size());
            int code = 100; // 100 = Continue
            string Protokoll;
            stringstream firstLine; // Die erste Linie ist anders aufgebaut als der Rest
            while(code == 100)
            {
                GetLine(Socket, firstLine);
                firstLine >> Protokoll;
                firstLine >> code;
                if(code == 100)
                {
                    GetLine(Socket, firstLine); // Leere Zeile nach Continue ignorieren
                }
            }
            cout << "Protokoll: " << Protokoll << endl;
    
            sleep(5);
    
            cout << "2.Send\n";
            SendAll(Socket, request.c_str(), request.size());
            code = 100; // 100 = Continue
            while(code == 100)
            {
                GetLine(Socket, firstLine);
                firstLine >> Protokoll;
                firstLine >> code;
                if(code == 100)
                {
                    GetLine(Socket, firstLine); // Leere Zeile nach Continue ignorieren
                }
            }
            cout << "Protokoll: " << Protokoll << endl;
    
            if(code != 200)
            {
                firstLine.ignore(); // Leerzeichen nach dem Statuscode ignorieren
                string msg;
                getline(firstLine, msg);
                cout << "Error #" << code << " - " << msg << endl;
                return 0;
            }
    
            bool chunked = false;
            const int noSizeGiven = -1;
            int size = noSizeGiven;
    
            while(true)
            {
                stringstream sstream;
                GetLine(Socket, sstream);
                if(sstream.str() == "\r") // Header zu Ende?
                {
                    break;
                }
                string left; // Das was links steht
                sstream >> left;
                sstream.ignore(); // ignoriert Leerzeichen
                if(left == "Content-Length:")
                {
                    sstream >> size;
                }
                if(left == "Transfer-Encoding:")
                {
                    string transferEncoding;
                    sstream >> transferEncoding;
                    if(transferEncoding == "chunked")
                    {
                        chunked = true;
                    }
                }
            }
    
            string filename = "download" + GetFileEnding(URL);
            cout << "Filename: " << filename << endl;
            fstream fout(filename.c_str(), ios::binary | ios::out);
            if(!fout)
            {
                cout << "Could Not Create File!" << endl;
                return 1;
            }
            int recvSize = 0; // Empfangene Bytes insgesamt
            char buf[1024];
            int bytesRecv = -1; // Empfangene Bytes des letzten recv
    
            if(size != noSizeGiven) // Wenn die Größe über Content-length gegeben wurde
            {
                cout << "0%";
                while(recvSize < size)
                {
                    if((bytesRecv = Recv(Socket, buf, sizeof(buf), 0)) <= 0)
                    {
                        throw CreateSocketError();
                    }
                    recvSize += bytesRecv;
                    fout.write(buf, bytesRecv);
                    cout << "\r" << recvSize * 100 / size << "%" << flush; // Mit \r springen wir an den Anfang der Zeile
                }
            }
            else
            {
                if(!chunked)
                {
                    cout << "Downloading... (Unknown Filesize)" << endl;
                    while(bytesRecv != 0) // Wenn recv 0 zurück gibt, wurde die Verbindung beendet
                    {
                        if((bytesRecv = Recv(Socket, buf, sizeof(buf), 0)) < 0)
                        {
                            throw CreateSocketError();
                        }
                        fout.write(buf, bytesRecv);
                    }
                }
                else
                {
                    cout << "Downloading... (Chunked)" << endl;
                    while(true)
                    {
                        stringstream sstream;
                        GetLine(Socket, sstream);
                        int chunkSize = -1;
                        sstream >> hex >> chunkSize; // Größe des nächsten Parts einlesen
                        if(chunkSize <= 0)
                        {
                            break;
                        }
                        cout << "Downloading Part (" << chunkSize << " Bytes)... " << endl;
                        recvSize = 0; // Vor jeder Schleife wieder auf 0 setzen
                        while(recvSize < chunkSize)
                        {
                            int bytesToRecv = chunkSize - recvSize;
                            if((bytesRecv = Recv(Socket, buf, bytesToRecv > sizeof(buf) ? sizeof(buf) : bytesToRecv, 0)) <= 0)
                            {
                                throw CreateSocketError();
                            }
                            recvSize += bytesRecv;
                            fout.write(buf, bytesRecv);
                            cout << "\r" << recvSize * 100 / chunkSize << "%" << flush;
                        }
                        cout << endl;
                        for(int i = 0; i < 2; ++i)
                        {
                            char temp;
                            recv(Socket, &temp, 1, 0);
                        }
                    }
                }
            }
            cout << endl << "Finished!" << endl;
        }
        catch(exception& e)
        {
            cout << endl;
            cerr << e.what() << endl;
        }
        close(Socket); // Verbindung beenden
    }
    

    sry ich hab leider kein spoiler tag entdeckt ... beachtet bitte in der main funktion das senden und empfangen

    was kann das problem sein? liegt es am source code oder sende ich nur ein falsches GET request?

    und sry wenn ich in eine falsche kategorie gepostet habe ... aber ich nehme an dass der fehler tatsächlich an dem GET request liegt ....

    also vielen dank fürs helfen 🙂

    hano



  • Du schickst ein "Connection: Close", womit der Server die Verbindung schließt. Also entweder neue Verbindung aufbauen, oder "Connection: keep-alive" angeben. Ach, und auch in Requests werden Zeilen mit "\r\n" statt nur "\n" angeschlossen. Siehe auch http://www.jmarshall.com/easy/http/ 🙂



  • oh sehr gut danke 🙂
    das hat geholfen ... jedenfalls an diesem beispiel ....

    nur ich hab versucht das zu abstrahieren mit klassen .... sende aber das selbe get request ... und trotzdem tritt das selbe problem auf ... obwohl ich ein keep alive sende ...

    void inet::sendGet()
    {
        cout << "inet::sendGet\n";
        string req;
        req+="GET ";
        req+=zurl->getPfad();
        req+=" HTTP/1.1\r\nHost: ";
        req+=zurl->getHostname();
        req+="\r\nConnection: keep-alive\r\n\r\n";
        cout << req << endl;
    
        sendAll(&req);
    }
    
    void inet::sendAll(string *tosend)
    {
        cout << "inet::sendAll\n";
        unsigned int bytesSent = 0; // Anzahl Bytes die wir bereits vom Buffer gesendet haben
        do
        {
            int res = send(Socket, tosend->c_str() + bytesSent, tosend->length() - bytesSent, 0);
            if (res < 0) // Wenn send einen Wert < 0 zurück gibt deutet dies auf einen Fehler hin.
            {
                cout << "Fehler in inet::sendAll!\n";
            }
            bytesSent += res;
        }
        while (bytesSent < tosend->length());
    }
    
    void inet::getAll(string *toget)
    {
        cout << "inet::getAll\n";
        int ret=1;
        char buf[2048];
        while (ret>0)
        {
            ret=recv(Socket,&buf,sizeof(buf),0);
            if (ret<=0)
            {
                break;
            }
            toget->append(buf,ret);
        }
        cout << toget->length() << " Bytes empfangen\n";
        if (ret<0)
        {
            cout << "Fehler in inet::getAll!\n";
            return;
        }
        cout << "Dateien erfolgreich empfangen.\n";
    }
    

    das problem sollte damit nicht mehr das gesendete paket sein, sondern eigentlich die abstrahierte recv-funktion ..... nur ich komm nicht auf den fehler ....

    vielen vielen dank für eure hilfe 👍

    hano



  • Hm, kann es sein, dass dein Code aus der Empfangs-Schleife gar nicht mehr rauskommt? Das ist bei HTTP leider etwas schwierig, eigentlich müsstest du den Antwort-Header parsen um zu wissen, wieviel du insgesamt empfangen musst. Und nutzt du blockierende oder nicht-blockierende Sockets? Und wäre für dich vielleicht eine Option, eine (wenigstens etwas) abstrahierende Bibliothek zu nutzen, z.B. SFML?

    edit: Ja, ich denke um's Parsen kommst du nicht drumherum. Ich bastele zur Zeit nebenbei auch mit HTTP rum, ich kann gleich mal skizzieren, wie ich das Parsen von der Struktur gelöst hab, wenn du magst 🙂



  • ok es scheint wohl so als müsste man dabei tatsächlich immer den header parsen ... das ist ein komischer fehler ... ich schaus mir morgen nochmal an, vielleicht ergibt sich ja mehr ...

    hmm ich hab schon drüber nachgedacht eine fertige library zu nehmen (libcurl) aber ich wollte eigentlich gerade diese abstraktionsklasse selber schreiben ... sozusagen nur auf den systemfunktionen aufbauend ....

    also nochmals vielen herzlichen dank 🙂 echt klasse hilfe

    mfg hano



  • Ich bastele zur Zeit nebenbei auch mit HTTP rum, ich kann gleich mal skizzieren, wie ich das Parsen von der Struktur gelöst hab, wenn du magst 🙂

    zeig mal bitte 🙂



  • Ohne den code jetzt untersucht zu haben, geht man grundsätzlich so vor:

    - finde \r\n\r\n um das header-ende bestimmen zu können
    - teile in header und data
    - parse header um bestimmen was für Daten gesendet wurden: Content-Length, Chunked oder "HTTP 1.0 Standard"
    - behandle die Daten dementsprechend in einer Schleife bis
    entweder alle Bytes empfangen wurden (Content-Length oder Chunked)
    oder 0 Bytes gesendet wurden (gilt für alle 3 Transfertypen, da so einige server mitunter auch fehlerhaft programmiert sind)


Anmelden zum Antworten