Overview
Michitai LAN Multiplayer is a comprehensive networking library for building local area network multiplayer games and applications. It provides both Unity and .NET implementations with support for multiple platforms including Windows, Linux, macOS, Android, and iOS.
Key Features
- Cross-platform LAN discovery and communication
- UDP broadcast for server discovery
- TCP client-server architecture for reliable data transfer
- Binary length-prefixed framing with NoDelay and SocketAsyncEventArgs I/O — built for 30-60 Hz messaging
- Unreliable UDP state channel for per-frame synchronization (positions, transforms, inputs)
- Transparent GZip payload compression
- Queued request/response with fire-and-forget sends
- Thread-safe data synchronization
- JSON serialization for game data
- Player authentication and management
- Command system for terminal-like operations
Architecture
The library is organized into several key namespaces:
Michitai.Lan
Root namespace containing platform enumeration and core types.
EPlatform- Platform enumeration (Windows, Linux, MacOS, Android, iOS, Standalone, Mobile)
Michitai.Lan.Data
Data structures and JSON storage interfaces for game data management.
IJsonStorage- Interface for JSON serialization/deserializationPlayerGameData- Player game data with JSON capabilitiesPlayerCharacterData- Player character-specific dataPlayerWorldData- Player world-specific data
Michitai.Lan.Net
Core networking functionality for LAN operations.
Lan- Static class for IP address and broadcast mask operationsUDPBroadcast- UDP broadcast functionality for network discoveryUDPChannel- Unreliable UDP datagram channel for high-frequency state syncFrame/FrameBuffer- Binary wire framing and streaming decoderCompressor- GZip payload compression with configurable thresholdTCPClient- TCP client for reliable connectionsTCPServer- TCP server for accepting client connectionsTCPServerClient- Server-side representation of connected clientsPortRange- Port range management with predefined rangesMessage- Base message classAppMessage- Application-level messageIdentifiedMessage- Message with client identificationLocatedMessage- Message with sender endpoint information
Michitai.Lan.Net.Multiplayer
High-level multiplayer game networking components.
Client- Multiplayer client for connecting to serversServer- Multiplayer server for managing game sessionsBroadcastClient- Client-side broadcast discoveryBroadcastServer- Server-side broadcast announcementMultiplayer- Main multiplayer coordination class
Michitai.Lan.Net.Multiplayer.Data
Data structures specific to multiplayer networking.
ClientGameData- Client-side game dataServerGameData- Server-side game data with player managementServerClientGameData- Combined server client dataCredentials- Player authentication credentialsServerInfo- Server information for discoveryLocatedServerInfo- Discovered server informationServerInfoStack- Stack of discovered serversMultiplayerGamesData- Multiplayer game metadata
Michitai.Lan.Net.Multiplayer.Commands
Command system for terminal-like operations.
Command- Command structure and executionTerminal- Terminal interface for command input
Directory Structure
.NET Implementation (dotnet/)
dotnet/ ├── Data/ │ ├── IJsonStorage.cs # JSON storage interface │ ├── PlayerGameData.cs # Player game data │ ├── PlayerCharacterData.cs # Player character data │ └── PlayerWorldData.cs # Player world data ├── Net/ │ ├── Lan.cs # LAN IP operations │ ├── UDPBroadcast.cs # UDP broadcast │ ├── UDPChannel.cs # Unreliable UDP state channel │ ├── Frame.cs # Binary frame codec + streaming decoder │ ├── Compression.cs # GZip payload compression │ ├── TCPClient.cs # TCP client │ ├── TCPServer.cs # TCP server │ ├── TCPServerClient.cs # Server client representation │ ├── PortRange.cs # Port range management │ ├── Message.cs # Base message │ ├── AppMessage.cs # Application message │ ├── IdentifiedMessage.cs # Identified message │ ├── LocatedMessage.cs # Located message │ ├── Multiplayer/ │ │ ├── Client.cs # Multiplayer client │ │ ├── Server.cs # Multiplayer server │ │ ├── BroadcastClient.cs # Broadcast discovery client │ │ ├── BroadcastServer.cs # Broadcast announcement server │ │ ├── Multiplayer.cs # Main multiplayer class │ │ ├── Data/ # Multiplayer data structures │ │ │ ├── ClientGameData.cs │ │ │ ├── ServerGameData.cs │ │ │ ├── ServerClientGameData.cs │ │ │ ├── Credentials.cs │ │ │ ├── ServerInfo.cs │ │ │ ├── LocatedServerInfo.cs │ │ │ ├── ServerInfoStack.cs │ │ │ └── MultiplayerGamesData.cs │ │ └── Commands/ # Command system │ │ ├── Command.cs │ │ └── Terminal.cs │ └── Debugging/ # Debugging utilities ├── EPlatform.cs # Platform enumeration ├── michitai-lan.csproj # Project file └── michitai-lan.sln # Solution file
Unity Implementation (unity/)
unity/ ├── Data/ │ ├── IJsonStorage.cs # JSON storage interface │ └── JsonStorage.cs # JSON storage implementation ├── Net/ │ ├── Lan.cs # LAN IP operations │ ├── UDPBroadcast.cs # UDP broadcast │ ├── UDPChannel.cs # Unreliable UDP state channel │ ├── Frame.cs # Binary frame codec + streaming decoder │ ├── Compression.cs # GZip payload compression │ ├── TCPClient.cs # TCP client │ ├── TCPServer.cs # TCP server │ ├── TCPServerClient.cs # Server client representation │ ├── PortRange.cs # Port range management │ ├── Message.cs # Base message │ ├── AppMessage.cs # Application message │ ├── IdentifiedMessage.cs # Identified message │ ├── LocatedMessage.cs # Located message │ ├── Multiplayer/ │ │ ├── Client.cs # Multiplayer client │ │ ├── Server.cs # Multiplayer server │ │ ├── BroadcastClient.cs # Broadcast discovery client │ │ ├── BroadcastServer.cs # Broadcast announcement server │ │ ├── MobileBroadcastClient.cs # Mobile broadcast client │ │ ├── MobileBroadcastServer.cs # Mobile broadcast server │ │ ├── Multiplayer.cs # Main multiplayer class │ │ ├── Data/ # Multiplayer data structures │ │ │ ├── ClientGameData.cs │ │ │ ├── ServerGameData.cs │ │ │ ├── ServerClientGameData.cs │ │ │ ├── Credentials.cs │ │ │ ├── ServerInfo.cs │ │ │ ├── LocatedServerInfo.cs │ │ │ ├── ServerInfoStack.cs │ │ │ └── MultiplayerGamesData.cs │ │ ├── Commands/ # Command system │ │ │ ├── Command.cs │ │ │ └── Terminal.cs │ │ └── Chat/ # Chat functionality │ │ ├── ChatClient.cs │ │ └── ChatServer.cs │ └── Debugging/ # Debugging utilities └── EPlatform.cs # Platform enumeration
Platform Support
The library supports multiple platforms through the EPlatform enumeration:
| Platform | Value | Description |
|---|---|---|
| Windows | 1 | Windows desktop platform |
| Linux | 2 | Linux desktop platform |
| MacOS | 4 | macOS desktop platform |
| Standalone | 7 | All desktop platforms (Windows + Linux + macOS) |
| Android | 8 | Android mobile platform |
| iOS | 16 | iOS mobile platform |
| Mobile | 24 | All mobile platforms (Android + iOS) |
Platform flags can be combined using bitwise operations for multi-platform support.
Key Components
LAN Operations
The Lan static class provides utilities for working with local network addresses:
// Get all local IPv4 addresses
IPAddress[] addresses = Lan.LocalIPv4Addresses(EPlatform.Standalone);
// Get broadcast masks for discovery
IPAddress[] masks = Lan.LocalIPv4Masks(EPlatform.Standalone);
// Try-get pattern with error handling
if (Lan.TryGetLocalIPv4Addresses(EPlatform.Standalone, out var ips)) {
// Use addresses
}
UDP Broadcast
UDPBroadcast enables network discovery through UDP broadcasting:
// Create broadcast client var broadcast = new UDPBroadcast(ipAddress, port); // Send discovery message await broadcast.SendAsync(broadcastAddress, portRange, message); // Receive responses var response = await broadcast.ReceiveAsync(timeoutMs); // Cleanup broadcast.Stop();
Multiplayer Server
The Server class manages multiplayer game sessions:
// Create server
var server = new Server(
name: "MyGameServer",
serverGameData: gameData,
ip: ipAddress,
port: 7777
);
// Subscribe to events
server.OnClientConnected += (id) => Console.WriteLine($"Client {id} connected");
server.OnClientDisconnected += (id) => Console.WriteLine($"Client {id} disconnected");
server.OnRequest += (msg) => HandleRequest(msg);
// Start server
server.Start();
// Register new player
var credentials = server.RegisterNewPlayer(playerData);
// Log in player
server.LogInPlayer(clientId, credentials);
// Send response
server.Response(identifiedMessage);
// Stop server
server.Stop();
Multiplayer Client
The Client class connects to multiplayer servers:
// Create client
var client = new Client(
clientGameData: myData,
gameData: playerData,
ip: serverIp,
port: 7777
);
// Subscribe to events
client.OnResponse += (msg) => HandleResponse(msg);
client.OnDisconnected += () => Console.WriteLine("Disconnected");
// Start client
client.Start();
// Send request
if (client.CanRequest) {
client.Request(message);
}
// Stop client
client.Stop();
Port Management
PortRange provides predefined port ranges and port management:
// Use predefined ranges var broadcastPorts = PortRange.Broadcast; // 64512-65535 var dynamicPorts = PortRange.Dynamic; // 49152-65535 var registeredPorts = PortRange.Registered; // 1024-49151 // Create custom range var customRange = new PortRange(8000, 9000); // Get port store for random port selection var store = customRange.RangeStore; int randomPort = store.RandomPort; // Gets and removes a random port
Data Serialization
All game data implements IJsonStorage for JSON serialization:
// Create player data
var playerData = new PlayerGameData();
// Serialize object to JSON
playerData.Set(myPlayerObject);
// Get JSON string
string json = playerData.Json;
// Deserialize JSON to object
var player = playerData.Get<MyPlayerType>();
// Thread-safe access
lock (playerData) {
// Access data safely
}
Thread Safety
The library implements thread-safe operations using locks:
- All data structures use private lock objects for synchronization
- JSON storage properties are thread-safe with lock guards
- Server client collections are protected with locks
- Credentials and ID properties are thread-safe
Message Flow
The library uses a message-based communication pattern:
- Discovery Phase: Servers broadcast their presence via UDP
- Connection Phase: Clients connect via TCP to discovered servers
- Authentication Phase: Players authenticate with credentials
- Game Phase: Request/response over TCP for reliable data; UDP datagrams for per-frame state
- Disconnection Phase: Clean disconnect with resource cleanup
Transport Protocol
The current release uses a binary wire protocol on two channels:
Reliable Channel (TCP)
Every message is sent as a frame: [4-byte little-endian payload length][1-byte flags][UTF-8 payload]. Flag 0x01 marks a GZip-compressed payload. The streaming decoder handles fragmented reads and multiple frames per read, and rejects frames announcing more than Frame.MaxPayloadSize (32 MB by default). Sockets run with NoDelay and SocketAsyncEventArgs for low-latency, low-allocation I/O, and writes are queued so concurrent sends never interleave.
Use this channel for anything that must arrive: commands, login, world data, request/response traffic.
State Channel (UDP)
UDPChannel carries datagrams of [1-byte flags][payload] — unreliable, unordered, no retransmission. It binds to the same port number as the TCP server (UDP and TCP port spaces are independent), so a discovered ServerInfo endpoint already tells clients where to send state.
Use this channel for transient per-frame values at 30-60 Hz where a dropped packet is immediately superseded by the next one — positions, transforms, inputs. Keep datagrams under UDPChannel.SafeDatagramSize (1472 bytes) to avoid IP fragmentation.
// Client -> server, each frame
Multiplayer.SendState(new Message(JsonUtility.ToJson(playerTransform)));
// Server: receive state from clients
Multiplayer.OnState += (IPEndPoint from, Message msg) => {
var transform = JsonUtility.FromJson(msg.GetMessage);
ApplyState(from, transform);
};
// Server -> all clients that have sent state
Multiplayer.BroadcastState(new Message(JsonUtility.ToJson(worldSnapshot)));
// Server -> one client
Multiplayer.SendState(clientEndPoint, new Message(JsonUtility.ToJson(playerSnapshot)));
Compression
Payloads of 256 bytes or more are GZip-compressed automatically when compression shrinks them. Tune with Compressor.Enabled and Compressor.Threshold.
Request Semantics
Client.Request() implements one-at-a-time request/response: calls made while awaiting a response are queued (Client.PendingRequests) and flushed in order. Client.Send() bypasses the queue for fire-and-forget TCP messages. Backpressure is observable via TCPClient.PendingWrites and TCPServer.PendingWrites.
Usage Examples
Example 1: Complete Server Setup
This example shows how to set up a complete multiplayer server with discovery, player registration, and game state synchronization:
// Initialize platform
#if UNITY_STANDALONE
EPlatform platform = EPlatform.Standalone;
#elif UNITY_ANDROID
EPlatform platform = EPlatform.Android;
#endif
// Configure server
Multiplayer.Name = "Car Driving Multiplayer";
IPAddress[] ips = null;
bool success = Lan.TryGetLocalIPv4Addresses(platform, out ips);
Multiplayer.IpAddress = success ? ips[0] : IPAddress.Any;
// Create server data
ServerGameData serverData = new ServerGameData(Guid.NewGuid().ToString());
// Start server with broadcast discovery
Multiplayer.StartServer(
platform: platform,
serverGameData: serverData,
processMessage: (LocatedMessage msg) => {
// Process broadcast discovery requests
Command command = JsonUtility.FromJson(msg.Message.Message);
if (msg.Message.Name == "car-driving-multiplayer" &&
command == Command.New("get-server-info"))
{
ServerInfo info = new ServerInfo(
Multiplayer.Server.IPEndPoint.Port,
Multiplayer.Name,
serverData.ServerID,
serverData.Clients.Length
);
return new AppMessage(1, "car-driving-multiplayer",
JsonUtility.ToJson(Command.New("server-info").Arg(JsonUtility.ToJson(info))));
}
return new AppMessage(1, "car-driving-multiplayer", "denied");
},
receiveRequestsDelayMilliseconds: 500
);
// Handle server requests
Multiplayer.Server.OnRequest += (identifiedMessage) => {
Terminal terminal = JsonUtility.FromJson(identifiedMessage.Message.GetMessage);
Command[] commands = terminal.Commands;
Terminal response_terminal = Terminal.New();
foreach (Command cmd in commands)
{
if (cmd == Command.New("get-server-id"))
{
response_terminal.Next("server-id")
.Arg($"{Multiplayer.Server.PublicServerData.ServerID}");
}
else if (cmd == Command.New("register"))
{
JsonStorage data = new JsonStorage(cmd.Arguments[1]);
Credentials credentials = Multiplayer.Server.RegisterNewPlayer(data);
response_terminal.Next("credentials")
.Arg($"{JsonUtility.ToJson(credentials)}");
}
else if (cmd == Command.New("log-in"))
{
Credentials credentials = JsonUtility.FromJson(cmd.Arguments[1]);
if (Multiplayer.Server.Contains(credentials))
{
Multiplayer.Server.LogInPlayer(identifiedMessage.ID, credentials);
response_terminal.Next("log-in-successful");
}
else
{
response_terminal.Next("log-in-error");
}
}
else if (cmd == Command.New("set-game-data"))
{
bool success = Multiplayer.Server.TryGetLoggedInPlayerPrivateData(
identifiedMessage.ID, out ServerClientGameData data);
if (success)
{
data.Data.Json = cmd.Arguments[1];
}
}
else if (cmd == Command.New("get-server-data"))
{
response_terminal.Next("server-data")
.Arg($"{JsonUtility.ToJson(Multiplayer.Server.PublicServerData)}");
}
}
Multiplayer.Server.Response(new IdentifiedMessage(
new Message(JsonUtility.ToJson(response_terminal)),
identifiedMessage.ID
));
};
Example 2: Complete Client Setup
This example shows how to set up a client that discovers servers, connects, and synchronizes game state:
// Initialize platform
#if UNITY_STANDALONE
EPlatform platform = EPlatform.Standalone;
#elif UNITY_ANDROID
EPlatform platform = EPlatform.Android;
#endif
// Start broadcast discovery in main menu
Multiplayer.StartBroadcastClient(
platform: platform,
request: new AppMessage(1, "car-driving-multiplayer",
JsonUtility.ToJson(Command.New("get-server-info"))),
onReceiveResponse: (LocatedMessage response) => {
if (response.Message.Name == "car-driving-multiplayer")
{
Command command = JsonUtility.FromJson(response.Message.Message);
if (command == Command.New("server-info"))
{
ServerInfo serverInfo = JsonUtility.FromJson(command.Arguments[1]);
// Store server info for user selection
discoveredServers.Add(new LocatedServerInfo(serverInfo, response.IPEndPoint));
}
}
},
receiveResponsesMilliseconds: 5000,
repeatAfterMilliseconds: 5000
);
// User selects server - connect to it
Multiplayer.IpAddress = selectedServer.IPEndPoint.Address;
Multiplayer.Port = selectedServer.ServerInfo.Port;
// Start client
Multiplayer.StartClient();
// Handle responses
Multiplayer.Client.OnResponse += (message) => {
Terminal terminal = JsonUtility.FromJson(message.GetMessage);
Command[] commands = terminal.Commands;
foreach (Command cmd in commands)
{
if (cmd == Command.New("server-id"))
{
server_id = cmd.Arguments[1];
// Register with game data
GameData data = new GameData(new CharacterData(...));
Multiplayer.Client.GameData = new JsonStorage(JsonUtility.ToJson(data));
Multiplayer.Client.Request(new Message(
JsonUtility.ToJson(Terminal.New("register").Arg(JsonUtility.ToJson(data)))
));
}
else if (cmd == Command.New("credentials"))
{
Credentials credentials = JsonUtility.FromJson(cmd.Arguments[1]);
Multiplayer.Client.ClientData = new ClientGameData(server_id, credentials);
Multiplayer.Client.Request(new Message(
JsonUtility.ToJson(Terminal.New("log-in").Arg(JsonUtility.ToJson(credentials)))
));
}
else if (cmd == Command.New("log-in-successful"))
{
Multiplayer.Client.Request(new Message(
JsonUtility.ToJson(Terminal.New("get-server-data"))
));
}
else if (cmd == Command.New("server-data"))
{
ServerGameData server_data = JsonUtility.FromJson(cmd.Arguments[1]);
Multiplayer.Client.ServerData = server_data;
// Update remote players
UpdateRemotePlayers(server_data.Clients);
}
}
};
// Game loop - send player state at ~30-60 Hz over the unreliable UDP channel.
// Requests that must arrive (login, world data) still go through Client.Request over TCP.
private void Update()
{
if (Multiplayer.IsClient)
{
if (Time.time >= updateRate + lastUpdate)
{
lastUpdate = Time.time;
// Per-frame state: fast, no head-of-line blocking, latest-wins
Multiplayer.Client.SendState(new Message(
JsonUtility.ToJson(new GameData(characterData))
));
}
}
}
Example 3: Game Data Serialization
This example shows how to create serializable game data structures:
// Define serializable game data classes
[Serializable]
public class GameData
{
public CharacterData character_data;
public WorldData world_data;
}
[Serializable]
public class CharacterData
{
public float position_x;
public float position_y;
public float position_z;
public float rotation_x;
public float rotation_y;
public float rotation_z;
public bool lights_on;
public int car_index;
// Add more fields as needed
}
[Serializable]
public class WorldData
{
public string world_name;
public int level_index;
// Add world-specific data
}
// Use JSON storage for serialization
PlayerGameData playerData = new PlayerGameData();
playerData.Set(new GameData(characterData, worldData));
// Get JSON string
string json = playerData.Json;
// Deserialize back
GameData data = playerData.Get<GameData>();
Example 4: Command Processing Pattern
This example shows the recommended pattern for processing commands on the server:
// Server-side command processing
Multiplayer.Server.OnRequest += (identifiedMessage) => {
Terminal terminal = JsonUtility.FromJson<Terminal>(identifiedMessage.Message.GetMessage);
Command[] commands = terminal.Commands;
Terminal response_terminal = Terminal.New();
foreach (Command cmd in commands)
{
switch (cmd.Arguments[0])
{
case "/get-server-id":
// Return server ID
response_terminal.Next("server-id")
.Arg($"{Multiplayer.Server.PublicServerData.ServerID}");
break;
case "/register":
// Register new player
JsonStorage data = new JsonStorage(cmd.Arguments[1]);
Credentials credentials = Multiplayer.Server.RegisterNewPlayer(data);
response_terminal.Next("credentials")
.Arg($"{JsonUtility.ToJson(credentials)}");
break;
case "/log-in":
// Authenticate player
Credentials credentials = JsonUtility.FromJson<Credentials>(cmd.Arguments[1]);
if (Multiplayer.Server.Contains(credentials))
{
Multiplayer.Server.LogInPlayer(identifiedMessage.ID, credentials);
response_terminal.Next("log-in-successful");
}
else
{
response_terminal.Next("log-in-error");
}
break;
case "/set-game-data":
// Update player game state
bool success = Multiplayer.Server.TryGetLoggedInPlayerPrivateData(
identifiedMessage.ID, out ServerClientGameData data);
if (success)
{
data.Data.Json = cmd.Arguments[1];
}
break;
case "/get-server-data":
// Return all players' public data
response_terminal.Next("server-data")
.Arg($"{JsonUtility.ToJson(Multiplayer.Server.PublicServerData)}");
break;
default:
// Unknown command
response_terminal.Next("error").Arg("Unknown command");
break;
}
}
// Send response
Multiplayer.Server.Response(new IdentifiedMessage(
new Message(JsonUtility.ToJson(response_terminal)),
identifiedMessage.ID
));
};
Example 5: Thread-Safe Data Access
This example shows how to safely access data in a multi-threaded environment:
// All data structures use locks for thread safety
// Example from ServerGameData
private readonly object _clients_lock;
private ServerClientGameData[] _clients;
public ServerClientGameData[] Clients
{
get
{
lock (_clients_lock)
{
return _clients;
}
}
set
{
lock (_clients_lock)
{
_clients = value;
}
}
}
// Safe access pattern
public void AddPlayer(ServerClientGameData player)
{
lock (_clients_lock)
{
ServerClientGameData[] players = new ServerClientGameData[_clients.Length + 1];
for (int i = 0; i < _clients.Length; i++)
{
players[i] = _clients[i];
}
players[_clients.Length] = player;
_clients = players;
}
}
// Usage in game code
lock (serverData)
{
// Access data safely
var players = serverData.Clients;
foreach (var player in players)
{
// Process player data
}
}
Example 6: Platform-Specific Initialization
This example shows how to handle different platforms:
// Platform detection
private EPlatform GetPlatform()
{
#if UNITY_STANDALONE_WIN
return EPlatform.Windows;
#elif UNITY_STANDALONE_LINUX
return EPlatform.Linux;
#elif UNITY_STANDALONE_OSX
return EPlatform.MacOS;
#elif UNITY_ANDROID
return EPlatform.Android;
#elif UNITY_IOS
return EPlatform.IOS;
#else
return EPlatform.Standalone;
#endif
}
// Platform-specific IP retrieval
EPlatform platform = GetPlatform();
IPAddress[] addresses = Lan.LocalIPv4Addresses(platform);
// Platform-specific broadcast
if ((platform & EPlatform.Mobile) != 0)
{
// Use mobile broadcast implementation
// MobileBroadcastClient / MobileBroadcastServer
}
else
{
// Use desktop broadcast implementation
// BroadcastClient / BroadcastServer
}
Building
.NET Project
# Build the project dotnet build dotnet/michitai-lan.csproj # Run tests (if available) dotnet test
Unity Project
Copy the Unity folder contents to your Unity project's Assets folder. The Unity version uses Unity's JSON serialization instead of System.Text.Json.
Dependencies
.NET Version
- .NET Framework 4.8 or .NET 6.0+
- System.Net.Sockets
- System.Threading
- No external JSON dependency — a built-in serializer shim (DataContractJsonSerializer) is included
Unity Version
- Unity 2019.4 or later
- Unity's built-in JSON serialization
License
This project is released under the MIT No Attribution (MIT-0) license. You are free to use, modify, and distribute it for personal or commercial projects without payment or attribution. See the Terms and Conditions and the repository LICENSE file for the full text.
Support
For support and inquiries, contact: support@michitai.com
Website: https://michitai.com