Laboratorium 13: Strumienie sieciowe, Łącza, Mapowanie plików #
Kod startowy #
- .gitignore
- Quack.Client/
- Quack.Engine/
- Core/
- Extra/
- Quack.Engine.csproj
- Resources/
- Quack.Messages/
- Quack.Server/
- Quack.Tests/
- QuackIIIArena.sln
Rola: Główny Inżynier Sieciowy (Lead Network Engineer)
Termin: 2 godziny do premiery
Status: Krytyczna porażka
Sytuacja #
Gratulacje! Objąłeś stanowisko głównego programisty sieciowego długo wyczekiwanego Quack III Arena. Oprawa wizualna zachwyca, fizyka działa bez zarzutu, a kaczki są zwarte i gotowe do walki.
Niestety, poprzedni programista (stażysta, który zdążył już “wyemigrować” do innej firmy) pozostawił kod sieciowy w opłakanym stanie. Klienci nie są w stanie nawiązać połączenia, wiadomości gubią się lub są błędnie przesyłane, a cała warstwa komunikacyjna jest niespójna, co skutkuje losowymi rozłączeniami i brakiem stabilności.
Do premiery zostały 2 godziny. Twoim zadaniem jest zaimplementowanie warstwy komunikacji sieciowej tak, aby klienci mogli stabilnie łączyć się z serwerem, przesyłać sterowanie i odbierać aktualizacje stanu gry w sposób niezawodny i wydajny.
Protokół #
Gra wykorzystuje niestandardowy protokół oparty na TCP, który łączy przesyłanie danych w formacie JSON oraz binarnym. Każda wiadomość przesyłana przez sieć musi być opatrzona ścisłym, 5-bajtowym nagłówkiem.
Format wiadomości #
[ Długość ] [ Typ ] [ Dane (Payload) ]
| 4 Bajty | | 1 Bajt | | N Bajtów ... |
| Int32 | | Enum | | Dane JSON/Binarne |- Rozmiar Danych (4 bajty): 32-bitowa liczba całkowita (
Int32) określająca liczbę bajtów danych następujących po nagłówku. - Typ Wiadomości (1 bajt): Bajt reprezentujący wartość wyliczeniową
MessageType(np.Join,Input,UpdateState). - Dane (N bajtów): Zserializowana treść wiadomości.
Ważne: Zarówno klient, jak i serwer muszą rygorystycznie przestrzegać tego formatu. Odczytanie zbyt małej lub zbyt dużej liczby bajtów doprowadzi do desynchronizacji połączenia i krytycznych błędów komunikacji.
Twoja misja #
W kodzie źródłowym (projekty Quack.Messages oraz Quack.Client) znajdziesz komentarze // TODO. Wykonaj poniższe etapy, aby uratować premierę gry.
Uwaga: Każdy etap można zweryfikować za pomocą zautomatyzowanych testów znajdujących się w projekcie Quack.Tests.
Etap 1: Serializacja wiadomości (3 punkty) #
Projekt: Quack.Messages
Pliki: JsonMessages.cs, BinaryMessages.cs
Aby komunikacja była możliwa, obiekty gry muszą zostać przekształcone w ciąg bajtów (serializacja), a odebrane bajty z powrotem w obiekty (deserializacja). Jest to podstawa wymiany danych w sieci.
- Zadania:
- Wiadomości JSON (
JsonMessages.cs): Zaimplementuj metodySerialize()orazDeserialize(). - Wiadomości Binarne (
BinaryMessages.cs): Zaimplementuj metodySerialize()orazDeserialize()dla niestandardowego formatu binarnego.
- Wiadomości JSON (
Specyfikacja formatu binarnego #
Aby zapewnić kompatybilność z serwerem, musisz ściśle przestrzegać poniższego układu bajtów dla wiadomości binarnych:
1. JoinMessage Wiadomość wysyłana w momencie dołączania gracza do areny.
- Rozmiar: 4 bajty na Długość Nazwy + N bajtów na Dane Nazwy.
- Układ:
- Długość Nazwy (4 bajty): Standardowy
Int32reprezentujący liczbę bajtów (UTF8) w ciągu znakówName. - Dane Nazwy (N bajtów): Nazwa gracza (
Name) zakodowana w formacie UTF8.
- Długość Nazwy (4 bajty): Standardowy
2. InputMessage Wiadomość przesyłająca sterowanie gracza. Zaprojektowana z myślą o minimalnym zużyciu pasma.
- Rozmiar: Dokładnie 1 bajt.
- Układ: Pojedynczy bajt, w którym poszczególne bity odpowiadają stanom klawiszy:
- Bit 0:
Up(true jeśli wciśnięty, w przeciwnym razie false) - Bit 1:
Down(true jeśli wciśnięty, w przeciwnym razie false) - Bit 2:
Left(true jeśli wciśnięty, w przeciwnym razie false) - Bit 3:
Right(true jeśli wciśnięty, w przeciwnym razie false) - Bit 4:
Sprint(true jeśli wciśnięty, w przeciwnym razie false) - Bity 5-7 pozostają nieużywane.
- Bit 0:
Etap 2: Połączenie klienta (2 punkty) #
Projekt: Quack.Client
Plik: GameClient.cs
Gdy mechanizm konwersji wiadomości jest gotowy, kolejnym kluczowym krokiem jest nawiązanie połączenia z serwerem. Bez tego Twoja kaczka pozostanie wiecznie samotna.
- Zadanie: Zaimplementuj metodę
ConnectAsync. - Wymagania:
- Rozwiąż hosta (np.
localhost,192.168.1.111,pw.mini.edu.pl) na adres IP. Jeżeli jest to już adres IP, to go po prostu sparsuj. - Nawiąż połączenie ze wskazanym adresem IP i portem serwera.
- Rozwiąż hosta (np.
Etap 3: Pętla odczytu (4 punkty) #
Projekt: Quack.Client
Plik: NetworkConnection.cs
Po nawiązaniu połączenia klient musi nieprzerwanie nasłuchiwać aktualizacji stanu gry z serwera, ściśle trzymając się zdefiniowanego protokołu. Błąd na tym etapie doprowadzi do desynchronizacji i zdezorientowania kaczek.
- Zadanie: Zaimplementuj metodę
StartReadingAsync. - Wymagania:
- Odczytuj wiadomości w nieskończonej pętli ze strumienia
NetworkStreamtak długo, jak klient pozostaje połączony. - Odczyt Nagłówka: Pobierz dokładnie 5 bajtów ze strumienia. Wyodrębnij z nich 4-bajtową długość danych (
PayloadLength) oraz 1-bajtowy typ wiadomości (MessageType). - Odczyt Danych: Pobierz dokładnie tyle bajtów, ile wskazuje
PayloadLength, zapisując je do bufora. - Deserializacja: Przekształć pobrane bajty na obiekt
IJsonMessage(wykorzystując logikę z Etapu 1). - Zdarzenie: Wywołaj zdarzenie
MessageReceived, przekazując zdeserializowaną wiadomość.
- Odczytuj wiadomości w nieskończonej pętli ze strumienia
Etap 4: Wysyłanie wiadomości (3 punkty) #
Projekt: Quack.Client
Plik: NetworkConnection.cs
Ostatni krok to umożliwienie klientowi komunikacji zwrotnej.
- Zadanie: Zaimplementuj metodę
SendAsync. - Wymagania:
- Serializacja Wiadomości: Przekształć obiekt
IMessagena jego reprezentację binarną (używając logiki z Etapu 1). - Konstrukcja Nagłówka: Utwórz 5-bajtowy nagłówek zawierający długość danych (
PayloadLength– na podstawie zserializowanej wiadomości) oraz typ wiadomości (MessageType). - Wysłanie Danych: Zapisz do strumienia
NetworkStream5-bajtowy nagłówek, a bezpośrednio po nim zserializowaną treść wiadomości.
- Serializacja Wiadomości: Przekształć obiekt
- Wskazówka: Rozważ użycie
ArrayPooldo wydajnego zarządzania tymczasowymi buforami pamięci.
Przykładowe rozwiązanie #
- .gitignore
- Quack.Client/
- Quack.Engine/
- Core/
- Extra/
- Quack.Engine.csproj
- Resources/
- Quack.Messages/
- Quack.Server/
- Quack.Tests/
- QuackIIIArena.sln