Network Messages
For the most part, we recommend the high-level ServerRpc/ClientRpc calls and SyncVar, but you can also send low-level network messages. This can be useful if you want clients to send messages that are not tied to game objects, such as logging, analytics, or profiling information.
Usage
- Define a new struct (rather than a class to prevent GC allocations) that will represent your message.
- Add any supported Mirage types as public fields of that struct. This will be the data you want to send.
- Register a handler for that message on the NetworkServer and/or NetworkClient's
MessageHandlerdepending on where you want to listen for that message being received. - Use the
Send()method on the NetworkClient, NetworkServer, or NetworkPlayer classes depending on which way you want to send the message.
Example
// optional: add NetworkMessage attribute so that it is easier for Mirage to find
[NetworkMessage]
// using structs to prevent GC allocations
public struct ScoreMessage
{
public int score;
public Vector3 scorePos;
public int lives;
}
public class Scores : MonoBehaviour
{
// attach these in the inspector
public NetworkServer Server;
public NetworkClient Client;
private void Awake()
{
Client.Started.AddListener(ClientStarted);
}
private void ClientStarted()
{
// Register Client to listen for the ScoreMessage
Client.MessageHandler.RegisterHandler<ScoreMessage>(OnScore);
}
private void OnScore(INetworkPlayer player, ScoreMessage msg)
{
Debug.Log("ScoreMessage received on client with score " + msg.score);
}
// Send from server
public void SendScore(int score, Vector3 scorePos, int lives)
{
var msg = new ScoreMessage()
{
score = score,
scorePos = scorePos,
lives = lives
};
// also send to host player so we can update ui
Server.SendToAll(msg, authenticatedOnly: true, excludeLocalPlayer: false);
}
}
Note that there is no serialization code for the ScoreMessage struct in this source code example. Mirage will generate a reader and writer for ScoreMessage when it sees that it is being sent.
Protecting Network Messages from Allocation Attacks
When writing custom NetworkMessage structs or classes that contain strings or collections (such as arrays or lists), you should protect the receiver from memory allocation attacks by applying the [MaxLength(int)] attribute to those fields:
[NetworkMessage]
public struct ChatMessage
{
[MaxLength(128)]
public string message;
}
[NetworkMessage]
public struct BulkDataMessage
{
[MaxLength(500)]
public int[] ids;
}
Why this is necessary
Without [MaxLength], a malicious client could send a packet indicating an extremely high length (e.g., billions of elements). The receiver would read this length first and attempt to pre-allocate memory for the collection (e.g. new T[size]), leading to Out of Memory (OOM) crashes or high garbage collector overhead.
With [MaxLength(N)], Mirage verifies the size before allocating. If the size exceeds N, deserialization is aborted, a SerializationLimitException is thrown, the sender is flagged with PlayerErrorFlags.SerializationLimit, and a penalty of 100 is applied.