Problem mit HTTP & Sockets
-
So, ich hatte langeweile und hab das mal programmiert.
#include <stdexcept> #include <iostream> #include <string> #include <sstream> #include <fstream> #include <Windows.h> #include <WinSock.h> #pragma comment(lib, "ws2_32.lib") struct WSAInit { WSADATA wsa; WSAInit() { if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { throw std::runtime_error("WSAStartup()"); } } ~WSAInit() { WSACleanup(); } } WSAInitializer; class Socket { SOCKET s; SOCKADDR_IN addr; hostent *host; std::string buf; public: Socket() : s(-1) { s = socket(AF_INET, SOCK_STREAM, 0); if (s == -1) { throw std::runtime_error("socket()"); } memset(&addr ,0, sizeof(SOCKADDR_IN)); addr.sin_family = AF_INET; } ~Socket() { if (s != -1) { closesocket(s); } } bool Connect(const std::string& hostname, unsigned short port) { addr.sin_port=htons(port); host = gethostbyname(hostname.c_str()); addr.sin_addr = *(struct in_addr *) host->h_addr; return connect(s, (SOCKADDR *) &addr, sizeof(SOCKADDR)) != -1; } void Send(const std::string& str) { const char *C_STR = str.c_str(); size_t len = str.length() + 1; size_t bytesSent = 0; size_t bytesLeft = len; while (bytesSent < len) { int rc = send(s, C_STR + bytesSent, bytesLeft, 0); if (rc <= 0) throw std::runtime_error("Send()"); bytesSent += rc; bytesLeft -= rc; } } int Recv(char *data, size_t len) { if (buf.empty()) { size_t bytesRecv = 0; size_t bytesLeft = len; while (bytesRecv < len) { int rc = recv(s, data + bytesRecv, bytesLeft, 0); if (rc <= 0) throw std::runtime_error("Recv()"); bytesRecv += rc; bytesLeft -= rc; } return len; } if (buf.length() >= len) { memcpy(data, buf.c_str(), len); buf.erase(0, len); } else { int left = len - buf.length(); memcpy(data, buf.c_str(), buf.length()); recv(s, data + buf.length(), left, 0); buf.clear(); } return len; } int RecvLine(std::string& line) { size_t pos; while ((pos = buf.find('\n')) == std::string::npos) { char current[1024]; int rc = Recv(current, sizeof(current) - 1); if (rc <= 0) return -1; buf.append(current, rc); } line = buf.substr(0, pos - 1); buf.erase(0, pos + 1); return 0; } }; bool GetFile(const std::string& host, unsigned short port, const std::string& path, const std::string& dest); int main() { try { if (GetFile("www.netron.de", 80, "/bilder/schlecht.jpg", "Test.jpg")) { std::cout << "Done.\n" << std::endl; } } catch (std::exception& e) { std::cerr << e.what() << std::endl; } catch (...) { std::cerr << "Error: unknown error" << std::endl; } return 0; } bool GetFile(const std::string& host, unsigned short port, const std::string& path, const std::string& dest) { Socket http; if (!http.Connect(host, port)) { std::cerr << "Connect()" << std::endl; return false; } http.Send("GET " + path + " HTTP/1.0\r\nHost: " + host + "\r\n\r\n"); std::string line; int len = -1; for (;;) { http.RecvLine(line); if (line.find("Content-Length:") != std::string::npos) { std::stringstream strm(line); std::string buf; strm >> buf >> len; } else if (line == "") break; } std::cout << "Size: " << len << " byte(s)" << std::endl; int totalBytes = 0; int leftBytes = len; std::ofstream fOut(dest, std::ios::binary); size_t bytesRecv = 0; size_t bytesLeft = len; while (bytesRecv < len) { char buf[1024]; int rc = http.Recv(buf, bytesLeft < sizeof(buf) ? bytesLeft : sizeof(buf)); if (rc <= 0) throw std::runtime_error("Recv()"); fOut.write(buf, rc); bytesRecv += rc; bytesLeft -= rc; } fOut.close(); std::cout << "Received " << bytesRecv << " byte(s)" << std::endl; return len == bytesRecv; }
Ich bleibe aber dabei: Nutz lieber libcurl.
-
cooky451 schrieb:
EOP schrieb:
(wobei byteweise zu lesen die ineffektivste aller Möglichkeiten ist)
Aha, abgesehen davon dass das wohl kaum ein spürbarer Unterschied sein dürfte, welche Alternative würdest du denn vorschlagen?
Ohh, da täuscht du dich. Wenn man immer nur ein Byte liest, dann ist das absolut ineffizient. Alternative: s. mein Code.
-
#include <stdexcept> #include <iostream> #include <string> #include <sstream> #include <fstream> #include <algorithm> #include <Windows.h> #include <WinSock.h> #pragma comment(lib, "ws2_32.lib") struct WSAInit { WSADATA wsa; WSAInit() { if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { throw std::runtime_error("WSAStartup()"); } } ~WSAInit() { WSACleanup(); } } WSAInitializer; class Socket { SOCKET s; SOCKADDR_IN addr; hostent *host; std::string buf; public: Socket() : s(-1) { s = socket(AF_INET, SOCK_STREAM, 0); if (s == -1) { throw std::runtime_error("socket()"); } memset(&addr ,0, sizeof(SOCKADDR_IN)); addr.sin_family = AF_INET; } ~Socket() { if (s != -1) { closesocket(s); } } bool Connect(const std::string& hostname, unsigned short port) { addr.sin_port=htons(port); host = gethostbyname(hostname.c_str()); addr.sin_addr = *(struct in_addr *) host->h_addr; return connect(s, (SOCKADDR *) &addr, sizeof(SOCKADDR)) != -1; } void Send(const std::string& str) { const char *C_STR = str.c_str(); size_t len = str.length() + 1; size_t bytesSent = 0; size_t bytesLeft = len; while (bytesSent < len) { int rc = send(s, C_STR + bytesSent, bytesLeft, 0); if (rc <= 0) throw std::runtime_error("Send()"); bytesSent += rc; bytesLeft -= rc; } } int Recv(char *data, size_t len) { if (buf.empty()) { return recv(s, data, len, 0); } if (buf.length() > len) { memcpy(data, buf.c_str(), len); buf.erase(0, len); } else { memcpy(data, buf.c_str(), buf.length()); recv(s, data + buf.length(), len - buf.length(), 0); buf.clear(); } return len; } int RecvLine(std::string& line) { size_t pos; while ((pos = buf.find('\n')) == std::string::npos) { char current[1024]; int rc = Recv(current, sizeof(current)); if (rc <= 0) return rc; buf.append(current, rc); } line = buf.substr(0, pos - 1); buf.erase(0, pos + 1); return line.length(); } }; bool GetFile(const std::string& host, unsigned short port, const std::string& path, const std::string& dest); int main() { try { if (GetFile("www.netron.de", 80, "/bilder/schlecht.jpg", "Test.jpg")) { std::cout << "Done.\n" << std::endl; } } catch (std::exception& e) { std::cerr << e.what() << std::endl; } catch (...) { std::cerr << "Error: unknown error" << std::endl; } return 0; } bool GetFile(const std::string& host, unsigned short port, const std::string& path, const std::string& dest) { Socket http; if (!http.Connect(host, port)) { std::cerr << "Connect()" << std::endl; return false; } http.Send("GET " + path + " HTTP/1.0\r\nHost: " + host + "\r\n\r\n"); std::string line; int len = -1; for (;;) { http.RecvLine(line); std::transform(line.begin(), line.end(), line.begin(), tolower); if (line.find("content-length:") != std::string::npos) { std::stringstream strm(line); std::string buf; strm >> buf >> len; } else if (line.empty() || line == "\r") break; } std::cout << "Size: " << len << " byte(s)" << std::endl; int totalBytes = 0; int leftBytes = len; std::ofstream fOut(dest, std::ios::binary); size_t bytesRecv = 0; size_t bytesLeft = len; while (bytesRecv < len) { char buf[1024]; int rc = http.Recv(buf, bytesLeft < sizeof(buf) && len != -1 ? bytesLeft : sizeof(buf)); if (rc <= 0) if (len != -1) throw std::runtime_error("Recv()"); else break; fOut.write(buf, rc); bytesRecv += rc; bytesLeft -= rc; } fOut.close(); std::cout << "Received " << bytesRecv << " byte(s)" << std::endl; return len == -1 || len == bytesRecv; }
Einige Dinge wegen der Kompatibilität zu anderen Webservern geändert.
-
cooky451 schrieb:
EOP schrieb:
(wobei byteweise zu lesen die ineffektivste aller Möglichkeiten ist)
Aha, abgesehen davon dass das wohl kaum ein spürbarer Unterschied sein dürfte, welche Alternative würdest du denn vorschlagen?
cooky451 schrieb:
EOP schrieb:
(wobei byteweise zu lesen die ineffektivste aller Möglichkeiten ist)
Aha, abgesehen davon dass das wohl kaum ein spürbarer Unterschied sein dürfte, welche Alternative würdest du denn vorschlagen?
Ich mach's so in einem Programm, das auch chunked Binärdaten korrekt runterlädt:
1. ein recv Puffer + lesen von je 16kb oder auch mehr
2. mit strstr nach "\r\n\r\n" suchen (vorher '\0' anhängen), wenn vorhanden ->
sind die Daten auch schon komplett?
wenn ja, alles im recv Puffer verarbeiten
wenn nein, den Header parsen und die Daten mit memcpy in einen anderen Puffer verschieben wenn chunked und nicht komplett, sonst Daten schreiben bzw. den nicht kompletten Teil von chunked in den Puffer verschieben und wenn chunk-size komplett, size merken und nur die Daten in den Puffer
wenn chunked an Puffer anhängen und testen ob chunk size + chunk komplett, sonst Daten schreiben
3. wenn Header nicht komplett, alles in einen Puffer verschieben/anhängen
4. merken bis zu welchem Punkt man nach "\r\n\r\n" gesucht hat und als letzte Suchposition speichern
5. weiterlesen...
6. ist Header verarbeitet und nicht chunked -> schreiben sonst wieder an Puffer anhängen und testen ob chunk komplett
7. wenn chunk komplett, diesen schreiben und RestPuffer mit memmove an den Anfang verschieben
8. usw. usf.So in etwa. Ist zwar kompliziert, da ich jede Eventualität behandeln muss, aber extrem schnell.
Übrigens bin ich auch schon Headern begegnet, wo das cookie alleine schon über 2000 Bytes groß war.
Edit:
Da meine Einrückungen beim Posten leider verloren gehen ist das nicht so leicht lesbar/nachvollziehbar. Sorry.Ich kann dir mein tool schicken (ohne source) und du wirst sehen, daß es irre schnell ist.
-
hjkhjk schrieb:
cooky451 schrieb:
EOP schrieb:
(wobei byteweise zu lesen die ineffektivste aller Möglichkeiten ist)
Aha, abgesehen davon dass das wohl kaum ein spürbarer Unterschied sein dürfte, welche Alternative würdest du denn vorschlagen?
Ohh, da täuscht du dich. Wenn man immer nur ein Byte liest, dann ist das absolut ineffizient. Alternative: s. mein Code.
Nur daß du damit keine Binärdateien lesen kannst.
Und effektiv ist auch was anderes.
Und wenn du schon bei jeder unerwarteten Aktion throwst, warum nicht auch bei gethostbyname.
Was wenn ich 127.0.0.1 als Adresse angebe?
Was wenn ich http://host.com:81/file.rar eingebe?
[cpp]else if (line.empty() || line == "\r")
break;[/cpp]???
...
-
Es handelt sich lediglich um Beispielcode. Ich würde das nie produktiv verwenden, sondern auf libcurl zurückgreifen, wie es jeder vernünftige Programmierer auch tun würde.
Es wird sicher genügend Webseiten geben, die mit euren Codes nicht funktionieren, weil ihr etwas nicht beachtet habt.
Im übrigen ist es ein leichtes den Code umzuschreiben, um ohne std::string auszukommen.
-
EOP schrieb:
[..]
Ok, so kann man das auch machen
Wirklich sinnvoll finde ich das aber nicht, da der Unterschied kaum spürbar ist. 2000 recv() Aufrufe haut mein Rechner eh in nicht spürbarer Zeit raus, da macht das auch keinen Unterschied.. (Und ich hab jetzt nicht gerade die high-end CPU.)hjkhjk schrieb:
[..]
Omg. Man kann ein so simples Problem auch *leicht* aufblähen. Du tust vielleicht wirklich besser daran, libcurl zu nutzen
-
hjkhjk schrieb:
Es handelt sich lediglich um Beispielcode. Ich würde das nie produktiv verwenden, sondern auf libcurl zurückgreifen, wie es jeder vernünftige Programmierer auch tun würde.
Es wird sicher genügend Webseiten geben, die mit euren Codes nicht funktionieren, weil ihr etwas nicht beachtet habt.
Im übrigen ist es ein leichtes den Code umzuschreiben, um ohne std::string auszukommen.
Ich biete auch dir an, dir meinen simple downloader zu schicken. Ich denke aber nicht, daß er irgendwo versagt.
Bisher habe ich auch mehrere 100mb große Binärdateien fehlerfrei runtergeladen. Chunked oder nicht - kein Problem.Und alles nur mit send und recv. Wobei ich hinzufügen muss, das es absichtlich kein HTTP-error-checking macht.
Das war ja der Grund, wieso ich es mir geschrieben habe. Alle tools brachen bei 500 Internal Server Error ab obwohl 300 mb Daten gesendet wurden.
Und unterstützten kein POST, und...
-
Lad ihn irgendwo hoch und ich werde das mal antesten.
-
Nicht public - gibt's denn bei c-plusplus.net nicht sowas wie pm?
Manche Optionen sind - ähmm - sagen wir mal grenzwertig.