Lab 13: Network Streams, Pipes, Memory-Mapped Files #
Starting code #
- .gitignore
- Quack.Client/
- Quack.Engine/
- Core/
- Extra/
- Quack.Engine.csproj
- Resources/
- Quack.Messages/
- Quack.Server/
- Quack.Tests/
- QuackIIIArena.sln
Role: Lead Network Engineer
Deadline: 2 Hours to Launch
Status: Critical Failure
The Situation #
Congratulations! You are the lead network developer for the highly anticipated Quack III Arena. The game looks great, the physics are bouncy, and the ducks are ready to fight.
However, the previous developer (an intern who has since “migrated” to another company) left the networking code in a shambles. Clients can’t connect, messages aren’t being sent or received correctly, and the entire communication layer is broken, leading to random disconnections and instability.
We launch in 2 hours. Your job is to implement the network communication so that clients can connect, send input, and receive game state updates reliably and efficiently.
The Protocol #
The game uses a custom TCP-based protocol mixed with JSON and Binary payloads. Every message sent over the wire follows a strict 5-byte header format.
Message Framing #
[ Length ] [ Type ] [ Payload ]
| 4 Bytes | | 1 Byte | | N Bytes ... |
| Int32 | | Enum | | JSON/Binary Data |- Payload Size (4 bytes): A 32-bit integer indicating how many bytes of the payload follow.
- Message Type (1 byte): A byte representing the
MessageTypeenum (e.g.,Join,Input,UpdateState). - Payload (N bytes): The serialized data.
Important: The client and server must strictly adhere to this framing. If you read too few or too many bytes, the connection will desynchronize, causing critical communication failures.
Your Mission #
You will find // TODO comments in the codebase (Quack.Messages and Quack.Client projects). Complete the following stages to save the launch.
Note: Each stage can be verified using the automated tests provided in the Quack.Tests project.
Stage 1: Message Serialization (3 points) #
Project: Quack.Messages
Files: JsonMessages.cs, BinaryMessages.cs
Before any communication can happen, game objects need to be converted into bytes (serialize) and bytes back into game objects (deserialize). This is the foundation of network communication.
- Tasks:
- JSON Messages (
JsonMessages.cs): ImplementSerialize()andDeserialize()methods. - Binary Messages (
BinaryMessages.cs): ImplementSerialize()andDeserialize()for the custom binary format.
- JSON Messages (
Binary Layout Specification #
You must adhere to the following byte layout for the binary messages to ensure compatibility with the server:
1. JoinMessage Used when a player connects to the arena.
- Size: 4 bytes for Name Length + N bytes for Name Data.
- Layout:
- Name Length (4 bytes): A standard
Int32(Little Endian) representing the number of UTF8 bytes in theNamestring. - Name Data (N bytes): The UTF8 encoded bytes of the player’s
Name.
- Name Length (4 bytes): A standard
2. InputMessage Used to send player controls. Designed for minimal bandwidth.
- Size: Exactly 1 byte.
- Layout: A single byte, where each bit represents a specific input state.
- Bit 0:
Up(true if key is pressed, false otherwise) - Bit 1:
Down(true if key is pressed, false otherwise) - Bit 2:
Left(true if key is pressed, false otherwise) - Bit 3:
Right(true if key is pressed, false otherwise) - Bit 4:
Sprint(true if key is pressed, false otherwise) - Bits 5-7 are unused.
- Bit 0:
Stage 2: Client Connection (2 points) #
Project: Quack.Client
File: GameClient.cs
With your messages now able to be converted, the next critical step is for the client to establish a connection to the server. Without this, your duck is forever alone.
- Task: Implement the
ConnectAsyncmethod. - Requirements:
- Resolve the host (e.g.,
localhost,192.168.1.111,pw.mini.edu.pl) to an IP address. If it already is an IP address simply parse it. - Establish a
TcpClientconnection to the specified server IP address and port.
- Resolve the host (e.g.,
Stage 3: The Reading Loop (4 points) #
Project: Quack.Client
File: NetworkConnection.cs
Once connected, your client needs to continuously listen for incoming game state updates from the server, adhering strictly to the defined message protocol. Failure here leads to desynchronization and a very confused duck.
- Task: Implement the
StartReadingAsyncmethod. - Requirements:
- Read the messages in a continuous loop that reads from the
NetworkStreamwhile the client is connected. - Read Header: Read exactly 5 bytes from the stream. From these bytes, extract the 4-byte
PayloadLengthand the 1-byteMessageType. - Read Payload: Read exactly
PayloadLengthbytes into a buffer. - Deserialize: Convert these payload bytes into an
IJsonMessageobject (using your Stage 1 logic). - Event: Invoke the
MessageReceivedevent with the deserialized message.
- Read the messages in a continuous loop that reads from the
Stage 4: Sending Messages (3 points) #
Project: Quack.Client
File: NetworkConnection.cs
Finally, enable the client to talk back.
- Task: Implement the
SendAsyncmethod. - Requirements:
- Serialize Message: Convert the
IMessageinto its binary byte representation (using your Stage 1 logic). - Construct Header: Create the 5-byte header containing the
PayloadLength(from your serialized message) and theMessageType. - Send Data: Write the 5-byte header to the
NetworkStream, immediately followed by the serialized message payload.
- Serialize Message: Convert the
- Hint: Consider using
ArrayPoolto efficiently manage temporary buffers.
Example solution #
- .gitignore
- Quack.Client/
- Quack.Engine/
- Core/
- Extra/
- Quack.Engine.csproj
- Resources/
- Quack.Messages/
- Quack.Server/
- Quack.Tests/
- QuackIIIArena.sln