Node.js'de bir şablonu yayınlama ve paylaşma

0 Hisse senetleri
0
0
0
0

giriiş

Pub/Sub modeli, bir yayıncının veri/mesaj ürettiği ve abonelerin belirli türdeki mesajları almak için abone olduğu çok yönlü bir tek yönlü mesajlaşma modelidir. İletişimleri aracılık etmek için eşler arası bir mimari veya bir mesaj aracısı kullanılarak uygulanabilir.


Yukarıdaki görsel, yayıncının aracılar olmadan doğrudan abonelere mesaj gönderdiği Eşler Arası Yayınlama/Abonelik modelini göstermektedir. Abonelerin mesajı alabilmeleri için yayıncının adresini veya uç noktasını bilmeleri gerekir.


Yukarıdaki görselde, Pub/Sub modeli, yayıncılar ve aboneler arasında mesaj göndermek için merkezi bir merkez olarak bir mesaj aracısı kullanır. Aracı, mesaj alışverişine aracılık eder ve mesajları yayıncılardan abonelere dağıtır. Abone düğümleri, yayıncı yerine doğrudan aracıya abone olur. Bir aracının varlığı, hem yayıncı hem de aboneler yalnızca aracıyla etkileşim kurduğu için sistem düğümlerinin izolasyonunu artırır. Bu eğitimde, bu modeli daha ayrıntılı olarak göstermek için gerçek zamanlı bir sohbet uygulaması oluşturacaksınız.

Ön koşullar
  • İşletim sisteminize Node.js (sürüm >= 12) kuruludur.
  • VSCode gibi bir kod düzenleyici
  • Redis makinenize kuruldu.
  • HTML, DOM, VanillaJS ve WebSocket hakkında temel bilgi.

Adım 1 – Sunucu tarafı uygulaması

Sunucu tarafını çalıştırmaya başlamak için, başlangıç komutunu kullanarak temel bir Nodejs uygulamasını başlatıyoruz:

npm init -y

Yukarıdaki komut varsayılan bir package.json dosyası oluşturur.

Daha sonra, bu derlemenin tamamı boyunca gerekli olan WebSocket (ws) bağımlılık paketini yüklüyoruz:

npm install ws

Sunucu tarafı uygulaması basit bir sunucu tarafı sohbet uygulaması olacak. Aşağıdaki iş akışını izleyeceğiz:

  1. Bir sunucu kurun
  2. HTML dosyasını tarayıcıda görüntülemek için okuyun
  3. Bir WebSocket bağlantısı kurun.
Sunucu kurulumu

Dizininizde app.js adında bir dosya oluşturun ve içine aşağıdaki kodu yazın:

const http = require("http");
const server = http.createServer((req, res) => {
res.end("Hello Chat App");
});
const PORT = 3459;
server.listen(PORT, () => {
console.log(`Server up and running on port ${PORT}`);
});

Yöntem createServer Modülde http Yerel Düğüm.js Sunucuyu başlatmak için kullanılacaktır. Sunucunun istekleri dinleyeceği port ayarlanır ve oluşturulan sunucu örneğindeki listen metodu, belirtilen porttan gelen istekleri dinlemek için çağrılır.

Emir düğüm uygulaması.js Terminalinizde çalıştırın ve aşağıdaki gibi bir yanıt almalısınız:

OutputServer is up and running on port 3459

Tarayıcınızda bu portu talep ederseniz, yanıt olarak aşağıdakine benzer bir şey almalısınız:


HTML dosyasını tarayıcıda görüntülemek için okuyun

Ana klasörde index.html adında bir dosya oluşturun ve aşağıdaki kodu kopyalayın:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>Serving HTML file</p>
</body>
</html>

Bu, Hello'yu oluşturan temel bir HTML dosyasıdır. Şimdi, sunucumuza bir HTTP isteği gönderildiğinde bu dosyayı okuyup yanıt olarak oluşturmamız gerekiyor.

// app.js
const server = http.createServer((req, res) => {
const htmlFilePath = path.join(__dirname, "index.html");
fs.readFile(htmlFilePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end("Error occured while reading file");
}
res.writeHead(200, { "Content-Type": "text/html" });
res.end(data);
});
});

Burada dahili yol modülünü ve fonksiyonu kullanıyoruz katılmak Bunu yol parçalarını birleştirmek için kullanırız. Daha sonra şu işlevi kullanırız: readFile Dosyayı okumak için dizin.html Eşzamansız olarak kullanılır. İki bağımsız değişken gerektirir: okunacak dosyanın yolu ve geri okuma. Durum kodu 500 Bir yanıt başlığı gönderilir ve istemciye bir hata mesajı gönderilir. Veriler başarıyla okunursa, bir başarı durum kodu döndürülür. 200 Yanıt başlığını ve yanıt verilerini istemciye (bu durumda dosya içeriği) gönderiyoruz. UTF-8 kodlaması gibi herhangi bir kodlama belirtilmezse, ham arabellek döndürülür. Aksi takdirde, dosya HTML İade edilir.

