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 |
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 |
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;
} |
// 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.