Pobieranie kodu HTML stron w C++ za pomocą WinSock2
Dzisiejsza notka będzie o tym jak pobrać kod HTML strony WWW w aplikacji C++ korzystając z WinSock2.
Pierwszą rzeczą, którą trzeba wiedzieć jest to w jaki sposób robi to przeglądarka internetowa. Zacznę od tego jak wygląda typowy adres strony WWW na przykładzie adresu do działu artykułów na stronie http://www.gamedev.pl/:
http://www.gamedev.pl/articles.php |
Powyższy adres składa się z kilku części:
- “http://” – protokół reprezentujący sposób transmisji danych, dzięki niemu przeglądarka wie w jaki sposób komunikować się z serwerem oraz na jaki port wysyłać żądania
- “www.gamedev.pl” – domena na którą będzie wysłane zapytanie – podany adres jest tłumaczony na adres IP przez serwer DNS
- “/articles.php” – adres żądanego plik lub żądanie dla serwera WWW, które aplikacja wykorzystuje do stworzenia nagłówka
Przeglądarka mając adres strony tworzy odpowiedni nagłówek, który zostaje wysłany do serwera WWW. Jak już wspomniałem adresem tego serwera jest domena zawarta w adresie WWW natomiast port jest określany na podstawie protokołu, czyli w przypadku “http://” jest to port 80. Nagłówek HTTP powinien zawierać następujące elementy:
- rodzaj zapytania – w przypadku pobrania strony jest to “GET”
- adres żądanego pliku lub żądanie – to co chcemy od serwera otrzymać, najczęściej jest to adres pliku na serwerze, czyli “/articles.php” w tym przykładzie
- wersja protokołu – typowo HTTP/1.1
- domena hosta
Dodatkowo, jeśli jest to konieczne, można wysłać informacje o tym, jakiej przeglądarki używamy (UserAgent), informacje o akceptowanych plikach, kodowaniu, ciasteczkach oraz czasie trwania połączenia, więcej można dowiedzieć się z tej strony wiki oraz samych nagłówków, które wysyła przeglądarka. Jako ciekawostkę mogę dodać, że istnieje fajna wtyczka dla Firefoxa, która pokazuje nagłówki wysyłane przez przeglądarkę – Live HTTP headers.
Oto nagłówek uzyskany z pomocą tej wtyczki podczas łączenia się do przykładowej strony:
1 2 3 4 5 6 7 8 9 | GET /articles.php HTTP/1.1 Host: www.gamedev.pl User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; pl; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: pl,en-us;q=0.7,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive |
Jak widać jest on trochę długi (usunąłem informacje o ciasteczkach, nie są tutaj potrzebne). Aby pobrać kod strony wystarczą tak naprawdę dwie pierwsze linijki. Jednak ważną rzeczą jest, aby po każdej linijce występowała para znaków \r\n a koniec nagłówka reprezentowany był przez dwie pary tych znaków. W przypadku, gdy nagłówek nie będzie zawierał tak skonstruowanego nagłówka, serwer po prostu udrzuci zapytanie.
Teraz pora na kod C++ z WinSock2, oto on:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | // Usuwa zbędne definicje z nagłówka #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <winsock2.h> #include <ws2tcpip.h> #include <string> #define BUFFER_SIZE 2048 #pragma comment(lib, "ws2_32.lib") // Niezbędna biblioteka using namespace std; int main() { //************************************************ // Inicjalizacja WinSock2 //************************************************ WSADATA wsaData; int error; string answer; ZeroMemory(&wsaData, sizeof(wsaData)); if(FAILED(WSAStartup(MAKEWORD(2,2), &wsaData))) // MAKEWORD(2,2) - Wersja WinSock { return 1; } char recvBuffer[BUFFER_SIZE]; addrinfo hint; // Struktura przechowująca dane o połączeniu addrinfo* wsResult; // Wskaźnik na rezultat SOCKET pSocket; // Właściwy pSocket ZeroMemory(recvBuffer, sizeof(recvBuffer)); ZeroMemory(&hint, sizeof(hint)); hint.ai_family = AF_UNSPEC; // Rodzaj transmisji - nieokreślony hint.ai_socktype = SOCK_STREAM; // Typ gniazda - strumień wsResult = NULL; pSocket = INVALID_SOCKET; //************************************************ // Tworzenie zapytania //************************************************ // Wyciąganie informacji z adresu string httpAddress = "http://www.gamedev.pl/articles.php"; string temp = httpAddress.substr(httpAddress.find("http://") + sizeof("http://") - 1); // Tylko http:// więc można wyciąć string domain = temp.substr(0, temp.find_first_of('/')); // Domena string addressTail = temp.substr(temp.find_first_of('/')); // Żądanie pliku // Tworzenie nagłówka temp = string("GET ") + addressTail + " HTTP/1.1\r\n" + "Host: " + domain + "\r\n" //+ "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5\r\n" //+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" //+ "Accept-Language: pl,en-us;q=0.7,en;q=0.3\r\n" //+ "Accept-Encoding: gzip,deflate\r\n" //+ "Accept-Charset: ISO-8859-2,utf-8;q=0.7,*;q=0.7\r\n" //+ "Keep-Alive: 300\r\n" //+ "Connection: keep-alive\r\n" + "\r\n\r\n"; // Pobieranie IP serwera z serwera DNS if(FAILED(getaddrinfo(domain.c_str(), TEXT("80"), &hint, &wsResult))) { printf("Nie udalo sie pobrac IP\n"); return 1; } // Wskaźnik na zwrócone adresy addrinfo* ptr = wsResult; // Tworzenie socketu if(FAILED(pSocket = socket(wsResult->ai_family, wsResult->ai_socktype, wsResult->ai_protocol))) { freeaddrinfo(wsResult); printf("Nie udało się utworzyc socketu\n"); return 1; } //************************************************ // Wysyłanie zapytania //************************************************ // Łączenie się do serwera if(FAILED(connect(pSocket, ptr->ai_addr, (int)ptr->ai_addrlen))) { printf("Nie udalo sie polaczyc do serwera\n"); goto Error; } // Wysyłanie nagłówka if(FAILED(send(pSocket, temp.c_str(), temp.size(), 0))) { printf("Nie udalo sie wyslac naglowka\n"); goto Error; } // Kończenie wysyłania if(FAILED(shutdown(pSocket, SD_SEND))) { printf("Nie udalo sie zamknac polaczenia\n"); goto Error; } //************************************************ // Odbieranie danych //************************************************ // Odbieranie danych do { ZeroMemory(recvBuffer, sizeof(recvBuffer)); error = recv(pSocket, recvBuffer, sizeof(recvBuffer), 0); if(error > 0) { answer += string(recvBuffer); } } while(error > 0); closesocket(pSocket); // Zamknięcie socketu freeaddrinfo(wsResult); // oraz zwolnienie struktury addrinfo printf("Kod HTML strony \"http://www.gamedev.pl/articles.php\":\n\n%s", answer.c_str()); return 0; Error: closesocket(pSocket); freeaddrinfo(wsResult); return 1; } |
Wynikiem tego kodu jest wyświetlony kod HTML przykładowej strony.
Reg:
WinAPI zawiera też bibliotekę WinINet, która ma obsługę protokołów HTTP i FTP.
24 January 2010, 3:14 pmTomcat:
Niesławna instrukcja goto :P W końcu jakiś dłuższy i ciekawszy artykuł od Ciebie, który na dodatek zaciekawił na tyle by samemu się tym pobawić ;)
12 March 2010, 9:00 amNetrix:
Cieszę się, postaram się więcej takich pisać :).
12 March 2010, 10:56 pmSimon:
Fajne to by bylo jakby jeszcze cos robilo :P u mnie nic nie zwraca :P nie wspominajac ze sie wywala na goto :P
13 June 2010, 11:14 amNetrix:
Hmm, wychodzi na to że przypadkiem wkleiłem wcześniej niedziałający kod albo przywróciłem po padzie taki. Już jest poprawiony i działa. Dzięki za info.
17 June 2010, 11:10 pm