Tarayıcınızda sunucuya bir istekte bulunun ve aşağıdaki sonucu elde etmelisiniz:


WebSocket bağlantısı kurma
// app.js
const webSocketServer = new WebSocket.Server({ server });
webSocketServer.on("connection", (client) => {
console.log("successfully connected to the client");
client.on("message", (streamMessage) => {
console.log("message", streamMessage);
distributeClientMessages(streamMessage);
});
});
const distributeClientMessages = (message) => {
for (const client of webSocketServer.clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
}
};

Önceki kod satırında, adında yeni bir WebSocket sunucusu oluşturduk. webSocketServer Bunu oluşturup sunucuya gönderiyoruz. HTTP Mevcut olana bağlanıyoruz. Bu sayede hem standart HTTP isteklerini hem de WebSocket bağlantılarını tek bir 3459 portu üzerinden yönetebiliyoruz.

on() bağlantı olayı, başarılı bir WebSocket bağlantısı kurulduğunda tetiklenir. İşlevdeki istemci geri çağırmak WebSocket bağlantı nesnesi, bir istemciye bağlantıyı temsil eder. Mesaj gönderip almak ve istemci mesajları gibi olayları dinlemek için kullanılır.

distributeClientMessages işlevi, gelen mesajları bağlı tüm istemcilere göndermek için burada kullanılır. Bir message argümanı alır ve sunucumuza bağlı istemciler üzerinde yineleme yapar. Ardından her istemcinin bağlantı durumunu kontrol eder (readyState === WebSocket.OPEN). Bu, sunucunun yalnızca açık bağlantıları olan istemcilere mesaj göndermesini sağlamak içindir. İstemci bağlantısı açıksa, sunucu mesajı client.send(message) yöntemini kullanarak o istemciye gönderir.

Adım 2 – İstemci tarafı uygulaması

İstemci tarafını çalıştırmak için dosya dizin.html Kendimizi biraz değiştiriyoruz.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>Pub/Sub Pattern with Chat Messaging</p>
<div id="messageContainer"></div>
<form id="messageForm">
<form id="messageForm">
<input
type="text"
id="messageText"
placeholder="Send a message"
style="
padding: 10px;
margin: 5px;
border-radius: 5px;
border: 1px solid #ccc;
outline: none;
"
onfocus="this.style.borderColor='#007bff';"
onblur="this.style.borderColor='#ccc';"
/>
<input
type="button"
value="Send Message"
style="
padding: 10px;
margin: 5px;
border-radius: 5px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
"
onmouseover="this.style.backgroundColor='#0056b3';"
onmouseout="this.style.backgroundColor='#007bff';"
/>
</form>
</form>
<script>
const url = window.location.host;
const socket = new WebSocket(`ws://${url}`);
</script>
</body>
</html>

Bu kod parçasına, bir girdi ve mesaj göndermek için bir düğme içeren bir form öğesi ekledik. WebSocket bağlantıları istemciler tarafından başlatılır ve başlangıçta kurduğumuz bir WebSocket sunucusuyla iletişim kurmak için, kullanmak istediğimiz sunucuyu belirten ws://url'yi belirten bir WebSocket nesnesi örneği oluşturmamız gerekir. Oturum açtığınızda URL değişkeni, sunucumuzun dinlediği 3459 numaralı bağlantı noktasına bağlanan URL'yi içerecektir.

// app.js
console.log("url", url); // localhost:3459
console.log("socket", socket); // { url: "ws://localhost:3459/", readyState: 0, bufferedAmount: 0, onopen: null, onerror: null, onclose: null, extensions: "", protocol: "", onmessage: null, binaryType: "blob" }

Yani tarayıcınızda sunucuya bir istekte bulunduğunuzda şunu görmelisiniz:


İstemciden sunucuya mesaj gönderip sunucudan mesaj alabilmemiz için scriptimizi güncelleyelim.

// index.html
<script>
const url = window.location.host;
const socket = new WebSocket(`ws://${url}`);
const messageContainer = document.getElementById("messageContainer");
socket.onmessage = function (eventMessage) {
eventMessage.data.text().then((text) => {
const messageContent = document.createElement("p");
messageContent.innerHTML = text;
document
.getElementById("messageContainer")
.appendChild(messageContent);
});
};
const form = document.getElementById("messageForm");
form.addEventListener("submit", (event) => {
event.preventDefault();
const message = document.getElementById("messageText").value;
socket.send(message);
document.getElementById("messageText").value = "";
});
</script>

