|
|
pl:prolog:prolog_lab:prolog_lab_http [2009/05/20 10:37] piw09 Brakowało nawiasu zamykającego w linii zaczynającej się od :- use_module |
pl:prolog:prolog_lab:prolog_lab_http [2019/06/27 15:50] |
====== 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. | |
| |
===== -. Wątki ===== | |
| |
Wielowątkowość w SWI-Prolog oparta jest na standardzie POSIX. | |
| |
Dokumentacja: [[http://www.swi-prolog.org/pldoc/doc_for?object=section(1%2c%20%278%27%2c%20swi(%27%2fdoc%2fManual%2fthreads.html%27))|Multi-threaded applications]] | |
| |
==== Tworzenie wątków ==== | |
| |
Tworzenie oddzielnego wątku odbywa się przy użyciu predykatu thread_create/3: | |
<code prolog> | |
thread_create(:Goal, -Id, +Options) | |
</code> | |
| |
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: | |
<code 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). | |
</code> | |
| |
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: [[http://www.swi-prolog.org/pldoc/man?predicate=mutex_lock%2f1|mutex_lock/1]] i [[http://www.swi-prolog.org/pldoc/man?predicate=mutex_unlock%2f1|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: | |
<code prolog> | |
write_ID(Times) :- | |
thread_self(ID), | |
write(ID), write(': I am running! '), | |
NewTimes is Times - 1, | |
write_ID(NewTimes). | |
</code> | |
| |
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: | |
<code 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). | |
</code> | |
| |
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 [[http://pl.wikipedia.org/wiki/Problem_ucztuj%C4%85cych_filozof%C3%B3w|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. | |
| |
| |
===== -. 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.** | |
| |
<code prolog> | |
:- 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). | |
</code> | |
| |
* Predykat //create_client/0// tworzy obsługę gniazda TCP po stronie serwera, 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.pl|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: | |
<code prolog> | |
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. | |
</code> | |
| |
===== -. HTTP ===== | |
| |
W SWI-Prolog do dyspozycji są dwie biblioteki integrujące z protokołem HTTP od strony klienta: | |
* [[http://www.swi-prolog.org/pldoc/doc_for?object=section(3%2c%20%272.1%27%2c%20swi(%27%2fdoc%2fpackages%2fhttp.html%27))|http/http_open]] - oparta jedynie na metodzie HTTP GET, | |
* [[http://www.swi-prolog.org/pldoc/doc_for?object=section(3%2c%20%272.2%27%2c%20swi(%27%2fdoc%2fpackages%2fhttp.html%27))|http/http_client]] - bardziej rozbudowana. | |
| |
==== 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:** | |
| |
<code prolog> | |
:- use_module(library(http/http_open)). | |
| |
view_site :- | |
http_open('http://www.google.pl',In,[]), | |
copy_stream_data(In,user_output), | |
close(In). | |
</code> | |
| |
==== 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** | |
| |
<code prolog> | |
:- 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). | |
</code> | |
| |
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:** | |
| |
<code prolog> | |
:- use_module(library(http/http_client)). | |
| |
view_site :- | |
http_get('http://www.google.pl',Reply,[]), | |
write(Reply). | |
</code> | |
| |
==== Dla zainteresowanych ==== | |
| |
SWI-Prolog udostępnia także bibliotekę HTTP od strony serwera. Pełna dokumantacja znajduje się na stronie: [[http://www.swi-prolog.org/pldoc/package/http.html|SWI-Prolog HTTP support]]. | |
| |
====== Uwagi, komentarze, propozycje ====== | |
Tu studenci mogą wpisywać swoje uwagi. | |
| |
--- //[[gjn@agh.edu.pl|Grzegorz J. Nalepa]] 2009/05/06 09:13// | |
| |