Files
Thesis/kidiplom.tex
T
Martin Slachta 6956e40f43 Initial
2026-04-08 11:21:35 +02:00

1235 lines
98 KiB
TeX

%%% Ukázkový text a dokumentace stylu pro text závěrečné (bakalářské a
%%% diplomové) práce na KI PřF UP v Olomouci
%%% Copyright (C) 2012 Martin Rotter, <rotter.martinos@gmail.com>
%%% Copyright (C) 2014 Jan Outrata, <jan.outrata@upol.cz>
%% Pro získání PDF souboru dokumentu je třeba tento zdrojový text v
%% LaTeXu přeložit (dvakrát) programem pdfLaTeX.
%% V případě použití programu BibLaTeX pro tvorbu seznamu literatury
%% je poté ještě třeba spustit program Biber s parametrem jméno
%% souboru zdrojového textu bez přípony a následně opět (dvakrát)
%% přeložit zdrojový text programem pdfLaTeX.
%% Postup získání Postscriptového souboru je popsán v dokumentaci.
%% Třída dokumentu implementující styl pro závěrečnou práci. Vybrané
%% nepovinné parametry (ostatní v dokumentaci):
%% 'master' pro sazbu diplomové práce, jinak se sází bakalářská práce
%% 'program=kód' pro Váš studijní program/obor (specializaci), kódy
%% pro diplomovou práci 'infoi' pro Informatiku (Obecná informatika),
%% 'infui' pro Informatiku (Umělá inteligence), 'ainfpst' pro
%% Aplikovanou informatiku (Počítačové systémy a technologie), 'uinf'
%% pro Učitelství informatiky pro střední školy, 'binf' pro
%% Bioinformatiku, 'inf' pro Informatiku (bez specializací) a 'ainf'
%% pro Aplikovanou informatiku (bez specializací), jinak je výchozí
%% ainfvs pro Aplikovanou informatiku (Vývoj software), a pro
%% bakalářskou práci 'infoi' pro Informatiku (Obecná informatika),
%% 'itp' pro Informační technologie v prezenční formě, 'itk' pro
%% Informační technologie v kombinované formě, 'infv' pro Informatiku
%% pro vzdělávání, 'binf' pro Bioinfomatiku, 'inf' pro Informatiku
%% (bez specializací), 'ainfp' pro Aplikovanou informatiku (bez
%% specializací) v prezenční formě, 'ainfk' pro Aplikovanou
%% informatiku (bez specializací) v kombinované formě, jinak je
%% výchozí infpvs pro Informatiku (Programování a vývoj software)
%% 'printversion' pro sazbu verze pro tisk (nebarevné logo a odkazy,
%% odkazy s uvedením adresy za odkazem, ne odkazy do rejstříku),
%% jinak verze pro prohlížeč
%% 'biblatex' pro zapnutí podpory pro sazbu bibliografie pomocí
%% BibLaTeXu, jinak je výchozí sazba v prostředí thebibliography
%% 'language=jazyk' pro jazyk práce, jazyky english pro anglický,
%% slovak pro slovenský, jinak je výchozí czech pro český
%% 'font=sans' pro bezpatkový font (Iwona Light), jinak je výchozí
%% serif pro patkový (Latin Modern)
%% 'figures, tables, theorems a sourcecodes' pro sazbu seznamu
%% obrázků, tabulek, vět a zdrojových kódů, jinak při =false se
%% nesází (u theorems a sourcecodes výchozí)
\documentclass[
% master,
% program=ainfvs,
% printversion,
biblatex,
% language=english,
% font=sans,
figures=false,
% tables=false,
% theorems,
% sourcecodes,
glossaries,
index
]{kidiplom}
\title{Hra}
\title[english]{A style for thesis}
%% Informace pro úvodní strany. V jazyku práce (pokud není v komentáři
%% uvedeno česky) a anglicky. Uveďte všechny, u kterých není v
%% komentáři uvedeno, že jsou volitelné. Při neuvedení se použijí
%% výchozí texty. Text pro jiný než nastavený jazyk práce (nepovinným
%% parametrem language makra \documentclass, výchozí český) se zadává
%% použitím makra s uvedením jazyka jako nepovinného parametru.
%% Název práce, česky a anglicky. Měl by se vysázet na jeden řádek.
\title{Styl pro text závěrečné práce}
\title[english]{A style for thesis}
%% Volitelný podnázev práce, česky a anglicky. Měl by se vysázet na
%% jeden řádek. Výchozí je prázdný.
\subtitle{Ukázkový text a dokumentace stylu v \LaTeX{}u}
\subtitle[english]{Sample text and documentation of the \LaTeX{} style}
%% Jméno autora práce. Makro nemá nepovinný parametr pro uvedení
%% jazyka.
\author{Martin Šlachta}
%% Jméno vedoucího práce (včetně titulů). Makro nemá nepovinný
%% parametr pro uvedení jazyka.
\supervisor{Mgr. Tomáš Urbanec, Ph.D.}
%% Volitelný rok odevzdání práce. Výchozí je aktuální (kalendářní)
%% rok. Makro nemá nepovinný parametr pro uvedení jazyka.
%\yearofsubmit{\the\year}
%% Anotace práce, včetně anglické (obvykle překlad z jazyka
%% práce). Jeden odstavec!
\annotation{Ukázkový text závěrečné práce na Katedře informatiky
Přírodovědecké fakulty Univerzity Palackého v Olomouci, který je
zároveň dokumentací stylu pro text práce v \LaTeX{}u. Zdrojový text
v \LaTeX{}u je doporučeno použít jako šablonu pro text skutečné
závěrečné práce studenta.}
\annotation[english]{Sample text of thesis at the \kitextdepten,
\kitextfacultyen, \kitextuniven{} and, at the same time,
documentation of the \LaTeX{} style for the text. The source text in
\LaTeX{} is recommended to be used as a template for real student's
thesis text.}
%% Klíčová slova práce, včetně anglických. Oddělená (obvykle) středníkem.
\keywords{styl textu; závěrečná práce; dokumentace; ukázkový text}
\keywords[english]{text style; thesis; documentation; sample text}
%% Volitelná specifikace příloh textu práce, i anglicky. Výchozí je
%% 'elektronická data v systému katedry informatiky / electronic data
%% in system of department of computer science'.
%\supplements{nejlepší software všech dob}
%\supplements[english]{the best software of all times}
%% Volitelné poděkování. Stručné! Výchozí je prázdné. Makro nemá
%% nepovinný parametr pro uvedení jazyka.
\thanks{Děkuji, děkuji, děkuji.}
%% Cesta k souboru s bibliografií pro její sazbu pomocí BibLaTeXu
%% (zvolenou nepovinným parametrem biblatex makra
%% \documentclass). Použijte pouze při této sazbě, ne při (výchozí)
%% sazbě v prostředí thebibliography.
\bibliography{bibliografie.bib}
%% Další dodatečné styly (balíky) potřebné pro sazbu vlastního textu
%% práce.
\usepackage{lipsum}
\usepackage{longtable}
\begin{document}
%% Sazba úvodních stran -- titulní, s bibliografickými údaji, s
%% anotací a klíčovými slovy, s poděkováním a prohlášením, s obsahem a
%% se seznamy obrázků, tabulek, vět a zdrojových kódů (pokud jejich
%% sazba není vypnutá).
\maketitle
%% Vlastní text závěrečné práce. Pro povinné závěry, před přílohami,
%% použijte prostředí kiconclusions. Povinná je i příloha s obsahem
%% elektronických dat.
%% -------------------------------------------------------------------
\newcommand{\BibLaTeX}{\textsc{Bib}\LaTeX}
\section{Úvod}
V práci se budeme zabývat technikami a způsoby pro optimalizaci datového přenosu, ať už pro šířku pásma nebo odezvu.
\newpage
\section{Distribuované systémy}
V této kapitole si představíme problematiku distribuovaných systémů a princip jejich fungovaní. Distribuované systémy jsou takové, které svůj úkol plní pomocí více počítačů, propojených v jedné síti. Příkladem takové sítě je Internet, která je největší a skládá se z mnoha podsítí. Výhoda oproti paralelním systémům je spolehlivost a škálovatelnost.
\begin{definition}[Počítačová síť]
Skupina komunikujících počítačů, které komunikují s ostatními v síti pomocí komunikačních protokolů, se nazývá počítačová síť.
\end{definition}
Počítačovou síť si lze představit jako graf, kde vrcholy jsou jednotlivé počítače a hrana je přímé spojení mezi dvěma počítači, jako třeba měděný kabel nebo optické vlákno. Každý počítač v síti, neboli vrchol, má síťovou adresu, jeden nebo více procesů a vlastní paměť, kterou mezi sebou nemohou počítače sdílet. Namísto sdílené paměti musejí vrcholy komunikovat pomocí zpráv.
\begin{definition}[Distribuovaný systém]
Systém je \uv{distribuovaný}, když svůj úkol plní pomocí více počítačů, které jsou propojené v síti a mohou tak spolu komunikovat.
\end{definition}
Podobné jsou paralelní systémy, které také řeší společný problém více procesy, které běží souběžně. Rozdíl je ten, že v distribuovaném systému má každý proces vlastní paměť, kterou mezi sebou počítače nemohou sdílet. Procesy mezi sebou musejí komunikovat posíláním zpráv.
\subsection{Komunikace v síti}
V síti počítače komunikují pomocí komunikačního protokolu, kterému musejí rozumět. Komunikační protokol je soubor pravidel pro výměnu informací mezi počítači. Mají často různé charakteristiky, např. jestli jsou vyžadují navázání spojení nebo ne. To chápejme jako domluvu mezi dvěma stranami před tím, než začne přenos aplikačních dat pro systém.
Nejčastěji se strany domlouvají na tajném klíči pro symetrické šifrování zpráv mezi sebou tak, aby nemohli být odposlouchávány. Dále si mohou vyměnit informace o verzi protokolu a omezeních, jako rychlost přenosu apod. Opakem jsou protokoly bez připojení. V takovém protokolu může na počítač A přijít zpráva z počítače B, o kterém A nemuselo nikdy předtím slyšet. Protokol pak může mít definované různé chování pro tento případ.
\begin{definition}[Komunikační protokol]
Soubor pravidel pro výměnu informací mezi počítači se nazývá \uv{komunikační protokol}.
\end{definition}
\subsection{TCP/IP}
Rodina protokolů pro komunikaci v síti Internet se nazývá TCP/IP. Název se skládá ze dvou nejdůležitějších protokolů: IP, neboli Internet Protocol a TCP (Transmission Control Protocol) - protokol pro řízení přenosu. IP umožňuje komunikaci libovolných dvou uzlů počítačů v propojených sítích. TCP zajišťuje spolehlivý obousměrný přenos dat mezi procesy na dvou počítačích (ne nutně různých).
Síťová komunikace je rozdělena do několika vrstev, každá s jinou zodpovědností. Jednotlivé vrstvy pak mezi sebou komunikují, podobně jako vidíme na obrázku . Konkrétně architektura TCP/IP je rozdělena do čtyř vrstev: aplikační, transportní, síťová a síťové rozhraní. Každá vrstva obsahuje vlastní protokoly, které lze měnit podle toho, co je třeba.
\begin{description}
\item[{Síťové rozhraní}] \hfill \\
Nejnižší vrstva, která se stará o přenos přes fyzické médium. Pro různé sítě bude vyžadovat různé protokoly. Příkladem jsou Ethernet, Token ring, apod.
\item[{Síťová}] \hfill \\
Pro adresaci máme vrstvu síťovou. Ta směruje a předává jednotlivé datagramy. Příkladem protokolů jsou IP, ARP, RARP nebo IPSEC. Je nutné, aby stejně jako vrstva síťového rozhraní, byla implementována ve všech prvcích sítě.
\item[{Transportní}] \hfill \\
Předposlední vrstva je transportní. Tato vrstva je rozbalena až na koncových bodech, nikoliv po cestě. Není totiž důležitá pro směrování packetů. Stará se o to, jaké packety přišli, jestli nejsou poškozené a nebo jestli jsou všechny. Příkladem je TCP, UDP nebo QUIC.
\item[{Aplikační}] \hfill \\
Poslední vrstva je aplikační a v ní jsou právě aplikace. Příkladem protokolů jsou HTTP nebo gRPC, které spadají do kategorie tzv. middleware. To znamená, že protokol není nutně specifický pro konkrétní aplikaci.
\end{description}
V práci se zaměříme na rodinu protokolů TCP/IP, která na síťové vrstvě využívá Internet protokol a na transportní TCP nebo UDP. Na obrázku TODO vidíme, jak mezi sebou jednotlivé vrstvy komunikují. Aplikace na počítači $A$ odešle zprávu, kterou počítač $D$ uvidí ve své instanci aplikace. Vrstva zná pouze vrstvu pod sebou. Plnou čarou vidíme datový tok zprávy, jak putuje přes jednotlivé vrstvy. Šrafovaná čára reprezentuje abstrahovaný datový tok, tak jak ho vidí jednotlivé vrstvy, které neřeší vrstvu pod nimi. Počítače B a C jsou tzv. redistribuční body. Všimněme si, že se vůbec nedostanou do transportní nebo aplikační vrstvy, pouze si rozbalí IP paket a zjistí, kam mají pakety posílat dál. Vrstva síťového rozhraní pracuje s různými médii, jak také vidíme na obrázku. ř
\begin{figure}
\begin{center}
\includegraphics[width=1\textwidth]{graphics/TcpIp}
\end{center}
\end{figure}
Vrstvy jsou modelované pomocí hlaviček. Poslaný paket obsahuje hlavičku pro každou vrstvu. Nejnižší (linková) vrstva, např. s Ethernet protokolem, přenáší \uv{rámce}. Síťová vrstva s Internet protokolem přenáší IP pakety a hlavička obsahuje IP adresu odesílatele, příjemce a další informace potřebné k nasměrování rámce. TCP protokol přenáší \uv{segmenty} a UDP \uv{datagramy}.
% Zaměřím se na tzv. rodinou protokolů TCP/IP která definuje, jak má probíhat komunikace v síti Internet, na kterou bych se v práci chtěl zaměřit. Síť internet je celosvětová síť sítí, na které najdeme webové stránky, souborová úložiště a informační systémy. Problém jakékoliv komunikace, včetně té na internetu, je propustnost, která je omezená. Malá propustnost může způsobit pomalé načtení webové stránky, což může potenciálního zákazníka odradit. Nebo například příliš opožděné reakce v informačním systému, který tak může snižovat produktivitu.
\subsection{Architektury}
Distribuované systémy mají různé architektury. Liší se např. v tom, kdo má v systému jak velké slovo a kdo komunikuje s kým. My zmíníme dvě: klient-server a peer-to-peer.
\subsubsection{Klient-Server}
První architekturu, kterou zmíníme, je klient-server. Vrcholy v síti jsou rozděleny na
\subsubsection{Peer-to-Peer}
\subsection{Rodina protokolů TCP/IP}
Mezi uzly v síti je potřeba komunikovat. Narozdíl od komunikace mezi procesy na stejném počítači, nemáme k dispozici sdílenou paměť. Komunikace v síti je založena na posílání primitivních zpráv. Primitivních znamená formou n-tice bytů. Pokud proces A chce poslat zprávu procesu B, který je na jiném počítači, tak sestaví zprávu M. Tuto zprávu odešle procesu B pomocí systémového volání `send`. Toto systémové volání podporuje většina operačních systémů. Proces B jiným systémovým voláním `read` zprávu přečte a může na ni zareagovat, například provedením výpočtu a zasláním výsledku zprávou zpět na proces A. V této interakci mezi procesy počítáme s tím, že se oba shodnou, co jednotlivé bity ve zprávě znamenají.
% ref: https://cs.wikipedia.org/wiki/TCP/IP
Komunikační protokol je množina pravidel, která určují syntaxi a význam jednotlivých zpráv při komunikaci.
Síťová komunikace je rozdělena do vrstev. Každá vrstva má svou zodpovědnost. Internet protokol, neboli IP, určuje navigaci zpráv v síti. IP adresa počítače je protějšek adresy domu.
\subsection{Protokoly v transportní vrstvě}
Nejčastější protokoly transportní vrstvy pro komunikaci v síti Internet jsou TCP a UDP. Problém jiných protokolů je, že ne všechny uzly je musí podporovat, což může způsobovat ztracené packety, nebo dokonce žádná cesta nemusí existovat. Protokoly TCP i UDP si představíme.
\subsubsection{Sockety}
Tyto protokoly se v aplikacích používají pomocí tzv. socketů. Jedná se o objekt, který se chová jako soubor. Můžeme ho otevřít, zapsat, přečíst a zavřít. Představím přesně tyto funkce, protože je budu v práci využívat. Konkrétně budu používat Linuxové rozhraní.
Pro začátek komunikace pomocí těchto protokolů musím otevřít socket pomocí `socket`. Funkce má parametr pro typ protokolu, který chceme použít. Po vytvoření socketu ho můžeme navázat na konkrétní port pomocí `bind` s číslem portu. Pokud socket nenavážeme, přiřadí nám operační systém libovolný volný port.
\subsubsection{TCP}
Nejběžnější protokol je TCP. Při použití mezi sebou dva koncové body vytvoří spojení, přes které mohou posílat zprávy obousměrně. Protokol garantuje spolehlivé doručení dat v pořadí, ve kterém byla odeslána. Zároveň za Vás řeší tzv. congestion control. Jedná se o problém přetížení sítě, kdy v případě, že uzel může začít packety zahazovat. Příkladem může být omezení rychlosti stahování. Pokud platíte u poskytovatele 300Mb/s a začnou Vám chodit data rychlostí 500Mb/s, přebytečné packety se prostě zahodí.
Po vytvoření socketu pro TCP můžeme použít funkci `accept`, která přijme první připojení ve frontě. Pokud se na náš socket někdo pokusí připojit, operační systém vykoná tzv. handshake a přidá nové připojení do fronty. Z fronty můžeme vytahovat právě pomocí `accept`. Zjistit, jestli ve frontě už nějaké připojení máme, můžeme pomocí funkce `listen`, která blokuje, dokud nějaké připojení nepřibyde.
Nové přijaté připojení bude mít svůj vlastní socket s vlastním portem. Když zavoláme `write` a do argumentu dáme ukazatel na data a jejich délku, operační systém je odešle na druhou stranu, která s námi navázala spojení. Zaručí to, že na druhou stranu dojdou všechna data, nebo žádná. Druhá strana může pomocí `read` do alokované paměti přečíst to, co mu do socketu přišlo. Je nutné si uvědomit, že pokud zavoláme `write` pro data o délce řekněme 1000 bytů, tak druhá strana nemusí dostat všechna data pomocí jednoho `read`. Tento příkaz vrací počet bytů, které přečetl. Pokud tak přečte méně, než očekáváme, musíme provést volání znova, dokud nepřečteme vše co očekáváme. Mohlo by nás zarazit: jak může druhá strana vědět, kolik má očekávat dat? Pro to slouží tzv. komunikační protokol, který rozeberu v kapitole...
\subsubsection{Spolehlivost}
Popíšeme si, jak TCP zajišťuje spolehlivost pomocí ACK zpráv. Pokaždé, co dostane packet, odešle druhému koncovému bodu zprávu ACK s číslem packetu. V případě, že odesílatel ACK, neboli potvrzení o přijetí, nedostane, odešle packet znova. Příjemce si u sebe postupně skládá seřazený proud bytů. Jakmile je na socketu seřazená posloupnost bytů, umožní nám ji přečíst pomocí `read`. Pokud odešle jedna strana packety A, B a C, a příjemci přijde nejprve C a B, `read` nebude nic vracet, dokud nepřijde i packet A. Zmíním konkrétní případ, kdy to nemusí být žádoucí: Představme si, že A tvoří jeden příkaz a C a B tvoří druhý příkaz. Oba příkazy jsou na sobě nezávislé a příjemce je dokáže vykonat v libovolném pořadí. Stále potřebujeme spolehlivost doručení, ale stačí nám jen částečné uspořádání.
Mezi aplikační protokoly využívající TCP patří HTTP/2, email, nebo SSH.
Mezi nevýhodu bych zařadil to, že přenáší proud dat. Jinými slovy, v protokolu není proud rozdělen na jednotlivé, řekněme, "zprávy". Tuto logiku si musíme implementovat sami. Později si ukážeme, jak toho lze docílit.
\subsubsection{UDP}
Méně spolehlivá alternativa je UDP, neboli Unrealiable Datagram Protocol. Ten komunikuje pomocí tzv. datagramů, na rozdíl od proudu bytů, jako je tomu v případě TCP. Znamená to, že jedním systémovým voláním `write` odešleme právě jeden datagram, a na příjemci právě jedním `read` přečteme celý datagram.
Zde již nebudeme volat `listen` ani `accept`. Výhoda je, že pokud zavoláme `write` a odešleme data o velikosti 100 bytů, je zaručeno, že druhá strana dostane celý tento blok právě jedním zavoláním `read`, ve kterém nebude nic dalšího. UDP totiž rozděluje data na datagramy, které v podstatě reprezentují packety. To usnadňuje práci s komunikačním protokolem. Protokol je nespolehlivý: datagramy mohou dorazit v jiném pořadí nebo vůbec. Jsou ale způsoby, jak spolehlivost, alespoň částečnou, implementovat.
\subsubsection{QUIC}
QUIC je spolehlivý protokol transportní vrstvy od společnosti Google. Jeho cílem je být rychlejší varianta TCP, která využívá protokol UDP. Mezi dvěma koncovými body vytváří spolehlivé spojení. Na rozdíl od TCP umožňuje otevřít více proudů dat v jednom spojení a modelovat tak částečné uspořádání. Když se čeká na paket v TCP, tak se pozastaví celý tok dat, ale v případě QUIC se zastaví jen konkrétní proud.
V protokolu se nachází pakety, rámce a čísla koncových bodů. Paketů máme více druhů a liší se především hlavičkou, ale také typem šifrování a její silou. Pro otevření spojení odešle klient serveru paket typu Initial, který má v hlavičce číslo lokálního spojení (LCID - Local connection ID) a číslo cílového spojení (DCID - Destination connection ID). Když server obdrží paket typu Initial, s číslem cílového spojení takovým, které nemá v registru, vytvoří úplně nové spojení s jiným číslem. Zpět klientovi pošle opět paket typu Initial a v hlavičce bude jako lokální číslo spojení mít nově vygenerované ID a jako cílové bude mít to, co klient poslal jako lokální. Touto výměnou se dva koncové body shodnou na svých číslech. Podotknu že čísla na obou koncích se liší, a prvotní cílové ID, které posílá klient, tak server nepoužívá. Jeho využití zmíním později.
Paket v sobě obsahuje různé rámce, každý nějakého typu. Mezi typy patří třeba: ACK, CRYPTO, PADDING, PING, STREAM apod. Postupně je rozeberu. Rámec typu ACK funguje podobně jako v TCP a obsahuje čísla paketů, které chce druhé straně oznámit jako zpracované. Tento rámec najdeme právě v paketu typu Initial od serveru na klienta, kdy server chce oznámit klientovi, že paket přijal a zpracoval. Druhý rámec který už při handshake uvidíme je CRYPTO. Ten je v Initial paketu a obsahuje klient secret resp. server secret. Server je v moment, kdy mu přijde první Initial paket odvodit tzv. session key z klient secret a svého vygenerovaného server secret. Na klienta odešle paket Initial s CRYPTO rámcem obsahujícím server secret. Hned za ním odešle Handshake paket, který už je zašifrovaný pomocí session klíče a obsahuje certifikát serveru. Jakmile klient obdrží od serveru Initial a Handshake, může začít bezpečná komunikace. Druhá strana musí na všechny zprávy odpovědět pomocí ACK rámce.
QUIC se tak shodl na tajném klíči i navázal spojení dvěma roundtripy, namísto čtyřmi, jako je tomu klasicky u TCP s TLS. Důvod je, že pakety může spojovat do jednoho datagramu. Takže server odpovídá na první Initial paket dvěma pakety: Initial a Handshake, ale v jednom datagramu. Dalším typem paketu, který už v tento moment může použít, jsou RTT-0 a RTT-1. RTT-0 lze poslat už v datagramu, ve kterém klient posílá na server Initial, a může obsahovat STREAM rámce. To znamená, že obsahuje užitečné aplikační data. Pokud nám tak v datagramu zbývá místo, nebo nechceme čekat na odpověď, můžeme okamžitě poslat RTT-0. Tento paket je sice šifrovaný, ale slabě a není odolný proti replay útoku, kdy útočník paket odposlechne a pošle znova. RTT-1 už je plně šifrovaný pomocí session klíče a nelze použít replay útok.
Jak už jsem zmínil, užitečná aplikační data jsou ve STREAM rámcích. Pro začátek proudu ho musí jedna ze stran otevřít pomocí specifického rámce. Kdykoliv ho pak může zase zavřít. Dokonce je možné v jednom datagramu proud otevřít, poslat data a zase zavřít. Vidíme, že lze snadno modelovat částečné uspořádání a snížit blokování, které třeba v TCP běžně nastává.
Čísla spojení, které mají dva koncové body spojení vybrané, se využívají pro identifikaci odesílatele. Normálně by TCP využilo adresu z IP hlavičky, ale ta se může třeba při přepnutí z mobilních dat na Wi-Fi, změnit. V ten moment nastává zdlouhavý TCP timeout, kdy se socket nakonec zavře a aplikace se pokusí navázat spojení znovu. V případě QUIC je přepnutí okamžité, protože druhý koncový bod se podívá jen na číslo cílového spojení v hlavičce a ví, od koho mu datagram přišel.
Jak QUIC řeší spolehlivost už jsem trochu vysvětlil při handshake. Obecně používá ACK rámce, které posílá druhé straně, a který obsahuje úseky čísel paketů, které obdržel. Číslo paketu je v hlavičce a je postupně se inkrementující. Pokud druhá strana neobdrží ACK rámec, například pro paket n, pak odešle identický paket n, ale s novým číslem, které mu přiřadí čítač. Pokud druhá strana ACK odeslala, ale ten zrovna nepřišel, musí nějak řešit duplicity. Každý STREAM rámec obsahuje úsek dat proudu, který obsahuje, například od x do y. Díky tomu může koncový bod zjistit, že tuto část proudu už má a rámec zahodit.
\subsection{Protokoly v aplikační vrstvě}
Nejvyšší v rodině protokolů TCP/IP je aplikační vrstva. V ní najdeme protokoly jako HTTP, gRPC nebo SMTP. Aplikačním protokolům, které ale nejsou specifické pro konkrétní aplikaci, se říká middleware. Mezi takové řadíme právě HTTP nebo gRPC, které jsou pro obecné posílání zpráv. V aplikační vrstvě děláme autentizaci a autorizaci.
\subsubsection{HTTP}
Velmi používaným protokolem je HTTP, neboli Hyper Text Transport Protocol. Používá se např. na přenos HTML stránek. Byl navržen ke komunikaci mezi webovými prohlížeči a webovými servery, ale často se používá i pro API pro komunikaci mezi dvěma programy. Tento protocol je bezestavový. To znamená, že žádná data si neukládá mezi dvěma HTTP dotazy. Každý HTTP dotaz musí obsahovat kompletní informace k jeho vyhodnocení.
\subsubsection{HTTP/3}
Poměrně zajímavou novinkou je HTTP/3, která běží na protokolu QUIC. Jinak je podobný protocolu HTTP/2.
\subsubsection{gRPC}
Další protokol od Googlu je gRPC, který je binární a snaží se simulovat volání metod v objektech. Je to intuitivní alternativa k posílání zpráv.
\subsubsection{Middleware}
Speciálním případem protokolů v aplikační vrstvě je tzv. middleware. Je charakteristický tím, že není specifický pro žádnou aplikaci. Příkladem jsou právě protokoly zmíněné výše, jako gRPC nebo HTTP, protože libovolná aplikace je může využívat. Typické jsou různé typy rozhraní pro posílání zpráv. Pokročilejším případem jsou autentizační a autorizační middleware. Authentizace je ověření, že klient je opravdu ten, za koho se vydává. Autorizace je ověření, že klient má právo na to, co se snaží udělat. Je jasné, že tato problematika bude trápit více různých aplikací s ne nutně jinak společnou částí.
\subsection{Posílání zpráv}
V této sekci se budu soustředit na middleware pro komunikaci mezi procesy pomocí zpráv. Rozeberu jak lze zprávy kódovat a strukturovaná data serializovat. Komunikovat pomocí zpráv sice neschovává síťovou komunikaci, jako třeba RPC.
\subsubsection{Serializace}
V systému jsem chtěl zprávu reprezentovat jako instanci konkrétního objektu. To znamená pro zprávu, která říká, že hráč napsal něco do chatu, bude existovat třída \kiinlinecode{C++}{!}{NewChatMessage} a konkrétní hodnoty budou v instanci této třídy. Díky tomu jsem schopný snadno vytvářet zprávy v systému. Alternativou je implementovat dynamický strom, který bych následně kódoval jako JSON. Uzly by byly JSON objekty a listy JSON hodnoty. Podmínkou je, že každý list musí být převeditelný na hodnotu v JSONu a musí mít klíč. Druhý přístup ale zbytečně obchází vlastnosti jazyka C++, jako dědičnost, kontrolu typů (i když ta je v C++ slabá) a další. Na druhou stranu je tento přístup hodně dynamický, proto se hodí například, kdyby schéma zprávy definoval sám uživatel, až po kompilaci programu.
Pokud jsem chtěl posílat instance objektů, musel jsem instance převést na n-tici bytů. Tomuto procesu se říká serializace (resp. deserializace při převedení n-tice bytů na instance objektu). Velmi častým příkladem je serializace do JSON formátu, hojně využívaná v aplikacích s REST API rozhraním, nebo do XML v případě SOAP API rozhraní. To jsou tzv. textové formáty, které jsou pro člověka čitelné. Díky tomu jsou vhodné pro webové aplikace, kde rozhraní musí být srozumitelné pro snadnou práci. Nevýhoda je paměťová náročnost. Například číslo 1 000 000 se v JSON zakóduje do 7 bytů, i když binární reprezentace stejného čísla se vleze do 4 bytů. Rozdíl je směšný, ale téměř dvojnásobný. Druhým kamenem úrazu je struktura. Každý atribut objektu se v JSONu reprezentuje řetězcem, namísto třeba čísla o 2 bytech, které by unikátně atribut identifikovalo.
Všechna tato snížení prostorové náročnosti řeší binární formáty. Známým příkladem je ProtoBuf, FlatBuffers, Parquet nebo Cap'n'Proto. Postupně je rozeberu jejich specializace. ProtoBuf je volně dostupný protokol od Googlu pro serializaci strukturovaných dat. Pro použití je třeba definovat v univerzálním jazyce to, jakou má zpráva strukturu. Jazyk je podobný definici struktur v jazyce C. Pro přestavu si ukážeme:
\begin{kicode}{C++}{}{C++}
message EntitySpawnMessage {
uint32 entity_id = 1;
bool is_player = 2;
string name = 3;
}
\end{kicode}
Vidíme, že místo \kiinlinecode{C++}{!}{struct} píšeme \kiinlinecode{C++}{!}{message} a atributy musí mít unikátní ID. Důvodem je kompatibilita verzí definice. Pokud bychom měli systém o dvou službách, a v jedné bychom přidali atribut `position`, jak vidíme v příkladě, mohla by být druhá služba stále schopná serializovaná data první službou deserializovat.
\begin{kicode}{C++}{}{C++}
message EntitySpawnMessage {
uint32 entity_id = 1;
Vector3 position = 4;
bool is_player = 2;
string name = 3;
}
\end{kicode}
Stejnou vlastnost má i JSON, kdy unikátní ID je právě řetězec pro klíč. Serializující služba může přidávat atributy, ale deserializaci to neovlivní. Z .proto souboru se vygenerují přímo třídy do potřebného jazyka. V mém případě C++. Výhoda definovat zprávy v univerzálním jazyce je čitelnost, možnost převést do libovolného programovacího jazyka a především reflexe.
FlatBuffers je opět protokol od Googlu, který se vyvýjel pro použití v herních enginech pro hry více hráčů. Jeho výhoda je, že není třeba zprávu "deserializovat". Instanci třídy čte atributy přímo z n-tice bytů. V případě 60 zpráv za vteřinu zprávy od stovek hráčů, je rozdíl mezi deserializací a FlatBuffers znát.
Parquet je formát pro sloupcová data, který umožňuje v serializovaných datech vyhledávat a má menší paměťovou náročnost, takže je vhodný i pro přenos.
Posledním příkladem je Cap'n'Proto, čteno Captain Proto. Je to volně dostupný protokol, který nemá deserializaci, a je tak velmi rychlý. Jeho tvůrce navíc pracoval právě na ProtoBuf.
\subsubsection{Kódování zpráv}
Při čtení dat ze socketu pomocí TCP protokolu dostáváme proud bytů. Je potřeba z proudu zprávy dekódovat a naopak je být schopni do proudu zakódovat. Proto jsem definoval kódování zpráv a komponentu Messenger. TCP je spolehlivý protokol, proto se málokdy stane, že zprávy nebudou následovat hned po sobě. Pro zakódování zprávy zakóduju nejprve tzv. hlavičku. To je prvních pár bytů, které ukládají informaci o zprávě, jako třeba délku nebo typ. Za hlavičkou následuje serializovaná zpráva. V hlavičce budou první 4 byty tzv. magické číslo, které označuje začátek kóvání a slouží k synchronizaci čtecí hlavy v případě, že došlo při deserializaci k chybě. Dalšími 4 byty je n pro n-tici bytů pro serializovanou zprávu.
Při zavolání peek na instanci Messenger, se do bufferu v instanci, který má velikost 64KB, přečte funkcí recv z TCP socketu. Volání není nutné opakovat, protože peek budeme opakovat hned další snímek za pár milisekund. Po zavolání `recv` skenujeme vstupní buffer a hledáme magické číslo. Jakmile ho najdeme, čteme další 4 byty jako délku $n$. Pokud v bufferu zbývá ještě alespoň $n$ bytů, vložíme je do fronty zpráv a odebereme z bufferu pro uvolnění místa. Nakonec funkce `peek` ověří, jestli něco ve frontě je, pokud ano, vrátí typ nejstarší zprávy ve frontě.
Druhou funkcí je `pop`, která má template s argumentem T pro datový typ zprávy. Uživatel instance `Messenger` musí umět z čísla, které reprezentuje typ, dostat datový typ. To jsem udělal pomocí tzv. template specialization. Uživatel musí zavolat `peek` a podle vracené hodnoty zavolat `pop` se správným template argumentem. Funkce `pop` zavolá deserializační funkci generovanou ProtoBuf.
\subsubsection{Implementace}
Bylo potřeba zadefinovat, jak se zpráva bude posílat přes síť. Cílem nebyla univerzálnost a čitelnost, jako je například protokol HTTP, ale optimalizace velikosti. Je třeba definovat, jak zpráva v toku dat vypadá, a jak ji dekódovat.
První čtyři byty obsahují tzv. `MAGIC` číslo. Může to být libovolná konstanta, protože jediný její význam je synchronizace toho, kde zpráva začíná. Důvod si ukážeme v části o nápravě chyb. Další byte je typ zprávy. Další 2 byty je délka zprávy. Průměrná délka packetu je 1200-1500 bytů, nemá proto smysl posílat delší zprávy. K tomu tento protokol není. Nakonec následuje samotná serializovaná zpráva. Těmto prvním 6 bytům se říká hlavička.
\subsubsection{Detekce a náprava chyb}
Občas mohou nastat v proudu chyby, kdy zprávu nelze dekódovat, nebo nastane chyba při deserializaci. Proto jsem do kódování implementoval nápravu chyb. Definoval jsem maximální délku bytů, kterou lze přeskočit při hledání magického čísla. To nestačí, pokud jsou data náhodná, protože se může stát, že číslo objeví i tak (i když je pravděpodobnost 1/(256\^4)). Proto si dekodér pro socket počítá chyby. Pokud se objeví 3 po sobě jdoucí chyby, tak spojení ukončí. Příkladem takové chyby je selhání deserializace nebo `recv` vrátí chybu.
\subsection{Asynchroní IO}
Čtení a zápis na socket je běžně blokující operace. To znamená, že při zavolání `read` pro čtení 10 bytů, bude volání blokovat, dokud na socket nepřijde 10 bytů. V případě serveru pro online hru je to nevhodné, protože pokud nám nic nepřišlo, můžeme mezitím vykonávat ostatní logiku hry. Socket lze nastavit jako neblokující tak, že pokud zavolám `read` a budu chtít číst 10 bytů, a ty tam ještě nebudou, vrátí mi ty co zatím přečetl (pokud přišlo jen 5 bytů, vrátí mi jen 5 bytů). Tuto menší část si můžu u klienta ukládat do bufferu a postupně zkoušet dekódovat jako zprávu.
Podobnému přístupu se říká *asynchronní sockety*. Jinými slovy to znamená oddělení volání a samotného vykonání. V tomto případě sice zavoláme `read`, ale toto volání by se uložilo někam do fronty, a jiné samostatné vlákno by ho mohlo vykonat později. Protějšek z reálného světa je hodit dopis do schránky a pokračovat ve svém klasickém programu dne. Doručení se stane v nejbližších dnech a to někým jiným, takže my už se o dopis nemusíme starat. Pro lepší představu si pár existujících implementací představíme:
\subsubsection{ZeroMQ}
Při použití knihovny ZeroMQ vytváříme objekt `ctx = zmq\_context(num\_threads)`. Ten vytvoří námi specifikovaný počet vláken `num\_thread`. Když následně zavoláme `zmq\_send(ctx, x)`, protějšek klasického `send(x)`, tak nedojde k blokování. Místo toho se zpráva `x` umístí do fronty, ze které pak vlákna z `zmq\_context` tzv. "kradou" (work-stealing), a odesílají v pozadí.
To samé platí o `zmq\_receive`, které si v pozadí bufferuje celou zprávu. TCP totiž nemá žádnou strukturu zprávy, a proto když zavoláme `recv`, můžeme dostat jen část. ZeroMQ místo toho v pozadí bufferuje, dokud neobdrží celou zprávu, a až poté vrací `true`.
\subsubsection{Work stealing}
Běžnou strategií pro dynamický multithreading, je tzv. work stealing. Jiné strategie jsou třeba work sharing. Implementuje se thread-safe fronta. Při asynchroním volání úkolu se vloží do této fronty. Následně máme X vláken, které se ve smyčce dotazují na frontu. Pokud v ní něco je, vytáhnou to a začnou na tom pracovat. Těmto vláknům se někdy říká "Worker". Většinou jich je právě tolik, kolik je fyzických jader v procesoru, protože cílem je paralerismus, a ten s vyšším počtem vláken nezlepšíme. Zajímavou vlastností je, že součástí úkolu může být právě vložení dalších úkolu do fronty.
\subsubsection{Async/Await}
Populární implementací, kterou vidíme například v Rust nebo C\#, je async/await. Async už jsme si vysvětlili. Await slouží pro volání asynchroních funkcí. Místo `queue.push(make\_task(...))` tak úkol může být právě volání funkce. Práce je tak daleko intuitivnější. Zde pozor, že pouhým voláním await na asynchroní funkci v C\# nebo Rust paralerismu nedocílíme! Na to je třeba v jeden moment await více úkolů.
Příklad z reálného světa je poslat dopis s otázkou. Opět stačí hodit dopis do schránky. Až si ho eventuélně někdo přečte, může nám poslat odpověď. Když dostaneme odpověď, vzpomeneme si, kde jsme skončili, když jsem práci přerušili a odeslali dopis s otázkou, a když už máme odpověď, tak pokračujeme.
\subsubsection{Boost Asio}
Velmi podobná ZeroMQ je Boost ASIO. Ta ale nechává vytváření vláken na nás. Pouze vytvoříme objekt `asio\_context`, který má metodu `run`. Když z vlákna zavoláme metodu `run`, stane se vlákno workerem, který krade práci z fronty, která je taky `context`.
Tento přístup je intuitivnější, protože se jedná přesně o to, jak worker thready fungují. Pouze spuštěná funkce, která se opakovaně dotazuje na frontu. Pokud něco najde, vytáhne to, a začne na tom pracovat. Jakmile práci dokončí, opět se dotazuje na frontu. Zde si také všimněme, že se nejedná o klasický rychlý context switching, který dodává iluzi více jader v operačních systémech, ale worker opravdu práci nejprve dokončí, než změní kontext a začne pracovat na něčem jiném.
Otázka by mohla být, proč nevytvořit thread pro každou zprávu, kterou chceme odeslat. Důvodem je, že vytváření vláken je sice rychlejší, než celých procesů, ale pořád vyžaduje systémové volání, které může způsobit zpomalení.
Proto se často používají tzv. green thready, fibery, user thready, ... což jsou thready, ale spravované kompletně v user-space. Není třeba žádné systémové volání.
Na začátku se spustí několik workerů, což jsou thready, které právě kradou práci z fronty a vykonávají je. Většinou mají dlouhou životnost.
\subsubsection{Stack pointer}
Problém může být definovat práci pro thready. Jak vypadá takový objekt `Job`? Intuitivně by nás napadlo, že by obsahoval ukazatel na funkci. Nebo v případě, že máme jen velmi omezený počet různých implementací, může obsahovat jen typ, a worker už si dohledá, co má dělat.
Druhým způsobem, který vidíme při použití `await/async` v jazycích jako Rust nebo C\#, je prostě zavolat funkci, která reprezentuje práci, pomocí `await`. Funkce await si uloží aktuální ukazatel na stack, vytvoří nový stack speciálně pro job, a objekt `Job` tak vypadá tak, že obsahuje tento nový stack pointer, ukazatel na funkci, a všechny potřebné registry.
Všimněme si, že můžeme job vytvářet i z jiného jobu, a tím v podstatě větvit. Zároveň můžeme kód paralerizovat tak, že najednou budeme awaitovat více jobů. Představme si, že máme fyzický systém s metodou `step`. Ta zavolá `awaitAll(jobs)`, kde `jobs` je list jobů. Automaticky se všechny dostanou do fronty, a různé workery je mohou začít vykonávat paralerně.
\subsection{Metriky}
Abych byl schopný správně optimalizovat tok dat, potřeboval jsem sbírat metriky. Proto jsou třeba dvě komponenty. První je úložiště, kde budu metriky ukládat. Může se jednat o CSV soubor nebo databázi. Druhá je rozhraní, ve kterém budu metriky vizualizovat. Představím dvě služby, které pro to využívám. Následně si představíme samotné metriky, které budu měřit a proč. Všechny metriky budu měřit po vteřinách, některé jako sumu, jako např. množství odchozích dat, a jiné jako průměr, jako např. odezvu.
\subsubsection{TimescaleDB}
Jako úložiště jsem zvolil TimescaleDB. Jedná se o rozšíření do PostgreSQL, takže není třeba spouštět a starat se o další službu. Stačí mít spuštěnou PostgreSQL databázi. TimescaleDB umožňuje vytvářet tzv. hypertables, které jsou optimalizované pro rychlý insert. Vytvořil jsem komponentu `NetworkBandwidthReporter`, která má na rozhraní `report\_outbound` a `report\_inbound`. Tyto údaje posílá do komponenty `TimescaleDBReportOutputter`, která už zajišťuje připojení do databáze a umožňuje `report(metric\_name, value)`. V rámci optimalizace přidávám hodnoty do bufferu a posílám je pouze jednou za vteřinu jako sumu.
\subsubsection{Grafana}
Grafana je služba, která skrze webovou stránku poskytuje rozhraní pro vizualizaci metrik. Lze nastavit svůj dashboard a v něm různé grafy. V mém případě graf pro množství odchozích a množství příchozích dat, zatížení procesoru a nebo zatížení paměti RAM.
\subsubsection{ImGui}
Chtěl jsem mít i možnost se na metriky podívat přímo ve hře při běhu. Pro uživatelské rozhraní používám knihovnu ImGui a ImPlot. Tyto knihovny využívají pro vykreslování grafickou kartu a snadno jsem je mohl integrovat do svého vykreslovacího enginu. Knihovna funguje procedurálně, nikoliv objektově. Každý snímek, který vykresluju, musím celé rozhraní definovat. To udělám pomocí volání funkcí. Nejprve zavolám funkci `Begin("Název okna")`, které zobrazí okno, do kterého můžu vložit další prvky, jako texty, textové vstupy, nebo tlačítka. Tento přístup je pro herní enginy a podobné kreativní aplikace ideální, protože umožňuje velmi snadno a rychle dělat dynamické uživatelské rozhraní.
\subsubsection{Tracy}
Pro měření výkonu programu serveru používám výborný profiler Tracy. Je zdarma, open-source, napsaný v C++ a využívající ImGui. Poskytuje přesnost na nanosekundy, což se u serverů, na kterém může být miliony různých entity, hodí. Navíc umožňuje se k programu, který měří, připojit vzdáleně, případně sbírat metriky do souboru, ten si ze serveru stáhnout a analyzovat později.
\subsubsection{Konkrétní metriky}
Ukážu různé metriky, které lze měřit, jak a hlavně proč. Je dobré zmínit, že místo, ze kterého budu metriky sbírat, je server. To ale nevadí, protože jak uvidíte, vše potřebné na něm měřit jde, a to co ne, tak nás nemusí zajímat.
\begin{description}
\item[{Inbound/Outbound bandwidth}] \hfill \\
Nejpřirozenější metrika je příchozí a odchozí množství dat v bytech. Už zde pro mě bylo nutné si uvědomit důležitost oddělit vrstvu, která kóduje a dekóduje zprávy, od zbytku. Přesně na tomto místě totiž budu metriky dělat. Metriku budu sčítat.
\item[{Odezva}] \hfill \\
Budu měřit odezvu mezi odesláním vstupu od klienta pro snímek x a odpovědí od serveru pro snímek x. Tuto metriku budu průměrovat.
\item[{Počet snímků za vteřinu}] \hfill \\
Ze začátku se mi stávalo, že uzké hrdlo nebyla síť, ale síťová vrstva v aplikaci, která třeba dlouho skládala zprávy. Proto si udržuju i přehled o počtu snímků, které server stíhá. Podle toho se lze orientovat při výběru hardware pro server.
\end{description}
\subsubsection{Sbírání v reálném čase}
Přímo v hráčově klientské aplikaci jsme chtěli mít možnost vidět metriky v reálném čase. Do síťové vrstvy jsme přidali komponentu pro reportování událostí a jejich metrik zvanou \kiinlinecode{C++}{!}{\\NetworkMetricReporter}, která obsahuje metody jako \kiinlinecode{C++}{!}{\\reportInbound} a \kiinlinecode{C++}{!}{\\reportOutbound}. První metoda hlásí příchozí byty a druhá odchozí.
\newpage
\section{Hra pro více hráčů}
Pro zkoušku různých technik jsme se rozhodli vytvořit hru pro více hráčů. Nejprve představíme, jak vypadá architektura hry jednoho hráče. Podporu pro více hráčů jsme implementovali jako rozšiřující vrstvu, která příliš neovlivňuje klasickou architekturu her. Ve hře se jednotlivý hráči uvidí, budou se moct pohybovat a interagovat s vybranými objekty.
Ukážu, jaké komponenty jsem přidal a proč, jak jsem je měřil a analyzoval a případné optimalizace, které jsem pro ně použil. Představím svůj protokol QUICr, který je inspirovaný QUIC, ale upouští od spolehlivosti, což je ideální pro hry.
\subsection{Hry}
Program hry většinou obsahuje smyčku, kde jedné iteraci říkáme snímek. Moderní hry iterují od 60 do až 240 snímků za sekundu, někdy více. V našem případě jsme se rozhodli pro jednoduchou 3D hru s vykreslováním 60 snímků za vteřinu. Zajímá nás aplikace různých technik. Hra si také udržuje svůj stav, jako entity ve světě, počasí, terén, herní pravidla apod.
Každý snímek nejprve posbírá vstupy od hráče, převede je na jednotlivé akce, podle toho, co pro hru vstupy znamenají, a aplikuje je na stav hry. Například stisknutí tlačítka W převede na akci: pohyb dopředu. Takto postupně iterujeme z počátečního stavu hry.
Architektura programů her je specifická tím, že tolik nevyužívá dědičnost a především virtuální funkce. V případě milionu entity, pro které voláme funkci 60 krát za sekundu, se i virtualizované volání negativně projeví ve výkonu. Např. chceme li mít obrovský svět s různými postavami, kde každá má různé schopnosti a vlastnosti, špatně by se nám modeloval strom dědičnosti.
https://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
Namísto toho se logika vlastností a schopností, které různé entity mohou mít, rozdělí do tzv. komponent. Např. komponenta pro obchodníka, stráž, kouzelníka, truhlu, dialog apod. Ve světě pak máme entity, kde každá má svůj individuální list komponent, které má. Tento styl je podobný kompozici objektů, ale s tím rozdílem, že komponenty skládáme do entity až za běhu programu. Nedefinujeme třídu pro každý typ entity, který může ve hře být.
Samotná logika je pak v tzv. systémech. Každý iteruje přes konkrétní komponentu, která v sobě musí mít stav a zároveň derivaci, kterou systém aplikuje, resp. provede integraci. Tomuto návrhu se říká Entity, Component, System, neboli zkráceně ECS.
\subsubsection{Entt}
V našem případě využíváme knihovnu entt, která obsahuje různé nástroje pro ECS a vývoj tak zjednodušuje. Příkladem her, které ji využívají taky je třeba Minecraft pro Windows. Nejprve vytvoříme registr, což je objekt typu `entt::registry`, který spravuje své entity a jejich komponenty. Entita je v tomto případě pouze unikátně identifikační číslo, komponenta je libovolný typ a systém je lambda. Pro vytvoření systému musíme definovat pohled, tedy n-tici komponent. Tento pohled pak bude iterátor přes všechny entity, které mají každou z těchto komponent. Můžeme pak podle stavu jedné aktualizovat jinou a dělat různé interakce mezi nimi.
\begin{kicode}{C++}{}{C++}
entt::registry registry;
entt::entity entity = registry.create();
registry.emplace<Transform>(entity, Transform::from_position(position));
registry.view<Transform, CharacterBody>()
.each([&](Transform& ts, CharacterBody& rb) {
auto character = rb.m_character;
auto transform = character->GetWorldTransform();
memcpy(&ts.transform, &transform, sizeof(glm::mat4));
});
\end{kicode}
Představíme komponenty, které jsme pro hru definovali a proč:
\begin{description}
\item[{Transform}] \hfill \\
Obsahuje transformační matici. Entity, které chceme zobrazit ve světě, tuto komponentu potřebují. Příkladem entity, která ji mít nebude, je zpráva v chatu.
\item[{Mesh}] \hfill \\
Obsahuje mesh, který má hra využít pro vykreslení entity. Mesh je n-tici bodů, které dohromady dávají 3D povrch objektu. Systém, který entity vykresluje si vytvoří pohled na Mesh a Transform, protože je potřeba vědět kam entitu vykreslit.
\item[{Rigidbody}] \hfill \\
Slouží pro fyzikální informace o entitě, jako hmotnost a sílu, kterou na entitu budeme aplikovat. Systém pro tuto komponentu bude fyzický engine.
\end{description}
\subsection{Svět}
Stav hry reprezentujeme kompletně ve třídě Svět, neboli `World`. Ta bude obsahovat právě entt registr pro entity ve světě. Další komponenta, zvaná ovladač, pak bude sbírat vstupy a stav světa postupně aktualizovat. V případě hry pro jednoho hráče získá ovladač vstupy z klávesnice a myši a vhodně aktualizuju svět. Například při zmáčknutí klávesy W pro posun dopředu, nastavím rychlost RigidBody entity pro hráče tak, aby při zavolání integrace posunula Transform hráče. Oddělím tak logiku která drží stav a tu která stav mění.
\subsubsection{Ovladač pro hru jednoho hráče}
Definujeme, jak bude vypadat ovladač světa, tedy vrstva, která transformuje vstupy od hráče na akce ve hře, a podle nich postupně mění stav světa. Pro tento přístup jsme vytvořili `LocalWorldController`.
\subsubsection{Ovladač pro hru více hráčů}
V případě hry pro více hráčů jsme vytvořil \kiinlinecode{C++}{!}{\\ClientWorldController}. Ve jméně je zmíněný *Client*, protože pro peer-to-peer architekturu by ovladač vypadal jinak. Nejedná se tedy o univerzální řešení pro hru více hráčů. Další součástí systému je `WorldServer`. Ten má v případě klient-server architektury zodpovědnost přijímat vstupy od klientů a odesílat zpátky stav světa. Architektura je podobná té pro hru jednoho hráče, pouze se změnilo kdo integruje.
% #image("ClientServerArchitecture.png")
Protějškem je hra šachů, kterou ovládáme hlasem a někdo jiný za nás táhne. Zvoláme "pěšec XY na ZW", tahač táhne a my vidíme, jak se našim vstupem změnil stav hry. Stejně tak to vidí i druhý hráč. Všimněme si, že druhý hráč vůbec nemusí vědět, co byl náš vstup (ten může dostat derivací), ale stačí mu pouze stav.
\subsection{Klient-Server architektura}
Hry budeme programovat s architekturou klient-server. To znamená, že hráči budou mít klienta a někde v síti musí být dostupný server, na který se klienti budou připojovat. Server bude mít absolutní autoritu. Klienti budou na server posílat pouze vstupy, server se k nim bude chovat stejně, jako by se jednalo o hru jednoho hráče a on je přečetl z klávesnice a myši. Server bude na klienty posílat co nejčastěji stav světa. Tento stav se bude postupně měnit. Zodpovědnost programu klienta je tento stav správně zobrazit. Například vykreslit všechny entity na správných pozicích apod.
Alternativou je peer-to-peer architektura, kdy každý uzel je zároveň server i klient, neboli peers. Na rozdíl od klient-server ale nemá nikdo autoritu. Uzly si mezi sebou posílají opět jen vstupy. Hra se simuluje na každém peerovi zvlášť. Zde nastává problém synchronizace, protože simulace se nám mohou snadno rozejít. Musíme tedy definovat, jak zjistit, že se desynchronizace stala a co s ní dělat. Musíme si dát pozor na pseudo-náhodné generátory, aby náhodné prvky hry se vyhodnotili všude stejně. Další problém může nastav v přesnosti čísel s plovoucí desetinou čárkou. Některé procesory mohou udělat operaci jinak a způsobit desynchronizaci.
Detekovat desynchronizaci můžeme tak, že si jednou za čas peers pošlou snapshot svého stavu.
\subsection{Server}
Rozebereme si jak vypadá program pro server. Ukážeme komponenty, které jsme pro podporu více hráčů museli přidat. Architektura se skládá z replikátoru a manažera zájmu. Stav světa, který server odesílá na klienty, se skládá z entity a komponent, které chceme synchronizovat. Replikátor pro každého hráče zakóduje entity a jejich komponenty a udržuje je synchronizované. Pomocná komponenta je manažer zájmu. Ta říká, které entity se mají synchronizovat kterým. Základní implementace by pro hráče $A$ mohla omezit entity, které mu server bude synchronizovat, na ty v maximální vzdálenosti $d$ metrů.
\subsubsection{Registr spojení}
Vytvořili jsme registr, ve kterém jsou všichni připojení hráči. Tato komponenta slouží jako sifon pro všechny zprávy, které klienti odesílají. Zároveň z ní lze zjistit kdo se právě připojil a kdo odpojil. Třída na to má dvě metody: \kiinlinecode{C++}{!}{\\popDisconnectedPlayers} a \kiinlinecode{C++}{!}{\\popConnectedPlayers}. Třída si udržuje seznam nových připojení od posledního zavolání \kiinlinecode{C++}{!}{\\popConnectedPlayers}.
Také zde udržujeme počet selhání, které u klienta nastali a jakmile počet překročí určitou hranici, např. 5 chyb, relaci s klientem ukončíme.
% Pro odstínění zbytku logiky od síťové vrstvy slouží registr spojení. O to se stará pouze třída `ClientSessionManager`. Ta hlídá, jestli se nesnaží připojit někdo další. Pokud ano, udělá s ním patřičný handshake, ve kterém si vymění důležité informace o spojení jako session ID a tajný klíč. Na rozhraní bude mít funkci `update`, která při zavolání zkontroluje nová připojení a stavy handshake. V případě, že se klient nebude dlouho ozývat, vyřadí ho z registru. Tuto skutečnost o vyřazení, a samozřejmě o připojení, by měla dát vědět. Vytvořil jsem dvě fronty, jednu pro nově připojené a jednu pro odpojené klienty. Ovladač pro svět si každý snímek tyto fronty vyprázdní.
\subsubsection{Replikátor}
Systém hry pro více hráčů se snaží mít na všech zařízeních stejný stav. V případě klient-server architektury odesílá server svůj stav klientům. O to se stará replikátor. Pro každého klienta si drží seznam entit, které mu musí synchronizovat. Jeho zodpovědnost je odesílat zprávy klientům tak, aby vždy měli aktuální stav. Základní implementace by posílala každý snímek kompletní stav entity se všemi komponentami. Lepší varianta je posílat pouze změny. Pokud se například pozice nezmění, není třeba posílat stejnou pozici jako minule. Na druhé straně u klienta máme replicator receiver. Ten se stará o dekódování zpráv a správné aktualizaci komponent entit.
% Druhým důležitým systémem je replikátor. Ten pro každého klienta získá z Interest manageru seznam entit, které odebírá. Ty mu pak replikuje. Replikátor je už vrstva, která má zájem o to minimalizovat bandwidth. Všechny entity, které klient odebírá, mu musí udržovat synchronizované, ale může dělat například delta-compression. Tedy pokud se něco nezmění, tak to nebude synchronizovat. Je ale třeba, aby u každé komponenty věděl, jestli a jak ji má synchronizovat.
\subsubsection{Interest manager}
Zájem, neboli seznam entit, které se musí klientovi $A$ synchronizovat, vybírá komponenta manažer zájmů.
Systém, který se bude starat o to, které entity se musejí klientům synchronizovat, se nazývá *Interest manager*. Nejprve klientovi, podle jeho ID, přiřadíme tzv. delegáta ve světě. Bude to entita s transformací. Pro každého delegáta bude v pravidelných intervalech kontrolovat a přiřazovat do zájmu ty entity, které jsou v dostatečné blízkosti. Nejprve jsem udělal základní ground-truth implementaci, která funguje na principu brute-force a nevyužívá akceleračních struktur. Jednoduše pro každou jinou entitu pomocí Pythagorovy věty zjistí, jestli je dostatečně blízko.
Dotazu na všechny body, které jsou v určité vzdálenosti, se říká `range-query`. Datové struktury, které mi pomohli tento proces zrychlit jsou spatial grid, quad stromy a nebo kd-stromy. Techniky představím a porovnám:
\subsection{Klient}
V klientské aplikaci mám komponenty svět a ovladač. ClientWorldController, která obstarává vše kolem síťování. Třída World funguje stejně jako na serveru a stará se o klasický běh hry.
\subsubsection{Replicator controller}
Abychom oddělili to, jak replikátor funguje, vytvořili jsme pro něj na straně klienta ovladač. To nám později umožnilo měnit protokol mezi klientem a server podle potřeby.
\subsubsection{Interpolace}
Když jsme začali systém měřit, zjistili jsme, že áž příliš zatěžuje síť. Graf s průměry pro počet hráčů vidíme na grafu. Prvně jsme snížili počet snímků za sekundu, které server na klienty posílá, na 20 za sekundu. To snížilo zátěž na třetinu. Problém ale byl neplynulost hry. Klient sice vykresloval 60 krát za vteřinu, ale méně časté aktualizace způsobili neplynulý pohyb. Řešení je na klientovi interpolovat spojité proměnné, jako pozice nebo rotace. Do ovladače pro replikátor jsme přidali komponentu interpolátor. Do něj registrujeme jednotlivé komponenty, které chceme interpolovat. Interpolátor si je ukládá i s časovou známkou do bufferu. Na rozhraní jsem vytvořili funkci s argumentem čas a typ komponenty.
Technika měla nepříjemný důsledek zvýšení odezvy. To vadí především pro entity, které hráč ovládá, jako např. hráčova postava. Problém je, že když na interpolátor přijde snapshot $n$, může teprve začít interpolovat z pozice $n-1$. Proto
% Když jsem ale architekturu vyměnil za hru pro více hráčů, narazil jsem na obrovské zatížení sítě, protože posílat 60x za vteřinu $n$ vstupů a pak $n$ set snapshotů zpátky má objem. Především řeším velikost snapshotů, protože vstupy hráče lze reprezentovat velmi úsporně, a to bitem pro každou akci, kterou mohl vyvolat.
% Pro snížení náročnosti jsem umožnil nastavit počet snímků za vteřinu a nastavil 20. To pomohlo, ale za cenu velmi neplynulého hraní. Naštěstí pro plynulost hraní nám stačí pozice a rotace, a ty můžeme interpolovat. Přidal jsem tedy klientovi možnost si pozice uložit do bufferu a postupně, s fixním zpožděním, je interpolovat.
\subsubsection{Rollback a simulace vlastního hráče}
Interpolovat sice pomohlo pro plynulost, ale zvýšilo odezvu, což je nepříjemné. Způsob, jakým jsem se rozhodl tento problém řešit, je integrovat hráče pro daného klienta přímo na klientovi. Nejen že odezva bude v podstatě nulová, ale navíc bude ještě nižší než před interpolací. Musel jsem ale vytvořit mechanismus, který v případě, že se simulace neshodnou, tak na klientovi stav hráče opraví.
Do bufferu jsem si ukládal pro konkrétní snímky vstup od hráče, který pošlu na server, a aktuální stav, ve kterém hráč je. Následně když ve snímku $x$ přijde snapshot pro snímek $x - t$, který má až moc velkou odchylku, tak vrátíme stav světa na $x - t$ a podle vstupů z bufferu znova integrujeme. Tím dostaneme novou a opravenou pozici.
\subsection{Komunikační protokol}
Představíme komunikační protokol, který replikátor využívá.
% Pro použití UDP jsem musel přepracovat jak komponenta Messenger funguje. Shrnu co všechno bylo potřeba při návrhu vzít v potaz. Spolehlivost tak, aby nemusela být potřeba vždycky. TCP se čte jako proud bytů bez jasného rozdělení na jednotlivé packety, zatímco když na UDP socket zavolám `recv`, dostanu právě jeden datagram. Potřeboval jsem kódování zpráv, které bude pasovat na oba typy.
% Rozdělil jsem program architektonicky do tří vrstev. První je síťová, která ze socketu čte data a dekóduje z nich rámce. Komponenta, která toto řeší, jsem pojmenoval `QuicrEndpoint`. Ten po vytvoření instance otevře socket. Obsahuje rozhraní pro `bind`, který socketu přiřadí konkrétní port. k
% Problém je, kam umístit fronty. V případě serveru totiž chceme jednu velkou příchozí frontu rámců, ale frontu zvlášť pro každé připojení. Důvod univerzální fronty je, že můžeme mít více workerů, které zprávy budou obstarávat. Důvodem individuální fronty pro každé připojení je, aby se nestalo, že jeden hladový socket zablokuje všechny ostatní. Je proto třeba být v posílání packetů fér.
\subsection{Protokol QUICr}
Naše hra používala ke komunikaci mezi vrcholy protokol TCP. Zmínili jsme ale, že ten pro hry nemusí být vhodný. Vytvořil jsme proto protokol inspirovaný QUIC, který využívá UDP, ale upustil jsem podmínky pro spolehlivost, konkrétně jistotu doručení a uspořádání. V protokolu je možné na každém rámci definovat spolehlivost zvlášť. Ty, které budou obsahovat pozice hráčů, tak budu moct odesílat nespolehlivě, zatímco změny stavu, jako výměna animace nebo modelu, budu posílat spolehlivě. Pro vytvoření spojení budu používat dva objekty: koncový bod a spojení.
Koncový bod se stará o socket, tedy čtení a zápis na něj, a udržuje si objekty spojení, které ho využívají. Když přijde na socket datagram, správně ho přiřadí spojení. Jeho chování je definované jako ťik. V něm posbírá zprávy, které se spojení snaží poslat a pošle je, následně přečte socket a roztřídí datagramy.
\begin{center}
\begin{longtable}{>{\bfseries}l >{\ttfamily}c L{cm}}
\caption{Rozhraní třídy QuicrEndpoint}\label{tab:prepinace}\\
{\normalfont Název metody} & {\normalfont Popis} \\
\hline
bind(port) & Přiřadí socketu konkrétní port. \\
write(adress, payload) & Odešle `payload` přes socket na adresu `address`. Tato funkce bude volána objektem typu QuicrConnection, která tak bude odesílat celé datagramy. \\
poll & Přečte ze socketu všechny datagramy, které najde a podle hlavičky dekóduje ID spojení. Po zjištění ID zprávu přiřadí právě tomu spojení.. \\
\end{longtable}
\end{center}
Druhý objekt, spojení, je navenek stavový stroj a fronta zpráv, podobně jako TCP spojení. Stavový stroj mění stavy v reakci na příchozí rámce. Když na koncový bod přijde datagram, odešle ho do správného objektu připojení. Tento objekt si datagram přečte a postupně prochází rámce v něm. Stavový stroj bude důležitý hlavně v procesu zvaném "handshake", který definuju níže.
Zároveň jsem se rozhodl, že na spojení nebude zdánlivě nekonečný proud bytů, jako je tomu například v TCP, ale fronta zpráv. Výhoda je, že když začnu číst datagram, tak mám jistotu, že nenarazím na konec bufferu a budu si muset načíst další část. V mém případě jedno čtení znamená čitelná zpráva, tak jak jsme byla odeslána.
\subsubsection{Frame}
Inspiroval jsem se u protokolu QUIC, který datagram rozděluje na tzv. framy neboli \uv{rámce}. Tyto rámce můžu kódovat jak v TCP, tak v UDP. V případě UDP jsem se rozhodl u každého rámce definovat jeho spolehlivost, tedy jestli musí frame dojít vždy. Připojení pak bude mít jednotku pro spolehlivost, která bude spolehlivost zaručovat.
U UDP je další problém, a to ten, že když implementuju připojení pro UDP, tak všechny datagramy budou chodit na socket, na kterém poslouchám. Když budu ze socketu číst, dostanu libovolný datagram, který zrovna přišel, společně s adresou, ze které přišel. Tuto adresu přečtu z IP hlavičky, která není zašifrovaná, a tudíž ji kdokoliv může zfalšovat. Tomu se říká tzv. IP spoofing. Tento útok je poměrně slabý, protože v případě odpovědi budu stále odesílat data zpět na správnou adresu. Útok může sloužit například pro poslání požadavku na odpojení, nebo posílat jiné vstupy. Jedná se tedy spíše o neškodný ale otravný charakter útoku.
\subsubsection{Handshake}
Po vytvoření instance připojení z koncového bodu je připojení ve stavu `Closed`, neboli uzavřené. Pro navázání spojení jsem potřeboval, aby se dva objekty domluvili, například na svých ID, tajném klíči a verzi. Proto jsem definoval **handshake**. Mějme dva koncové body $A$ a $B$, které se mezi sebou budou snažit navázat spojení. Začne koncový bod $A$, a to tím, že odešle na koncový bod $B$ paket typu Initial, který obsahuje rámec typu `Hello`. Jakmile na koncový bod dorazí QUICr paket, který má v hlavičce jako Destination ID číslo, které nemá v registru, tak si ho přidá a přiřadí novou instanci připojení. Nová instance se chová stejně jako na straně $A$ a vygeneruje si unikátní identifikátor. Po vytvoření instance nechá koncový bod toto připojení tento paket zpracovat celý.
Nové připojení projde všechny rámce v paketu. Měl by narazit na rámec Hello, který když najde, a je ve stavu `Initial`, tak z hlavičky zjistí unikátní ID druhé strany a uloží si ho. Reaguje na rámec tak, že druhé straně odešle přes koncový bod paket, který obsahuje: rámec ACK o tom, že paket s číslem přijal, a rámec Hello. ACK si pro začátek představme zakódovaný jako: první 4 byty reprezentují kladné celé číslo pro počet rámců, řekněme $n$, které chceme označit jako přijaté. Následně přečteme $4n$ bytů. Každé 4 byty představují číslo paketu, který označíme jako ACKed. Jednotce, která udržuje stav o tom, jaké pakety jsou potřeba oznámit jako ACK a které je třeba poslat znovu, budu říkat `RealiabilityUnit` a podrobněji ji rozeberu později.
Přeskočme zpět na klienta, který dostává paket typu Initial, ve kterém je rámec Hello a ACK. Pro rámec Hello se chová stejně jako druhá strana, a pokud je ve stavu `SendHello`, nastaví si Destination ID. Všechna čísla paketů z ACK rámce předá do jednotky pro spolehlivost. Jelikož víme, že tam je číslo paketu, který obsahuje právě prvotní Hello, tak si jednotka tento rámec odebere z fronty pro opakované odeslání.
\begin{description}
\item[{Closed}] \hfill \\
Počátečním stavem je `Closed`. Zároveň se jedná o stav, do kterého se spojení dostane, pokud dlouho s druhou stranou nekomunikuje.
\item[{SentHello}] \hfill \\
Po odeslání Hello packetu se spojení dostane do stavu `SentHello`. V tomto stavu čeká na `HelloAck` zprávu.
\item[{ReceivedHello}] \hfill \\
Jakmile server dostane zprávu Hello, dostane se do stavu `ReceivedHello`.
\item[{Established}] \hfill \\
Po odeslání HandshakeDone zprávy se klient dostane do stavy Established. Jakmile server přijme zprávu HandshakeDone, taky se dostává do stavu Established.
\end{description}
Klient následně odpovídá Handshake done, protože už má všechny informace, které potřebuje o spojení. Upustil jsem od packetů a nechal pouze rámce.
\subsubsection{Spolehlivost}
Pro spolehlivost se protokol stará podobně jako QUIC, s tím rozdílem, že ji rozlišuje nad jednotlivými rámci. Všimněme si, že něco podobného má i samotný QUIC. Pokud odešleme paket, ve kterém je pouze ACK rámec, tak nedostaneme nikdy zpátky ACK. Rámec ACK se tedy chová stejně, jako by byl nespolehlivý. Rámce tak můžeme dělit na dvě kategorie: spolehlivé a nespolehlivé. Nespolehlivé rámce spojení odešle a hned je zapomene, tzv. fire and forget. O spolehlivé rámce se stará třída zvaná `QuicrReliabilityUnit`. Každá instance připojení na koncovém bodě má svou instanci, kterou ke spolehlivosti využívá. Její rozhraní tvoří 4 hlavními metodami:
\begin{description}
\item[\texttt{push\_reliable\_frame(deadline, frame)}] \hfill \\
Uloží si zakódovaný rámec a nastaví si u něj čas deadline. Jakmile bude po deadline, jednotka se bude snažit odeslat rámec znovu.
\item[\texttt{peek/pop\_reliable\_frames\_to\_send(packet\_number)}] \hfill \\
Vrátí rámce, které je potřeba v tomto okamžiku odeslat. Číslo paketu je důležité proto, aby jednotka věděla, které všechny rámce může ACK pro určitý paket označit jako hotové.
\item[\texttt{push\_ack(packet\_number)}] \hfill \\
Uloží si do seznamu číslo paketu a v další iteraci ho odešle jako ACK.
\item[\texttt{peek/pop\_acks\_to\_send()}] \hfill \\
Vrátí seznam paketů, které je třeba odeslat jako ACK. Zároveň si je ze seznamu odebere.
\end{description}
Jednotka pro spolehlivost si každý vložený rámec uloží, společně s časem, do kdy je potřeba ho odeslat znova, tzv. `deadline`. Tento přístup mimo jiné umožňuje vložit spolehlivý rámec rovnou do jednotky s deadline aktuálního času. Rámec se odešle co nejdříve a zajistí se jeho spolehlivost.
Dále si jednotka ukládá čísla paketů, které jsou v oběhu. Každý takový paket v oběhu ukazuje na seznam odkazů na spolehlivé rámce, které obsahuje. Když přijde číslo paketu jako ACKed, jednotka odstraní všechny rámce, na které paket ukazuje, a nakonec zapomene celý paket. Mohlo by se stát, že pro jisté pakety se nedoručí ACK nikdy, ale tyto pakety můžeme jednoduše vyčistit kontrolou, jestli obsahují alespoň jeden rámec, který nebyl takto označen.
Do jednotky si ukládám už zakódované rámce, protože tak můžu snadno odhadnout jejich velikost. Když bude
Možná jste si všimli, že nezaručujeme spolehlivost pořadí. Tuto záruku jsme do protokolu zatím nepřidávali, protože si stejně do paketu ukládáme číslo snímku, které je potřebné pro správnou aktualizaci světa. Toto číslo bychom z čísla paketu nebyly schopni vyčíst. Zároveň nás v našem případě nezajímá, ve kterém pořadí pakety pro jeden snímek přijdou. Kdybychom měli úplné uspořádání, a odeslali bychom z replikátoru datagramy $A$ a $B$, které by přišli na klienta v pořadí $B$, $A$, musel by klient čekat na datagram $A$, i když by mohl zpracovávat datagram $B$.
\subsubsection{Enkodér}
Pro kódování jsme vytvořili komponentu \kiinlinecode{C++}{!}{\\QuicrEncoder}, která přes konstruktor získá cílový buffer, do kterého má kódovat a na rozhraní má různé pomocné metody pro kódování hlaviček a rámců. Specifická vlastnost, kterou jsme potřebovali, je kódovat, dokud je v bufferu místo. Pokud zbývá $r$ bytů místa a replikátor by chtěl zakódovat rámec, který má $> r$ bytů, musí metoda vrátit informaci o neúspěchu, ale nevyhazovat vyjímku, ani neposunovat kurzor. Důvodem je, že UDP je nad protokolem IP, který může začít fragmentovat datagramy. Proto se většina protokolů snaží držet velikost datagramů kolem 1200 bytů. Podobný přístup jsme adaptovali my.
Dále musel umožňovat zakódovat hodnotu zpětně, jako například délku, kterou kvůli omezení bufferu víme až později.
\subsubsection{Testování}
Při implementaci jsme zjistili, že je dobré začít testy, které definují jednotlivé vlastnosti, jako například: "Spojení začne komunikaci rámcem Hello", "Po rámci Hello odpoví spojení rámcem Hello a ACK", "Na pakety obsahující spolehlivé rámce odpoví spojení ACK rámcem" apod. V testu jsem pak tyto vlastnosti ověřil. Tomuto přístupu se říká test driven development.
% Pro vývoj protokolu jsem si nejprve zkoušel zapsat specifikaci, následně udělat vývoj a nakonec otestovat. Tento přístup ale způsoboval časté chyby v návrhu protokolu. Daleko více se mi osvědčil tzv. Test Driven Development, kdy začneme tvorbou testů, které slouží v podstatě jako specifikace chování. Příkladem jsou testy: "Client begins with Hello datagram", "Connection reacts to Hello with ACK \& Hello" nebo "Both connections have correct IDs after Initial exchange". Tímto způsobem jsem definoval jak chci, aby se protokol a komponenty v něm chovali.
% Popsal jsem problémy, které jsem potřeboval vyřešit. Představím protokol, který je pro mě řeší. Vytvořil jsem třídu `QuicrConnection`, která bude reprezentovat stavový stroj připojení a zároveň umožní číst a zapisovat. Rozhodl jsem se, že zapisovat bude zprávy, nikoliv pouze proud bytů. Proto jsem použil tzv. message frame. Pokud něco popnu z QuicrConnection, je to vždy celá zpráva, která přišla. Stejný princip používá i ZeroMQ. Vytvořil jsem dvě třídy: QuicrConnection a QuicrConnectionListener. QuicrConnection obsahuje stavový stroj připojení a rozhraní pro příjímání a odesílání zpráv. QuicrConnectionListener obsahuje rozhraní pro poslouchání nově příchozích spojení. Nasluchač bude spravovat UDP socket. Když přijde nový datagram z adresy, kterou ještě nezná, a přišla zpráva Hello, tak si do seznamu přidá připojení ve stavu HelloReceived. Při datagramu z adresy, kterou zná, pošle tento datagram přímo do spojení na spracování. Každé spojení si bude udržovat svoji frontu příchozích zpráv.
Protokol měl za cíl snížit odezvu na nespolehlivé síti. Proto jsme vytvořili test, ve kterém máme klienta, který na server posílá datagram a měří, za jak dlouho dostane odpověď pro konkrétní snímek. Tato metrika se někdy nazývá RTT, neboli round-trip-time. Jak vidíme na obrázku, náš protokol je v případě ztráty paketů stabilnější.
Pro nasimulování nespolehlivosti sítě jsme využili Linuxový nástroj \uv{tc}. Ten obsahuje network emulator, který umožňuje například nastavit, kolik procent paketů má zahodit, jaká má být odezva nebo jak moc se budou prohazovat pakety. Nástroj a jeho vlastnosti, které budeme využívat, si představíme.
\begin{figure}
\begin{center}
\includegraphics[width=0.8\textwidth]{graphics/latencycomparison.pdf}
\end{center}
\end{figure}
\begin{figure}
\begin{center}
\includegraphics[width=1\textwidth]{graphics/integrationcomparison.pdf}
\end{center}
\label{integrationcomparison}
\caption{Integrace}
\end{figure}
Následně jsme zkusili, jak se nespolehlivost sítě projeví pro integraci proměnné. Přidali na server hodnotu, kterou pomocí hodnot od klienta inkrementujeme. Pro derivaci jsme použili sinusoidu, abychom ji mohli zreplikovat snadno v TCP i QUICr. Na obrázku \ref{integrationcomparison} vidíme, že TCP při ztrátě paketů způsobuje skoky, které nemusí být pro hráče příjemné. Naopak náš protokol má daleko méně znatelné skoky.
Nakonec jsme integrovali protokol přímo do replikátoru. Na obrázku \ref{playerbenchmark} vidíme 301 hráčů, kde 300 je simulovaných, avšak individuálních připojení a jeden je náš hráč. Server se občas zasekl, ale zátěž zvládl.
\begin{figure}
\begin{center}
\includegraphics[width=0.5\textwidth]{graphics/playerbenchmark.png}
\end{center}
\label{playerbenchmark}
\caption{301 připojených hráčů}
\end{figure}
\subsubsection{Odesílání}
Nejprve představím, jak jsem navrhnul odesílání zpráv. Na obrázku vidíme, celou architekturu. Vidíme dvě velké komponenty, a to Network Sender a Player Session Registry. První komponenta je celá logika, která práci posílání zpráv organizuje. Vytahuje z fronty zprávy, které pomocí Message Serializeru převede na rámce. Reliability System je jednotka, která určuje, které rámce budou v dalším datagramu. Důvod je ten, aby se kontrolovali rámce, pro které nemáme ACK a prioritizovalo se jejich odeslání. Výstupem Reliability System je tedy přímo sestavený datagram. To kdo a jak sestavuje datagram z rámců je detail.
\begin{figure}
\begin{center}
\includegraphics[width=1\textwidth]{graphics/OutboundArchitecture}
\end{center}
\end{figure}
\subsubsection{Čtení}
Představím architekturu přijímání zpráv. Opět vidíme registr spojení. Když nám ze sítě přijde nový datagram, přepošleme ho do reliability systému. Ten z něj vytvoří rámce a pošle do Frame Routeru. Zodpovědnost Frame Routeru je třídit rámce podle toho, jestli se má serializovat na zprávu, nebo jestli je to tzv. ovládací rámec. Příklad ovládacích rámců je třeba HELLO, HELLO\_ACK, nebo CRYPTO. Handshake handler si poté drží stav handshaků a v případě, že některý dokončil, registruje v Player Session Registry novou session.
% #image("InboundArchitecture.png")
\subsubsection{Šifrování}
Jednotlivé datagramy a zprávy je třeba šifrovat a pro to je třeba, aby si dvě strany vyměnili tajný klíč. Představím metodu, kterou pro výměnu budu využívat.
\subsubsection{SPR-6}
Metodu, kterou jsem viděl například u síťového protokolu pro hru World Of Warcraft je SRP-6. Jedná se o protokol, který nevyžaduje, aby server znal heslo klienta, ale měl u sebe pouze validátor, a klient mu díky němu dokáže, že heslo zná. Pro tento přístup je třeba udělat handshake.
\subsubsection{Lockstep}
Aby tato technika fungovala správně, musí být intergrace ve fyzickém enginu stejné na klientovi a serveru.
\subsubsection{Congestion control}
\subsubsection{Bandwidth control}
Entity, které jsou od hráče daleko, není třeba aktualizovat tak často jako ty, které jsou k němu blízko. Proto jsem do zájmu ke každé entitě přidal důležitost. Replikátor se při odesílání snaží nejprve nacpat do packetu entity s vyšší prioritou. Aby nedošlo k vyhladovění, tak odeslané entitě trochu klesne důležitost. Tuto informaci už si řeší replikátor.
\subsection{Sharding}
Proces, při kterém hráče rozdělíme do skupin, které se navzájem nevidí, i když jsou na stejném serveru blízko sebe, se říká sharding. Podobný princip funguje i v databázích. Tato technika složitá udělat správně. Vývojář musí dobře definovat logiku toho, že dva hráči mohou skončit na jiném shardu.
\subsection{Komprese}
Poslední optimalizací, kterou jsem pro přenos informací použili, byla komprese. Nejprve jsme nahradili byte buffer, který pracuje pouze s byty, bit bufferem, který nám umožnil pracovat s jednotlivými bity.
\subsubsection{Bit Buffer}
Snadným zmenšením packetů je zaměřit se na bity namísto bytů. Původně jsem nově příchozí datagramy obalil pomocí ByteBuffer, který umožnil dekódovat postupně příchozí byty. Často ale potřebujeme mít možnost zakódovat informaci pomocí jednotlivých bitů.
\subsubsection{Komprese QUICr}
Protokol QUICr kóduje pakety a rámce. Toto kódování jsme se rozhodli zkompresovat.
\subsubsection{Komprese protokolu replikátoru}
Komprese, která lze provést na úrovni QUICr protokolu, je značně omezená. Komprese lze ale provést i na samotných aplikačních zprávách. Pro
% \noindent\textcolor{red}{\LARGE Upozornění: Následující text
% dokumentace stylu, vyjma přílohy~\ref{sec:ObsahData}, je rozpracovaná
% a (značně) neúplná verze!!!}
\section{Styly pro psaní bakalářských a diplomových prací}
Toto jsou styly pro psaní bakalářských a diplomových prací přes typografický systém \LaTeX{}, tedy \textbf{kistyles}.
\subsection{Požadavky a podprovaná prostředí}
Sada balíku \textbf{kistyles} podporuje následující distribuce systému \LaTeX{}:
\begin{itemize}
\item \TeX{} Live.
\end{itemize}
Jsou podporovány všechny výstupní ovladače, tedy jak \textbf{dvi}, tak \textbf{pdf} i \textbf{ps}. Funkčnost zmiňovaných distribucí byla ověřena na několika operačních systémech, mezi které patří:
\begin{enumerate}
\item Windows $8.1$,
\item Archlinux,
\item Debian GNU/Linux.
\end{enumerate}
Důrazně se doporučuje používat aktuální verzi dané distribuce systému \LaTeX{}.
%%% Po přeložení programem CSLaTeX (třikrát) je potřeba použít
%%% program DVIPS a takto získaný PostScriptový soubor vytisknout
%%% na PostScriptové tiskárně nebo pomocí programu GhostScript.
%%%
%%% Rovněž je možné použít program DVIPDFM a vytvořit z dokumentu
%%% soubor ve formátu PDF včetně hypertextových odkazů.
\subsection{Přepínače}
Styl kidiplom je z hlediska uživatele zastoupen ekvivalentně nazvanou třídou, kterou je třeba volat na záčátku dokumentu:
\begin{kicode}{TeX}{}{Volání třídy \textbf{kidiplom}}
\documentclass[
master,
program=ainfvs,
printversion,
biblatex,
language=english,
font=sans,
figures=false,
tables=false,
theorems,
sourcecodes,
joinlists,
glossaries,
index,
encoding=utf8,
bibencoding=utf8
]{kidiplom}
\end{kicode}
Následuje přehled přepínačů, je vždy uvedeno jméno přepínač, včetně výchozí hodnoty. Přepínače uvádí tabulka \ref{tab:prepinace}.
%\begin{table}
\begin{center}
\begin{longtable}{>{\bfseries}l >{\ttfamily}c L{8cm}}
\caption{Seznam přepínačů}\label{tab:prepinace}\\
{\normalfont Přepínač} & {\normalfont Výchozí hodnota} & {\normalfont Popis} \\
\hline
master & false & Povolí nebo zakáže režim diplomové práce. Výchozí režim je tedy bakalářská práce. \\
printversion & false & Je-li zapnuto, pak budou odkazy vysázeny optimalizovaně pro knižní sazbu. Tuto volbu je nutno použít pro tisk práce. \\
biblatex & false & Zapne sazbu bibliografie přes balík \BibLaTeX{}. \\
language & czech & Jazyk textu práce. Možné hodnoty jsou \textbf{czech},
\textbf{english} a \textbf{slovak}. \\
font & serif & Zapne či vypne podporu pěkného bezpatkového fontu. Možné hodnoty jsou:\newline
\begin{description}\setlength{\itemsep}{-1ex}
\item[serif] Patkové písmo (Computer Modern).
\item[sans] Bezpatkové písmo (Iwona).
\end{description} \\[-3ex]
figures & true & Je-li zapnuto, pak v seznamech položek bude zahrnut seznam obrázků. \\
tables & true & Je-li zapnuto, pak v seznamech položek bude zahrnut seznam tabulek. \\
theorems & false & Je-li zapnuto, pak v seznamech bude zahrnut seznam teorémů. \\
sourcecodes & false & Je-li zapnuto, pak v seznamech bude zahrnut seznam zdrojových kódů. \\
%%% Argument `joinlists' způsobí zřetězení seznamů obrázků, tabulek,
%%% vět a zdrojových kódů. Není-li použít, všechny seznamy jsou
%%% uvedeny na samostatných stránkách.
joinlists & true & Je-li zapnuto, pak seznamy obrázků, tabulek, vět a
zdrojových kódů sázené za obsahem nebudou rozděleny na samostatné stránky. \\
glossaries & false & Je-li zapnuto, pak na konci dokumentu bude vysázen seznam zkratek. \\
index & false & Zapíná podporu sazby rejstříku. \\
%% 'encoding=kódování' pro kódování tohoto a vložených zdrojových
%% textů v kódování jiném než výchozím utf8
encoding & utf8 & Kódování souboru dokumentu, doporučuje se ponechat výchozí hodnotu. \\
bibencoding & utf8 & Kódování souboru bibliografie. Tato volba má
smysl pouze, pokud je použita bibliografie skrze balíček \BibLaTeX{}. \\
program & \vtop{\hbox{\strut infpvs}\hbox{\strut ainfvs}} & Specifikuje studijní program/obor (specializaci):\newline
\begin{description}
\item[infoi] Informatika (Obecná informatika)\,--\,bakalářský i navazující magisterský,
\item[infpvs] Informatika (Programování a vývoj software)\,--\,bakalářský,
\item[itp] Informační technologie\,--\,bakalářský, prezenční forma,
\item[itk] Informační technologie\,--\,bakalářský, kombinovaná forma,
\item[infui] Informatika (Umělá inteligence)\,--\,navazující magisterský,
\item[ainfvs] Aplikovaná informatika (Vývoj software)\,--\,navazující magisterský,
\item[ainfpst] Aplikovaná informatika (Počítačové systémy a technologie)\,--\,navazující magisterský,
\item[infv] Informatika pro vzdělávání\,--\,bakalářský,
\item[uinf] Učitelství informatiky pro střední školy\,--\,navazující magisterský,
\item[binf] Bioinformatika\,--\,bakalářský i navazující magisterský,
\item[inf] Informatika (bez specializací)\,--\,bakalářský i navazující magisterský,
\item[ainfp] Aplikovaná informatika (bez specializací)\,--\,bakalářský, prezenční forma,
\item[ainfk] Aplikovaná informatika (bez specializací)\,--\,bakalářský, kombinovaná forma,
\item[ainf] Aplikovaná informatika (bez specializací)\,--\,navazující magisterský.
\end{description}
\end{longtable}
\end{center}
%\end{table}
\subsection{Geometrie stránky}
Tento styl používá list velikosti $A4$. Pro sazbu prací je třeba použít jednostrannou sazbu. Levý okraj je rozšířen s ohledem na vazbu výsledné knižní podoby práce.
\section{Sazba částí dokumentu}
\subsection{Sazba úvodní strany či obsahu}
Vysázení všech podstatných částí úvodu práce obstará makro \kiinlinecode{TeX}{!}{\\maketitle}. Pro správné vysázení všech částí a meta-informací je potřeba použí makra \kiinlinecode{TeX}{!}{\\title}, \mbox{\kiinlinecode{TeX}{!}{\\author}} a další. Jejich přehled lze najít ve zdrojovém souboru tohoto dokumentu. V případě použítí \textbf{pdf} výstupu se generuje i dodatečná hlavička souboru s meta-informacemi jako je autor dokumentu, název práce či dalšími.
\subsection{Závěry}
Závěr práce by se měl poskytnout jak v původním jazyce práce, tak v jazyce anglickém. Pro sazbu závěru jsou k dispozici příslušná makra. Berte na vědomí, že v anglickém závěru se aktivuje plně anglická sazba se všemi konvencemi. Tedy je třeba používat anglické uvozovky a další správné typografické prvky.
\begin{kicode}{TeX}{}{Sazba závěrů}
% Tiskne český závěr práce.
\begin{kiconclusions}
Závěr práce v \uv{českém} jazyce.
\end{kiconclusions}
% Tiskne anglický závěr práce.
\begin{kiconclusions}[english]
Thesis conclusions written in \uv{English}.
\end{kiconclusions}
\end{kicode}
\subsection{Matematika}
Pro sazbu matematiky je k dispozici sada standardních maker.
$$\langle f \rangle, \lfloor g \rfloor,
\lceil h \rceil, \ulcorner i \urcorner$$
$$\left\{\frac{x^2}{y^3}\right\}$$
$$
A_{m,n} =
\begin{pmatrix}
a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\
a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\
\vdots & \vdots & \ddots & \vdots \\
a_{m,1} & a_{m,2} & \cdots & a_{m,n}
\end{pmatrix}
$$
$$
M = \begin{bmatrix}
\frac{5}{6} & \frac{1}{6} & 0 \\[0.3em]
\frac{5}{6} & 0 & \frac{1}{6} \\[0.3em]
0 & \frac{5}{6} & \frac{1}{6}
\end{bmatrix}
$$
\subsection{Sazba literatury}
Pro sazbu literatury má uživatel dvě možnosti. Může použít služeb balíků \BibLaTeX{}, který je pro \textbf{kistyles} zapnutý, či lze použít manuální sazbu bibliografie.
\subsubsection{Sazba bibliografie přes \BibLaTeX{}}
Při použití tohoto balíku se data o použité literatuře ukládají do dedikovaného textového souboru, ukázku najdete i v tomto stylu pod jménem \kiinlinecode{text}{!}{bibliografie.bib}.
Formát daného souboru je nad rámec této dokumentace a je na každém uživateli, aby si jej nastudoval. Bibliografie se tiskne makrem \kiinlinecode{TeX}{!}{\\printbibliography}. Taktéž v preambuli dokumentu je třeba definovat, který soubor data bibliografie obsahuje, tedy například \kiinlinecode{TeX}{!}{\\bibliography\{bibliografie.bib\}}.
Dokument, který využívá \BibLaTeX{} je následně nutné přeložit jak pomocí překladače zvoleného ovladače, tak pomocí aplikace \kiinlinecode{text}{!}{biber}. Více informací poskytne soubor \kiinlinecode{text}{!}{Makefile} z distribuce tohoto stylu.
Výhodou tohoto přístupu je, že bibliografie se vysází automaticky a (obvykle) není třeba manuální úprava formátování.
\subsubsection{Manuální sazba bibliografie}
Manuální sazba obnáší vysázení prostředí \kiinlinecode{text}{!}{thebibliography} ručně. To je nad rámec tohoto dokumentu. Ukázku tohoto přístupu lze samozřejmě nalézt ve zdrojovém souboru tohoto dokumentu nebo také \href{http://www.math.uiuc.edu/~hildebr/tex/bibliographies.html}{zde}.
Pro aktivaci manuální sazby bibliografie je třeba volat třídu \kiinlinecode{text}{!}{kidiplom} s parametrem \kiinlinecode{text}{!}{biblatex=false}. Mějte, prosím, na paměti, že v tomto módu jsou makra \kiinlinecode{text}{!}{\\bibliography} a \kiinlinecode{text}{!}{\\printbibliography} nedostupná.
\subsection{Drobná makra}
Základní styl definuje hned několik maker pro usnadnění práce. Například makro \kiinlinecode{TeX}{!}{\\buno} vysází řetezec \uv{bez újmy na obecnosti}. Je k dispozici i verze s prvním velkým písmenem, \kiinlinecode{TeX}{!}{\\Buno}.
Je rovněž možno přidávat položky do seznamu zkratek. K tomu slouží makro \mbox{\kiinlinecode{TeX}{!}{\\newacronym}}, které lze použít například jednoduše jako \kiinlinecode{TeX}{!}{\\newacronym\{UPOL\}\{UPOL\}\{\\kitextunivcz\}}. Na danou zkratku se pak lze odkazovat jednoduše, \mbox{\kiinlinecode{TeX}{!}{\\gls\{UPOL\}}}.
Sazba uvozovek respektuje nastavení částí dokumentu, a proto se doporučuje používat makro \kiinlinecode{TeX}{!}{\\uv}. V anglické závěru práce toto platí taky, viz tato PDF ukázka.
Styl podporuje sazbu odstavců v tabulkách, více obsahuje tabulka \ref{tab:odstavce}.
\begin{table}
\begin{center}
\caption{Odstavce v tabulkách}\label{tab:odstavce}
\begin{tabular}{L{4cm}|R{4cm}|L{4cm}}
\lipsum[23] & \lipsum[22] & \lipsum[21]
\end{tabular}
\end{center}
\end{table}
K dispozici jsou také makra pro sazbu \csharp{} (\kiinlinecode{TeX}{!}{\\csharp}) či \cpp{} (\kiinlinecode{TeX}{!}{\\cpp}).
%% v případě tvorby rejstříku přeložit vygenerovaný soubor .idx
%% programem Makeindex a v případě tvorby seznamu zkratek spustit
%% program Makeglossaries s parametrem jméno souboru zdrojového textu
%% bez přípony a následně opět (dvakrát) přeložit zdrojový text
%% programem pdfLaTeX.
\subsection{Sazba rejstříku}
Sazba rejstříku sestává z několika kroků:
\begin{enumerate}
\item Je třeba přes volbu \kiinlinecode{TeX}{!}{index=true} rejstříkování povolit.
\item Použítím makra \kiinlinecode{TeX}{!}{\\index} rejstříkovat vybrané pojmy.
\item Kompilovat s použitím utility \kiinlinecode{TeX}{!}{makeindex}. Pro specifika tohoto kroku si stačí prohlédnout soubor \kiinlinecode{text}{!}{Makefile}.
\end{enumerate}
Makro \kiinlinecode{TeX}{!}{\\index} je redefinováno tak, že sází klikací odkaz na výraz v rejstříku. Je doporučeno jej použít ihned za výrazem\index{výraz}.
\textbf{Omezení redefinovaného makra \kiinlinecode{TeX}{!}{\\index}}: klikací odkaz nefunguje, pokud použijete konstrukci \kiinlinecode{TeX}{!}{\\index\{výraz|makro\}} (resp. \kiinlinecode{TeX}{!}{\\index\{výraz|(makro\}}), např. \kiinlinecode{TeX}{!}{\\index\{výraz|textit\}}.
Rejstřík lze vysázet pomocí makra \kiinlinecode{TeX}{!}{\\printindex}.
\subsection{Sazba zdrojových kódů}
Styl nabízí dva způsoby sazby zdrojových kódů:
\begin{enumerate}
\item Sazbu řádkových kódů, například \kiinlinecode{CSS}{!}{background-color: white;}. K tomu slouží makro formátu \kiinlinecode{TeX}{!}{\\kiinlinecode\{jazyk\}\{separátor\}\{kód\}}. Za separátor je vhodné volit jakýkoliv znak, který se nevyskytuje v samotném sázeném zdrojovém kódu. Za jazyk je nutno dosadit jeden z těchto: C, TeX, PHP, HTML, Lisp, SQL, TeX, Python, Java, TutorialD, text, csharp, cpp, JavaScript, CSS.
\item Sazbu zdrojových kódu do separátních prostředí. Takto vytištěný kód se objeví v seznamu zdrojových kódů. Ukázka například zdrojový kód \ref{kod:cpp}. Ukázku sazby naleznete ve zdrojovém kódu tohoto dokumentu.
\end{enumerate}
\newacronym{UPOL}{UPOL}{\kitextunivcz}
\begin{definition}[Název definice]
Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. \gls{UPOL}
\end{definition}
\begin{proof}[Název důkazu]
Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd.
\end{proof}
\begin{remark}[Pumpovací věta]
Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd.
\end{remark}
\begin{example}[Pumpovací věta]
Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd.
\end{example}
\begin{lemma}[Název definice]
Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd.
\end{lemma}
\begin{consequence}[Název důkazu]
Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd.
\end{consequence}
\begin{theorem}[Pumpovací věta]
Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd. Abcd.
\end{theorem}
\begin{kicode}{cpp}{kod:cpp}{\cpp}
int main("cs acsa") // komentar
int main("cs acsa") // komentar
int main("cs acsa") // komentar
int main("cs acsa") // komentar
int main("cs acsa") // komentar
\end{kicode}
\begin{kicode}{JavaScript}{}{JS}
new object() // komentar
\end{kicode}
\begin{kicode}{csharp}{}{\csharp}
public static int main("cs acsa") // komentar
\end{kicode}
\begin{kicode}{SQL}{}{SQL}
SELECT * FROM table_1; /* komentar */
\end{kicode}
\begin{kicode}{TutorialD}{}{TutorialD}
table_1 AND table_2;
\end{kicode}
%% Závěry práce. V jazyce práce a anglicky. Text pro jiný než
%% nastavený jazyk práce (nepovinným parametrem language makra
%% \documentclass, výchozí český) se zadává použitím makra s uvedením
%% jazyka jako nepovinného parametru.
\begin{kiconclusions}
Závěr práce v \uv{českém} jazyce.
\end{kiconclusions}
\begin{kiconclusions}[english]
Thesis conclusions in \uv{English}.
\end{kiconclusions}
%% Přílohy obsahu textu práce, za makrem \appendix.
\appendix
\section{První příloha}
Text první přílohy
\section{Druhá příloha}
Text druhé přílohy
%% Obsah elektronických dat. Poslední příloha. Upravte podle vlastní
%% práce!
\section{Obsah elektronických dat} \label{sec:ObsahData}
Na samotném konci textu práce je uveden stručný popis obsahu
elektronických dat odevzdaných v systému katedry informatiky spolu s
textem. Tato data jsou nedílnou součástí práce a tvoří (datovou)
přílohu textu práce. Povinné položky struktury dat jsou:
\begin{description}
\item[\texttt{text/}] \hfill \\
Adresář s textem práce ve formátu PDF, vytvořený s~použitím
závazného stylu KI PřF UP v~Olomouci pro závěrečné práce, včetně
všech (textových) příloh, a~všechny soubory potřebné pro
bezproblémové vytvoření PDF dokumentu textu (případně v~ZIP
archivu), tj.~zdrojový text textu a příloh, vložené obrázky, apod.
\item[\texttt{README.*}] \hfill \\
Textový soubor (s příponou např. \texttt{.txt}) s informacemi o
opakovatelném způsobu použití ostatních dat práce -- typicky plně
reprodukovatelný co nejúplnější funkční postup zprovoznění software
vytvořeného v~rámci práce, tzn. jeho případné instalace/nasazení a
spuštění, včetně uvedení všech požadavků pro bezproblémový provoz;
za zprovoznění software se nepovažuje zpřístupnění (např. po
Internetu) již někde zprovozněného software.
\item[\texttt{*}] \hfill \\
Adresáře a soubory s veškerými ostatními autorskými daty práce
(případně v~ZIP archivu) -- typicky spustitelné a další soubory
software vytvořeného v rámci práce potřebné pro bezproblémový provoz
software, případně jeho instalační program, a kompletní zdrojové
texty software a další data nutná pro plně reprodukovatelné korektní
vytvoření spustitelných souborů.
\end{description}
Dále mohou data obsahovat například:
\begin{itemize}
%\item[\texttt{data/}] \hfill \\
\item
ukázková a~testovací data použitá v~práci nebo pro potřeby posouzení
práce v rámci její obhajoby,
%\item[\texttt{literature/}] \hfill \\
\item
položky bibliografie v elektronické podobě, příp.~jiná relevantní literatura
a dokumentace vztahující se k~práci,
%\item[\texttt{install/}] \hfill \\
\item
cizí data (software) potřebná pro bezproblémové použití autorských
dat práce (software), která nejsou standardní součástí
předpokládaného (softwarového) vybavení uživatele.
\end{itemize}
U~veškerých cizích obsažených materiálů jejich
zahrnutí dovolují podmínky pro jejich veřejné šíření nebo přiložený souhlas
držitele práv k užití. Pro všechny použité (a~citované) materiály,
u~kterých toto není splněno a~nejsou tak obsaženy, je uveden
jejich zdroj, např.~webová adresa, v~bibliografii nebo textu práce
nebo souboru \texttt{README.*}.
%% -------------------------------------------------------------------
%% Sazba volitelného seznamu zkratek, za přílohami.
\printglossary
%% Sazba povinné bibliografie, za přílohami (případně i za seznamem
%% zkratek). Při použití BibLaTeXu použijte makro
%% \printbibliography. jinak prostředí thebibliography. Ne obojí!
%% Sazba i v textu necitovaných zdrojů, při použití
%% BibLaTeXu. Volitelné.
\nocite{*}
%% Vlastní sazba bibliografie při použití BibLaTeXu.
\printbibliography
%% Bibliografie, včetně sazby, při NEpoužití BibLaTeXu.
% \begin{thebibliography}{9}
%\bibitem{kniha2} \uppercase{Hawke}, Paul. NanoHttpd: Light-weight HTTP server designed for embedding in other applications. GitHub [online]. 2014-05-12. [cit. 2014-12-06]. Dostupné z: \url{https://github.com/NanoHttpd/nanohttpd}
%
%\bibitem{jeske13} \uppercase{Jeske}, David; \uppercase{Novák}, Josef. Simple HTTP Server in \csharp: Threaded synchronous HTTP Server abstract class, to respond to HTTP requests. CodeProject: For those who code [online]. 2014-05-24. [cit. 2014-12-06]. Dostupné z: \url{http://www.codeproject.com/Articles/137979/Simple-HTTP-Server-in-C}
%
%\bibitem{uzis2012} \uppercase{ÚSTAV ZDRAVOTNICKÝCH INFORMACÍ A STATISTIKY ČR}. Lékaři, zubní lékaři a farmaceuti 2012 [online]. Praha 2, Palackého náměstí 4: Ústav zdravotnických informací a statistiky ČR, 2012 [cit. 2014-12-06]. ISBN 978-80-7472-089-5. Dostupné z: \url{http://www.uzis.cz/publikace/lekari-zubni-lekari-farmaceuti-2012}
% \end{thebibliography}
%% Sazba volitelného rejstříku, za bibliografií.
\printindex
\end{document}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: t
%%% End: