Sending UDP Messages and Reliability
This frame work is setup to handle quite a bit of the messaging necessary to maintain online game state. However, there is a few situations I can imagine where you want to send a specific message out to someone or all players on a server that must arrive reliably that is not already baked into the example. Well looks at how to go about that.
(Skip this paragraph if you don’t care why we do it this way) Under the hood I’m using LiteNetLib for the networking specific code and then built the game state stuff on top of that. I picked LiteNetLib because of its focus on UDP messages, which trades out the reliability of TCP for speed. LiteNetLib has implemented a reliability system to cover the difference for UDP, so that’s a pretty big win. This means it can send UDP messages either unreliably(which I use for gamestate since we send so many snapshots per second) or reliably(useful for things like the server telling you which map to load up, send chat text to players, kick a player etc).
So to send a message reliably, we need to define that type of message in the PacketType enum first:

This is in NetworkPacketProcessor, you can see the enum PacketType has a bunch already setup in here. For sake of example, lets say I want to do a game half time alert message to notify clients how far into the game they are in. I would add something like gameHalfTime into the enum.
Ok, next is writing the code to send the message out to clients. In ServerConnection and ClientConnection there are a bunch of useful examples, but I’ll show how I reliably send who the winner of the game is to everyone:

We’re re-using the netDataWriter over and over, so its important to Reset() it so that there is no data from a previous packet in there. Then we put the PacketType in next, followed by whatever data you want to send(can be multiple bits of data, just add multiple netDataWriter.Put lines)
So using our halftime example might look like:
foreach(PlayerConnection pc in playerConnections)//all clients
{
netDataWriter.Reset();
netDataWriter.Put((int)PacketType.gameHalfTime);
netDataWriter.Put("Its half time baby!");
//add more Puts if you have more data
pc.netPeer.Send(netDataWriter, DeliveryMetho.ReliableUnordered);
}
And reading a packet that’s arrived may look like this:

Packets get processed in PacketProcessor classes. With the winner text example we’re sending from ServerConnection to a ClientConnection which runs messages into its ClientPacketProcessor. You can see its already read the type of the packet earlier and that’s being used in this switch block, which makes it easy for us to just check each type by case. Then we read the rest of the data out(winners name in this case) and do something with that information(up to you)
So with the gameHalfTime example it might look like
case PacketType.gameHalfTime:
string halfTimeMessage = dataReader.GetString();
//add more dataReader.Gets to read more from packet, e.g GetInt, GetBool etc
//then do something with the read in data
break;
That’s about it.
So the examples shown was from Server to Client, but Clients can send reliable messages to the server too. If you need to send a message from Client to Client, it needs to broken into 2 messages. First one is Client to Server and then the server can pass it onto the correct other Client based on data in the packet you send.
If you’re interested in digging deeper, here is where the documentation for LiteNetLib is https://revenantx.github.io/LiteNetLib/index.html