Ruby po kapkách (22.) – základy síťování

24. srpna 2009

Prošli jsme již hodně funkcionality týkající se vstupu a výstupu. Zabývali jsme se nejzákladnější formou, kterou je práce se soubory. V současném světě však pomalu začíná být pro aplikace důležitější schopnost domluvit se s internetem. Síťová komunikace je o něco obtížnější, ale Ruby nám jako obvykle vychází vstříc s rozumně organizovanou knihovnou. O tom, že networking zas tak těžký není, svědčí to, že na konci tohoto dílu budeme umět naprogramovat (triviální ale náš) webový server.

Pro začátek si ale zjednodušeně a z rychlíku projdeme nejdůležitější pojmy a principy, na nich je založen internet. Počítače spolu navzájem komunikují podle dohodnutých pravidel – jinými slovy podle protokolu. Základním protokolem internetu je IP (Internet Protocol). Ten zajišťuje možnost doručení dat jednotlivým počítačům, které jsou identifikovány IP adresou. Ta je tvořena 4 byty a zapisuje se v podobě čtyř dekadických čísel oddělených tečkami. Na každém uzlu označeném IP adresou existuje ještě jemnější adresace na takzvané porty, které jsou číslovány dvoubytovým číslem. Port je (nebo ve většině případů není) již obsluhován konkrétním procesem. Na portu 80 například obvykle naslouchá web server. Protokol IP využívají další protokoly poskytující dokonalejší služby. Nejznámnější je asi protokol TCP (Transmission Control Protocol), který zajišťuje obousměrné spojení. TCP je pro změnu využíván protokoly jako SMTP (přenos e-mailu) nebo HTTP (přenos webových stránek). Častěji než samotná IP adresa se používají symbolická jména počítačů (www.interval.cz), která musí při komunikaci být nejdříve přeložena na IP adresu pomocí DNS serveru.

Posledním termínem, který je třeba zmínit, než se vrhneme na praxi, je socket. Jedná se o obousměrný komunikační kanál mezi dvěma uzly sítě. Když bychom hledali v knihovně tříd Ruby, narazili bychom na třídu BasicSocket. Ta má potomka IPSocket a tato třída zase TCPSocket. Zajímavé je, že třída BasicSocket je odvozena z nám již známé třídy IO, což zaručuje v mnoha případech velmi podobné chování, na jaké jsme již zvyklí.

Aplikace vystupují při komunikaci na síti ve dvou rolích – buď jako server nebo jako klient (případně i obojí současně). Ukázku velmi jednoduchého serveru, který prostřednictvím protokolu TCP vrací text s aktuálním časem jsem mírně upravil z knihy Ruby – kompendium znalostí pro začátečníky i profesionály od Hala Fultona.

require ‚socket‘

PORT = 12321

server = TCPServer.new(‚127.0.0.1‘, PORT)
while (session = server.accept)
session.puts Time.new
session.close
end

Protože síťová podpora je ve standardní knihovně, je třeba na začátku knihovnu načíst pomocí require. Důležitou hodnotou pro nás je číslo portu, na kterém bude server obsluhovat spojení. Pro pokusy je obecně vhodné zvolit nějaké větší číslo – řekněme od 10000, protože nižší čísla bývají často obsazena některými běžnými aplikacemi a mohli bychom vyvolat něchtěnou kolizi. Číslo portu uložíme do konstanty PORT. Následuje vytvoření instance třídy TSCPServer. Ta je v Ruby speciálním případem TCP socketu (a tedy potomkem třídy TCPSocket). K vytvoření instance serveru je třeba specifikovat již zmíněný port a případně také IP adresu. To proto, že počítač může být vybaven více síťovými rozhraními a server může pracovat jen na některých z nich. V našem případě zvolíme adresu vnitřního rozhraní zvaného loopback, které umožňuje testovat síťovou komunikaci, aniž by nějaká data opouštěla náš počítač (případně i na počítači, který žádné skutečné síťové rozhraní nemá). Zde je třeba upozornit, že na takto spuštěný server se nedá připojit z jiného počítače. Pokud bychom to chtěli, je třeba zvolit buď reálnou adresu některého síťového rozhraní nebo 0.0.0.0, což je speciální hodnota, která říká, že má server naslouchat na všech rozhraních.

