ProjektPR/sprawozdanie/sprawozdanie.tex
2018-04-15 11:35:43 +02:00

270 lines
12 KiB
TeX

\documentclass[]{article}
\usepackage[T1]{fontenc}
\usepackage{polski}
\usepackage[utf8]{inputenc}
\usepackage[polish]{babel}
\usepackage[margin=1.25in]{geometry}
\usepackage{titling}
\usepackage{pdfpages}
\usepackage{float}
\usepackage{amsmath}
\usepackage{amstext}
\usepackage{pgfplots}
\usepackage{tikz}
\usepackage{enumerate}
\usepackage{lmodern}
\usepackage{amsfonts}
\usepackage{mathtools}
\usepackage{algorithm}
\usepackage{algpseudocode}
\usepackage{wrapfig}
\usepackage{braket}
\usepackage{subcaption}
\usepackage{multirow}
\usepackage{etoolbox}
\usepackage{color}
\usepackage{pgfkeys}
\usepackage{siunitx}
\pgfplotsset{compat=1.15}
\usepgfplotslibrary{groupplots}
\usepgfplotslibrary{external}
\usetikzlibrary{decorations.pathmorphing, arrows.meta, positioning}
\usetikzlibrary{shapes.geometric, arrows, intersections}
\newcommand{\areyousure}[1]{{\color{red}\textbf{???} #1 \textbf{???}}}
\newcommand{\todo}[1]{{\color{blue}\textbf{TODO:} #1}}
\makeatletter
\newenvironment{packet}{%
\def\byte{*8mm}%
\newrobustcmd{\member}[3][]{%
\node[packet, minimum width=##3\byte] (##2) {##2};
\node[below=of ##2] {##3};
\node[above=of ##2.north, text depth=0pt] {##1};
\tikzstyle{packet}+=[right=of ##2];
}%
\newrobustcmd{\preamble}[1][15]{%
\node[packet, minimum width=2\byte, dashed] (Preamble) {$\cdots$};
\node[below=of Preamble] {##1};
\node[above=of Preamble.north] {Preambuła};
\tikzstyle{packet}+=[right=of Preamble];
}%
\begin{tikzpicture}[%
packet/.style={draw, auto, align=center, minimum height=7mm, font=\ttfamily, right=of start},
optional/.style={dotted},
every node/.style={text depth=0pt},
node distance=0,
x=8mm, y=1.5cm
]%
\node (start) {};
}{%
\end{tikzpicture}%
\let\byte\undefined
\let\member\undefined
\let\preamble\undefined
}
\makeatother
% opening
\title{Przetwarzanie Rozproszone - Projekt}
\author{\protect\begin{tabular}{ll|l}
Weronika Czarnecka & \textit{165600} & \multirow{4}{*}{\shortstack[l]{Informatyka 2016/2017 \\ Semestr IV, gr. 1}} \tabularnewline
Anna Piliczewska & \textit{165436} & \tabularnewline
Maciej Borzyszkowski & \textit{165407} & \tabularnewline
Kacper Donat & \textit{165581} & %
\protect\end{tabular}}
\floatname{algorithm}{Program}
\begin{document}
\maketitle
\section{Zasady rozgrywki}
Gracze poruszają się po wspólnej planszy czołgami strzelając do siebie i starając się zniszczyć. Każdy gracz kontroluje
tylko i wyłącznie swój czołg.
\section{Wykorzystane technologie}
\begin{itemize}
\item C\#
\item SFML.net
\item System.Threading
\item System.Net.Sockets
\end{itemize}
\section{Proponowana Architektura}
Z założenia, rozgrywka ma odbywać się w czasie rzeczywistym zatem wykorzystanie protokołu \textit{TCP} mogłoby
wprowadzać niepotrzebne opóźnienia wynikające z niepotrzebnych retransmisji pakietów. Komunikacja będzie odbywała się za
pomocą datagramów UDP, które pozwalają na szybszą - acz mniej dokładną transmisję informacji pomiędzy klientami. W
wypadku normlanej rozgrywki jednak strata części pakietów jest jak najbardziej akceptowalna i nie wpłynie na rozgrywkę w
znaczący sposób.
W projekcie przyjęto architekturę klient-klient, tj. nie występuje żaden serwer stanowiący źródło prawdy dla rozgrywki.
Zamiast tego jeden z klientów zawsze będzie miał status \textit{mastera}. Poprawnym stanem rozgrywki z definicji jest
stan symulacji \textit{mastera}. \textit{masterem} zawsze jest klient o najmniejszym \texttt{ClientId}, tj. klient,
który podłączył się najwcześniej. Takie podejście zapewnia rozwiązanie sytuacji, w której aktualny \textit{master} się
rozłączy bez konieczności przeprowadzania negocjacji.
Dodatkowo, przyjmuje się zasadę, że każdy klient jest w 100\% odpowiedzialny za swoją część symulacji, dla której
stanowi źródło prawdy. tj. każdy gracz wysyła tylko i wyłącznie informacje o zmianach rzeczy, na które on ma wpływ - np.
pozycję własnego czołgu, czy wystrzelenie pocisku. W gestii innych klientów jest przetworzyć te informację i
zsynchronizować się. Przewidziano również obiekty o zachowaniu deterministycznym, które każdy klient może aktualizować
samodzielnie w ramach swojej symulacji.
Takie podejście zapewnia brak konfliktu o zasoby, ponieważ każdy obiekt jest modyfikowany wyłącznie przez jedną osobę,
zatem nigdy nie nastąpi konflikt. Nie występuje również problem odczytywania zmienionych danych ponieważ wyświetlanie
oraz aktualizacja stanu gry odbywają się w ramach jednego wątku.
Każdy klient będzie obsługiwany przez 4 osobnych wątki, których kompetencje nie kolidują ze sobą:
\begin{enumerate}
\item Aktualizacja i renderowanie stanu symulacji \label{thread:render}
\item Input z sieci (obiór pakietów, kolejkowanie) \label{input:net}
\item Input użytkownika (odczyt klawiatury itd.) \label{input:user}
\item Output sieciowy (wysyłanie pakietów, retranmisje) \label{output:net}
\end{enumerate}
Wątki \ref{input:net} oraz \ref{input:user} będą umieszczały akcje do wykonania w jednej kolejce akcji, zatem kolejka ta
musi być \textit{thread-safe} i udostępniać atomowe metody \texttt{enqueue} i \texttt{dequeue}, gwarantujące że stan
kolejki nie zostanie uszkodzony, tj. żadna akcja nie zostanie wykonana 2-krotnie ani żadna nie zostanie zgubiona.
Zostanie to osiągnięte poprzez ustawienie Mutexa\footnote{https://msdn.microsoft.com/en-us/library/system.threading.mutex(v=vs.110).aspx}
w metodach.
Zakładamy, że akcje użytkownika muszą zostać zostać umieszczone na kolejce akcji natychmiastowo, zatem nie
ma potrzeby synchronizacji z siecią. Co za tym idzie, dostęp do kolejki pakietów (potrzebnej ze względu na możliwość
otrzymania pakietów w nieodpowiedniej kolejności) i jej obsługa będzie w całości po stronie wątku \ref{input:net} -
czyli problem wyścigów nie następuje.
Poprzez identyczną kolejkę będzie obsługiwana wysyłka pakietów będzie obsługiwana przez identyczną kolejkę w celu
skomunikowania wątków \ref{input:user} oraz \ref{output:net}. Dodatkowo w wątku \ref{output:net} będzie działać
samodzielna kolejka odpowiadająca za retransmisję pakietów.
Niektóre pakiety zmieniają stan rozgrywki (np. śmierć, wystrzelenie pocisku), a co za tym idzie muszą zostać
przetworzone przez wszystkich klientów w jednakowej kolejnosci i w miarę jednakowym czasie (nie ma możliwości
zagwarntowania idealnej synchronizacji). W tym celu wprowadzony zostanie system numerowania kolejnych pakietów a każdy
pakiet, będzie zawierał informację na temat stanu od którego "zależy" - tj. numeru ostatniej aktualizacji. W wypadku
gdyby doszły pakiety konkurujące ze sobą (tj. o tym samym numerze pakietu, oraz zależące od tego samego stanu) wygrywa
ten, który przyszedł pierwszy ponieważ są one od siebie niezależne.
\subsection{Protokół}
Protokół opiera się o wysyłanie datagramów UDP między wszystkimi klientami. Ze względu na specyfikę UDP (brak gwarancji
dotarcia pakietów, brak gwarancji kolejności, brak gwarancji poprawności) przyjmuje się następujące założenia:
\begin{itemize}
\item nie wszystkie pakiety są ważne, niektóre można pominąć
\item wszystkie pakiety aby zostać przetworzone muszą dojść w całości poprawnie
\item pakiety są numerowane
\item pakiety mogą wymagać konkretnego poziomu stanu gry
\end{itemize}
\begin{figure}[H]
\centering
\begin{tikzpicture}[
client/.style = { circle, draw },
master/.style = { very thick },
node distance=2cm
]
\node[client, master] (C1) {$C_1$};
\node[client, right=of C1] (C2) {$C_2$};
\node[client, below=of C2] (C3) {$C_3$};
\node[client, left=of C3] (C4) {$C_4$};
\foreach \u/\v in {C1/C2,C1/C3,C1/C4,C2/C3,C2/C4,C3/C4} \draw[<->] (\u) -- (\v);
\end{tikzpicture}
\caption{Proponowana architektura sieciowa}
\end{figure}
Ze względu na konieczność szybkiego przesyłania informacji pomiędzy klientami, zaprojektowany protokół jest protokołem
binarnym, o konstrukcji pakietu widocznej poniżej.
\begin{figure}[H]
\center
\begin{packet}
\member{CRC16$_p$}{2}
\member{Type}{1}
\member{Size}{2}
\member{ClientId}{2}
\member{State}{4}
\member{PacketId}{4}
\node[packet, dashed, right=of PacketId, text width=15mm] {CRC16$_d$};
\end{packet}
\caption{Preambuła pakietu}
\end{figure}
\begin{table}[H]
\centering
\begin{tabular}{l|l|l}
\textbf{Właściwość} & \textbf{Typ} & \textbf{Opis} \\ \hline
\texttt{CRC16$_p$} & \texttt{int16} & Suma kontrolna danych w preambule \\
\texttt{Type} & \texttt{uint8} & Rodzaj wysyłanego pakietu \\
\texttt{Size} & \texttt{uint16} & Rozmiar wysłanych danych, razem z pierwszym bajtem \\
\texttt{ClientId} & \texttt{uint16} & Identyfikator klienta \\
\texttt{State} & \texttt{uint32} & Wymagany stan gry do przetworzenia pakietu \\
\texttt{PacketId} & \texttt{uint32} & Timestamp oryginalnego wysłania pakietu \\ \hline
\texttt{CRC16$_d$} & \texttt{int16} & Suma kontrolna danych pakietu (o ile jakieś są) \\
\end{tabular}
\end{table}
Dla ułatwienia implementacji przyjmuje się, że dla pakietów, których najstarszy bit typu (\texttt{Type \& 0x80}) jest
ustawiony na 1 nie zachodzi potrzeba retransmisji - czyli są to pakiety mało istotne bądź szybko przedawniające się.
Przykładem takiego pakietu może być aktualizacja pozycji gracza - nie ma potrzeby retransmisji ponieważ prawdopodobnie i
tak w drodze jest następny pakiet z nowszą pozycją.
W celu zapewnienia poprawności transmisji, każdy pakiet poprzedzany jest 32-bitową sumą kontrolną \texttt{CRC32},
pozwalającą upewnić się, że dane zostały odebrane poprawnie. Suma kontrolna liczona jest ze wszystkich składowych
pakietu poczynając od \texttt{Type} włącznie.
W wypadku niezgodności przesłanej sumy kontrolnej z sumą wyliczoną po stronie odbierającego, odbierający wysyła pakiet
\texttt{RET = 0x81}. Dla pakietów mało ważnych nie zachodzi potrzeba retransmisji.
Aby upewnić się, że transmisja ważnego pakietu się powiodła, każdy klient musi odpowiedzieć pakietem \texttt{ACK}
(\texttt{Type = 0x80}). W wypadku, gdyby odpowiedź \texttt{ACK} nie dotarła w ciągu $t\ \si{ms}$ pakiet zostaje
wysłany ponownie.
W przypadku wielokrotnego otrzymania pakietu o tym samym \texttt{PacketId} (np. w wypadku automatycznej retramsnisji),
każdorazowo należy potwierdzić jego otrzymanie odpowiedzią \texttt{ACK} jednak akcja związana z tym pakietem powinna
zostać wykonana tylko raz.
Pakiety \texttt{ACK} oraz \texttt{RET} w polu \texttt{PacketId} zawierają id pakietu, którego dotyczą. Dodatkowo ze
wzgledu na to, że \texttt{ACK} nie jest bezpośrednio związany z rozgrywką, wymagany stan (\texttt{Sate}) zawsze wynosi
0.
\begin{figure}[H]
\center
\begin{packet}
\member{CRC16$_p$}{2}
\member[Type]{0x80}{1}
\member[Size]{15}{2}
\member{ClientId}{2}
\member[State]{0}{4}
\member{PacketId}{4}
\end{packet}
\caption{Pakiet \texttt{ACK}}
\end{figure}
\subsection{Zestawienie połączeń}
Ze względu na to, że port na którym nasłuchują klienci nie jest deterministyczny istnieje konieczność wprowadzania
scentralizowanego \textit{lobby} pozwalającego na wymianę tych informacji pomiędzy klientami. Jedynym zadaniem
\textit{lobby} jest przechowywanie informacji o graczach chcących wspólnie zagrać, zatem nie zachodzi potrzeba szybkiego
działania.
Stąd przyjęto, że do realizacji lobby zostanie wykorzystany protokół TCP. Ze względu na to, że lobby stanowi jedynie
pośrednika do zestawiania połączeń, dokładny opis działania zdecydowano się pominąć.
\section{Rozwiązania}
O nie w tym wypadku też ciężko
\end{document}