1394 lines
109 KiB
TeX
1394 lines
109 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{Metody optimalizace přenosu dat v distribuovaném systému}
|
|
\title[english]{Methods of data transfer optimizations in distributed systems}
|
|
|
|
%% 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}
|
|
\usepackage{svg}
|
|
|
|
\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}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\newpage
|
|
|
|
\section{Distribuované systémy}
|
|
|
|
% Chtěl bych začít síťovými systémy jako takovými. Udělat rozdělení na centralizované a distribuované. V síťovém systému běží služby, kde každá je implementována jako kolekce procesů a zdrojů rozprostřených na více počítačů.
|
|
|
|
Počítačový systém se skládá ze služeb, každá implementována jako kolekce procesů. Pokud jsou počítače propojeny v síti a procesy systémů jsou rozprostřeny přes více komunikujících počítačů, dostaneme \uv{síťový systém}. Ty mohou mít různé topologie podle toho, jak v systému komunikují. Příkladem takového systému je World Wide Web, který poskytuje dokumenty podle adresy \uv{URL}, tedy Unique Resource Location.
|
|
|
|
Distribuovaný systém se snaží skrýt svou distribuovanost pomocí \uv{middleware}. Ten slouží jako vrstva mezi síťí a problémy s ní spjaté a samotnou aplikací. Představíme dva přístupy: Message Oriented Middleware (MOM) a Remote Procedure Call (RPC).
|
|
|
|
RPC je přirozenější přístup, který umožňuje zavolat proceduru lokálně, která se zabalí do zprávy a odešle na jiný počítač. Ten zprávu přečte a podle instrukcí začne vykonávat. Výsledek pak odešle zpět jako zprávu. Odesílatel tedy implementačně nemusí vědět, že se vykonala na jiném počítači. Problémem tohoto přístupu je jednak to, že strana která odesílá zprávu musí znát adresu druhé strany. Druhá nevýhoda je, že obě strany musí běžet současně.
|
|
|
|
Druhým přístupem je posílání zpráv. V tomto přístupu aplikace posílají zprávy na čistě logický cíl. Např. identifikovaný typem zprávy. Aplikace pak může projevit zájem o různé typy zpráv a middleware se postará o to, že tyto zprávy aplikace dostane. Přístupu se říká \uv{publish-subscribe}.
|
|
|
|
|
|
|
|
\subsection{Počítačové sítě}
|
|
|
|
% Různé typy sítí jako LAN a WAN
|
|
|
|
Nedílnou součástí distribuovaného systému je síť, přes kterou je schopný komunikovat. Počítačová síť je skupina propojených počítačů, které si mezi sebou přenášejí data. Například lokální sítě (LAN) propojují až tisíce počítačů, které jsou geograficky blízko, například v rámci jedné budovy. Rozsáhlé sítě (WAN) propojují miliony různých zařízení po celém světě. Příkladem je síť Internet. Můžeme díky ní snadno posílat zprávy z jednoho kontinentu na druhý. Kvůli této rozmanitosti musí síť podporovat různá přenosová média, jako měděný kabel pro rychlé ale krátké vzdálenosti, nebo bezdrátové připojení pomocí satelitu pro pomalejší, ale na delší vzdálenost apod.
|
|
|
|
% Lze si představit jako graf - vrcholy a hrany
|
|
|
|
Počítačovou síť si lze představit jako graf, ve kterém počítače představují vrcholy a fyzická média mezi nimi jsou hrany. Vrcholy dále dělíme na \uv{koncové body} a \uv{propojovací prvky}. Koncové body jsou počítače, mobilní telefony a jiná zařízení, na kterých běží procesy. Dva procesy na dvou různých počítačích si mezi sebou mohou posílat zprávy. Propojovací prvky jsou přepínače, rozbočovače a opakovače. Ty naopak slouží pouze k směřování zpráv po síti. Využití je propojení skupiny počítačů do sítě přes jedno spojení jako vidíme na obrázku \ref{fig:network01}. Propojovací body jsou E a D. Není potřeba připojovat každý počítač s každým.
|
|
|
|
\begin{figure}
|
|
\begin{center}
|
|
\includegraphics[width=0.5\textwidth]{graphics/network01}
|
|
\end{center}
|
|
\caption{Příklad využití propojovacích bodů}
|
|
\label{fig:network01}
|
|
\end{figure}
|
|
|
|
|
|
|
|
Dva počítače, přímo propojené fyzickým médiem, komunikují posíláním n-tic bytů zvané \uv{rámce}. Variantou jsou \uv{přepínané} sítě, které nemusejí mít mezi všemi dvojicemi počítačů přímé spojení, ale využívají \uv{přepínače}. Proces, kdy doručujeme zprávu na cílový počítač přes vícero přepínačů se nazývá \uv{směrování}. K tomu je třeba, aby každé zařízení mělo přiřazenou unikátní adresu a posílali se formátované rámce zvané \uv{pakety}. Ty se skládají z hlavičky, ve které najdeme informace ke směřovaní, a tělu obsahujícím samotnou přenášenou zprávu.
|
|
|
|
Do přepínače můžeme připojovat počítače. Na obrázku TODO vidíme, jak tato topologie vypadá, říká se jí hvězda. Tato skupina propojených zařízení přes přepínače tvoří síť. Dva počítače v ní mezi sebou nemusejí mít přímé spojení, protože si pakety posílají přes přepínač, který ho nasměruje na správný počítač. Tato skupina počítačů propojených přepínačem tvoří síť. Můžeme propojovat i přepínače a vytvořit tak síť sítí. Bežným příkladem je Internet, největší síť sítí na světě.
|
|
% Jednotka pro přenos v síti je \uv{rámec}. Ta ale slouží jen mezi dvě počítači spojenými přímo. Rámec jako takový neobsahuje nic konkrétního, a to ani ke směřování. Představme si to jako obálku, na které není nic, ani adresa. Jediná naše možnost je hodit jej do cílové schránky přímo. Nelze využít služby pošty, která by anonymní obálku doručila.
|
|
|
|
% Pro využití služeb pošty musíme splňovat jejich protokol, tedy soubor pravidel pro komunikaci. Jedno z nich je právě to, že na obálku musíme napsat adresu. Zároveň musí být v konkrétním formátu. Představíme protějšek pro komunikaci v síti, tzv. Internetový protokol. Definuje, že prvních $n$ bytů je tzv. hlavička a zbytek je . Prvky pro směřování se podívají pouze na hlavičku a zbytek rámce neřeší. Podle adresy odešle nový rámec se stejným obsahem tam, kam patří. Takovému rámci se říká IP \uv{paket}.
|
|
|
|
% V analogii s poštou je každý dům protějškem počítače v síti a adresa je jeho síťová adresa. Mezi domy můžeme posílat dopisy. Představme si poštu, jako komunikační protokol. Pokud ji chceme využít, musíme na obálku správně napsat adresu. Nemůžeme ji napsat dovnitř nebo kamkoliv jinam. Stejně tak musíme do IP paketu vložit hlavičku, kterou si propojovací prvek přečte a nasměruje.
|
|
|
|
% Pokud se dům nachází v jiném státě, dopis necestuje přímo, ale přes různé prostředníky, například depa, kde se třídí podle cílové destinace. Takto funguje přepínač. Pokud má na cílový počítač přímé spojení, odešle rámec rovnou tam. Jinak zvolí další propojovací body, které mohou být cílovému počítači o něco blíž. Propojovací prvek může být satelit nebo router.
|
|
|
|
|
|
% 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.
|
|
|
|
|
|
% 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 této části představíme jak probíhá komunikace v síti. Aby si dva počítače navzájem rozuměly, používají tzv. \uv{komunikační protokol}: soubor pravidel pro výměnu informací mezi počítači. Definují syntaxi, sémantiku a synchronizaci zpráv. Mají různé charakteristiky, např. jestli potřebují navázat spojení, které obě strany odsouhlasí, aby mohla začít komunikace mezi procesy. Další vlastností je způsob řešení spolehlivosti doručení rámců.
|
|
|
|
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. Pokud protokol nevyžaduje navázání spojení, může přes něj na proces přijít zpráva z libovolného počítače bez předešlého souhlasu. Protokol často definuje, co se má v takových situacích stát.
|
|
|
|
% komunikaci rozdělujeme na Message Oriented Middleware a Remote Procedure Call.
|
|
|
|
\subsection{Rodina protokolů TCP/IP}
|
|
|
|
Představíme rodinu protokolů pro komunikaci v síti Internet, která se nazývá TCP/IP. Jedná se o celou soustavu protokolů, které fungují ruku v ruce jako vrstvy. Název se skládá ze dvou důležitých protokolů: IP (Internet Protocol) a TCP (Transmission Control Protocol). IP slouží pro směrování paketů a TCP 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).
|
|
|
|
Rodina je tvořena vrstvami. Vrstva čte a zapisuje do vrstvy pod ní. Konkrétně architektura TCP/IP je rozdělena do čtyř vrstev: aplikační, transportní, síťová a síťové rozhraní. Každá vrstva obsahuje množinu protokolů a pro každou situaci lze sestavit ideální čtveřice. Vrstvy podrobněji představíme.
|
|
|
|
\begin{description}
|
|
\item[{Aplikační}] \hfill \\
|
|
Nejvyšší je \uv{aplikační} vrstva, ve které jsou aplikace. Příkladem protokolů jsou HTTP nebo gRPC, které spadají do kategorie tzv. \uv{middleware}. To znamená, že protokol není nutně specifický pro konkrétní aplikaci. Narozdíl od protokolu pro hru World of Warcraft nebo STMP.
|
|
|
|
\item[{Transportní}] \hfill \\
|
|
Druhá vrstva je \uv{transportní}. Tato vrstva je rozbalena až na koncových bodech, jak vidíme na obrázku \ref{fig:tcpip}, nikoliv po cestě. Není totiž důležitá pro směrování packetů. Stará se o to, jaké packety přišli, v jakém pořadí a jestli nejsou poškozené. Příkladem protokolů jsou TCP, UDP nebo QUIC.
|
|
|
|
\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 vrcholech na cestě mezi dvěma počítači, které chtějí v internetu komunikovat.
|
|
|
|
\item[{Síťové rozhraní}] \hfill \\
|
|
Nejnižší vrstva, která se stará o přenos přes fyzické médium. Pro různé média bude vyžadovat různé protokoly. Příkladem jsou Ethernet, Token ring, apod.
|
|
\end{description}
|
|
|
|
Na obrázku \ref{fig:tcpip} 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í vrstva. Počítače B a C jsou propojovací body. Všimněme si, že se nepoužívají 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}
|
|
\caption{Komunikace vrstev TCP/IP}
|
|
\label{fig:tcpip}
|
|
\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{Protokoly v aplikační vrstvě}
|
|
|
|
Nejvyšší v rodině protokolů TCP/IP je aplikační vrstva s 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. Další příkladem protokolu je API pro službu STAG.
|
|
|
|
\subsubsection{Middleware}
|
|
|
|
Speciálním případem protokolů v aplikační vrstvě je \uv{middleware}. Je to takový protokol, který není specifický pro žádnou konkrétní aplikaci. Příkladem jsou protokoly gRPC, HTTP, OAuth apod. Existuje celá řada různých aplikací, které je mohou využívat právě díky své obecnosti. 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í.
|
|
|
|
\subsubsection{HTTP}
|
|
|
|
% - Velmi častý
|
|
% - Původně pro přenos HTML dokumentů, ale i obrázků, videí apod.
|
|
% - Klient-server protokol - pouze jedna strana posílá požadavky
|
|
|
|
% Velmi používaným protokolem je HTTP, neboli Hyper Text Transport Protocol. Používá se převážně pro přenos HTML dokumentů. 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 si žádná data neukládá mezi dvěma HTTP dotazy. Každý 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}
|
|
|
|
|
|
|
|
|
|
|
|
\subsubsection{Internet protocol}
|
|
|
|
V síťové vrstvě, která se stará o směrování zpráv, se v síti Internet používá tzv. Internet Protocol.
|
|
|
|
|
|
\subsection{Protokoly v transportní vrstvě}
|
|
|
|
Nejčastější protokoly transportní vrstvy jsou TCP a UDP. V roce 2012 představila společnost Google ještě protokol QUIC, který využívá UDP. Tyto protokoly si představíme, protože jejich vlastností budeme využívat při optimalizacích.
|
|
|
|
\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ány. Zároveň za Vás řeší zahlcení sítě. Internet protokol říká, že propojovací prvky mohou v případě, že jsou zahlcené, pakety zahodit. Příkladem může být omezení rychlosti stahování poskytovatelem internetového připojení. Pokud platíte za 300Mb/s a začnou Vám chodit data rychlostí 500Mb/s, přebytečné pakety se prostě zahodí.
|
|
|
|
Spojení v TCP musí odsouhlasit obě strany. Jedna strana se připojuje a druhá pomocí volání \cpp{accept} spojení přijme.
|
|
|
|
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{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ě.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\newpage
|
|
|
|
|
|
\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}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\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.
|
|
|
|
Představíme komponenty, které jsme do programu implementovali a proč. Pro komunikaci jsme vytvořili nový protokol QUICr, který je podobný QUIC, ale upouští od spolehlivost a nabízí tak lepší odezvu.
|
|
|
|
\subsection{Hry}
|
|
|
|
Program hry většinou obsahuje smyčku, kde jedné iteraci říkáme \uv{snímek}. Moderní hry iterují od 60 do 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 sekundu, která pro naše účely stačí. Zajímají nás různé metody pro optimalizaci her více hráčů. 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. Tyto akce budou tvořit derivaci stavu hru. Tu následně aplikuje na aktuální stav hry, čímž ho integrujeme a dostaneme se do dalšího stavu. 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 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. Druhý důvod je čistě praktický. Pokud chceme 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. Daleko lépe se různé entity modelují skládáním jejich vlastností.
|
|
|
|
https://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
|
|
|
|
Logika vlastností a schopností, které různé entity mohou mít, se rozdělí do tzv. \uv{komponent}. Příkladem je komponenta pro obchodníka, stráž, kouzelníka, truhlu, dialog apod. Ty skládáme do tzv. \uv{entit}, která je pouhým identifikátor se svým seznamem komponent. 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, který může ve hře být.
|
|
|
|
Seznam všech entit a jejich komponent je stav hry. Pro integraci stavu z derivace máme tzv. \uv{systémy}. Ty iterují přes seznam všech komponent konkrétního typu a provádí jejich transformaci. Tomuto přístupu se říká Entity, Component, System, zkráceně ECS.
|
|
|
|
|
|
|
|
\subsubsection{Entt}
|
|
|
|
Pro ECS využíváme knihovnu Entt. 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}{cpp}{}{Příklad použití knihovny Entt}
|
|
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`. Obsahuje registr všech entit a jejich komponent. Objekt 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{cpp}{!}{\\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{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{cpp}{!}{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}{cpp}{}{cpp}
|
|
message EntitySpawnMessage {
|
|
uint32 entity_id = 1;
|
|
bool is_player = 2;
|
|
string name = 3;
|
|
}
|
|
\end{kicode}
|
|
|
|
Vidíme, že místo \kiinlinecode{cpp}{!}{struct} píšeme \kiinlinecode{cpp}{!}{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}{cpp}{}{cpp}
|
|
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{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{cpp}{!}{\\popDisconnectedPlayers} a \kiinlinecode{cpp}{!}{\\popConnectedPlayers}. Třída si udržuje seznam nových připojení od posledního zavolání \kiinlinecode{cpp}{!}{\\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. O to se stará replikátor. Pro každého klienta si drží seznam entit, které musí danému klientovi synchronizovat, aby svůj úkol splnil. V případě klient-server architektury odesílá server svůj stav klientům. Naopak klienti posílají pouze své vstupy. 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{Správa zájmů}
|
|
|
|
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.
|
|
|
|
|
|
% 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{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{cpp}{!}{\\NetworkMetricReporter}, která obsahuje metody jako \kiinlinecode{cpp}{!}{\\reportInbound} a \kiinlinecode{cpp}{!}{\\reportOutbound}. První metoda hlásí příchozí byty a druhá odchozí.
|
|
|
|
|
|
\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í. Nakonec jsme TPC a QUICr porovnali, abychom se o optimalizaci přesvědčili.
|
|
|
|
Protokol využívá UDP a přenáší tak datagramy. Ty se skládají z paketu a ten z jednoho nebo více rámců. Rámce můžeme rozdělit na dva typy: datové a ovládací. Ovládací slouží pro handshake nebo ukončení spojení a budou vždy spolehlivě doručené. Datové obsahují aplikační data a mohou definovat svou spolehlivost. Ty, které budou obsahovat pozice hráčů, tak budeme moct odesílat nespolehlivě, zatímco změny stavu, jako výměna animace nebo modelu, budu posílat spolehlivě. Pro spojení budu používat dva objekty: \uv{koncový bod} a \uv{spojení}.
|
|
|
|
Objekt pro koncový bod se stará o soket: čtení a zápis na něj, a udržuje si objekty spojení, které ho využívají. Když přijde na soket datagram, správně ho přiřadí spojení. Jeho chování je definované jako tik. V něm posbírá zprávy, které se spojení snaží poslat a pošle je, následně přečte data ze soketu a roztřídí je do navázaných spojení.
|
|
|
|
\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}
|
|
|
|
Objekt spojení je navenek stavový stroj a fronta zpráv, podobně jako TCP spojení. Stavy se mění 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 přeloží na pakety a rámce. Ty postupně prochází. 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. Zároveň mám z aplikace kontrolu nad datagramy. Replikátor může sám od sebe vytvořit namísto jedné velké zprávy více malých a odeslat je. Klient nemusí čekat, až by mu přišla celý zpráva a protokol by mu ji poskládal.
|
|
|
|
|
|
|
|
|
|
\subsubsection{Frame}
|
|
|
|
Inspiroval jsem se u protokolu QUIC, který datagram rozděluje na tzv. frame neboli \uv{rámce}. Tyto rámce jsou vhodná reprezentace pro ovládání protokolu, protože je můžeme kódovat proudem bytů v TCP i datagramem v UDP. Využili jsme nespolehlivosti a rychlosti UDP a u každého rámce definujeme jeho spolehlivost, tedy jestli musí rámec vždy dojít. Objekt pro připojení obsahuje jednotku pro spolehlivost, kterou si představíme v kapitole o spolehlivosti.
|
|
|
|
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 na svých ID, tajném klíči a verzi. Proto jsem definoval proces zvaný \uv{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\_to\_send(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 spolehlivě doručené.
|
|
|
|
\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{cpp}{!}{\\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. 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.
|
|
|
|
\subsubsection{Měření}
|
|
|
|
Protokol měl za cíl snížit odezvu na nespolehlivé síti. Vytvořili jsme test, který simuluje program hry. V testu jsou dvě vlákna, jedno pro klienta a druhé pro server. Oba mají svou vlastní smyčku, ve kterém iterují snímky. Klient odesílá hodnotu derivace typu double. Server si udržuje stav hodnoty, nazvěme ji \uv{pozice}. Při obdržení derivace ji přičte k derivaci. Každý snímek server odesílá na klienta stav pozice, kterou si klient aplikuje.
|
|
|
|
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. Nastavili jsme odezvu na 20-100ms a 2\% paketů se zahodí.
|
|
|
|
V prvním testu jsme pouze měřili odezvu mezi odesláním vstupu od klienta pro snímek $n$ a získáním jeho integrovaného stavu ze serveru. Na obrázku \ref{fig:latencycomparison} vidíme, že TCP obsahuje skoky a QUICr je stabilnější.
|
|
|
|
\begin{figure}
|
|
\begin{center}
|
|
\includegraphics[width=0.8\textwidth]{graphics/latencycomparison.pdf}
|
|
\end{center}
|
|
\caption{Porovnání odezvy TCP a QUICr}
|
|
\label{fig:latencycomparison}
|
|
\end{figure}
|
|
|
|
\begin{figure}
|
|
\begin{center}
|
|
\includegraphics[width=0.8\textwidth]{graphics/integrationcomparison.pdf}
|
|
\end{center}
|
|
\label{fig:integrationcomparison}
|
|
\caption{Porovnání integrace TCP a QUICr}
|
|
\end{figure}
|
|
|
|
Následně jsme zkusili, jak se nespolehlivost sítě a skoky odezvy z prvního měření projeví v integraci proměnné. Pro derivaci jsme použili sinusoidu, abychom ji mohli zreplikovat snadno v TCP i QUICr. Na obrázku \ref{fig: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á integraci plynulejší.
|
|
|
|
Nakonec jsme integrovali protokol přímo do replikátoru. Vytvořili jsme simulovaného klienta, který na server posílá náhodné vstupy a způsobuje na serveru svůj pohyb. Těchto simulovaných klientů můžeme spouštět libovolný počet. Každý naváže se serverem spojení a komunikuje s ním klasicky přes replikátor a ovladač. Můžeme tak snadno měřit, jak se zatížení projeví v síti, a zároveň můžeme využít nástroje jako tc pro různé podmínky. Na obrázku \ref{fig:playerbenchmark} vidíme 301 hráčů, kde 300 je simulovaných individuálních připojení a jeden je náš hráč. Server počítal každý snímek dlouho, asi 250ms, ale zátěž zvládl. Dalším krokem bylo zvýšit počet snímku za sekundu pro stovky hráčů.
|
|
|
|
\begin{figure}
|
|
\centering
|
|
\includegraphics[width=0.5\textwidth]{graphics/playerbenchmark.png}
|
|
\label{fig:playerbenchmark}
|
|
\caption{301 připojených hráčů}
|
|
\end{figure}
|
|
|
|
|
|
\subsection{Optimalizace replikátoru}
|
|
|
|
Test s 301 hráči odhalil nedostatečný výkon serveru. Pomocí Tracy jsme začali program analyzovat. Na obrázku \ref{fig:tracyreplicator} vidíme, že pouze aktualizace replikátoru trvá každý snímek kolem 100ms. Pro představu, pokud bychom chtěli, aby server integroval 20 krát za sekundu, museli bychom všechno, nejen aktualizaci replikátoru, stihnout do 50 milisekund. Proto je aktuální stav nepřijatelně dlouhá doba. Úzké hrdlo najednou nebyla síť, ale příprava smysluplných dat, kterými síť zatížíme. Soustředili jsme další optimalizace právě na replikátor.
|
|
|
|
\begin{figure}
|
|
\centering
|
|
\includegraphics[width=1\textwidth]{graphics/300playerstrace.png}
|
|
\label{fig:tracyreplicator}
|
|
\caption{Analýza replikátoru}
|
|
\end{figure}
|
|
|
|
Z analýzy vyšlo najevo, že problém je skládání zprávy. Tedy vkládání pozic entit, o které se klient zajímá, do objektu. Je to právě tento objekt, který se serializuje přes ProtoBuf a odesílá skrze soket na klienta.
|
|
|
|
Replikátor pro každého klienta zjistil jeho zájem a pro každou entitu v něm vyhledával jeho komponenty. Vyhledávání je v případě Entt v $O(1)$. Začali jsme měřit i jiná řešení. Rozhodli jsme optimalizovat pro ECS architekturu. Vytvořili jsme buffer instancí zpráv pro každého klienta. Přes komponentu, kterou jsme chtěli replikovat, jsme vytvořili Entt pohled a iterovali. Pro každý prvek jsem komponentu zapsali do zprávy pro každého klienta, kterého zajímala. Výslednou analýzu vidíme na obrázku \ref{fig:ecs_optimization_tracy_01}. Optimalizace snížila dobu, kterou trvá aktualizace replikátoru, na polovinu.
|
|
|
|
\begin{figure}
|
|
\centering
|
|
\includegraphics[width=1\textwidth]{graphics/ecs_optimization_tracy_01.png}
|
|
\label{fig:ecs_optimization_tracy_01}
|
|
\caption{Analýza optimalizace replikátoru pro ECS}
|
|
\end{figure}
|
|
|
|
\begin{figure}
|
|
\centering
|
|
\includegraphics[width=1\textwidth]{graphics/ecs_optimization_tracy_02.png}
|
|
\label{fig:ecs_optimization_tracy_02}
|
|
\caption{Analýza optimalizace replikátoru pro ECS}
|
|
\end{figure}
|
|
|
|
Replikátor jsme začali analyzovat do větších detailů. Na obrázku \ref{fig:ecs_optimization_tracy_02} vidíme, které jeho části zabrali jakou dobu. Zjistili jsme, že problém byl samotný ProtoBuf. Formát je podobně jako JSON orientovaný na strukturu objektů. V našem případě je ale zpráva pro snapshot světa primitivní. Rozhodli jsme udělat porovnání s FlatBuffers. V testu jsme simulovali 100 snímků, tak, abychom si mohli dopředu alokovat všechnu potřebnou paměť, kterou můžeme mezi snímky využívat. Dopředu si vytvoříme vektor pozic, které v testu budeme serializovat. Stejně jako v replikátoru používáme architekturu orientovanou na ECS a procházíme všechny pozice, ty pak umisťujeme do alokovaných bloků paměti pro každého klienta a simulujeme tak skládání rámců, které můžeme odesílat. Všimli jsme si, že v případě primitivní zprávy, jako je snapshot stavu světa, by nám stačila klasická funkce ze standartní C knihovny zvaná \kiinlinecode{cpp}{!}{memcpy} a do testu jsme ji zařadili. Výsledky vidíme v tabulce \ref{table:serialization_comparison}.
|
|
|
|
\begin{figure}
|
|
\begin{center}
|
|
\begin{tabular}{ | m{5em} | m{3cm}| m{3cm} | m{3cm} | }
|
|
\hline
|
|
& ProtoBuf & FlatBuffers & Memcpy \\
|
|
\hline
|
|
Čas (s) & 9.455691762 & 10.345692894 & 0.754350866 \\
|
|
\hline
|
|
\end{tabular}
|
|
\label{table:serialization_comparison}
|
|
\caption{Porovnání serializátorů}
|
|
\end{center}
|
|
\end{figure}
|
|
|
|
Zjistili jsme, že problémem byl ProtoBuf a FlatBuffers by nám nepomohl. Nikoho nepřekvapí, že memcpy byl nejrychlejší. Rozhodli jsme se tedy, že budeme pro serializaci a deserializaci snapshotů světa používat vlastní enkodér a dekodér.
|
|
|
|
Pro kódování jsme využili už existující \kiinlinecode{cpp}{!}{ByteBuffer}, který umožňuje snadné kódování po bytech do vektoru. Třída \kiinlinecode{cpp}{!}{WorldStateWriter} obsahuje konkrétní kódování pro tyto zprávy a přes konstruktor je nutné předat instanci \kiinlinecode{cpp}{!}{ByteBuffer}.
|
|
|
|
Zpráva obsahuje hlavičku, ve které je počet aktualizovaných entit a počet nových entit. Počet odebraných entit můžeme vynechat, ten jsme schopni zjistit z délky zprávy a zbylých počtů.
|
|
|
|
\subsection{Optimalizace manažera zájmů}
|
|
|
|
Další na řadě byla optimalizace manažera zájmů, který trval každý snímek kolem 60 milisekund. Nejprve jsme se rozhodli přidat datové struktury. Druhým cílem bylo zlepšit API, kterým se replikátor dotazuje, jestli je entita pro klienta zajímavá.
|
|
|
|
Jedna s datových struktur, která má nízkou složitost pro dotaz na seznam entit ve vzdálenosti maximálně, je klasická mřížka. Charakteristika hráčů ve hrách je, že se hodně pohybují. Mřížka nepotřebuje žádný přepočet, pouze vložit do předem alokovaného bufferu, nebo z něj naopak odebrat.
|
|
|
|
Testovali jsme 4 implementace, z toho 3 různé datové struktury a jednou naivní přístup. Simulovali jsme pohyb 100 000 různých entit po dobu 100 snímků. Všechny možnosti představíme. Naivní přístup využívá pouze Pythagorovu větu, aby získal vzdálenost dvou bodů. Jediná optimalizace je vynechat odmocninu a umocnit místo toho vzdálenost. Druhá a třetí implementace využívá mřížku a entity rozděluje do svých polí. Fixní mřížka je vhodná pro světy, které nemění svou velikost, jako např. World of Warcraft. Naopak hashovací mřížka přiřazuje entity do polí pomocí hashe jejich pozice. Jsou tak ideální pro dynamické a potencionálně nekonečné světy, jako např. Minecraft. Poslední je quad tree. Jedná se o obdobu binárního stromu rozšířenou o rozměr. V tabulce \ref{table:range_query_comparison} vidíme výsledky testu. Quad tree implementace se ukázala jako nevhodná. S přibývající hloubkou je navíc pomalejší. Ideální se ukázali varianty s fixní a hashovací mřížkou.
|
|
|
|
\begin{figure}
|
|
\begin{center}
|
|
\begin{tabular}{ | m{5em} | m{3cm} | m{3cm} | m{3cm} | }
|
|
\hline
|
|
Název testu & Celkový čas & Čas vkládání & Čas dotazování \\
|
|
\hline
|
|
Naivní & 38770 & 0 & 38650 (99.96\%) \\
|
|
\hline
|
|
Fixní mřížka & 467 & 301 (64.56\%) & 17 (3.74\%) \\
|
|
\hline
|
|
Hashovací mřížka & 1070 & 736.63 (68.76\%) & 42 (3.93\%) \\
|
|
\hline
|
|
Quad tree & 1830 & 1560 (85.45\%) & 30.29 (1.66\%) \\
|
|
\hline
|
|
\end{tabular}
|
|
\label{table:range_query_comparison}
|
|
\caption{Porovnání algoritmů}
|
|
\end{center}
|
|
\end{figure}
|
|
|
|
\subsection{Optimalizace QUICr}
|
|
|
|
|
|
|
|
\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:
|