Smyčka while zajišťuje v každém cyklu vyřízení jednoho spojení. Funguje to tak, že volání metody server.accept program pozastaví až do chvíle, kdy přijde požadavek od klienta na spojení na dané IP adrese a portu. Když se tak stane, vrací instanci třídy TCPSocket. Do ní pak zapíšeme aktuální čas a uzavřeme spojení zcela běžnými metodami vstupu a výstupu. Funkci programu si můžeme vyzkoušet pomocí programu telnet (ten naleznete běžně v unixovém prostředí, do některých verzí Windows je třeba si jej doinstalovat v podobě nějaké volně šiřitelné implementace). Výše uvedený prográmek uložíme a spustíme a v jiném terminálovém okně spustíme telnet.

~$ telnet localhost 12321
Trying 127.0.0.1…
Connected to localhost.
Escape character is ‚^]‘.
Mon Aug 24 07:34:04 +0200 2009
Connection closed by foreign host.

Pochopitelně má náš jednoduchý server značná omezení. Například ukončit ho musíme prostředky operačního systému – nejlépe stisknutím Ctrl-C. Další věc je, že takto pojatý server dokáže uspokojovat vždy právě jeden požadavek na spojení v jednom okamžiku. Vynechali jsme také kontrolu případných chyb. V tomto směru budeme server zdokonalovat později. Nyní se podívejme, jak by mohl vypadat jednoduchý klient. Kód si opět vypůjčím od Hala Fultona.

require „socket“

PORT = 12321
HOST = ARGV[0] || „localhost“

session = TCPSocket.new(HOST, PORT)
time = session.gets
session.close
puts time

Začátek kódu je stejný jako v případě serveru. Zajímavé to začíná být na řádku, kde se zjišťuje adresa nebo název cílového počítače. Zkrácené vyhodnocení logické spojky OR způsobí, že se použije hodnota prvního parametru příkazové řádky, pokud je vyplněna. Pokud není, použije se jako default řetězec localhost, který odpovídá adrese 127.0.0.1. Mimochodem Ruby nám šetří práci tím, že v tomto případě můžeme volně používat jak symbolické názvy, tak IP adresy. Knihovní metody si s tím poradí. Dále vytváříme instanci třídy TCPSocket, přižemž specifikujeme cílový počítač a port. Z otevřeného socketu načteme jeden řádek, uzavřeme spojení a vypíšeme získaný čas. Vyzkoušet můžeme program obdobně jako před tím, akorát místo telnetu spustíme našeho klienta.

~$ ruby timecl.rb
Mon Aug 24 09:41:33 +0200 2009

Na začátku jsem slíbil webový server. Pojďme se tedy podívat, jak by vypadala jedna možná úprava časového serveru tak, aby s ním mohl komunikovat webový prohlížeč.

require ‚socket‘

head = „HTTP/1.1 200/OK\r\nContent-type: text/html\r\n\r\n“

text = <<„EOS“
<html>
<body>
<h1>Právě je xxx</h1>
</body>
</html>\r\n
EOS

server = TCPServer.new(‚127.0.0.1‘, 12321)

puts „Start serveru…“
while (session = server.accept)
puts „Požadavek: #{session.gets}“
session.print head
send = text.sub(/xxx/, Time.now.to_s)
session.print send
session.close
end

Prohlížeč s webovým serverem komunikuje pomocí protokolu HTTP. Každou klientovi odeslanou stránku předchází hlavička. Pro naše účely postačí konstantní řetězec, který si uložíme do proměnné head. Čas budeme odesílat zabalený do HTML. Připravíme si velmi jednoduchou šablonku – ponechme v tomto případě stranou požadavky moderního webového designu. V šabloně jsou znaky xxx, které později nahradíme aktuálním časovým řetězcem. Pak následuje kód, který je nám již z části známý. Ve zpracovací smyčce nejdříve načteme požadavek klienta a pro informaci si ho zároveň vypíšeme na terminál. Pak odešleme hlavičku a šablonu HTML s nahrazením za aktuální čas a uzavřeme spojení. Program spustíme a můžeme nasměrovat prohlížeč na http://localhost:12321. Pokud vše funguje, jak má, vypíše server na terminál ‚Požadavek: GET / HTTP/1.1‘ a prohlížeč zobrazí čas.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *