[c] socket pobieranie kanałów RSS

0

Potrzebuję napisać program który będzie pobierał kanał RSS z podanego adresu.
Korzystam z przykładu z tutoriala o socketach ze strony firsthost
Kod wygląda tak:

#include <stdio.h>
#include <winsock2.h>

int resolveHost( char *host )
{
	LPHOSTENT hostEntry = gethostbyname(host);

	if ( !hostEntry )
	{
		unsigned int addr = inet_addr( host );
		hostEntry = gethostbyaddr((char *)&addr, 4, AF_INET);

		if ( !hostEntry )
		{
			return 0;
		}
	}

	return *((int*)*hostEntry->h_addr_list);
}

int main(int argc, char* argv[])
{
	WSADATA		wsaData;
	SOCKADDR_IN saddr;
	SOCKET		sock;
	char		*http_req = "GET http://www.tvn24.pl/sport.xml HTTP/1.0\n\n";
	char		index[1024*20] = { 0 };

	WSAStartup( MAKEWORD(2,2), &wsaData );

	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	saddr.sin_addr.S_un.S_addr = resolveHost ("www.tvn24.pl");
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(80);

	if ( connect(sock, (SOCKADDR*)&saddr, sizeof(SOCKADDR)) == SOCKET_ERROR )
	{
		/* połączenie się nie powiodło */
		sock = 0;
		return -1;
	}

	send(sock,http_req,strlen(http_req),0);
	recv(sock,index,1024*20,0);

	printf("%s", index);;

	closesocket(sock);
	WSACleanup();
	return 0;
}

