Sarıkaya Dev Logo

Build a Serverless Real‑Time Multiplayer Backend with .NET 8, Azure Functions, SignalR & Redis

Mahmut Sarıkaya 4 min read 5 Views 0
Build a Serverless Real‑Time Multiplayer Backend with .NET 8, Azure Functions, SignalR & Redis

Why serverless matters for real‑time multiplayer

Imagine a battle‑royale match that spikes from 100 to 10,000 concurrent players in seconds. Traditional VMs require you to guess capacity, over‑provision resources, and risk costly idle time. Serverless platforms such as Azure Functions automatically scale to zero when no users are connected and instantly provision thousands of instances when demand surges, keeping latency under 50 ms for most actions.

Setting up a .NET 8 Azure Functions project

Start with the .NET 8 SDK (released November 2023) and the Azure Functions Core Tools version 4.0 or newer. The minimal system requirement is Windows 10/11, macOS 12+, or a recent Linux distro with Docker support.

Run the following commands in a terminal:

dotnet new tool-manifest
dotnet tool install Microsoft.Azure.Functions.Worker.Sdk --local
dotnet new func -n MultiplayerBackend --worker-runtime dotnetIsolated --target-framework net8.0

This creates a clean isolated worker project ready for C# 12 features and async streams.

Integrating Azure SignalR Service

SignalR abstracts WebSocket management, group handling, and fallback transports. Create a SignalR service in the Azure portal (Standard tier, 1 GB message capacity) and copy the connection string.

Register the service in Program.cs:

using Microsoft.Azure.Functions.Worker.Extensions.SignalRService;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(s =>
{
s.AddSignalR()
.AddAzureSignalR(options =>
{
options.ConnectionString = Environment.GetEnvironmentVariable("AzureSignalRConnectionString");
});
})
.Build();
host.Run();

Now you can emit messages from any function using the IAsyncCollector<SignalRMessage> binding.

Managing transient game state with Redis

Redis excels at low‑latency key‑value storage and pub/sub, perfect for positions, health values, or matchmaking queues. Deploy Azure Cache for Redis (Basic tier, 250 MB) and note the host name and key.

Install the StackExchange.Redis package and create a singleton connection:

using StackExchange.Redis;
public static class RedisConnector
{
private static readonly Lazy<ConnectionMultiplexer> LazyConnection = new(() =>
ConnectionMultiplexer.Connect(Environment.GetEnvironmentVariable("RedisConnectionString"))
);
public static ConnectionMultiplexer Connection => LazyConnection.Value;
}

Example: updating a player’s coordinates atomically:

public async Task UpdatePositionAsync(string playerId, double x, double y)
{
var db = RedisConnector.Connection.GetDatabase();
await db.HashSetAsync($"player:{playerId}", new HashEntry[]{
new HashEntry("x", x),
new HashEntry("y", y)});
// Broadcast to the room via SignalR
await _signalRMessages.AddAsync(new SignalRMessage{
Target = "PlayerMoved",
Arguments = new[]{ playerId, x, y },
GroupName = "room-42"});
}

This pattern keeps the compute layer stateless while Redis holds the authoritative state.

Orchestrating matchmaking with durable functions

Durable Functions let you model a matchmaking saga without a dedicated server. A simple orchestrator can collect 10 players, assign them to a room, and fire a SignalR notification.

[Function("MatchmakerOrchestrator")]
public async Task RunOrchestrator([OrchestrationTrigger] IDurableOrchestrationContext ctx)
{
var players = await ctx.CallActivityAsync<List<string>>("CollectPlayers", null);
var roomId = Guid.NewGuid().ToString();
await ctx.CallActivityAsync("CreateRoom", new {roomId, players});
await ctx.CallActivityAsync("NotifyRoom", roomId);
}

Each activity runs as a separate Azure Function, automatically scaling with the load.

Testing locally with the Azure Functions Core Tools

Start the runtime with func start. The tool injects a local SignalR emulator when the AzureSignalRConnectionString environment variable points to Endpoint=https://localhost:7071;AccessKey=dev. Use a lightweight client like socket.io-client in Node.js to simulate 1,000 concurrent connections and verify that round‑trip latency stays below 30 ms.

Security and cost considerations

Enable Managed Identity for the Function App and grant it SignalR Service Contributor and Cache Contributor roles. This removes connection strings from code and allows Azure Policy to enforce encryption at rest.

From Azure pricing calculators (April 2024) a typical 5‑minute battle with 2,000 concurrent players costs under $0.12 in Functions execution time and $0.05 for SignalR messages, demonstrating the economic advantage of serverless.

Conclusion

By combining .NET 8’s performance, Azure Functions’ auto‑scale, SignalR’s real‑time messaging, and Redis’s ultra‑fast state store, you can deliver a production‑grade multiplayer backend without managing servers. The architecture stays stateless, testable, and cost‑efficient, letting you focus on game mechanics rather than infrastructure.

Sources

Microsoft Docs – Azure Functions developer guide; Microsoft Docs – Azure SignalR Service overview; Redis Labs – Redis performance benchmarks

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #.NET 8 #Azure Functions #SignalR #Redis #real-time multiplayer
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

3 + 3 =