As a part of my schools networking course we implemented a basic client-server architecture with the UDP protocol. I used this structure as a base for my specialization project, where I made a lobby to connect players to avalable servers.
Designing the flow of data
I started the project by planning out how i wanted the networked communication to flow. I also wanted to experiment with broadcasting since it's a concept that I was unfamiliar with and wanted to learn more about. After learning about the pros and cons of broadcasting I want to note that if this was a feature that was meant to be used in a commercial game, I would rather use multicast or unicast to reduce the amount of traffic in the local subnet. For this project however, broadcasting works perfectly fine.
I decided on a structure like this:
Both clients and servers broadcast to the subnet that they are up and running. The lobby catches these messages and registers them in a container.
The lobby sends out a message to a registered client/server, saying that they are registered and they should stop broadcasting. The newly registered client/server then does as its told and goes over to pinging the lobby back and forth through unicast.
A client can decide to connect to a server. The lobby then sends a message to the server in question, asking if its ok for said client to connect. The server then evaluates the situation and sends back an answer. If the answer was no, the client is asked to either try again or select another server. If the answer is yes, the client sends a connect message to the server.
To send messages over the network i use NetMessages that contains important message data. A NetMessage is also responsible for serializing and de-serializing the data. I do this using a serializer our school gave us during our networking course.
Heres some example code of the NetMessage base class that all NetMessages inherit from:
Due to the nature of the UDP protocol, reliably getting messages to where we want them to be can be a struggle. To counteract this, me and Flovin Michaelsen together implemented a system that guarantees a message to eventually reach its destination, similar to how the TCP protocol functions. That way I can send important packages like connect- and handshake messages without needing to use TCP asyncronously,
To mark a message as guaranteed we use a flag system that sets the state of individual bits in an enum. Outside of guaranteed messages, I use the flags to specify if a message is sent by a client or a server.
The client uses 3 primary classes:
A speaker class that sends messages
A listener class that receives messages
A broadcaster class that specializes in sending broadcast messages.
All 3 classes run on their own threads and use their own socket. This is to try and encapsulate functionality and improve performance.
The server runs a very similar structure to the client. Just like the client it has a speaker, a listener, and a broadcaster. The difference here lies in how the server handles client messages and that it runs on its own executable.
The lobby has a similar interface to both the client and the server and functions as a server in smaller scale. It has a listener that picks up client/server messages and a speaker that distributes messages to registered clients and servers. The lobby also runs on its own executable.