Daha önce de belirtildiği gibi, istemciden (tarayıcıdan) sunucumuza istek gönderen URL'yi alıyoruz ve bu URL ile yeni bir WebSocket nesnesi örneği oluşturuyoruz. Ardından, düğmeye tıklayarak Mesaj Gönder Form öğesinde bir olay oluşturuyoruz. Kullanıcının kullanıcı arayüzüne girdiği metin, giriş öğesinden çıkarılır ve mesajı sunucuya göndermek için soket örneğindeki gönderme yöntemi çağrılır.

Etkinlik mesajda Soket nesnesinde çağrılır ve sunucudan bir mesaj alındığında tetiklenir. Alınan mesajın kullanıcı arayüzünü güncellemek için kullanılır. Parametre olayMesajı İşlevde geri çağırmak Sunucudan gönderilen verileri (mesajı) içerir, ancak bir Blob olarak geri döner. Ardından, bir söz döndüren ve sunucudan gerçek metni almak için then() kullanılarak çözümlenen Blob verileri üzerinde text() yöntemi kullanılır.

Elimizde olanı test edelim. Sunucuyu çalıştırarak başlatalım:

node app.js

Daha sonra iki farklı tarayıcı sekmesi açın, http://localhost:3459/ Test etmek için sekmeler arasında mesaj göndermeyi açın ve deneyin:


Adım 3 – Uygulamayı ölçeklendirin

Diyelim ki uygulamamız büyümeye başladı ve sohbet sunucumuzun birden fazla örneğini kullanarak ölçeklendirmeye çalışıyoruz. Amacımız, iki farklı sunucuya bağlı iki farklı kullanıcının birbirlerine başarıyla kısa mesaj gönderebilmesi. Şu anda yalnızca bir sunucumuz var ve örneğin başka bir sunucu talep edersek, http://localhost:3460/, porttaki sunucu mesajları 3459 Sahip olmayacağız. Yani, yalnızca bağlı kullanıcılar 3460 Kendi aralarında sohbet edebilirler. Mevcut uygulama, çalışan sunucu örneğimize bir sohbet mesajı gönderildiğinde, mesajın yalnızca o sunucuya bağlı istemcilere yerel olarak dağıtılması şeklinde çalışır; bu, aşağıdaki şekilde gösterilmiştir: http://localhost:3459/ İki farklı tarayıcıda açıyoruz. Şimdi, iki farklı sunucuyu birbirleriyle iletişim kurabilecek şekilde nasıl entegre edebileceğimize bakalım.

4. Adım – Mesaj Aracısı Olarak Redis

Redis, hızlı ve esnek bir bellek içi veri yapısı deposudur. Genellikle veri depolamak için veritabanı veya önbellek sunucusu olarak kullanılır. Ayrıca, merkezi bir Pub/Sub mesaj değişim modelini uygulamak için de kullanılabilir. Redis'in hızı ve esnekliği, onu dağıtık bir sistemde veri paylaşımı için oldukça popüler bir seçenek haline getirmiştir.

Buradaki amaç, sohbet sunucularımızı Redis'i bir mesaj aracısı olarak kullanarak entegre etmektir. Her sunucu örneği, istemciden (tarayıcıdan) alınan her mesajı aynı anda mesaj aracısına yayınlar. Mesaj aracısı ise, sunucu örneklerinden gönderilen her mesaja abone olur.

Hadi dosyalayalım uygulama.js Kendimizi değiştirelim:

//app.js
const http = require("http");
const fs = require("fs");
const path = require("path");
const WebSocket = require("ws");
const Redis = require("ioredis");
const redisPublisher = new Redis();
const redisSubscriber = new Redis();
const server = http.createServer((req, res) => {
const htmlFilePath = path.join(__dirname, "index.html");
fs.readFile(htmlFilePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end("Error occured while reading file");
}
res.writeHead(200, { "Content-Type": "text/html" });
res.end(data);
});
});
const webSocketServer = new WebSocket.Server({ server });
webSocketServer.on("connection", (client) => {
console.log("succesfully connected to the client");
client.on("message", (streamMessage) => {
redisPublisher.publish("chat_messages", streamMessage);
});
});
redisSubscriber.subscribe("chat_messages");
console.log("sub", redisSubscriber.subscribe("messages"));
redisSubscriber.on("message", (channel, message) => {
console.log("redis", channel, message);
for (const client of webSocketServer.clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
}
});
const PORT = process.argv[2] || 3459;
server.listen(PORT, () => {
console.log(`Server up and running on port ${PORT}`);
});

