Server Setup
Altruist runs an HTTP server that hosts both REST controllers and real-time transport endpoints. You can run any combination of WebSocket, TCP, and UDP transports simultaneously — all configured in config.yml.
Minimal Server Config
altruist:
server:
http:
host: "0.0.0.0"
port: 8080
path: "/"
transport:
codec:
provider: json
websocket:
enabled: true
path: /ws
using Altruist;
await AltruistApplication.Run(args);
That's all you need. Altruist will:
- Build a
WebApplicationwith ASP.NET Core - Auto-discover and mount MVC controllers
- Set up all enabled transports
- Discover all
[Portal]classes and register their routes on every active transport - Start listening
Multi-Transport
Enable any combination of transports — they all run simultaneously:
altruist:
server:
http:
host: "0.0.0.0"
port: 8080
transport:
codec:
provider: messagepack
tcp:
enabled: true
port: 13000
udp:
enabled: true
port: 13001
websocket:
enabled: true
path: /ws
Each transport has its own enabled flag — set it to true or false independently. Portals are discovered once and registered on every active transport automatically.
Note:
Your portal code doesn't change when you add or remove transports. The same [Portal("/game")] + [Gate("move")] works on TCP, UDP, and WebSocket simultaneously. A desktop client can connect via TCP while a browser client connects via WebSocket — they both reach the same gate handlers.
Transport Properties
| Transport | Config Key | Properties |
|---|---|---|
| TCP | transport.tcp | enabled, port |
| UDP | transport.udp | enabled, port |
| WebSocket | transport.websocket | enabled, path |
Common Scenarios
# Web-only game (browser clients)
transport:
codec: { provider: json }
websocket: { enabled: true, path: /ws }
# Desktop game with reliable + fast channels
transport:
codec: { provider: messagepack }
tcp: { enabled: true, port: 13000 } # Login, inventory, chat
udp: { enabled: true, port: 13001 } # Position updates, combat
# All transports (cross-platform game)
transport:
codec: { provider: messagepack }
tcp: { enabled: true, port: 13000 }
udp: { enabled: true, port: 13001 }
websocket: { enabled: true, path: /ws }
Note:
The codec is shared across all transports. If you set provider: messagepack, all three transports use MessagePack encoding.
TCP + UDP: Reliable and Fast Channels
A common pattern in game servers is using TCP for reliable data (login, chat, inventory, trades) and UDP for fast, lossy data (movement, position sync, combat actions). The client connects to both and chooses which transport to send each packet type on.
altruist:
server:
http:
host: "0.0.0.0"
port: 8080
transport:
codec:
provider: messagepack
tcp:
enabled: true
port: 13000
udp:
enabled: true
port: 13001
Server Side: One Portal, Both Transports
On the server, you write one portal — it receives packets from both TCP and UDP. The framework doesn't distinguish which transport a packet arrived on. Your gate handlers are identical:
[Portal("/game")]
public class GamePortal : AltruistPortal
{
private readonly IAltruistRouter _router;
public GamePortal(IAltruistRouter router)
{
_router = router;
}
// Arrives via TCP (client chooses to send login over the reliable channel)
[Gate("login")]
public async Task OnLogin(LoginPacket packet, string clientId)
{
await _router.Client.SendAsync(clientId, new LoginSuccessPacket());
}
// Arrives via UDP (client chooses to send movement over the fast channel)
[Gate("move")]
public async Task OnMove(MovePacket packet, string clientId)
{
await _router.Broadcast.SendAsync(packet, excludeClientId: clientId);
}
// Arrives via TCP (inventory changes must be reliable)
[Gate("use-item")]
public async Task OnUseItem(UseItemPacket packet, string clientId)
{
// process item usage...
}
}
Note:
The server doesn't need to know which transport a packet came from — the same gate handler processes it regardless. The client decides which transport to use per packet type based on reliability requirements.
Client Side: Choose Per Packet
The client connects to both TCP and UDP, then sends each packet type on the appropriate transport:
// C# game client example
var tcpClient = new TcpClient();
await tcpClient.ConnectAsync("game.example.com", 13000);
var tcpStream = tcpClient.GetStream();
var udpClient = new UdpClient();
udpClient.Connect("game.example.com", 13001);
// Login → TCP (must be reliable)
await tcpStream.WriteAsync(Serialize(new LoginPacket { ... }));
// Movement → UDP (speed over reliability)
await udpClient.SendAsync(Serialize(new MovePacket { X = 100, Y = 200 }));
// Chat → TCP (messages must not be lost)
await tcpStream.WriteAsync(Serialize(new ChatPacket { Text = "Hello!" }));
// Position sync → UDP (30 times per second, ok to lose some)
while (gameRunning)
{
await udpClient.SendAsync(Serialize(new PositionPacket { ... }));
await Task.Delay(33); // ~30 Hz
}
When to Use Which
| Data Type | Transport | Why |
|---|---|---|
| Login / auth | TCP | Must succeed, one-time |
| Chat messages | TCP | Messages must arrive in order |
| Inventory / trades | TCP | Losing an item operation would be catastrophic |
| Movement / position | UDP | High frequency, losing one update is fine |
| Combat actions | UDP | Speed matters more than guaranteed delivery |
| Damage numbers | TCP | Player expects to see every hit |
Note:
UDP packets can arrive out of order, be duplicated, or be lost entirely. Only use UDP for data where the next packet makes the previous one obsolete (like position updates). Never use UDP for state-changing operations (trades, item drops, level-ups).
HTTP Controllers
Standard ASP.NET Core controllers are auto-discovered and mounted. No manual registration needed:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("/api/info")]
public class ServerInfoController : ControllerBase
{
[HttpGet]
public IActionResult GetInfo()
{
return Ok(new { status = "online", version = "1.0" });
}
}
Controllers are found automatically from all loaded assemblies. HTTP always runs on server.http.port regardless of which transports are enabled.
Codec Selection
Set a global codec, and optionally override per transport:
altruist:
server:
transport:
codec:
provider: messagepack # Global default
websocket:
enabled: true
path: /ws
codec:
provider: json # Browsers get JSON
tcp:
enabled: true
port: 13000
# No override — uses messagepack
| Codec | Size | Speed | Debugging | Use Case |
|---|---|---|---|---|
| JSON | Larger | Slower | Easy (readable text) | Development, web clients |
| MessagePack | ~50% smaller | ~10x faster | Harder (binary) | Production, game clients |
See Encoding & Decoding for custom codec implementation and per-transport override details.
Portal Route Discovery
All [Portal("/path")] classes are automatically registered on every active transport:
- WebSocket:
ws://host:port/ws/game - TCP / UDP: portal path used for packet routing
Multiple portals can share the same path — they each handle different [Gate] events.
Health Endpoint
Altruist automatically exposes a health check controller at /health that reports server readiness based on all IConnectable services.
Startup Output
On successful startup, you'll see each active transport listed:
The Portals Are Open! Connect At:
----------------------------------------------------
TCP: tcp://0.0.0.0:13000
UDP: udp://0.0.0.0:13001
WebSocket: ws://0.0.0.0:8080/ws
Transport: TCP, UDP, WebSocket
----------------------------------------------------
Welcome, traveler!
Now listening on: http://0.0.0.0:8080