Niestety działa tylko w niektórych przypadkach, a mianowicie wtedy gdy adres do kanału jest podany w odniesieniu do domeny głównej (nie przez subdomenę [np. http://sport.wp.pl/kat,1726,rss.xml]).

Czy wie ktoś co powinienem zrobić, aby program nie tylko potrafił pobierać kanały gdy dostanie adres do domeny głównej?

Przy jednej z prób otrzymałem taki wynik:
Location: http://http/index.php/pl/www.XXXXX.pl/index.php/pl
Gdy zmienne były ustawione tak:

char		*http_req = "GET http://www.XXXXX.pl/index.php/pl HTTP/1.0\n\n";
saddr.sin_addr.S_un.S_addr = resolveHost ("www.XXXXX.pl");
0

Dostajesz przekierowanie na innego hosta -> 303. Czyli jeśli dostaniesz od serwera odpowiedź o tym numerze, musisz wyłuskać wartość pola location, który jest url-em, i całą procedurę powtórzyć.

p.s. pod windowsem możesz użyć funkcji biblioteki //wininet//. Ich użycie uwolni Cię od zabawy z przetwarzaniem nagłówków, przekierowań etc.

0

W większości przypadków gdy próbuję pobrać stronę otrzymuję taką odpowiedź:

UNKNOWN 400 Bad Request
Server: aris
Content-Type: text/html
Date: Sun, 20 May 2012 12:56:48 GMT
Last-Modified: Sun, 20 May 2012 12:56:48 GMT
Accept-Ranges: bytes
Connection: close

<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>
<BODY><H2>400 Bad Request</H2>
Your request has bad syntax or is inherently impossible to satisfy.
<HR>
<ADDRESS><A HREF="http://www.wp.pl/">aris</A></ADDRESS>
</BODY></HTML>

Zmienne wyglądają wtedy tak:

char		*http_req = "GET http://media.wp.pl/rss.xml HTTP/1.0\n\n";
saddr.sin_addr.S_un.S_addr = resolveHost ("media.wp.pl");
0
char *http_req = "GET /rss.xml HTTP/1.1\r\nHost: media.wp.pl\r\n\r\n";

W HTTP koniec linii to sekwencja \r\n a nie \n.

0
0x666 napisał(a):
char *http_req = "GET /rss.xml HTTP/1.1\r\nHost: media.wp.pl\r\n\r\n";

W HTTP koniec linii to sekwencja \r\n a nie \n.

Zmieniłem na to co podałeś o od serwera otrzymuję takie coś:

HTTP/1.1 200 OK
Server: aris
Content-Type: text/xml; charset=ISO-8859-2
Pragma: no-cache
Set-Cookie: statid=89.72.161.154.4190:1337526707:1050699485:v1; path=/; expires=
Wed, 20-May-15 15:11:47 GMT
Set-Cookie: statid=89.72.161.154.4190:1337526707:1050699485:v1; domain=.wp.pl; p
ath=/; expires=Wed, 20-May-15 15:11:47 GMT
Content-Length: 8665
Connection: close

Czyli chyba że źle jest odczytywany adres który chcę wyświetlić. (w domain jest .wp.pl, a powinno być media.wp.pl)
Adres serwera dalej zostawiłem ten sam:

saddr.sin_addr.S_un.S_addr = resolveHost ("media.wp.pl");
0

Przecież to jest nagłówek odpowiedzi! Kod 200 oznacza, że wszystko ok, serwer odesłał ci 8665 bajtów danych (pomijając nagłówek). Dane znajdują się za nagłówkiem, czyli

...
ath=/; expires=Wed, 20-May-15 15:11:47 GMT
Content-Length: 8665
Connection: close

< 8665 bajtów danych >

pusta linia oznacza koniec nagłówka.

Poczytaj trochę o HTTP, bo widzę, że Twoja wiedza o tym protokole jest żadna.

0
0x666 napisał(a):

Przecież to jest nagłówek odpowiedzi! Kod 200 oznacza, że wszystko ok, serwer odesłał ci 8665 bajtów danych (pomijając nagłówek). Dane znajdują się za nagłówkiem, czyli

...
ath=/; expires=Wed, 20-May-15 15:11:47 GMT
Content-Length: 8665
Connection: close

< 8665 bajtów danych >

pusta linia oznacza koniec nagłówka.

Poczytaj trochę o HTTP, bo widzę, że Twoja wiedza o tym protokole jest żadna.

Dobrze widzisz, ale niestety nie mam w tej chwili czasu na naukę HTTP [co nie oznacza, że nie chcę tego się nauczyć](mam nadzieję, że aby pobrać zawartość strony nie będzie potrzebna zbyt rozległa wiedza na ten temat). Program muszę napisać na określony czas i niestety nie zostało go za dużo.

W programie mam stworzoną zmienną do której zapisuję to co zwróci serwer. Później wyświetlam to na ekran to co się tam zapisało. W zmiennej na pewno jest miejsce, aby pomieścić te 9000 bajtów (nawet dużo więcej), a mimo wszystko zapisuje się tam jedynie to co widać w poprzednim poście. Co jest nie tak?
Ważną uwagą, jest, że sprawdziłem to co mi wysłałeś na innej stronie i tam otrzymuję normalnie zawartość strony.

Również mam pytania które będą ważne gdy już będzie działało pobieranie stron bez problemu.

  1. Czy istnieje możliwość, aby pobrać z serwera jedynie samą liczbę (int) bajtów które zawiera dana strona? Dzięki temu można by stworzyć dynamiczną rezerwację pamięci bez jej marnowania lub bez zagrożenia, że coś się nie zapisze?
  2. Czy jest możliwość, aby zapisać zawartość strony bezpośrednio do pliku, bez zapisywania jej do zamiennej a później dopiero do pliku?
0

Co jest nie tak?

No np. to, że recv nie musi odebrać od razu całości odpowiedzi.

Co do pytań:

  1. w pewnym sensie tak, w nagłówku masz pole Content-Length, ono mówi o wielkości danych. Problem w tym, że to pole nie musi występować, wtedy czytasz z socketa, aż recv zwróci 0 (wtedy pole Connection jest ustawione na close).
  2. oczywiście, że tak. Możesz czytać z socketa fragmentami (np. po 1KB) i to, co przeczytasz, od razu zapisujesz do pliku.
0

Jednak się nie obędzie bez nauki

Znasz może jakąś stronę czy książkę, może wystarczy jakiś rozdział w książce gdzie dobrze są opisane sockety? Do tej pory korzystałem z tutoriala na stronie firsthost.nazwa.pl

0

Ok przeczytałem to co pisze na tej stronie i pisze tam tylko jak używać socketów (dużo mi to oczywiście pomogło), niestety brakuje tam przykładów z zapytaniami HTTP.

Mam więc pytania:

  1. Skoro odpowiedź od serwera HTTP nie musi zawierać w nagłówku ilości bajtów które przesyła do mnie to w jaki sposób mam je wszystkie odebrać? W tej chwili myślę, aby stworzyć pętlę która działa dopóki recv nie zwróci 0 lub -1 i pobierać z recv po 512 bajtów za każdym razem dopisując je do pliku. Tylko skąd mam wiedzieć kiedy kończy się nagłówek z zaczyna wiadomość skoro może on być zmiennej długości (w pliku chcę zapisać samą wiadomość)
  2. Czy istnieją jakieś funkcje obsługujące nagłówki HTTP (nie myślę tutaj o dodatkowych bibliotekach)?

I pytanie nie związane z moim programem:

Czy przez sockety można wysyłać pliki czy tylko dane tekstowe? (chodzi mi o pliki np. zdjęcie czy film nie tekstowe)

1

Ad.1. Możesz to zrobić tak:

char buf[2000];
int read = 0;
int c;

while((c = recv(sock, buf + read, sizeof(buf) - read, 0)) > 0)
{
	read += c;
	buf[c] = 0;
	
	const char *p = strstr(buf, "\r\n\r\n");
	if(p)
	{
		/*
			Między 'buf' a 'p + 4' masz cały nagłówek. Wyciągnij z niego odpowiednie informacje i czytaj dane.
			
			Od 'p + 4' do 'buf + read' może być jakaś porcja danych, które powinieneś zapisać.
		*/
		break;
	}
}

/* tu czytasz resztę danych z socketa */

Ad.2. W C nie ma obsługi HTTP. Choć, jak już wspomniałem, jeśli piszesz pod windowsa, możesz użyć funkcji biblioteki wininet, która wchodzi w skład systemu.

Czy przez sockety można wysyłać pliki czy tylko dane tekstowe?

Możesz wysyłać co Ci się podoba.

0

Dobrze wiedzieć, że nagłówek HTTP kończy się na "\r\n\r\n" (już miałem zrobione wyszukiwanie pierwszego znaku '<' i od tego miejsca przyjmowałem, że zaczyna się wiadomość [działa poprawnie]). Również napotkałem problem, że jak zrobiłem pętle dla recv = 0 lub -1 to stronę pobierało mi kilkukrotnie na to jedyne rozwiązanie jakie wymyśliłem to sprawdzenie czy w pobranym tekście nie jest zamykany znacznik </rss> jak tak to znaczy ze tu się kończy jedna wiadomość i żeby dalej nie pobierać (jakie mogę wykorzystać bardziej uniwersalne rozwiązanie?).

Gdy korzystam z wyszukiwania strstr(buf, "\r\n\r\n"); to wiadomość(to co znajduje się za "\r\n\r\n") zaczyna się od 2 dziwnych znaków o kodzie ANSII 17 (control-1) oraz 64 (@). Myślę że serwer wysyła znaki których nie ma w 256 znakach ANSII i wyświetla mi akurat te znaki. (tylko dlaczego zapisuje mi w pliku również polskie znaki? :p)

0

Dobrze wiedzieć, że nagłówek HTTP kończy się na "\r\n\r\n"

Dokładniej nagłówek kończy się pustą linią, pisałem o tym. Sekwencja \r\n oznacza koniec linii (EOL).

Jeśli chodzi o pytanie, to nie bardzo rozumiem o co chodzi.

0
0x666 napisał(a):

Dobrze wiedzieć, że nagłówek HTTP kończy się na "\r\n\r\n"

Dokładniej nagłówek kończy się pustą linią, pisałem o tym. Sekwencja \r\n oznacza koniec linii (EOL).

Jeśli chodzi o pytanie, to nie bardzo rozumiem o co chodzi.

  1. Jeżeli pobieram dane przez socket i zapisuje je do zmiennej typu char która może posiadać tylko jeden z 256 znaków ANSII. To dlaczego po pobraniu danych, następnie zapisaniu ich do tej zmiennej (tablica znaków) i ostatecznie zapisaniu danych przechowywanych w zmiennej do pliku (dopisaniu do pliku) zapisuje mi również polskie litery które nie wchodzą w skład znaków zawartych w ANSII? (mam nadzieję, że teraz bardziej zrozumiale)
  2. Chodzi o to, że nagłówek u mnie rzeczywiście się kończy na podwójnej pustej lini ("\r\n\r\n"), ale dwa następne znaki, które są rzekomo w treści wiadomości to znak o kodzie ANSII 17 i następny o kodzie 64. A gdy wyświetlam źródło strony w przeglądarce to nie widzę tam tych znaków (znaku o kodzie 17 rzeczywiście by nie było widać, ale znak 64 to jest znak widoczny na ekranie '@'). I pytanie skąd te dwa znaki zaraz po końcu nagłówka?
  3. Czy pod linuxem znajduje się analogiczna biblioteka do wininet? (może to jest ta <netinet/in.h>?)
0
  1. jak nie wchodzą w skład ANSI? Pierwsze 127 wartości to podstawowy zestaw znaków ASCII, powyżej tej wartości są znaki specyficzne dla danej lokalizacji i kodowania. W nagłówku odpowiedzi masz pole Content-Type, które mówi o typie danych i ich (ewentualnym) kodowaniu. Masz tam kodowanie ISO-8859-2, więc sprawa jasna.
  2. nie wiem skąd, ponieważ nie widzę kodu, który napisałeś.
  3. nie wiem, ale jeśli chcesz mieć przenośne rozwiązanie, użyj np. wspomnianej biblioteki cURL.

1 użytkowników online, w tym zalogowanych: 0, gości: 1