Burada yayınlama/paylaşma olanaklarından yararlanıyoruz. Redis Mesaj yayınlamak ve bir kanala abone olmak için iki farklı bağlantı örneği kullanıyoruz. İstemciden bir mesaj gönderildiğinde, redisPublisher örneğindeki yayıncı yöntemi kullanılarak redisPublisher adlı bir Redis kanalında yayınlanır. ""sohbet_mesajları"" Yayınlıyoruz. redisSubscribe örneğindeki subscribe metodu, chat_message ile aynı kanala abone olmak için çağrılır. Bu kanala bir mesaj yayınlandığında, redisSubscriber.on olay dinleyicisi tetiklenir. Bu olay dinleyicisi, şu anda bağlı olan tüm WebSocket istemcilerinde tekrarlanır ve alınan mesajı her istemciye gönderir. Bunun amacı, bir kullanıcı bir mesaj gönderdiğinde, her sunucu örneğine bağlı diğer tüm kullanıcıların bu mesajı gerçek zamanlı olarak almasını sağlamaktır.

Eğer iki farklı sunucu kuruyorsanız şunu söyleyin:

node app.js 3459
node app.js 3460

Sohbet metni bir örnekte gönderildiğinde, artık mesajları yalnızca belirli bir sunucu yerine bağlı sunucularımız genelinde yayınlayabiliriz. Bunu şu komutu çalıştırarak yapabilirsiniz: http://localhost:3459/ Ve http://localhost:3460/ Test edin, ardından aralarında sohbetler gönderin ve mesajların iki sunucu arasında gerçek zamanlı olarak yayınlandığını görün.

Bir kanalda yayınlanan mesajları şuradan görüntüleyebilirsiniz: redis-cli Düzenli mesajlar almak için kanalı takip edin ve abone olun:

Emir redis-cli Koş. Sonra gir. MONİTÖR Tarayıcınıza geri dönün ve sohbete başlayın. Wow sohbet mesajı gönderdiğinizi varsayarsak, terminalinizde buna benzer bir şey görmelisiniz:


Paylaşılan yayınlanmış mesajları görüntülemek için aynı komutu kullanın redis-cli Koş ve ABONE OL kanalAdı Hakkımızda channelName girin. sohbet_mesajları Eğer bir mesaj gönderirseniz terminalinizde buna benzer bir şey olmalı: Tarayıcıdan harika:


Artık sunucumuzun birden fazla örneğini farklı portlarda veya hatta farklı makinelerde çalıştırabiliriz ve aynı Redis kanalına abone oldukları sürece, bağlı tüm istemcilere mesaj gönderebilir ve yayınlayabilirler; böylece kullanıcıların örnekler arasında sorunsuz bir şekilde sohbet edebilmelerini sağlarız.

Giriş bölümünde Pub/Sub modelini bir mesaj aracısı kullanarak uygulamaktan bahsetmiştik, hatırlıyor musunuz? Bu örnek bunu mükemmel bir şekilde özetliyor.


Yukarıdaki şekilde, sohbet sunucularına 2 farklı istemci (tarayıcı) bağlıdır. Sohbet sunucuları doğrudan değil, bir Redis örneği aracılığıyla bağlıdır. Bu, istemci bağlantılarını bağımsız olarak yönetirken, ortak bir ortam (Redis) üzerinden bilgi (sohbet mesajları) paylaştıkları anlamına gelir. Yukarıdaki her sohbet sunucusu Redis'e bağlanır. Bu bağlantı, mesajları Redis'e yayınlamak ve mesajları almak için Redis kanallarına abone olmak için kullanılır. Bir kullanıcı bir mesaj gönderdiğinde, sohbet sunucusu bunu Redis'teki belirtilen kanalda yayınlar.

Redis yayınlanmış bir mesaj aldığında, mesajı katılımcı tüm sohbet sunucularına yayınlar. Her sohbet sunucusu daha sonra mesajı bağlı tüm istemcilere göndererek, hangi sunucuya bağlı olursa olsun her kullanıcının diğer kullanıcılar tarafından gönderilen mesajları almasını sağlar.

Bu mimari, ihtiyaç duyulduğunda daha fazla sunucu örneği ekleyerek sohbet uygulamamızı yatay olarak ölçeklendirmemize olanak tanır. Tüm örnekler arasında tek tip mesaj dağıtımı sağlayan Redis yayınlama/abone olma sisteminin yetenekleri sayesinde, her örnek kendi bağlı istemci kümesini yönetebilir. Bu kurulum, çok sayıda eşzamanlı kullanıcıyı yönetmek için verimlidir ve uygulamanızın kullanılabilirliğini garanti eder.

Sonuç

Bu eğitimde, Redis'i mesaj aracısı olarak kullanarak basit bir sohbet uygulaması oluştururken Yayınla/Abone Ol modelini öğrendik. Bir sonraki adım, bir mesaj aracısının en iyi çözüm olmayabileceği durumlarda, örneğin tek bir hata noktasının (aracı) mümkün olmadığı karmaşık dağıtılmış sistemlerde, eşler arası bir mesajlaşma sisteminin nasıl uygulanacağını öğrenmektir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Ayrıca Şunları da Beğenebilirsiniz