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 | // 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; 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))) { return 1; } // Wskaźnik na zwrócone adresy addrinfo* ptr = wsResult; // Tworzenie socketu if(FAILED(socket(wsResult->ai_family, wsResult->ai_socktype, wsResult->ai_protocol))) { freeaddrinfo(wsResult); return 1; } //************************************************ // Wysyłanie zapytania //************************************************ // Łączenie się do serwera if(FAILED(connect(pSocket, ptr->ai_addr, (int)ptr->ai_addrlen))) { goto Error; } // Wysyłanie nagłówka if(FAILED(send(pSocket, temp.c_str(), temp.size(), 0))) { goto Error; } // Kończenie wysyłania if(FAILED(shutdown(pSocket, SD_SEND))) { goto Error; } //************************************************ // Odbieranie danych //************************************************ string answer; // 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 stycznia 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 marca 2010, 9:00 amNetrix:
Cieszę się, postaram się więcej takich pisać :).
12 marca 2010, 10:56 pm