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:

  1. Build a WebApplication with ASP.NET Core
  2. Auto-discover and mount MVC controllers
  3. Set up all enabled transports
  4. Discover all [Portal] classes and register their routes on every active transport
  5. 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
Loading diagram...

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

TransportConfig KeyProperties
TCPtransport.tcpenabled, port
UDPtransport.udpenabled, port
WebSockettransport.websocketenabled, 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
Loading diagram...

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 TypeTransportWhy
Login / authTCPMust succeed, one-time
Chat messagesTCPMessages must arrive in order
Inventory / tradesTCPLosing an item operation would be catastrophic
Movement / positionUDPHigh frequency, losing one update is fine
Combat actionsUDPSpeed matters more than guaranteed delivery
Damage numbersTCPPlayer 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
CodecSizeSpeedDebuggingUse Case
JSONLargerSlowerEasy (readable text)Development, web clients
MessagePack~50% smaller~10x fasterHarder (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