Gereksinimler ve Ön Hazırlık
Projeye başlamadan önce bilgisayarınızda Node.js'in güncel bir LTS (Long Term Support) sürümünün yüklü olması gerekir. Projenin bağımlılıklarını yönetmek için npm veya yarn paket yöneticisini kullanacağız.
- Node.js: v20 veya üzeri sürüm.
- Socket.io: İstemci ve sunucu arasında çift yönlü iletişim için.
- Chess.js: Satranç kurallarını, hamle geçerliliğini ve oyun durumunu yönetmek için kullanılan kütüphane.
- Express: HTTP sunucusu oluşturmak için gerekli temel yapı.
Proje dizininizi oluşturun ve gerekli paketleri yüklemek için terminalinizde şu komutları çalıştırın:
mkdir satranc-oyunu
cd satranc-oyunu
npm init -y
npm install express socket.io chess.js
Sunucu Tarafı Yapılandırması ve Socket.io Kurulumu
Sunucu tarafında Socket.io kullanarak istemcilerden gelen bağlantıları dinleyeceğiz. İlk adım olarak bir server.js dosyası oluşturup Express ve Socket.io entegrasyonunu sağlayalım.
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server);
io.on('connection', (socket) => {
console.log('Bir oyuncu bağlandı:', socket.id);
socket.on('disconnect', () => {
console.log('Oyuncu ayrıldı:', socket.id);
});
});
server.listen(3000, () => {
console.log('Sunucu 3000 portunda çalışıyor.');
});
Bu kod bloğu, temel bir WebSocket sunucusu başlatır. io.on('connection') fonksiyonu, yeni bir tarayıcı sekmesi bağlandığında tetiklenir ve her bağlantıya benzersiz bir socket.id atar.
Satranç Mantığını Entegre Etme
Satranç kurallarını sıfırdan yazmak oldukça karmaşıktır. Bu yüzden chess.js kütüphanesini kullanarak hamlelerin geçerliliğini kontrol edeceğiz. Sunucuda her oda (room) için ayrı bir oyun durumu tutacağız.
const { Chess } = require('chess.js');
const games = {}; // Oda ID'lerini anahtar olarak tutar
io.on('connection', (socket) => {
socket.on('joinGame', (room) => {
socket.join(room);
if (!games[room]) {
games[room] = new Chess();
}
socket.emit('gameState', games[room].fen());
});
});
Burada FEN (Forsyth-Edwards Notation), satranç tahtasının o anki durumunu temsil eden standart bir metin dizisidir. İstemciye bu veriyi göndererek tahtanın güncel kalmasını sağlıyoruz.
İstemci Tarafı ve Hamle Yönetimi
İstemci tarafında, kullanıcı bir hamle yaptığında bu hamleyi sunucuya göndermeli ve sunucu da bu hamleyi diğer oyuncuya iletmelidir. İşte hamle doğrulama süreci:
socket.on('makeMove', ({ room, move }) => {
const game = games[room];
try {
const result = game.move(move);
if (result) {
io.to(room).emit('moveMade', result);
}
} catch (e) {
socket.emit('error', 'Geçersiz hamle!');
}
});
Bu yapı, sunucunun tek "doğruluk kaynağı" (single source of truth) olmasını sağlar. İstemci sadece hamleyi önerir, sunucu ise kurallara uygunluğunu kontrol eder.
Performans ve Güvenlik Karşılaştırması
Aşağıdaki tablo, WebSocket tabanlı sistemlerde kullanılan farklı yöntemlerin karşılaştırmasını sunar:
| Yöntem | Avantajı | Dezavantajı |
|---|---|---|
| Polling (HTTP) | Basit yapı | Yüksek sunucu yükü, gecikme |
| Websocket | Gerçek zamanlı, düşük gecikme | Sunucu durum yönetimi gerektirir |
| WebRTC | Eşten eşe (P2P) hız | Karmaşık bağlantı kurulumu |
Kritik Güvenlik Uyarısı: Üretim ortamında (production) Socket.io kullanırken mutlaka CORS (Cross-Origin Resource Sharing) ayarlarını yapılandırın. Ayrıca, istemciden gelen verileri her zaman doğrulayın; asla istemcinin gönderdiği hamleye doğrudan güvenmeyin.
Yaygın Hatalar ve Debug İpuçları
Geliştirme sürecinde karşılaşabileceğiniz en yaygın sorunlardan biri, oyun durumunun senkronize olmamasıdır. Eğer bir oyuncu sayfayı yenilerse, mevcut oyun durumunu sunucudan tekrar talep etmelidir. Ayrıca, socket.id değerlerinin oturum yönetimi için tek başına yeterli olmadığını unutmayın; daha güvenli bir sistem için JWT (JSON Web Token) kullanmayı düşünebilirsiniz.
Debug yapmak için tarayıcı konsolunu ve sunucu tarafındaki console.log çıktılarını kullanın. Socket.io'nun kendi debug modunu açmak için terminalde DEBUG=socket.io* node server.js komutunu çalıştırabilirsiniz.
Sıkça Sorulan Sorular
Websocket bağlantısı koptuğunda ne olur?
Websocket bağlantıları geçicidir. Bağlantı koptuğunda istemci tarafında otomatik yeniden bağlanma (reconnect) mantığı kurmalı ve sunucudan mevcut oyun durumunu tekrar istemelisiniz.
Aynı anda kaç oyun yönetebilirim?
Node.js tek iş parçacıklı (single-threaded) olduğu için, çok sayıda oyun için bellek yönetimine dikkat etmelisiniz. Binlerce oyun için Redis gibi bir veritabanı ile oyun durumlarını dışarıda tutmak daha verimlidir.
Satranç taşlarının hareketlerini görselleştirmek için ne kullanmalıyım?
Chessboard.js veya React-Chessboard gibi kütüphaneler, FEN dizilerini görsel bir tahtaya dönüştürmek için en popüler çözümlerdir.
Sunucu tarafında hamle geçmişini tutmalı mıyım?
Evet, oyunun analiz edilebilmesi veya hata durumunda geri alınabilmesi (undo) için hamle geçmişini bir dizide tutmak profesyonel bir yaklaşımdır.
Bu yapı mobil cihazlarda çalışır mı?
Evet, WebSocket protokolü hem masaüstü hem de mobil tarayıcılarda standart olarak desteklenmektedir.
Oyun Sunucusunu Ölçeklendirme ve Redis ile Durum Yönetimi
Uygulamanız büyüdükçe, tek bir Node.js sunucusu tüm WebSocket bağlantılarını yönetmekte zorlanabilir. Socket.io'nun varsayılan bellek içi (in-memory) adaptörü, birden fazla sunucu örneği (instance) çalıştırdığınızda senkronizasyon sorunlarına yol açar. Bu durumda, oyun durumunu merkezi bir noktada tutmak için Redis kullanmak en sağlıklı yaklaşımdır.
Redis adaptörünü projenize dahil ederek, farklı sunuculara bağlı olan oyuncuların aynı oyun odasında birbirini görmesini sağlayabilirsiniz:
// Sunucu tarafında Redis adaptörü kurulumu
const { createClient } = require("redis");
const { createAdapter } = require("@socket.io/redis-adapter");
const pubClient = createClient({ url: "redis://localhost:6379" });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
});
Üretim Ortamında (Deployment) Dikkat Edilmesi Gerekenler
Geliştirme ortamından canlıya geçiş yaparken, WebSocket bağlantılarının sürekliliğini sağlamak için ters vekil sunucu (reverse proxy) yapılandırmanızı optimize etmelisiniz. Nginx, WebSocket trafiğini düzgün bir şekilde iletmek için özel başlık (header) yapılandırmalarına ihtiyaç duyar.
Nginx konfigürasyonunuzda aşağıdaki ayarların olduğundan emin olun:
location /socket.io/ {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
Güvenlik İçin Hız Sınırlama (Rate Limiting)
WebSocket üzerinden gönderilen her hamle, sunucuda CPU yoğunluklu bir doğrulama işlemine neden olur. Kötü niyetli kullanıcıların sunucuyu hamle yağmuruna tutmasını engellemek için socket.io-ratelimit veya benzeri bir middleware kullanmalısınız.
// Basit bir hamle hızı sınırlayıcı mantığı
const rateLimit = new Map();
io.on("connection", (socket) => {
socket.on("make-move", (data) => {
const lastMove = rateLimit.get(socket.id) || 0;
const now = Date.now();
if (now - lastMove < 500) { // 500ms'den hızlı hamle yapılamaz
return socket.emit("error", "Çok hızlı hamle yapıyorsunuz!");
}
rateLimit.set(socket.id, now);
// Hamle işleme mantığı...
});
});
Test Süreçleri ve Birim Testleri
Satranç mantığınızın doğruluğunu kanıtlamak için chess.js kütüphanesini test ederken "Jest" gibi bir test çatısı kullanın. Özellikle oyun sonu durumları (mat, pat, beraberlik) için uçtan uca testler yazmak, canlıya çıkmadan önce hataları yakalamanızı sağlar.
| Test Senaryosu | Beklenen Sonuç |
|---|---|
| Geçersiz bir hamle yapıldığında | Sunucu hamleyi reddetmeli ve hata dönmeli |
| Şah çekildiğinde | Sunucu "check" bayrağını tetiklemeli |
| Bağlantı koptuğunda | Oyun durumu veritabanında korunmalı |
Sonuç
Node.js ve Socket.io kullanarak bir satranç oyunu geliştirmek, gerçek zamanlı sistemlerin çalışma prensiplerini anlamak için eşsiz bir deneyimdir. Bu rehberde temel bir sunucu altyapısı, hamle doğrulama mantığı ve WebSocket iletişimi üzerinde durduk. Bir sonraki adım olarak, oyuncuların birbirini bulabileceği bir eşleşme (matchmaking) sistemi ekleyebilir veya veritabanı (MongoDB gibi) kullanarak oyun kayıtlarını kalıcı hale getirebilirsiniz.
Kod güvenliği notu: Bu makaledeki örnekler eğitim amaçlıdır. Canlı bir uygulamada kullanıcı girdilerini sanitize etmeyi, giriş kontrollerini artırmayı ve sunucu kaynaklarını korumak için hız sınırlayıcı (rate limiting) mekanizmalar kullanmayı ihmal etmeyin.

Yorumlar (0)
Yorum Yaz