20:01 <%JaBoJa> Witam, mam nadzieje ze zdazyliscie zainstalwoac ;)
20:01 <%JaBoJa> To narazie ogolnie bedzie
20:02 <%JaBoJa> Dzisiaj pokarze wam ja w Pythonie napisac prosta gre podobna nieco do MUD-ow, a nastepnie dodac do niej serwer http z informacja o polaczonych graczach
20:02 <%JaBoJa> bedzie to wygladac tak: http://jagiello.homelinux.org/
20:03 <%JaBoJa> sprobojcie wejsc pod ten sam adres przez telnet
20:04 <%JaBoJa> gdy wejdziecie na stronie pojawi sie opid pokoju w grze w ktorym jestescie
20:04 <%JaBoJa> Teraz jak to dziala.
20:04 <%JaBoJa> Przede wszystkim w Pythonie pisanie programow sieciowych jest bardzo proste, bo mamy do dyspozycji wiele gotowych bibliotek
20:05 <%JaBoJa> i tak naprzyklad mamy gotowa klase do obslugi http
20:06 <%JaBoJa> (zamkne na chwile przykladowy serwer bo mi przeciaza komputer)
20:06 <%JaBoJa> zacznijmy moze od tej klasy
20:06 <%JaBoJa> uruchamiamy konsole pythona
20:07 <%JaBoJa> (w Linuxie: xterm python)
20:08 <%JaBoJa> aby zaimportowac biblioteke wpisujemy:
20:08 <%JaBoJa> import BaseHTTPServer
20:08 <%JaBoJa> # tworzymy obiekt serwera:
20:09 <%JaBoJa> httpd = BaseHTTPServer.HTTPServer(('', 80), BaseHTTPServer.BaseHTTPRequestHandler)
20:09 <%JaBoJa> # uruchamiamy go:
20:09 <%JaBoJa> httpd.serve_forever()
20:09 <%JaBoJa> jesli pracowalismy jako zwykly urzytkownik dostaniemy blad, bo nie mamy uprawnien do portu 80
20:10 <%JaBoJa> jesli jednak jako root, to mozemy wejsc przegladarka na nasz serwer: http://localhost/
20:10 <%JaBoJa> dostaniemy blad o nieobslugiwanej metodzie GET
20:10 <%JaBoJa> (bo serwer nic narazie nie robi)
20:10 <%JaBoJa> teraz zamknijmy konsole pythona
20:11 <%JaBoJa> i po kolei co oznaczaly komendy:
20:11 <%JaBoJa> BaseHTTPServer.HTTPServer - wywolanie funkcji HTTPServer z bibliteki BaseHTTPServer
20:12 <%JaBoJa> funkcja przyjmuje 2 (tak, dwa) parametry
20:12 <%JaBoJa> pierwszy z nich mial forme ('', 80)
20:12 <%JaBoJa> jest to nazwa hosta i port na ktorym dziala serwer
20:13 <%JaBoJa> nazwa hosta jest pusta, bo chcemy aby dzialal poprzez wszystkie interfejsy (=wszystkie karty sieciowe)
20:13 <%JaBoJa> 80 to port http
20:13 <%JaBoJa> ale dlaczego podalismy to jako jeden parametr?
20:14 <%JaBoJa> otorz w pythonie wystepuje taki typ danych jak tuple
20:14 <%JaBoJa> stanowi on tak jakby liste parametrow oderwana od wywolania funkcji
20:14 <%JaBoJa> dzieki temu funkcja ktora wywolalismy moze wywolywac inne funkcje z podanym przez nas zestawem parametrow sama nie wiedzac ile parametrow te funkcje przyjmuja
20:15 <%JaBoJa> dokladniej omowie to pozniej
20:15 <%JaBoJa> drogi parametr funkcji HTTPServer to z kolei nazwa klasy obslugujacej wywolania - w tym przypadku jest to klasa bazowa bez zaimplementowanych funkcji obslugi zadnych metod
20:16 <%JaBoJa> sa pytania?
20:16 < Yero> nie
20:16 <%JaBoJa> jesli nie, to przejdzmy dalej - stworzmy serwer ktory bedzie cokolwiek wyswietlal ;)
20:17 <%JaBoJa> w tym celu wlaczamy ponownie konsole python i ponownie importujemy biblioteke BaseHTTPServer
20:17 <%JaBoJa> ale zanim utworzymy obiekt serwera tworzymy wlasna klase do obslugi rzadan http, zawierajaca funkcje obslugi metody GET
20:18 <%JaBoJa> vide:
20:18 <%JaBoJa> class MyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
20:18 <%JaBoJa> def do_GET(this):
20:18 <%JaBoJa> # (nie pisac jeszcze)
20:19 <%JaBoJa> this.send_response(200)
20:19 <%JaBoJa> this.send_header('Content-type', 'text/html; charset=utf-8')
20:19 <%JaBoJa> this.end_headers()
20:20 <%JaBoJa> this.wfile.write(''+this.path+'')
20:22 <%JaBoJa> spacje sa potrzebne poniewaz python w przeciwienstwie do C++ czy Pascala nie ma klamr ani BEGIN END
20:22 <%JaBoJa> jesli mamy blok programu, to musi miec wciecie
20:23 <%JaBoJa> np. instrukcja IF:
20:23 <%JaBoJa> (spacje zamieniam na _)
20:23 <%JaBoJa> if_1==1:
20:24 <%JaBoJa> _print ':)'
20:24 < UniqAnomaly> tozto wykladzik ;]
20:24 <%JaBoJa> _print ':('
20:24 <%JaBoJa> print ':|'
20:24 <%JaBoJa> :| zostanie wydrukowane nawet jesli 1 nie rowna sie 1
20:25 <%JaBoJa> ale :) i :( tylko wtedy jesli warunek jest spelniony
20:25 <%JaBoJa> jakies pytania?
20:25 < Yero> no
20:25 < Yero> a nie nic
20:25 < Yero> kapuje
20:25 < Yero> lec dalej :)
20:25 <%JaBoJa> wrocmy wiec do naszej klasy
20:26 <%JaBoJa> ma ona zadeklarowana metode do_GET(this)
20:27 <%JaBoJa> this jest tym samym co this w C++, tylko w pythonie ta zmienna jest przekazywana jako parametr funkcji
20:28 <%JaBoJa> z tego wzgledu funkcja "na zewnatrz" nie ma parametrow, ale z jej punktu widzenia ma jeden parametr
20:28 <%JaBoJa> metoda do_GET jest wywolywana przez klase bazowa (BaseHTTPRequestHandler) gdy nadchodzi rzadanie GET
20:29 <%JaBoJa> nadchodzi ono gdy przegladarka chce pobrac strone
20:29 <%JaBoJa> moga byc tez rzadania POST, gdy wysylamy formularz metoda POST, wtedy klasa wywoluje funkcje do_POST
20:30 <%JaBoJa> w naszym wypadku funkcja ta wysyla 4 linie odpowiedzi do przegladarki
20:30 <%JaBoJa> w funkcji this.send_response(200) wysyla numer bledu 200, co oznacza brak bledu (inny numer bledu to np 404)
20:31 <%JaBoJa> potem wysyla naglowek Content-type, oznaczajacy typ MIME pliku
20:31 <%JaBoJa> this.send_header('Content-type', 'text/html; charset=utf-8')
20:31 <%JaBoJa> oznacza to plik HTML z kodowaniem utf-8
20:32 <%JaBoJa> UWAGA! to ze pliki HTML maja definicje kodowania w tagu meta jest informacja dla serwera, nie dla przegladarki, wiec ten naglowek powinien byc poprawny
20:32 <%JaBoJa> bo przynajmniej Firefox przedklada go nad tagi (IE nie)
20:33 <%JaBoJa> nastepnie konczymy wysylanie naglowkow funkcja this.end_headers()
20:33 <%JaBoJa> zgodnie ze standardem http wysyla ona pusta linie
20:33 <%JaBoJa> wszystko co nastepuje dalej to tresc pliku
20:34 <%JaBoJa> wysylamy ja piszac do "pliku" this.wfile
20:34 <%JaBoJa> ma on do tego celu metode write (jak wszystkie obiekty plikow w Pythonie)
20:35 <%JaBoJa> jest tam jeszcze jedna "tajemnicza rzecz". w tresci strony wklejamy zawartosc zmiennej this.path
20:35 <%JaBoJa> oznacza to ni mniej ni wiecej to ze wywolujac na naszym serwerze strone wyswietli nam sie scierzka do niej
20:35 <%JaBoJa> np. dla strony http://localhost/test.html bedzie to /test.html
20:36 <%JaBoJa> oczywiscie w praktyce ta zmienna sluzy do rozpoznawania jaki plik chce przegladarka
20:36 <%JaBoJa> mam nadzieje ze wszystko jest zrozumiale?
20:37 <%JaBoJa> jesli tak, to teraz kolej na wlasciwa czesc gry, czyli serwer telnet
20:38 <%JaBoJa> prosze zamknac konsole pythona, jesli ktos ma otwarta i uruchomic ja ponownie
20:38 <%JaBoJa> (na prawach roota)
20:39 <%JaBoJa> jesli ktos nie ma praw roota to niech zastepuje w dalszej czesci wykladu port 23 jakims portem wyrzszym od 1024
20:39 <%JaBoJa> aby napisac serwer telnet musimy siegnac do troche nizej i stworzyc wlasne gniazdo
20:40 <%JaBoJa> nasz serwer ma luzyc do laczenia sie z gra, wiec nie mamy gotowych bibliotek temu sluzacych
20:40 <%JaBoJa> zaimportujmy wiec bilioteke socket:
20:40 <%JaBoJa> import socket
20:41 <%JaBoJa> teraz utworzmy gniazdo sieciowe:
20:41 <%JaBoJa> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
20:42 <%JaBoJa> do konstruktora podalismy dwa parametry
20:42 <%JaBoJa> pierwszy oznacza typ gniazda
20:42 <%JaBoJa> mamy trzy podstawowe typy:
20:42 <%JaBoJa> INET - gniazdo IP v.4
20:42 <%JaBoJa> INET6 - gniazdo IP v.6
20:43 <%JaBoJa> UNIX - wewnetrzne strumienie w systemie unix (nie interesuje nas)
20:44 <%JaBoJa> nastepnie musimy wywolac metode bind, podajac jej jeden parametr - po raz kolejny tuple:
20:44 <%JaBoJa> s.bind(('', 23))
20:44 <%JaBoJa> i tu uwaga
20:45 <%JaBoJa> parametr ten ma rozny format zaleznie od wybranego typu gniazda:
20:45 <%JaBoJa> INET -> (HOST, PORT)
20:45 <%JaBoJa> INET6 -> (HOST, PORT, FLOWINFO, SCOPEID)
20:45 <%JaBoJa> UNIX -> string
20:46 <%JaBoJa> (string = zwykly tekst jakby co)
20:47 <%JaBoJa> teraz musimy spowodowac aby system otworzyl port i zaczal na nim nasluchiwac:
20:47 <%JaBoJa> s.listen(2)
20:47 <%JaBoJa> 2 to maksymalna liczba polaczen w kolejce
20:47 <%JaBoJa> nie powinnismy dawac zbyt wiele
20:48 <%JaBoJa> jesli serwer nie nadarza z obsluga rzadan to nawet zwiekszenie limitu zwykle nic nie da
20:48 <%JaBoJa> port jest niby otwarty, ale trzeba jeszcze jakos odbierac polaczenia
20:48 <%JaBoJa> w tym celu wpisujemy co nastepuje:
20:49 <%JaBoJa> conn, addr = s.accept()
20:49 <%JaBoJa> tu program sie zawiesi do czasu az ktos sie polaczy na porcie 23
20:49 <%JaBoJa> zrobmy wiec to: telnet localhost
20:50 <%JaBoJa> co do samej konstrukcji podanej instrukcji to byla analogiczna do stosowanej w PHP funkcji list()
20:50 <%JaBoJa> teraz mozemy obsluzyc naszego klienta:
20:51 <%JaBoJa> conn.sendall('Hello world!')
20:51 <%JaBoJa> sonn.close()
20:51 <%JaBoJa> tzn. conn.close()
20:51 <%JaBoJa> metoda sendall spowoduje wyslanie podanych w parametrze danych do klienta
20:52 <%JaBoJa> w przeciwienstwie do send() zapewnia ona wyslanie calosci danych (o ile polaczenie nie zostanie zerwane), podczas gdy send() w przypadku bledu przerywa wysylanie i go nie wznawia
20:53 <%JaBoJa> (szczegoly w dokumentacji: http://python.org/doc/)
20:54 <%JaBoJa> serwer dziala, nieprawdaz? jest tu jednak pewien problem - mozemy obslugiwac tylko jednego klienta naraz
20:54 <%JaBoJa> teraz zagadka: jak sobie poradze z tym problemem?
20:55 < Yero> nie wiem
20:55 <%JaBoJa> musimy w tym celu zaimportowac jeszcze jedna biblioteke
20:56 <%JaBoJa> a wlasciwie tylko jedna funkcje (tak, w pythonie da sie importowac pojedyncze funkcje z biblitek)
20:56 < Yero> a to takie buty :O
20:56 <%JaBoJa> biblioteka ta to thread - odpowiedzialna za obsluge watkow
20:56 <%JaBoJa> importujemy z niej funkcje:
20:57 <%JaBoJa> from thread import start_new_thread
20:57 <%JaBoJa> dzieki temu nie musimy podawac nazwy biblioteki przy wywolaniu funckji (wczesniej to robilismy, aby uniknac konfliktu nazw)
20:57 <%JaBoJa> teraz mozemy odbierac polaczenia nastepujaco:
20:58 <%JaBoJa> while (True):
20:58 <%JaBoJa> _conn, addr = s.accept()
20:58 <%JaBoJa> _ start_new_thread(naszafunkcja, (conn, addr))
20:59 <%JaBoJa> start_new_thread wywola wtedy nowy watek do obslugi klienta, a petla powroci do odbierania innych polaczen
20:59 < ant_> sory, masz moze gdzies caly kod, bo tak troche kiepsko sie to czyta?
21:00 <%JaBoJa> http://jagiello.homelinux.org/
21:00 <%JaBoJa> tam macie linki do trzech wersji
21:01 <%JaBoJa> (sam TELNET, TELNET + HTTP i TELNET + HTTP + te linki)
21:01 <%JaBoJa> wezcie pierwsza
21:02 <%JaBoJa> start_new_thread przyjmuje dwa parametry - funcje (uwaga! nie wywolajcie jej przez przypadek) i parametry dla niej jako tuple w drugim parametrze
21:03 <%JaBoJa> widac to w funkcji server w kodzie ktory tam macie
21:03 <%JaBoJa> niech sobie wszyscy wejda http://jagiello.homelinux.org/gra1.py
21:04 <%JaBoJa> na poczatku tego pliku mamy oczywiscie scierzke do interpretera i definicje kodowania (musi byc na poczatku pliku, a nie dalej, jesli jej brak to python uznaje ze ma zwykle ASCII)
21:05 <%JaBoJa> dalej import bibliotek, zmienna z nazwa poczatkowego pokoju i zmienna z tablica asocjacyjna opisujaca swiat gry
21:06 <%JaBoJa> potem funkcja server przyjmujaca w parametrze port i tworzaca gniazdo tak jak opisalem
21:06 <%JaBoJa> i funkcja player wywolywana przez server w nowym watku po nadejsciu polaczenia
21:07 < Nht|afk> ?
21:07 < Nht|afk> co
21:07 < _MorT_> ;d
21:07 < Nht|afk> nie rob gnoju tutaj
21:07 < Nht|afk> bo to powazny kanal
21:07 < _MorT_> :(
21:07 < _MorT_> wim przeca
21:07 < Nht|afk> ;]
21:07 <%JaBoJa> jak widac zmienne globalne podobnie jak w PHP musza w Pythonie byc zaimportowane do funkcji slowem kluczowym global
21:08 <%JaBoJa> funkcja server wykonuje u nas w nieskonczonej pentli takie cos:
21:08 <%JaBoJa> 1.) Wypisuje opis aktualnej lokacji
21:09 <%JaBoJa> 2.) tworzy liste dopuszczalnych komend (jednoznakowych)
21:09 <%JaBoJa> lista ta jest tworzona na podstawie tablicy asocjacyjnej o ktorej pisalem
21:10 <%JaBoJa> potem w pentli wczytuje jeden znak od urzytkownika tak dlugo az bedzie on wsrod komend
21:10 <%JaBoJa> zmienia pokoj i kreci sie od nowa
21:11 <%JaBoJa> to dziala, ale jest niedoskonale. podstawowe beldy (nie bede ich poprawial bo to tylko przyklad, ale w praktyce trzeba na to uwazac):
21:11 <%JaBoJa> nie obslugujemy sytuacji gdy klient rozlaczy sie z serwerem - przez to karzde polaczenie az do wylaczenia serwera zajmuje nam pamiec
21:12 <%JaBoJa> cos co u nas jest nieistotne ale moze byc jesli bysmy mieli wieloznakowe komendy:
21:12 <%JaBoJa> rozne klienty telnet roznie wysylaja dane
21:12 <%JaBoJa> np. linuxowy telnet wysyla cala linie po nacisnieciu enter
21:12 <%JaBoJa> wraz z tym enterem
21:13 <%JaBoJa> ale sa klienty ktore wysylaja kazdy znak osobno
21:13 <%JaBoJa> tak wiec funkcja conn.recv(1) zawsze zwroci jeden znak (0 chyba nie moze, ale nei jestem pewien)
21:13 <%JaBoJa> ale conn.recv(1000) moze czasem zwracac po jednym znaku, a czasem cale linie
21:14 <%JaBoJa> (napewno nie bedzie czekac na ten tysieczny bajt)
21:14 <%JaBoJa> teraz przejdzmy do drugiego kodu: http://jagiello.homelinux.org/gra2.py
21:14 <%JaBoJa> mamy tam dodatkowo obsluge wyswietlania listy graczy
21:15 <%JaBoJa> kod ten ma jednak powazny blad, ktorego mozna by dlugo szukac i nie znalesc
21:15 <%JaBoJa> zwiazany jest on z obsluga wielowatkowosci
21:15 <%JaBoJa> popatrzcie:
21:15 <%JaBoJa> id = len(players)
21:16 <%JaBoJa> players.append(def_room)
21:16 <%JaBoJa> zmienna players przechowuje nazwy pokoi w ktorych sa poszczegolni gracze, a id to indeks w tej tablicy odpowiadajacy aktualnemu graczowi
21:17 <%JaBoJa> jednak moze sie zdarzyc ze po wywolaniu len(players), a przed players.append(def_room) nadejdzie poalczenie i funkcja wywola len(players) jeszcze raz
21:17 <%JaBoJa> w takim wypadku dwoch graczy mialoby ten sam id
21:17 <%JaBoJa> co jest niedopuszczalne
21:18 <%JaBoJa> trzeba o tym pamietac i stosowac odpowiednie zabezpieczenia (np. przeniesc ten kod przed wywolanie nowego watku)
21:19 <%JaBoJa> na koniec mozecie jeszcze zanalizowac ostatni przyklad, rozbudowujacy serwer HTTP: http://jagiello.homelinux.org/gra3.py
21:19 <%JaBoJa> nie bede tu juz jednak nic tlumaczyl, zwroce tylko uwage na wysylanie roznych naglowkow dla roznych typow plikow
21:20 <%JaBoJa> dziekuje, na dzisiaj to wszystko, sa pytania?