Bir Oyunun Arka Ucunu Diğer Herşeyden Farklı Kılan Nedir?
Bir web uygulaması için arka uç geliştirdiyseniz, nasıl çalıştığını biliyorsunuzdur: Bir istemci bir istek gönderir HTTP, sunucu bunu işler, bir veritabanını sorgular ve bir JSON yanıtı döndürür. Döngü kendini tekrarlıyor her etkileşim. Birkaç milisaniye daha fazla gecikme mi? Kullanıcı bunu fark etmez bile.
Un çok oyunculu oyun arka ucu tamamen farklı bir evrende yaşıyor. Burada bahsetmiyoruz istekler ve cevaplar: hadi bir konu hakkında konuşalım sürekli akış her milisaniyede bir çift yönlü veri Yüzlerce oyuncunun saniyede onlarca kez değişen ortak bir durumu paylaştığı ve 100 ms'lik bir gecikme, zafer ile yenilgi arasındaki fark anlamına gelebilir.
Serinin bu ilk makalesinde Oyun Arka Uç Mühendisliğianatomiyi keşfedeceğiz çok oyunculu bir oyun arka ucuyla tamamlandı: ağ mimarisinden iletişim protokollerine, oyundan ölçeklendirme stratejileri ve platformlarına kadar mesaj serileştirmeye kadar sunucu tarafı döngüleri Hizmet Olarak Arka Uç. Sonunda her bileşenin ve seçimlerin tam bir zihinsel haritasına sahip olacaksınız. Başa çıkmanız gereken mimari sorunlar.
Seriye Genel Bakış
| # | Öğe | Odak |
|---|---|---|
| 1 | Buradasınız - Bir Oyunun Arka Uçunun Anatomisi | Mimari, protokoller, bileşenler |
| 2 | Durum Senkronizasyonu | Netcode, enterpolasyon, tahmin |
| 3 | Çöpçatanlık Motoru | ELO algoritmaları, kuyruklar, lobiler |
| 4 | Özel Oyun Sunucuları | Altyapı ve orkestrasyon |
| 5 | Hile Önleyici Mimari | Sunucu tarafı doğrulama, algılama |
| 6 | LiveOps ve Para Kazanma | Oyun içi ekonomi, canlı etkinlikler |
| 7 | Telemetri ve Analitik | Metrikler, veri hatları, gösterge tabloları |
| 8 | gözlemlenebilirlik | Günlüğe kaydetme, izleme, uyarı verme |
| 9 | Bulut Oyun Altyapısı | Akış, uç bilişim, gecikme |
| 10 | Açık Kaynaklı Oyun Yığınları | Nakama, Colyseus, Agones |
Ne Öğreneceksiniz
- Web arka ucu ile oyun arka ucu arasındaki temel farklar
- Ağ modelleri: istemci-sunucu, eşler arası ve aktarma
- Sunucu tarafı oyun döngüsü nasıl çalışır (yetkili sunucu)
- İletişim protokolleri: TCP vs UDP vs WebSocket vs WebRTC
- Mesaj serileştirme: Protokol Tamponları, FlatBuffer'lar, Mesaj Paketi
- Bağlantı yönetimi: oturumlar, yeniden bağlantılar, kalp atışları
- Veritabanı seçenekleri: Redis, PostgreSQL, zaman serisi
- Ölçeklendirme modeli: lobi sunucusu, dünya sunucusu, örneklenmiş bölgeler
- BaaS platformları: PlayFab, GameLift, Nakama, Colyseus
1. Web Arka Ucu ve Oyun Arka Ucu: İki Farklı Dünya
Bir oyunun arka ucunun neden tamamen farklı bir yaklaşım gerektirdiğini anlamak için gereksinimleri karşılaştıralım geleneksel bir web uygulamasınınkilerle temeldir.
Gereksinim Karşılaştırması: Web ve Oyun
| bekliyorum | Web Arka Ucu | Oyun Arka Uç Çok Oyunculu |
|---|---|---|
| İletişim modeli | İstek-yanıt (HTTP) | Sürekli çift yönlü akış |
| Kabul edilebilir gecikme | 200-500ms | 16-50 ms (60 fps'de bir kare = 16,6 ms) |
| Güncelleme sıklığı | İsteğe bağlı (tıklayın, gönderin) | Saniyede 20-128 kez (tik hızı) |
| Durum | Vatansız (her bağımsız istek) | Durum bilgisi olan (bellekte paylaşılan durum) |
| Tutarlılık | Kabul edilebilir herhangi bir tutarlılık | Gerçek zamanlı güçlü tutarlılık |
| Ölçeklenebilirlik | Yatay (yük dengeleyici + kopyalar) | Oturum başına dikey + oturum başına yatay |
| Veri kaybı toleransı | Sıfır (her işlem önemlidir) | Seçici (kaybedilen pozisyonlar sorun değil, satın almalar sorun değil) |
| Oturum süresi | Dakika (navigasyon) | Saatler (oyun oturumu) |
| Kullanıcı başına bant genişliği | Sporadik KB | 5-50 KB/sn sürekli |
Önemli olan doğa durum bilgisi olan oyunun arka ucundan. Bir web sunucusu olabilir istediğiniz zaman değiştirebilirsiniz; trafiği başka bir örneğe yönlendirmeniz yeterlidir. Bir oyun sunucusu Aktif bir oyunun durumunu hafızasında tutar: oyuncuların pozisyonları, uçuşan mermiler, aktif etkiler, skor. Bu sunucu çökerse oyun kaybedilir.
Oyunlarda Gecikme Sorunu
128 tıklama/sn'lik rekabetçi bir FPS'de, sunucu her seferinde bir güncelleme işler. 7,8 ms. Bir oyuncunun 50 ms ağ gecikmesi (RTT) varsa, girişi sunucuya 25 ms geç ulaşır ve yanıt başka bir 25 ms ile gelir. Oyuncu dünyayı 50ms geride görüyor sunucunun gerçekliğine. 200 ms'de oyun oynanamaz hale gelir. Bu nedenle bu gibi teknikler istemci tarafı tahmini ve gecikme telafisi bunlar temeldir.
2. Ağ Modelleri: Oyuncular Nasıl Bağlanır?
İlk mimari karar ağ modeliyle ilgilidir: istemcilerin birbirleriyle nasıl iletişim kurduğu ve sunucuyla? Her biri belirli ödünleşimlere sahip üç ana yaklaşım vardır.
2.1 İstemci-Sunucu (Yetkili Sunucu)
Modern oyun dünyasının baskın modeli. Tek bir merkezi sunucu tek sunucudur yetki açık Oyunun durumu. İstemciler girdilerini (tuşlara basma, fare hareketleri) sunucuya gönderirler. bunları doğrular, dünyanın durumunu günceller ve sonucu tüm müşterilere gönderir. Hiçbir müşteri yapamaz Oyunun durumunu doğrudan değiştirin.
Client A Server Client B
| | |
|--- Input (W,A) -->| |
| |--- Input (S,D) ---|<
| | |
| [Valida input] |
| [Aggiorna stato] |
| [Rileva collisioni] |
| [Calcola risultato] |
| | |
|<-- Stato mondo ---|--- Stato mondo -->|
| | |
| [Interpola/ | [Interpola/ |
| Predici] | Predici] |
Avantajları: maksimum güvenlik (sunucu her şeyi kontrol eder), entegre hile önleme, Tüm oyuncular için tutarlı durum, hata ayıklaması kolay. Dezavantajları: yüksek altyapı maliyeti (her oyun için bir sunucu), gecikme ek (her giriş gidiş-dönüş olmalıdır), tek arıza noktası.
2.2 Eşler Arası (P2P)
Her müşteri diğerleriyle doğrudan iletişim kurar. Merkezi bir sunucu yok; her oyuncu ve kendisi için hem istemci hem de "sunucu". Bu model dövüş oyunlarında popüler olmuştur ve oyuncu sayısının sınırlı olduğu (2-8) RTS'de.
Avantajları: sunucu maliyeti yok, eşler arasında minimum gecikme (doğrudan bağlantı), herhangi bir düğümün ölümünden sağ kurtulur. Dezavantajları: hileleri önlemek imkansızdır (her müşterinin kendi durumu üzerinde yetkisi vardır), Oyuncu sayısıyla (N*(N-1)/2 bağlantı) üstel karmaşıklık, NAT geçiş sorunları.
2.3 Aktarma Sunucusu (Proxy Olarak Sunucu)
İki model arasında bir uzlaşma. Merkezi bir sunucu şu şekilde hareket eder: röle: Her birinden mesaj alır istemcisine gider ve bunları herkese iletir, ancak oyun mantığını işlemez. Simülasyon gerçekleşir istemcilerde, sunucuda ve sadece bir "postacıda".
Ağ Modellerinin Karşılaştırılması
| karakteristik | İstemci-Sunucu | P2P | Röle |
|---|---|---|---|
| Emniyet | Yüksek (yetki sunucusu) | Düşük (yetki yok) | Ortalama (müşteriye bağlıdır) |
| Sunucu maliyeti | Yüksek | Hükümsüz | Bas |
| Ölçeklenebilirlik | Yüzlerce oyuncu | 2-8 oyuncu | Onlarca oyuncu |
| Gecikme | Ortalama (gidiş-dönüş) | Düşük (doğrudan) | Medya (sunucu aracılığıyla) |
| Tipik kullanım | FPS, MMO, Battle Royale | Dövüş, klasik RTS | İşbirliği, gündelik, mobil |
| Örnekler | Valorant, Fortnite, CS2 | Sokak Dövüşçüsü, StarCraft | Aramızda, Fall Guys |
3. Sunucu Tarafı Oyun Döngüsü
Yetkili bir oyun arka ucunun kalbi, oyun döngüsü: tekrarlanan bir döngü a sabit frekans ( onay oranı), girdiyi işleme, durumu güncelleme ve gönderme sonuçları müşterilere sunar. Tik hızı frekansı, sinyalin "zaman çözünürlüğünü" belirler. simülasyon.
Türe Göre Onay Oranı
| Tip | Onay Oranı | Aralık | Örnek |
|---|---|---|---|
| Rekabetçi FPS | 128 Hz | 7,8 ms | Valorant, CS2 |
| Standart FPS | 64 Hz | 15,6 ms | Aşırı Gözlem 2 |
| Savaş Royale | 20-30Hz | 33-50ms | Fortnite, PUBG |
| MMO | 10-20Hz | 50-100ms | Warcraft Dünyası |
| Stratejik/Dönüşler | 1-10Hz | 100ms-1s | Medeniyet, Hearthstone |
interface PlayerInput {
readonly playerId: string;
readonly tick: number;
readonly keys: ReadonlyArray<string>;
readonly mouseX: number;
readonly mouseY: number;
readonly timestamp: number;
}
interface GameState {
readonly tick: number;
readonly players: ReadonlyMap<string, PlayerState>;
readonly projectiles: ReadonlyArray<Projectile>;
readonly timestamp: number;
}
class AuthoritativeGameServer {
private readonly TICK_RATE = 64; // 64 aggiornamenti/secondo
private readonly TICK_INTERVAL = 1000 / 64; // ~15.625ms per tick
private currentTick = 0;
private gameState: GameState;
private readonly inputBuffer: Map<string, PlayerInput[]> = new Map();
start(): void {
console.log(`Server avviato a ${this.TICK_RATE} tick/s`);
setInterval(() => this.tick(), this.TICK_INTERVAL);
}
// Riceve input dal client (chiamato dalla rete)
onPlayerInput(input: PlayerInput): void {
const buffer = this.inputBuffer.get(input.playerId) ?? [];
// Crea nuovo array invece di mutare
this.inputBuffer.set(input.playerId, [...buffer, input]);
}
private tick(): void {
const tickStart = performance.now();
this.currentTick++;
// 1. Processa tutti gli input ricevuti
const processedInputs = this.processInputs();
// 2. Aggiorna la simulazione di gioco
const updatedState = this.updateSimulation(processedInputs);
// 3. Rileva collisioni
const stateAfterCollisions = this.detectCollisions(updatedState);
// 4. Aggiorna lo stato di gioco (immutabile)
this.gameState = {
...stateAfterCollisions,
tick: this.currentTick,
timestamp: Date.now(),
};
// 5. Invia lo stato aggiornato a tutti i client
this.broadcastState(this.gameState);
// 6. Monitora le performance del tick
const tickDuration = performance.now() - tickStart;
if (tickDuration > this.TICK_INTERVAL) {
console.warn(
`Tick ${this.currentTick} overrun: ${tickDuration.toFixed(2)}ms ` +
`(budget: ${this.TICK_INTERVAL.toFixed(2)}ms)`
);
}
}
private processInputs(): Map<string, PlayerInput> {
const latest = new Map<string, PlayerInput>();
for (const [playerId, inputs] of this.inputBuffer) {
if (inputs.length > 0) {
// Prendi l'ultimo input valido
const lastInput = inputs[inputs.length - 1];
if (this.validateInput(lastInput)) {
latest.set(playerId, lastInput);
}
}
}
// Svuota il buffer (nuovo Map, non mutare)
this.inputBuffer.clear();
return latest;
}
private validateInput(input: PlayerInput): boolean {
// Anti-cheat: verifica che i valori siano plausibili
const maxSpeed = 10;
return (
Math.abs(input.mouseX) <= 360 &&
Math.abs(input.mouseY) <= 90 &&
input.keys.length <= 6
);
}
}
Bütçe ve Aşım'ı işaretleyin
64 tik/sn hızında, her bir tik bir 15,6 ms'lik bütçe. Tik mantığı (fizik, çarpışmalar, yapay zeka, ağ oluşturma) daha uzun sürer, sunucu gecikme biriktirir ve oyuncular gecikmeyi algılıyorlar. Kene bütçesinin izlenmesi esastır: üretimde, s99 ortalama değil, tıklama süresi.
4. Devlet Yönetimi: Devletin Üç Düzeyi
Çok oyunculu bir oyunun durumu yekpare bir damla değildir: özelliklere sahip seviyelere bölünmüştür ve farklı gereksinimler. Her katman depolama, senkronizasyon ve kalıcılık stratejileri gerektirir farklı.
Oyun Durumunun Üç Düzeyi
| Seviye | Neler İçerir | Güncelleme Sıklığı | Kalıcılık | Depolamak |
|---|---|---|---|---|
| Çerçeve Durumu | Pozisyonlar, hızlar, dönüşler, mermiler | Her tıklama (20-128 Hz) | Sadece hafızada | Oyun sunucusu RAM'i |
| Oturum Durumu | HP, envanter, puan, buff/debuff | Olayda (hasar, tahsilat) | Maç süresince | RAM + Redis (yedekleme) |
| Kalıcı Durum | Profil, istatistikler, satın almalar, sıralama | Oyunun sonunda veya işlem sırasında | Kalıcı | PostgreSQL / MongoDB |
// === FRAME STATE: aggiornato ogni tick ===
interface FrameState {
readonly tick: number;
readonly entities: ReadonlyMap<string, EntityTransform>;
readonly projectiles: ReadonlyArray<ProjectileState>;
readonly effects: ReadonlyArray<ActiveEffect>;
}
interface EntityTransform {
readonly posX: number;
readonly posY: number;
readonly posZ: number;
readonly rotYaw: number;
readonly rotPitch: number;
readonly velocityX: number;
readonly velocityY: number;
readonly velocityZ: number;
}
// === SESSION STATE: aggiornato su eventi ===
interface SessionState {
readonly playerId: string;
readonly health: number;
readonly maxHealth: number;
readonly armor: number;
readonly inventory: ReadonlyArray<InventoryItem>;
readonly activeWeapon: string;
readonly kills: number;
readonly deaths: number;
readonly score: number;
readonly buffs: ReadonlyArray<BuffEffect>;
readonly team: string;
}
// === PERSISTENT STATE: salvato su database ===
interface PersistentPlayerData {
readonly odlayerId: string;
readonly username: string;
readonly elo: number;
readonly totalMatches: number;
readonly totalWins: number;
readonly totalKills: number;
readonly unlockedItems: ReadonlyArray<string>;
readonly purchaseHistory: ReadonlyArray<Purchase>;
readonly createdAt: Date;
readonly lastLoginAt: Date;
}
Seviyelere ayırma performans için temeldir. çerçeve durumu öyle olmalı mümkün olduğu kadar kompakt çünkü serileştirilir ve her tıklamada tüm istemcilere gönderilir. oturum durumu yalnızca değişiklik yapıldığında gönderilir. kalıcı durum asla yayınlanmaz: yalnızca sahibi talep edebilir ve kaydedilir veritabanında eşzamansız olarak.
5. İletişim Protokolleri: TCP, UDP, WebSocket, WebRTC
Aktarım protokolünün seçimi, bir mimarideki en etkili kararlardan biridir. oyun arka ucu. Her protokol güvenilirlik, gecikme ve karmaşıklık arasında farklı dengeler sunar.
5.1 TCP (İletim Kontrol Protokolü)
TCP, her paketin düzenli ve güvenilir şekilde teslim edilmesini sağlar. Bir paket kaybolursa TCP onu yeniden iletir ve kayıp paket gelene kadar sonraki paketlerin teslimini engeller. Bu fenomen denir hat başı engelleme ve gerçek zamanlı oyunların ölümcül düşmanıdır.
5.2 UDP (Kullanıcı Datagram Protokolü)
UDP ve "ateşle ve unut": Teslimat, sipariş veya bütünlük garantisi olmayan paketler gönderin. eğer bir paket kaybolur, yeniden iletilmez. Bu kulağa korkunç geliyor ama gerçek zamanlı bir oyun için ve tam olarak ihtiyaç duyulan şey: bir oyuncunun 100 ms önceki konumu ve eğer buna sahipseniz alakasız 16 ms öncesinden.
5.3 WebSocket'ler
WebSocket, TCP üzerinde çalışır ancak tam çift yönlü çift yönlü bağlantı sağlar. Ve protokol Yerel UDP'nin mevcut olmadığı tarayıcı tabanlı ve mobil oyunlar için standart. Gecikme e saf UDP'den daha büyüktür, ancak uygulama kolaylığı ve evrensel uyumluluk onu birçok tür için pragmatik bir seçim haline getiriyor.
5.4 WebRTC (Veri Kanalı)
WebRTC DataChannel, UDP üzerinden SCTP'ye dayalı eşler arası (veya istemci-sunucu) iletişim sunar. Kanal başına yapılandırılabilen hem güvenilir hem de güvenilmez modları destekler. Ve bunun için tek seçenek tarayıcınızda UDP benzeri iletişim elde edin.
Oyun İçin Ağ Protokollerinin Karşılaştırılması
| bekliyorum | TCP | UDP | WebSoketleri | WebRTC DC |
|---|---|---|---|---|
| Taşıma | TCP | UDP | TCP | SCTP/UDP |
| Garantili teslimat | Si | No | Si | Yapılandırılabilir |
| Garantili sipariş | Si | No | Si | Yapılandırılabilir |
| Hat başı engelleme | Si | No | Si | Hayır (güvenilmez) |
| Tipik gecikme | Yüksek (yeniden iletim) | Asgari | Ortalama | Düşük |
| Tarayıcı desteği | Hayır (doğrudan) | No | Si | Si |
| NAT geçişi | Gerekli değil | Sorunlu | Gerekli değil | Entegre (ICE) |
| Karmaşıklık | Düşük | Yüksek (özel güvenilirlik) | Düşük | Yüksek (sinyal verme) |
| Şunun için idealdir: | Sohbet, vardiyalar, lobiler | FPS, yarış, simülasyonlar | Tarayıcı oyunları, mobil | FPS tarayıcısı, VoIP |
WebTransport: Gelecek mi?
WebTaşıma ve birleştirmeyi vaat eden HTTP/3'e (QUIC) dayalı yeni bir standart tüm dünyaların en iyisi: çoğullanmış çift yönlü akışlar, güvenilir ve güvenilmez modlar, hat başı engelleme ve yerel tarayıcı erişimi yok. 2026'da tarayıcı desteği olgunlaşıyor (Chrome ve Edge bunu destekliyor), ancak sunucu tarafı desteği hala sınırlı. Ve yeni nesil tarayıcı tabanlı oyunları izlemek için protokol.
5.5 Hibrit Desen: Çok Kanallı
Modern oyun arka uçları nadiren tek bir protokol kullanır. En yaygın desen, çok kanallı: Farklı veri türleri için farklı kanallar.
Canale Unreliable (UDP / WebRTC unreliable):
- Posizioni dei giocatori (ogni tick)
- Rotazioni e animazioni
- Effetti particellari
- Dati audio posizionale
Canale Reliable (TCP / WebSocket / WebRTC reliable):
- Danno inflitto/subito
- Cambio arma/inventario
- Chat
- Eventi di gioco (kill, obiettivo)
- Transazioni economiche
- Comandi di matchmaking
6. Mesajların Serileştirilmesi: Her Bayt Önemlidir
100 oyuncuya saniyede 64 güncelleme gönderdiğinizde mesajdaki her ekstra bayt saniyede 6.400 kez çarpın. Serileştirme formatı seçiminin etkisi vardır doğrudan bant genişliğine, gecikmeye ve altyapı maliyetine bağlıdır.
Serileştirme Formatları Karşılaştırması
| Biçim | Tip | Boyut (göreceli) | Kodlama hızı | Kod çözme hızı | Şema |
|---|---|---|---|---|---|
| JSON | Metin | %100 (temel) | Yavaş | Yavaş | No |
| Mesaj Paketi | Parçalar | ~%60-70 | Hızlı | Hızlı | No |
| Protokol Tamponları | Parçalar | ~%30-40 | Çok hızlı | Çok hızlı | Evet (.proto) |
| Düz Tamponlar | Parçalar | ~%35-45 | Sıfır kopya | Sıfır kopya | Evet (.fbs) |
| Kaptan Proto | Parçalar | ~%35-45 | Sıfır kopya | Sıfır kopya | Si |
// === JSON: ~180 bytes ===
const jsonMsg = JSON.stringify({
type: "player_state",
playerId: "p_abc123",
posX: 123.456,
posY: 78.901,
posZ: 45.678,
rotYaw: 180.5,
rotPitch: -12.3,
health: 85,
weapon: "rifle",
tick: 15042,
});
// === Protocol Buffers: ~42 bytes ===
// Definizione .proto:
// message PlayerState {
// uint32 player_id = 1;
// float pos_x = 2;
// float pos_y = 3;
// float pos_z = 4;
// float rot_yaw = 5;
// float rot_pitch = 6;
// uint32 health = 7;
// WeaponType weapon = 8;
// uint32 tick = 9;
// }
// Risparmio: ~77% meno bandwidth
// A 64 tick/s, 100 giocatori:
// JSON: 180 * 100 * 64 = 1.15 MB/s
// Protobuf: 42 * 100 * 64 = 0.27 MB/s
// Risparmio: 0.88 MB/s = 76% in meno
Ne Zaman Kullanılmalı?
- JSON: Prototip oluşturma, adım adım oyunlar, harici web servisleriyle iletişim. Hata ayıklaması kolay, evrensel
- Mesaj Paketi: "İkili JSON" - kod mantığını değiştirmeden boyutu küçültmek istediğinizde. JSON için anında değiştirme
- Protokol Tamponları: Yüksek performanslı oyun sunucuları için fiili standart. Yazılı şema, çoklu dil kodlayıcı, geriye dönük uyumluluk
- Düz Tamponlar: Seri durumdan çıkarmanın maliyeti bile çok fazla olduğunda. Sıfır kopya erişimi: Bellek ayırmadan alanları doğrudan arabellekten okuyun
7. Bağlantı Yönetimi: Oturumlar, Kalp Atışı, Yeniden Bağlantı
Çok oyunculu bir oyunda ağ bağlantısı hiçbir zaman %100 güvenilir değildir. Oyuncular bunu yapar Kararsız WiFi, ağ değişikliği (4G/WiFi), istemci çökmesi veya basit bir nedenden dolayı bağlantının kesilmesi Çünkü oyunu kapatıyorlar. Sağlam bir oyun arka ucu tüm bunları şeffaf bir şekilde ele almalıdır.
7.1 Oturum Yaşam Döngüsü
[Client avvia connessione]
|
v
[Handshake + Autenticazione] -- Token JWT o session ticket
|
v
[Session creata sul server] -- SessionID, PlayerID, Timestamp
|
v
[Heartbeat loop avviato] -- Ping ogni 1-5 secondi
|
v
[Gioco attivo] -- Input/State loop
|
v
[Disconnessione rilevata] -- Timeout heartbeat (10-30s)
|
+--- [Reconnection window] -- 30-120s per riconnettersi
| |
| +--> Riconnesso: ripristina sessione, invia stato corrente
| |
| +--> Timeout: sessione distrutta, giocatore rimosso
|
v
[Sessione terminata] -- Salva statistiche, libera risorse
7.2 Kalp Atışı ve Bağlantı Kesilmesi Tespiti
Il kalp atışı ve istemcinin sunucuya (veya tam tersi) gönderdiği periyodik bir mesaj Bağlantının aktif olduğunu belirtmek için. Sunucu bir süre boyunca kalp atışı almazsa yapılandırılabilir, istemcinin bağlantısının kesildiğini varsayar. Kalp atışı aynı zamanda ölçümü de sağlar. gecikme (RTT) ve titreme.
interface ConnectionMetrics {
readonly lastHeartbeat: number;
readonly rttMs: number;
readonly jitterMs: number;
readonly packetLoss: number;
readonly rttHistory: ReadonlyArray<number>;
}
class ConnectionManager {
private readonly HEARTBEAT_INTERVAL = 2000; // 2 secondi
private readonly DISCONNECT_TIMEOUT = 15000; // 15 secondi
private readonly RECONNECT_WINDOW = 60000; // 60 secondi
private readonly sessions: Map<string, SessionData> = new Map();
handleHeartbeat(sessionId: string, clientTimestamp: number): void {
const session = this.sessions.get(sessionId);
if (!session) return;
const now = Date.now();
const rtt = now - clientTimestamp;
// Calcola jitter (variazione del RTT)
const prevRtt = session.metrics.rttMs;
const jitter = Math.abs(rtt - prevRtt);
// Crea nuovo oggetto metrics (immutabile)
const updatedMetrics: ConnectionMetrics = {
lastHeartbeat: now,
rttMs: rtt,
jitterMs: jitter,
packetLoss: session.metrics.packetLoss,
rttHistory: [...session.metrics.rttHistory.slice(-19), rtt],
};
// Aggiorna sessione con nuove metriche
this.sessions.set(sessionId, {
...session,
metrics: updatedMetrics,
});
}
checkDisconnections(): void {
const now = Date.now();
for (const [sessionId, session] of this.sessions) {
const elapsed = now - session.metrics.lastHeartbeat;
if (elapsed > this.DISCONNECT_TIMEOUT && session.status === 'connected') {
// Passa a stato "disconnesso" ma mantiene la sessione
this.sessions.set(sessionId, {
...session,
status: 'disconnected',
disconnectedAt: now,
});
console.log(`Player ${session.playerId} disconnesso. Reconnect window: 60s`);
}
if (session.status === 'disconnected') {
const disconnectElapsed = now - (session.disconnectedAt ?? now);
if (disconnectElapsed > this.RECONNECT_WINDOW) {
// Finestra di reconnection scaduta
this.destroySession(sessionId);
}
}
}
}
handleReconnect(playerId: string, newSocket: WebSocket): boolean {
// Cerca sessione disconnessa per questo player
for (const [sessionId, session] of this.sessions) {
if (session.playerId === playerId && session.status === 'disconnected') {
// Ripristina la sessione con la nuova connessione
this.sessions.set(sessionId, {
...session,
status: 'connected',
socket: newSocket,
metrics: { ...session.metrics, lastHeartbeat: Date.now() },
});
// Invia lo stato corrente del gioco al client riconnesso
this.sendFullStateSync(sessionId);
return true;
}
}
return false;
}
private destroySession(sessionId: string): void {
const session = this.sessions.get(sessionId);
if (session) {
console.log(`Sessione ${sessionId} distrutta per player ${session.playerId}`);
this.sessions.delete(sessionId);
}
}
}
8. Veritabanı: Kalıcılık Yığını
Bir oyunun arka ucu yalnızca bir veritabanı kullanmaz: bir tane kullanır yığın depolama motorları, her biri belirli bir veri türü ve erişim modeli için optimize edilmiştir.
Oyun Arka Ucu için Yığın Veritabanı
| Katmanlar | Teknoloji | Ne saklıyor | Gecikme |
|---|---|---|---|
| L1 - Bellek içi | RAM'deki veri yapıları | Çerçeve durumu, giriş arabelleği | < 0,001ms |
| L2 - Dağıtılmış önbellek | Redis/Yusufçuk | Oturum durumu, skor tablosu, eşleştirme kuyruğu | 0,1-1ms |
| L3 - İlişkisel veritabanı | PostgreSQL / HamamböceğiDB | Oyuncu profilleri, envanter, satın almalar, sıralamalar | 1-10ms |
| L4 - Zaman serisi | Zaman ÖlçeğiDB / InfluxDB | Telemetri, performans ölçümleri, analitik | 1-5ms |
| L5 - Nesne depolama | S3 / GCS | Tekrarlar, ekran görüntüleri, varlıklar, yedeklemeler | 50-200ms |
8.1 Gerçek Zamanlı Durum için Redis
Redis, bir oyunun arka ucunun veritabanı yığınının en kritik bileşenidir. Yapıları yerel veriler (sıralanmış kümeler, karmalar, listeler, pub/sub) doğrudan oyun kalıplarıyla eşleşir:
- Sıralanmış Setler liderlik tablosu ve sıralama için (ZADD, ZRANK, ZRANGE)
- Hash'ler oturum durumu için (HSET, HGETALL)
- Listeler eşleştirme kuyrukları için (LPUSH, RPOP)
- Pub/Sub oyun sunucuları ve hizmetler arasındaki iletişim için
- Akışlar olay kaynağı bulma ve denetim günlükleri için
8.2 Kalıcılık için PostgreSQL
Sunucunun yeniden başlatılmasından sonra hayatta kalması gereken tüm veriler için: oyuncu profilleri, ilerleme,
envanter, ekonomik işlemler, parti geçmişi. PostgreSQL ACID işlemleri sunar,
Yarı yapılandırılmış veriler için JSON(B) ve aşağıdaki gibi uzantılara sahip pgvector destekler
aynı zamanda beceriye dayalı eşleştirme için benzerlik aramaları.
9. Altyapı: Özel Sunucular, Konteynerler, Düzenleme
Bir yük dengeleyicinin arkasına kopyalar ekleyerek ölçeklenen durum bilgisi olmayan web sunucularının aksine, oyun sunucuları durum bilgisi olan: her örnek bir veya daha fazla aktif oyunu yönetir hafızadaki durumla birlikte. Bu, ölçeklendirmeyi ve düzenlemeyi çok daha karmaşık hale getirir.
9.1 Özel Oyun Sunucuları
Un özel oyun sunucusu ve tek bir simülasyonu çalıştıran bir süreç oyun (veya oyun dünyasının bir alanı). "Dinleyici sunucularından" farklı olarak (bir oynatıcının aynı zamanda bir sunucu görevi de görür), özel sunucu, buluttaki özel donanım üzerinde çalışır ve tutarlı performans ve adalet.
[Internet]
|
[CDN / Edge]
|
[Load Balancer (L7)]
/ \
[Gateway API] [Gateway API]
| |
+--------+--------+-------+--------+
| | | | |
[Matchmaker] [Auth] [Social] [Shop] [Leaderboard]
| |
v v
[Fleet Manager / Orchestrator] [Redis Cluster]
| |
+---+---+---+---+ |
| | | | | |
[GS] [GS] [GS] [GS] [GS] <---> [PostgreSQL]
Game Server Instances
Legenda:
GS = Game Server (una partita ciascuno)
Ogni GS e un container/pod con CPU e RAM dedicate
Il Fleet Manager scala il numero di GS in base alla domanda
9.2 Docker ile Konteynerleştirme
Her oyun sunucusu bir Docker konteynerinde çalışarak izolasyon, tekrarlanabilirlik ve dağıtım sağlar hızlı. Kapsayıcı, sunucu ikili dosyasını, yapılandırmaları ve bağımlılıkları içerir. Container'ların hafifliği, yeni örnekleri birkaç saniye içinde başlatmanıza olanak tanır.
9.3 Kubernetes ve Agones ile Düzenleme
Agonlar ve Google'ın Kubernetes'i yönetime genişleten açık kaynaklı bir projesi özel oyun sunucuları. GameServer'ı tanımlamak için Özel Kaynak Tanımları (CRD) sağlar, Filo ve FleetAutoscaler. Filo yöneticisi toplu talebi izler ve otomatik olarak ölçeklendirir kullanılabilir sunucuların sayısı.
Orkestrasyon Platformlarının Karşılaştırılması
| platformu | Tip | Bulut | Güçlü yönler |
|---|---|---|---|
| Agonlar | Açık kaynak (K8'ler) | Herhangi bir + şirket içi | Tam esneklik, satıcı bağımlılığı yok |
| Amazon GameLift | Yönetilen (AWS) | AWS | AWS entegrasyonu, FlexMatch eşleştirme |
| Azure PlayFab | Yönetilen (Azure) | Azure | Eksiksiz ekosistem (LiveOps, analiz, ekonomi) |
| Google Bulut Oyun Sunucuları | Yönetilen (GCP) | GCP | Agones tarafından desteklenmektedir, küresel ölçeklendirme |
10. Ölçekleme Deseni: Lobi, Dünya, Bölge
Tüm oyunlar aynı ölçeklendirmeye sahip değildir. Ölçeklendirme modeli cinsiyete ve oyun yapısı. İşte üç ana kalıp.
10.1 Lobi/Maç Sunucusu
FPS, Battle Royale, MOBA tarafından kullanılır. Her oyun bir sayıya sahip izole bir örnektir sabit oyuncu sayısı (10-100). Maçın sonunda sunucu yok edilir ve kaynaklar geri dönüştürülmüş. Ölçeklendirme = örnek sayısını artırın.
10.2 Dünya Sunucusu (Kalıcı Dünya)
MMO'lar tarafından kullanılır. Kalıcı ve bölünmüş bir dünya alanlar, her biri yönetilen özel bir sunucudan. Oyuncular şeffaf bir aktarımla bölgeler arasında hareket eder. Bölge sunucuları sınırları yönetmek için birbirleriyle iletişim kurar.
10.3 Örnek Bölgeler
Hibrit: Dünya kalıcıdır ancak belirli alanlar (zindanlar, baskınlar, arenalar) gelir somutlaştırılmış dinamik olarak. Her oyuncu grubu kendi kopyasını alır bölgenin. Bu, yüksek yoğunluklu alanları aşırı yüklemeden ölçeklendirmenize olanak tanır. dünya sunucusu.
LOBBY/MATCH SERVER (FPS, BR):
[Matchmaker] --> [Server Pool]
|
+---------+---------+
| | |
[Match 1] [Match 2] [Match 3]
10 players 10 players 10 players
(30 min) (25 min) (nuovo)
WORLD SERVER (MMO):
[World Manager]
|
+---+---+---+---+
| | | | |
[Zona A][Zona B][Zona C][Zona D]
Citta Foresta Dungeon PvP
200 p. 50 p. 30 p. 80 p.
^ ^
|--- Handoff zone ---|
INSTANCED ZONES (MMO + Dungeon):
[Zona Citta] --> [Ingresso Dungeon]
|
+---------+---------+
| | |
[Dungeon [Dungeon [Dungeon
Copia 1] Copia 2] Copia 3]
5 players 5 players 5 players
11. Oyun için Hizmet Olarak Arka Uç: Platformlar
Herkes sıfırdan bir oyun arka ucu oluşturmak zorunda değildir (veya bunu istemez). Platformlar var Kullanıma hazır bileşenler sunan Hizmet Olarak Arka Uç (BaaS): eşleştirme, skor tablosu, kimlik doğrulama, depolama, oyun içi ekonomi. Seçim bütçeye, kontrole bağlıdır İstenilen ve oyun türü.
Oyun İçin BaaS Platformlarının Karşılaştırması
| platformu | Sunucu dili | Açık Kaynak | Kendi Kendine Barındırılan | Güçlü yönler | Şunun için idealdir: |
|---|---|---|---|---|---|
| Nakama | Git, TS, Lua | Si | Si | Gerçek zamanlı, çöpçatanlık, depolama | Bağımsız, orta boy, mobil |
| Kolyseus | TypeScript | Si | Si | Yetkili, otomatik durum senkronizasyonu | Tarayıcı oyunları, prototipler |
| PlayFab | C# (Azure İşlevleri) | No | No | LiveOps, ekonomi, analiz | AAA, mobil F2P |
| Amazon GameLift | C++, C# | No | No | Filo yönetimi, FlexMatch | AAA çok oyunculu |
| Foton | C# | No | Kısmi | Birlik entegrasyonu, geçiş | Unity oyunları, mobil |
| Ayna | C# | Si | Si | Unity ağı, HLAPI değişimi | Unity bağımsız oyunları |
Yap vs Satın Al: Seçim Kriterleri
- BaaS kullanın eğer: sınırlı bütçe, öncelikli pazara çıkış süresi, standart tür (gündelik, bulmaca, kart oyunu), küçük ekip
- Özel oluştur se: aşırı gecikme gereksinimleri (<20 ms), benzersiz oyun mantığı, ağ kodu ve hile önleme üzerinde tam kontrol ihtiyacı, ölçek >100.000 CCU
- Hibrit yaklaşım: Kimlik doğrulama, skor tablosu ve sosyal medya için BaaS kullanın ancak oyun mantığı için özel oyun sunucusu oluşturun
12. Performans Metrikleri: Neler İzlenmeli?
Gözlemlenebilirliği ve saatli bombası olmayan bir oyun arka ucu. Bunlar metrikler Her ekibin üretimde izlemesi gereken temel ilkeler.
Temel Oyun Arka Uç Metrikleri
| Metrik | Tanım | Hedef | Alarm |
|---|---|---|---|
| CCU | Eşzamanlı Bağlı Kullanıcılar | Bu türe bağlıdır | > %90 kapasite |
| Tick Süresi (p99) | Kene işlem süresi | Onay bütçesinin < %80'i | > Bütçenin %90'ı |
| RTT (p50 / p95 / p99) | Gidiş-Dönüş Süresi istemci-sunucu | p50 < 50 ms, p99 < 150 ms | p99 > 200ms |
| Paket Kaybı | Kayıp paket yüzdesi | < %1 | > %3 |
| Titreşim | Gecikme değişimi | < 10ms | > 30ms |
| Mesaj/sn | Sunucu başına saniyede işlenen ileti sayısı | > 10.000 mesaj/sn | < 5K mesaj/sn |
| Oyuncu başına bant genişliği | Oyuncu başına KB/s çıktısı | 5-30KB/sn | > 50 KB/sn |
| Yeniden Bağlanma Oranı | Başarılı yeniden bağlantıların yüzdesi | > %90 | < %70 |
| Sunucu Başlangıç Süresi | Yeni bir örnek başlatma zamanı | < 10s | > 30'lar |
// Struttura per le metriche di un game server in Go
type TickMetrics struct {
TickNumber uint64
Duration time.Duration
PlayersActive int
InputsProcessed int
MessagesOut int
BytesOut int64
}
type ServerMetrics struct {
mu sync.RWMutex
tickDurations []time.Duration // ring buffer ultimi 1000 tick
totalTicks uint64
overrunCount uint64
ccu int32
}
func (m *ServerMetrics) RecordTick(metrics TickMetrics) {
m.mu.Lock()
defer m.mu.Unlock()
idx := m.totalTicks % 1000
m.tickDurations[idx] = metrics.Duration
m.totalTicks++
if metrics.Duration > tickBudget {
m.overrunCount++
log.Printf(
"TICK OVERRUN #%d: tick=%d duration=%v budget=%v players=%d",
m.overrunCount,
metrics.TickNumber,
metrics.Duration,
tickBudget,
metrics.PlayersActive,
)
}
}
// Calcola il percentile p99 della durata dei tick
func (m *ServerMetrics) P99TickDuration() time.Duration {
m.mu.RLock()
defer m.mu.RUnlock()
sorted := make([]time.Duration, len(m.tickDurations))
copy(sorted, m.tickDurations)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i] < sorted[j]
})
idx := int(float64(len(sorted)) * 0.99)
return sorted[idx]
}
13. Hepsini Bir Araya Getirmek: Eksiksiz Mimari
Burada modern bir çok oyunculu oyun arka ucunun tüm bileşenlerine ve bunların nasıl birbirleriyle etkileşime girerler.
+==============================================================+
| CLIENT LAYER |
+==============================================================+
| [Game Client] [Game Client] [Game Client] [Game Client] |
| | | | | |
| WebSocket/UDP WebSocket/UDP WebSocket/UDP WebSocket/UDP |
+======|==============|==============|==============|==========+
| | | |
+======|==============|==============|==============|==========+
| EDGE LAYER |
+==============================================================+
| [CDN] [DDoS Protection] [Load Balancer] [SSL Termination]|
+==========================|===================================+
|
+==========================|===================================+
| GATEWAY LAYER |
+==============================================================+
| [API Gateway] [WebSocket Gateway] |
| REST per login, Routing verso il game server |
| shop, profili corretto per la partita |
+=========|========================|===========================+
| |
+---------+----------+ +--------+--------+
| SERVICE LAYER | | GAME SERVER |
+====================+ | LAYER |
| [Auth Service] | +=================+
| [Matchmaker] | | [GS Instance 1] |
| [Leaderboard] | | [GS Instance 2] |
| [Shop/Economy] | | [GS Instance 3] |
| [Social/Chat] | | [GS Instance N] |
| [Analytics] | +=================+
+========|===========+ |
| |
+========|======================|===========================+
| DATA LAYER |
+============================================================+
| [Redis] [PostgreSQL] [TimescaleDB] [S3/GCS] |
| Sessions, Profili, Telemetria, Replay, |
| Leaderboard, Inventario, Metriche, Assets, |
| Match Queue Transazioni Analytics Backup |
+============================================================+
| |
| [Agones / GameLift] - Fleet Management & Autoscaling |
| [Prometheus + Grafana] - Monitoring & Alerting |
| [ELK / Loki] - Logging centralizzato |
+============================================================+
Sonraki Adımlar
Bu makalede bir oyunun arka ucunun anatomisini tam olarak anladınız çok oyunculu: ağ mimarisinden taşıma protokollerine, yetkili oyun döngüsünden bağlantı yönetiminden ölçeklendirme modellerine kadar mesaj serileştirmeye kadar. Her bileşenin diğerleriyle nasıl etkileşime girdiğini ve mimari değiş tokuşları gördünüz. tasarım seçimlerine rehberlik eder.
içinde sonraki makale çok oyunculu ağ kodunun kalbine gireceğiz: durum senkronizasyonu. İstemci tarafını derinlemesine inceleyeceğiz tahmin, sunucu mutabakatı, enterpolasyon, gecikme telafisi ve geri alma ağ kodu. Modern oyunların nasıl sorunsuz bir deneyim yanılsaması yarattığını göreceğiz. ağ gecikmesi.
Ek Kaynaklar
- Gabriel Gambetta: "Hızlı Tempolu Çok Oyunculu" - Oyunlar için istemci-sunucu mimarisi üzerine referans makaleleri serisi
- Valve Geliştirici Wiki'si: "Source Multiplayer Networking" - Source Engine ağ koduna ilişkin teknik belgeler
- Glenn Fiedler: "Oyun Ağı" - Protokoller, durum senkronizasyonu ve güvenlikle ilgili eksiksiz seri
- Agones Belgeleri: Kubernetes'te oyun sunucularını düzenlemeye yönelik eksiksiz kılavuz
- Colyseus.io: Otomatik durum senkronizasyonuna sahip açık kaynaklı Node.js oyun sunucusu çerçevesi
- Heroic Labs'tan Nakama: Eşleştirme, depolama ve gerçek zamanlı çok oyunculu özelliklere sahip açık kaynaklı oyun sunucusu







