# Datagramy V této kapitole budeme pracovat s novým typem objektu, který se může skrývat za popisovačem otevřeného souboru, totiž se socketem. Zdaleka nejdůležitější aplikací socketů je síťová komunikace – na úrovni softwaru je naprostá většina provozu na internetu realizovaná skrze sockety.¹ S obyčejnými soubory mají sockety dvě klíčové společné vlastnosti: • jedná se o objekt operačního systému, který má identitu, a na který se může odkazovat popisovač otevřeného souboru, • ze socketu můžeme číst data (bajty), a/nebo do něj data zapisovat. Zároveň se ale tento týden setkáme s novým způsobem práce s těmito daty. Budeme totiž pracovat s «datagramy» – na rozdíl od běžného souboru může být síťová komunikace rozčleněna na diskrétní celky. Práce s takovými celky je jednodušší a intuitivnější, než práce s proudem dat, proto se prozatím omezíme na tento způsob komunikace.² Každý datagram je nedělitelný a musí být odeslán i přijat jako celek. Není tedy možné obsah datagramu načítat po částech, jak jsme zvyklí u souborů. Je-li dodaná paměť (buffer) menší, než je velikost datagramu, část datagramu je čtením nenávratně ztracena. Další zjednodušení spočívá v tom, že budeme předpokládat komunikaci s jedinou protistranou. Přesto, že to není příliš realistický předpoklad (na internetový datagramový socket může kdykoliv dorazit zpráva odkudkoliv), vyhneme se tím práci s adresami, které představují jisté komplikace (jak jazykové, tak konceptuální).³ Pro práci s datagramy budeme přednostně používat systémová volání ‹send› a ‹recv›. Rozdíl mezi ‹send› a ‹write›, resp. mezi ‹recv› a ‹read› není příliš velký, ale zejména při práci s datagramy je použití ‹read› (kde jsme zvyklí zpracovávat data postupně) spíše neintuitivní. Ukázky: 1. ‹count› – základní použití ‹recv›, 2. ‹seq› – práce s celými čísly, 3. ‹pong› – interaktivní celočíselná sekvence. Přípravy: 1. ‹min› – velmi jednoduchý datagramový „server“, 2. ‹plog› – ukládání datagramů do souboru, 3. ‹otpd› – šifrování datagramů (keystream ze souboru), 4. ‹newsc› – jednoduchý klient pro stažení novinek, 5. ‹newsd› – server pro předchozí, 6. ‹tftpw› – † odeslání souboru – první interaktivní protokol. Řešené příklady: 1. ‹agree› – domluvení společné podmnožiny 2. ‹window› – sestavení fragmentované zprávy 3. ‹filter› – dva sockety, přeposílá datagramy podle podmínky 4. ‹slide› – sestavení proudu dat 5. ‹counter› – jednoduchý server-počítadlo 6. ‹tftpr› – RRQ, uloží data do souboru ¹ Sockety jsou klíčová součást implementace webových serverů (apache, nginx), aplikačních serverů (node.js, django, rails), databází, ale i klientského software (prohlížečů, mobilních aplikací). ² Na proudy se podíváme příští týden. ³ K adresám a realističtější datagramové komunikaci se vrátíme ve druhém bloku. ## Systémová volání V této kapitole přidáme volání ‹send› a ‹recv›, které jsou analogické k ‹write› a ‹read›. Pro práci s datagramovými sockety budeme upřednostňovat volání ‹send› a ‹recv›, zejména abychom čtenáře upozornili, že pracujeme s datagramy. Funkční rozdíl mezi ‹send› a ‹write›, resp. ‹recv› a ‹read›, prozatím žádný neuvidíme.¹ ### ‹send› Systémové volání ‹send› vyžádá odeslání datagramu (paketu) předem zvolené protistraně (tento týden budeme volbu protistrany delegovat na testy – později se setkáme se systémovým voláním ‹sendto›). Má 4 parametry: • ‹fd› – popisovač socketu, • ‹buf› – ukazatel na paměť, kde jsou uložena data určená k odeslání, • ‹nbytes› – počet bajtů (určuje velikost datagramu), • ‹flags› – prozatím nastavíme vždy na nulu. Jakmile dojde k návratu ze systémového volání, paměť ‹buf› je možné recyklovat – datagram je již sestaven v paměti operačního systému,² i přesto, že dosud nemusel být odeslán. Pokus o sestavení datagramu může samozřejmě selhat – v takovém případě je návratová hodnota -1 (a je nastaveno ‹errno›). Jinak je vrácen počet skutečně odeslaných bajtů. «Pozor», úspěšná návratová hodnota «nezaručuje» úspěšné doručení datagramu. Důležitou změnou je také to, že datagramy mají systémem omezenou velikost (danou použitým protokolem, typicky UDP). Pokus o odeslání většího objemu dat jedním voláním ‹send› (tzn. jako jednoho datagramu) selže s chybou ‹EMSGSIZE›. Jinými slovy, úspěšný ‹send› odešle vždy právě jeden datagram. ### ‹recv› Operace ‹recv› je «blokující» – nemá-li operační systém k dispozici žádný již doručený datagram, volání ‹recv› se «nevrátí» až dokud nějaký nedorazí. To mimo jiné znamená, že při komunikaci pomocí socketů musíme být velmi obezřetní, protože při neopatrném použití může lehce dojít k uváznutí. Dojde-li například k situaci, že obě (všechny) komunikující strany zároveň zavolají ‹recv›, systém jistě uvázne. Volání ‹recv› se podobá na ‹read› a jeho první tři parametry mají tentýž význam: • ‹fd› – popisovač socketu, ze kterého hodláme přijmout datagram, • ‹buf› – ukazatel na paměť, kam budou uložena přijatá data, • ‹nbytes› – maximální počet bajtů, které si přejeme uložit. Čtvrtý parametr ponecháme zatím nulový. Významná změna se ale objeví v návratové hodnotě (a datech zapsaných do paměti ‹buf›): • -1 jako obvykle znamená systémovou chybu, • hodnota ≥0 je počet bajtů skutečně zapsaných do ‹buf›; ◦ pozor, «není totéž» jako velikost datagramu (byl-li datagram větší než ‹nbytes›, výsledek bude ‹nbytes›), ◦ pozor, nula znamená «prázdný datagram», nikoliv „konec souboru“ (při datagramové komunikaci nelze o konci souboru nebo uzavření spojení mluvit).³ Typicky budeme v příkladech uvádět maximální přípustnou velikost datagramu. Je-li tato velikost určena protokolem, kterého dodržení je potřeba kontrolovat, je nutné použít buffer (alespoň) o bajt delší. ¹ To se změní ve druhém bloku, kdy budeme pracovat s adresami a volání ‹sendto› a ‹recvfrom›. ² Nemá-li systém dostatek paměti, volání bude «blokovat» než se tato paměť uvolní – z toho plyne mimo jiné riziko uváznutí. Podrobněji se budeme blokujícím vstupem a výstupem zabývat příští týden. ³ Neplatí pro proudové (spojované) sockety. ## Knihovní podprogramy Je-li v rámci síťové komunikace potřeba vyměňovat číselné hodnoty v rozsahu větším než jeden bajt, je nutné určit pořadí bajtů ve slově. Na síti je zvykem odesílat slova nejvýznamnějším bajtem napřed. Protože nativní reprezentace slov tomu nemusí odpovídat (a na většině moderních platforem ani neodpovídá), je potřeba takové hodnoty vhodně převést. K tomu slouží několik knihovních podprogramů: • ‹ntohl› – «network to host long», převede slovo typu ‹uint32_t› ze síťového (big endian, největší bajt první) do hostitelského pořadí bajtů (závislého na platformě), • ‹htonl› – «host to network long», analogicky (pouze opačným směrem), • ‹ntohs›, ‹htons› – «network to host short», «host to network short» převádí hodnoty typu ‹uint16_t›.