LAB: Integracja z HTTP/WWW

Celem laboratorium jest ukazanie podstawowych metod integracji języka Prolog z protokołem HTTP, używania gniazd TCP oraz zademonstrowanie wielowątkowości w SWI-Prolog.

1. Wątki

Wielowątkowość w SWI-Prolog oparta jest na standardzie POSIX.

Dokumentacja: Multi-threaded applications

Tworzenie wątków

Tworzenie oddzielnego wątku odbywa się przy użyciu predykatu thread_create/3:

thread_create(:Goal, -Id, +Options)

Predykat thread_self/1 zwraca identyfikator wątku, w którym został wywołany.

Ćwiczenie:

Proszę wkleić poniższy kod do pliku i wczytać go w SWI-Prolog:

start :-
	thread_create(write_ID(10),ID1,[]),
	write('Thread with ID: '), write(ID1), writeln(' has been started!'),
	thread_create(write_ID(10),ID2,[]),
	write('Thread with ID: '), write(ID2), writeln(' has been started!').
 
write_ID(0).
 
write_ID(Times) :-
	thread_self(ID),
	write(ID),
	NewTimes is Times - 1,
	write_ID(NewTimes).

Proszę uruchomić program predykatem start/0 kilkakrotnie i zaobserwować działanie.

Synchronizacja wątków

SWI-Prolog implementuje semafor, który można wykorzystać do kontroli sekcji krytycznej - mutex (MUTual EXclusive device). Proszę zapoznać się z predykatami: mutex_lock/1 i mutex_unlock/1.

Ćwiczenie

Kod programu z poprzedniego ćwiczenia proszę zmodyfikować tak, aby zaobserwować niepożądany efekt korzystania z jednego zasobu przez więcej niż jeden wątek jednocześnie. Druga klauzula predykatu write_ID/1 mogłaby wyglądać np. tak:

write_ID(Times) :-
	thread_self(ID),
	write(ID), write(': I am running! '),
	NewTimes is Times - 1,
	write_ID(NewTimes).

Proszę przetestować, czy zamierzony efekt zachodzi, a następnie tak zmodyfikować program, aby komunikaty jednego wątku (np. 2: I am running!) nie przeplatały się z komunikatami drugiego.

Komunikacja między wątkami

Każdy wątek ma przypisaną kolejkę wiadomości, z której może odczytywać wiadomości wysyłane przez inne wątki w postaci termu.

thread_send_message(+ThreadId, +Term) - wysyła wiadomość w postaci termu do kolejki wiadomości wskazanego wątku.

thread_get_message(?Term) - czeka na wiadomość w kolejce, która będzie mogła być zunifikowana z termem Term.

threads/0 - podaje informacje o pracujących/zakończonych wątkach i status każdego z nich.

Ćwiczenie

Proszę wkleić poniższy kod do pliku i wczytać go w SWI-Prolog:

start :-
	thread_create(do_nothing,ID1,[]),
	write('Thread with ID: '), write(ID1), writeln(' has been started!'),
	thread_create(do_nothing,ID2,[]),
	write('Thread with ID: '), write(ID2), writeln(' has been started!').
 
do_nothing :-
	thread_self(ID),
	thread_get_message(say(A)),
	write('Thread '), write(ID), write(' says: '), writeln(A).

Po użyciu predykatu start/0 dwa wątki pracują i czekają na wiadomość w postaci termu say/1.

Proszę użyć predykatu threads/0 i zauważyć, że główny wątek (ten, w którym wpisuje się polecenia w konsoli) także jest wylistowany i zachowuje się jak inne wątki (można mu wysłać wiadomość, można go synchronizować z innymi, itp.).

Proszę wpisać w konsoli: thread_send_message(ID,say('Something stupid!')), gdzie ID to identyfikator jednego z pracujących wątków. Po ponownym użyciu predykatu threads/0 proszę zauważyć, że jeden z wątków zmienił status. Został zakończony sukcesem (true). Proszę wysłać wiadomość także do drugiego oczekującego wątku.

Problem pięciu filozofów

Proszę zapoznać się z problemem pięciu filozofów w Wikipedii. To klasyczny problem, którego rozwiązanie korzysta z wielowątkowości i synchronizacji wątków. Proszę wczytać plik 5_philosophers.pl, zapoznać się z nim i uruchomić predykatem start/0. W początkowej części programu znajdują się ustawienia. Proszę je zmodyfikować wg własnego uznania i przetestować poprawność działania programu.

2. Gniazda TCP

Biblioteka socket udostępnia w SWI-Prolog obsługę gniazd TCP po stronie klienta i serwera.

Istotne predykaty:

  • tcp_socket(-SocketId) inicjalizuje gniazdo i unifikuje jego identyfikator ze zmienną logiczna SocketId,
  • tcp_connect(+Socket,+Host:+Port) dokonuje połączenia z hostem na wskazanym porcie przy użyciu uprzednio zainicjowanego gniazda,
  • tcp_open_socket(+SocketId,-InStream,-OutStream) otwiera dwa strumienie (wejścia i wyjścia) SWI-Prolog dla gniazda,
  • tcp_accept(+Socket,-Slave,-Peer) działa po stronie serwera, oczekuje na połączenie ze strony klienta, tworzy nowe gniazdo dla klienta o identyfikatorze Slave, zmienna logiczna Peer jest unifikowana z adresem IP klienta.
  • tcp_close_socket(+SocketId) zamyka gniazdo. Ten sam efekt można uzyskać zamykając strumienie gniazda predykatem close/1.

