Socket.io bağlantısı işin en basit kısmıydı. Asıl zorlanacağımız yer kullanıcıların attığı http-request'te o kullanıcının soket'ine erişmek ve onun üzerinde değişiklikler yapmak.
Geçenki serimizde kullandığımız sistem, express modülü ve socket.io modülünü farklı portlar üzerinden bağımsız şekilde çalıştırıyordu. Bu sistem'i tek port üzerinden yönetebiliriz. İlk önce onu yapalım.
Socket.io'yu express üzerinde aynı portta çalışırmak için http modülüne ihtiyacımız var. Bunun için server/uses.js dosyasında şu değişiklikleri yapalım:
...
const http = require('http'); // üst tarafta dahil ediyoruz
...
const server = http.createServer(app); // buradaki app index.js'teki uses(app)
server.listen({
port: 3000,
host: 'localhost',
}, () => {
console.log('çalışıyor');
});
const io = socket(server);
Connector.init(io);
...
Ardından express'te kullandığımız listen methodunu da kaldırmalıyız. O yüzden server/index.js dosyasını tamamen şuna çeviriyoruz:
const express = require('express');
const uses = require('./uses');
// sunucumu oluşturalım
const app = express();
uses(app);
Bizim aynı zamanda yapmamız gereken bir şey de socket.io'ya atılan isteğin express'in kullanıdığı session değerini değiştirebilmesi. Yani örnek olarak gameStart adında bir emitter yazdığımızı düşünelim. gameStart emitter'ını tetiklediğimizde isteği atan kişinin bir oyun istediğinin belirtiriz. Böylelikle uygulama üzerinde başka oyun arayan bir kişiyi bulmuş oluruz.
Yapmamız gereken ilk şey NOY#7de de açıkladığımız gibi bir http controller yazmak olacaktır.
server/api/models/games.js dosyasını oluşturalım ve şunları yazalım:
const Connector = require('../../socket');
class Games {
getSocket(socketID, sessionID) {
return new Promise((resolve, reject) => {
var socket;
var list = Connector.io.sockets.connected;
if (socketID) {
socket = list[socketID];
if (socket !== undefined) {
return resolve(socket);
}
}
reject({
error_code: 3,
message: 'Socket not found.',
});
});
}
}
module.exports = new Games();
Bu modeli server/api/models/index.js dosyasında belirtelim:
...
exports.Games = require('./games');
server/api/controllers/game.js dosyasını oluşturalım ve şunları yazalım:
const {
Games,
} = require('../models');
module.exports = (request, response) => {
switch(request.query.event) {
case 'new':
// burası istek atanın socket'ini bulmak için gerekli alan
var cookieIO = request.cookies.io;
Games.getSocket(cookieIO, request.sessionID).then(socket => {
// bunu yaparak kullanıcının oyun onamak istediğin belirtiyoruz şu an için
socket.wantsToGame = new Date();
response.end();
}, err => {
response.status(200);
response.send(err);
})
break;
default:
response.status(400);
response.end();
break;
}
}
Bu controller'ı server/api/controllers/index.js dosyasında belirtelim:
...
exports.game = require('./game');
Şimdi bu controller'ı bir post isteği ile karşılamak için server/api/routes.js dosyasında şöyle düzenlemeler yapalım:
const {
user,
register,
validate,
game,
} = require('./controllers');
module.exports = {
post: {
'/v1/user': user,
'/v1/register': register,
'/v1/validate': validate,
'/v1/game': game, // buraya ekledik.
},
get: {
},
}
Böylelikle /v1/game?event=new
şeklinde gelen bir isteği karşılayabileceğiz.
Ben kullanımı ve yapısı çok kolay olan wildemmiter'ı kullanmayı tercih ettim. Bununla ilgili bir makale de yazmıştım. Bunu kullanacağımız yer daha çok socket işlemlerinde olacağı için server/socket/index.js dosyasını şöyle düzenleyelim:
const Wildemitter = require('wildemitter');
...
// burada Connector sınıfına Wildemitter özelliğini de eklemiş oluyoruz
Wildemitter.mixin(Connector);
module.exports = new Connector();
Şimdi game controller üzerinde bir kişinin oyun isteğinin kabul olduğunu bildirmek için bir yapı ekleyelim. Bunun için server/controllers/game.js dosyasında şu değişiklikleri yapıyoruz:
const {
Games,
} = require('../models');
const Connector = require('../../socket');
module.exports = (request, response) => {
switch(request.query.event) {
case 'new':
// burası istek atanın socket'ini bulmak için gerekli alan
var cookieIO = request.cookies.io;
Games.getSocket(cookieIO, request.sessionID).then(socket => {
socket.wantsToGame = new Date();
// eğer önceden de istek atmışsa çakışma olmaması için siliniyor
Connector.off(`player:finded:${socket.id}`);
// boşta oyuncu bulunduğu zaman çalışıyor
Connector.on(`player:finded:${socket.id}`, (user) => {
Connector.off(`player:finded:${socket.id}`);
delete socket.wantsToGame;
console.log('oyuncu bulundu');
});
// listede wantsToGame olan kişileri buluyor
var list = Connector.io.sockets.connected;
for(var key in list) {
if (list[key].wantsToGame !== undefined && key !== cookieIO) {
Connector.emit(`player:finded:${key}`, list[key]);
// bir oyuncu bulduğu için kendine de haber etsin
Connector.emit(`player:finded:${cookieIO}`, list[cookieIO]);
}
}
response.end();
}, err => {
response.status(200);
response.send(err);
})
break;
default:
response.status(400);
response.end();
break;
}
}
Burada gördüğünüz Connector.on,off,emit gibi ifadeler wildemmiter'ın özellikleri aslında uygulamamızın her yerinden erişilebilecek bir yapı kurmuş oluyoruz
Connector.on('player:finded:<socket.id>', [Function])
Burada bir dinleyici oluşturuyoruz ve bunun bir tetikleyici tarafından çalıştırılmasını istiyoruz. Bütün mesele bu aslında.
Bu örnekleri denemek için bir yapı oluşturmalıyız onun için routes.js dosyasında şu değişiklikleri yapalım:
const {
user,
register,
validate,
game,
} = require('./controllers');
const express = require('express');
const path = require('path');
module.exports = {
...
use: {
'/examples': express.static(path.join(__dirname, '../examples'))
},
}
Gördüğünüz üzere bir üst dizindeki examples dosyasını statik olarak açtık. Şimdi use diye açtığımız objenin tanıtılması için uses.js dosyasında şunları yapalım:
...
for(var key in routes.post) {
app.post(key, routes.post[key]);
}
for(var key in routes.get) {
app.get(key, routes.get[key]);
}
for(var key in routes.use) {
app.use(key, routes.use[key]);
}
...
Böylelikle yanlış yaptığımız diğer post ve get methodlarını düzeltmiş olduk. Bunun ardından asıl önemli olan statik dosyalarımızı oluşturacağımız dosyamızı oluşturalım yani server/examples ve içine game-test.html adında dosya oluşturalım:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Socket Test</title>
</head>
<body>
<div id="result">Bağlantı Yok</div>
<button onclick="gonder()">Gönder</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.slim.js"></script>
<script>
var socket = io('http://localhost:3000');
socket.on('connect', function () {
document.getElementById('result').innerHTML = 'Bağlandı';
})
function gonder() {
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("POST", "http://localhost:3000/v1/game?event=new");
xhr.setRequestHeader("content-type", "application/json");
xhr.send();
}
</script>
</body>
</html>
Sunucumuzu başlatalım ve localhost:3000/examples/game-test.html adresine girelim. Bağlandı yazısı ile karşılaşıyorsak ne güzel :) Şimdi gizli sekmede (Ctrl+Shift+N) giriş yapalım ve aynı adrese tekrar girelim şimdi iki tarayıcı sekmesi de açık birinden bağlan seçeneğine tıklayalım ve diğer kişiden de tıklayalım. Sunucumuzu başlattığımız terminal'e bakalım ne görüyoruz :) iki adet "oyuncu bulundu" yazısı. Tamam o zaman başardık :D
Evet bu sayı en zorlandığım sayı oldu. Bunun için çok araştırmalar yaptım, çok mantık kurdum, çoğu yerde takıldım ve çoğu kişiye sorular sordum. Size yapı karışık gelmiş olabilir, kanalımdaki videodan izleyerek daha da iyi anlayabilirsiniz. Bundan sonraki bölümlerde artık bunun için bir client yazmaya başlayacağız. Ben React, React Native düşünüyorum siz ne düşünüyorsunuz? Fiikirlerinizi yorumlarda bekliyorum :) Görüşmek üzere :)
Düşündüklerin nedir ?