UWAGA! W poniższych ćwiczeniach z użyciem gniazd TCP proszę używać portów z zakresu 33700-33999.

Klient i serwer

W tym ćwiczeniu proszę zmienić numer portu na dowolny z zakresu 33700-33999, aby nie przeszkadzać kolegom.

:- use_module(library(socket)).
 
create_client :-
	tcp_socket(Socket),
	tcp_connect(Socket,localhost:33888),
	tcp_open_socket(Socket,Read,Write),
	write(Write,'matka(kasia,robert).'),nl(Write),
	close(Read),
	close(Write).
 
create_server :-
	tcp_socket(Socket),
	tcp_bind(Socket,33888),
	tcp_listen(Socket,5),
	tcp_open_socket(Socket,_,_),
 
	tcp_accept(Socket, Slave, _), % Akceptacja połaczenia ze strony klienta.
	tcp_open_socket(Slave, In, Out),
	read(In,Msg), % Odczyt wiadomości.
	write(Msg),
       close(In),
       close(Out).
  • Predykat create_client/0 tworzy obsługę gniazda TCP po stronie klienta, wysyła do serwera komunikat w postaci termu, a następnie zamyka połączenie.
  • Predykat create_server/0 dzieli się na dwie części. Pierwsza tworzy gniazdo, a druga obsługuję przychodzące połączenie. Predykat tcp_accept/3 oczekuje do momentu nawiązania połączenia przez klienta.

Ćwiczenie:

Proszę wczytać powyższy program, uruchomić serwer w osobnym wątku (thread_create/3) i nawiązać z nim połączenie.

Następnie proszę tak zmodyfikować predykat create_server/0, aby był w stanie obsłużyć więcej niż jedno połączenie (niekoniecznie symultanicznie, może być jedno po drugim) i przy każdym dopisywał przesłany term do bazy wiedzy (assert/1).

Komunikator

Program komunikator wykorzystuje gniazda TCP w celu stworzenia w Prologu prymitywnej wymiany wiadomości między użytkownikami. Proszę się z nim zapoznać Jest podzielony na część klienta i serwera. W rzeczywistości jest to rozbudowana wersja prostego programu z przykładu zamieszczonego wyżej. Dodana jest odpowiednia interpretacja komunikatów. Owe komunikaty są przesyłane w postaci termów, więc każdy musi być zakończony kropką.

Ćwiczenie:

Po zapoznaniu z programem proszę się porozumieć z kolegami wykonującymi to samo ćwiczenie. Niech jeden ze studentów uruchomi serwer komunikatora na ustalonym porcie przy użyciu predykatu start_server/0. Pozostali niech się z nim połączą przy użyciu predykatu start_client/1. Np. start_client(ania). Proszę przetestować działanie komunikatora.

Przykładowa sesja po stronie klienta może wyglądać następująco:

1 ?- start_client(ania).
|: Server message: Hello! I am a communication server
Server message: Type commands:
|: who.
|: Users connected to the server:
     - ania
     - bartek
|: msg(bartek,"Cześć Bartek!").
|: Message from bartek: Cześć Aniu!
|: quit.
|: Connection closed by server.

3. HTTP

W SWI-Prolog do dyspozycji są dwie biblioteki integrujące z protokołem HTTP od strony klienta:

http_open - get

Biblioteka http_open jest prostsza, daje mniejsze możliwości, ale wystarczająca, jeśli ograniczamy się do ściągnięcia kodu html strony.

Predykat http_open(+URL,-Stream,+Options) otwiera strumień dla wskazanego adresu URL. Po skorzystaniu ze strumienia należy go zamknąć tak jak każdy inny strumień w prologu: close(+Stream).

Ćwiczenie:

:- use_module(library(http/http_open)).
 
view_site :-
	http_open('http://www.google.pl',In,[]),
	copy_stream_data(In,user_output),
	close(In).

http_open - head

Opcja method(head) predykatu open/3 powoduje, że odczytywany jest tylko nagłówek strony. W połączeniu z opcją header(+Name,-AtomValue) można uzyskać konkretne pole nagłówka.

Ćwiczenie

:- use_module(library(http/http_open)).
 
view_date :-
	http_open('http://www.google.pl',In,[method(head),header(date,Date)]),
	copy_stream_data(In,user_output),
	close(In),
	writeln(Date).

Proszę odczytać także inne pola nagłowka, np. content_type (myślnik jest zastępowany przez podkreślenie).

http_client - GET

Predykat http_get(+URL, -Reply, +Options) unifikuje zmienna logiczną Reply z odpowiedzią serwera, uzyskaną przy użyciu metody GET.

Ćwiczenie:

:- use_module(library(http/http_client)).
 
view_site :-
	http_get('http://www.google.pl',Reply,[]),
	write(Reply).

Dla zainteresowanych

SWI-Prolog udostępnia także bibliotekę HTTP od strony serwera. Pełna dokumantacja znajduje się na stronie: SWI-Prolog HTTP support.

Uwagi, komentarze, propozycje

Tu studenci mogą wpisywać swoje uwagi.

Grzegorz J. Nalepa 2009/05/06 09:13

pl/prolog/prolog_lab/prolog_lab_http.txt · ostatnio zmienione: 2017/07/17 08:08 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0