Angular ve NodeJs Üzerinde SocketIO Kullanılarak Real Time Data Bildirilmesi
Selamlar,
https://youtu.be/JYycoqs9I80
Bu makalede, NodeJS üzerinde Socket.IO ile üzerinde değişiklik yapılan dataları, real time olarak diğer clientlara da bildirilmesi sağlanacaktır. Kısacası güncellenen, silinen ve yeni eklenen tüm kayıtları, connect olan yani ekranı açık olan tüm clientlara real time olarak bildirilecektir. Bu işlem yapılırken sayfa yenilenmeyecek, sadece dataların bağlı olduğu model’e müdahale edilecektir. Böylece, datanın en son hali clientlara her daim bildirilmiş olacaktır.
Bu makale, bir önceki NodeJs Üzerinde Redis Kullanımı ve Refactoring makalesinin devamıdır.
Yeni bir NodeJS Socket projesi oluşturulur. Aşağıdaki komut ile, Express ve Socket.IO kütüphaneleri projeye yüklenir.
1 |
npm install express socket.io --save |
UpdatePerson : İlk olarak gelin var olan bir kayıt güncellendiğinde, bunu tüm clientlara bildirip, hepsinde güncellenmesini sağlayalım.
Server Side / app.js: Var olan projeye, Socket.IO implementasyonu için http ve server aşağıdaki gibi tanımlanır.
- “const server = require(‘http’).Server(app)”: Http kütüphanesi kullanılması için, sayfanın üstünde tanımlanır.
- “const io = require(‘socket.io’)(server)” : Socket işlemler için Socet.IO, bu şeklide tanımlanır.
- “server.listen(1453)” : SocketIO’nun, 1453 portundan haberleşmesi sağlanır.
- “io.on(‘connection’, (socket) => {“: Herhangi bir client connect olduğu zaman, ilk çalışacak function budur.
- “console.log(‘User Socket Connected’)” : Herhangi bir client login olunca, “User Socket Connected” mesajı console’a yazılır.
- “socket.on(“disconnect”, () => console.log(
${socket.id} User disconnected.
))” : Herhangi bir client’ın disconnect olması durumunda, çalışması için tanımlanan methoddur. socket.on() olay yakalama için kullanılan methoddur.- console.log(
${socket.id} User disconnected.
): Size, örnek amaçlı Disconnect olan client’ın socketID’si console’a basılmıştır.
- console.log(
- “socket.broadcast.on(“sendUpdatePerson”, person => {” : Herhangi bir kayıt, client side tarafda güncellendiği zaman, bunun tüm clientlara bildirilmesi için, NodeJs tarafında “socket.broadcast.on()” methodu ile “sendUpdatePerson” olayı’i yakalanır. İlgili event’e güncellenen “Person” model, tüm clientlara “socket.broadcast.emit()” methodu ile Client Side taraftaki “Updatedperson” olayı(event’i) oluşturularak gönderilir.
Not : socket.broadcast.emit() : Kendisi hariç diğer tüm clientlara göndermek için kullanılır. socket.emit(): Olaya neden olan kişi dahil, yani kendisi dahil herkese gönderilir.
Server Side / app.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//Express const app = require('express')(); //SocketIO const server = require('http').Server(app); const io = require('socket.io')(server); server.listen(1453); io.on('connection', (socket) => { console.log('User Socket Connected'); socket.on("disconnect", () => console.log(`${socket.id} User disconnected.`)); socket.broadcast.on("sendUpdatePerson", person => { console.log("Update Person:" + person.name.first + ' ' + person.name.last); socket.broadcast.emit("Updatedperson", person); }); }); |
Client Side Angular/ Socket.IO :
Şimdi gelin frontend tarafında Angular projesine, Socket.IO kütüphanesini aşağıdaki komut ile ekleyelim.
1 |
npm i ngx-socket-io --save |
app/app.module.ts: Module dosyasına, aşağıdaki kütüphaneler eklenir. “http://localhost:1453” adresi SocketIO’nun çalıştığı url’dir.
1 2 3 4 |
// ...diğer import kütüphaneleri import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io'; const config: SocketIoConfig = { url: 'http://localhost:1453', options: {} }; |
app.module.ts / imports [ ]: “SocketIoModule.forRoot(config)”İlgili module, import dizisine config ayarı ile birlikte eklenir.
1 2 3 4 5 6 7 |
imports: [ BrowserModule, FormsModule, HttpClientModule, AppRoutingModule, SocketIoModule.forRoot(config) ], |
src/app/personService.ts:
- Öncelikle import dosyası olarak ‘ngx-socket-io’ sayfanın üstünde tanımlanır.
- Constructor’da, Dependency Injection ile “Socket” tanımlanır. Bu şekilde daha constructor’da, Socket.IO’ya bağlanılmış olunur.
- “sendUpdatePerson(person: Person) {“: Herhangi bir kayıt güncellendiği zaman, diğer clientlara bildirmek için çağrılacak methoddur.
- “this.socket.emit(‘sendUpdatePerson’, person)” : “sendUpdatePerson” olayı yaratılıp SocketIO’ya gönderilir.
- “updatedPerson = this.socket.fromEvent<Person>(‘Updatedperson’)” : “fromEvent()” function’ı ile NodeJS’den gönderilen, güncellenmiş person datası yakalanır. Ve “updatedPerson” değişkenine gelen “person” model atanır. “fromEvent()” methodunun, geri dönüş tipi her zaman “Observer“‘dır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
. . import { Socket } from 'ngx-socket-io'; @Injectable({ providedIn: 'root' }) export class PersonService { constructor(private httpClient: HttpClient, private socket: Socket) { } sendUpdatePerson(person: Person) { this.socket.emit('sendUpdatePerson', person); } updatedPerson = this.socket.fromEvent<Person>('Updatedperson'); . . } |
src/app/personService.ts/updatePerson(): Methodu aşağıdaki gibi değiştirilir. Sadece yeni eklenen satır, “this.sendUpdatePerson(data); // Send SocketIO“‘dır. Güncellenen “person” model, yukarıda tanımlanan method ile SocketIO’ya gönderilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public updatePerson(data: Person): Observable<any> { var refreshToken = (window.localStorage.getItem("refreshToken") != null && this.checkTokenTime()) ? window.localStorage.getItem("refreshToken") : ""; let httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.GetToken(), 'RefreshToken': refreshToken, }), observe: 'response' as 'body', } return this.httpClient.post<any>(this.updateUrl, JSON.stringify(data), httpOptions) .pipe( map(response => { var token = response.headers.get('token'); var refreshToken = response.headers.get('refreshToken'); debugger; if (token && refreshToken) { console.log("Token :" + token); console.log("RefreshToken :" + refreshToken); window.localStorage.setItem("token", token); window.localStorage.setItem("refreshToken", refreshToken); window.localStorage.setItem("createdDate", new Date().toString()); } this.sendUpdatePerson(data); // Send SocketIO return response.body; }), retry(1), catchError(this.errorHandel) ) } |
src/app/app.component.ts: ngAfterViewInit() methodu aşağıdaki gibi değiştirilir:
- “this.service.updatedPerson.subscribe((person: Person) => {” Servis tarafında, “Updatedperson” olayının sürekli dinlenmesi sağlanır. Cevap gelmesi durumunda, gelen data “Person”‘dır.
- “let updateIndex = this.peopleList.findIndex(per => per.username === person.username)” : Yeni bir data güncellendiğinde, SocketIO “Updatedperson” olayını oluşturur ve personService’de yakanan person datası buraya düşer. “findIndex()” methodu ile, “peopleList” dizisinden ilgili güncelenen person datasının index’i bulunur.
- “this.peopleList[updateIndex] = person” : Bulunan index’deki data ile güncelenen data değiştirilir.
1 2 3 4 5 6 7 8 9 10 11 |
. . ngAfterViewInit() { this.service.updatedPerson.subscribe((person: Person) => { debugger; let updateIndex = this.peopleList.findIndex(per => per.username === person.username) this.peopleList[updateIndex] = person; //alert(person.name.first + " " + person.name.last); }); . . |
InsertPerson: Şimdi sıra geldi yeni bir kayıt girdiğinde, bunun tüm clientlara bildirilmesi ve hepsine canlı olarak eklenmesine.
Server Side / app.js:
- “socket.broadcast.on(“sendSavePerson”, person => {“: Aşağıda görüldüğü gibi yeni bir person kaydedildiğinde, “sendSavePerson” olayı beklenmektedir.
- “socket.broadcast.emit(“Savedperson”, person)” : Yeni bir person model geldiğinde, gönderen kişi hariç tüm clientlara yeni eklenen “person” datası gönderilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
const app = require('express')(); const server = require('http').Server(app); const io = require('socket.io')(server); server.listen(1453); io.on('connection', (socket) => { console.log('User Socket Connected'); socket.on("disconnect", () => console.log(`${socket.id} User disconnected.`)); socket.broadcast.on("sendUpdatePerson", person => { console.log("Update Person:" + person.name.first + ' ' + person.name.last); socket.broadcast.emit("Updatedperson", person); }); // Yeni EKLENDİ Insert Person socket.broadcast.on("sendSavePerson", person => { console.log("Saved Person:" + person.name.first + ' ' + person.name.last); socket.broadcast.emit("Savedperson", person); }); //------------------------------- }); |
Client Side : src/app/personService.ts: Yeni bir kayıt eklendiği zaman, yapılacak işler için değiştirilen yerler aşağıda belirtilmiştir.
- “sendSavePerson(person: Person) {“: Yeni bir person kaydı kaydedilince, ilgili kayıdın tüm clientlara real time gönderilmesi için çağrılan methoddur.
- “this.socket.emit(‘sendSavePerson’, person)” : “sendSavePerson” olayı yaratılır ve SocketIO’ya gönderilir.
- “savedPerson = this.socket.fromEvent<Person>(‘Savedperson’)”: SocketIO’dan “Savedperson” olayı beklenir. Yeni bir kayıt girişinde, SocketIO’dan gönderilen “Person” model burada yakalanır.
- “insertPeople() {.. this.sendSavePerson(
data);..} // Send SocketIO” : insertPeople() methodunda diğer clientlara kaydedilen “person” datanın, real time gönderilmesi için yukarıda tanımlanan “this.sendSavePerson()” methodu çağrılır.- Düzeltme: Kaydedilen veri olarak data nesnesi gönderilir ise, MongoDB’nin her kaydedilen data için oluşturduğu “_id” kolonu atlanmış olunur. Bundan dolayı, diğer clientlara gönderilen data eksik olur. Bu da SocketIO ile datayı alan diğer clientların, güncelleme sırasında mongoDB’den alınan “_id” değerini veremiyeceğinden hataya düşmesine sebebiyet verir.
- “this.sendSavePerson(response.body)” : Doğru yolu NodeJS’den dönen “_id” kolonun da dahil olduğu datanın, tüm clientlara gönderilmesidir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
. . import { Socket } from 'ngx-socket-io'; @Injectable({ providedIn: 'root' }) export class PersonService { //Yeni Eklenen Insert Person sendSavePerson(person: Person) { this.socket.emit('sendSavePerson', person); } savedPerson = this.socket.fromEvent<Person>('Savedperson'); //-------------------------------------------------- public insertPeople(data: Person): Observable<any> { var refreshToken = (window.localStorage.getItem("refreshToken") != null && this.checkTokenTime()) ? window.localStorage.getItem("refreshToken") : ""; let httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.GetToken(), 'RefreshToken': refreshToken, }), observe: 'response' as 'body', } return this.httpClient.post<any>(this.inserteUrl, JSON.stringify(data), httpOptions) .pipe( map(response => { var token = response.headers.get('token'); var refreshToken = response.headers.get('refreshToken'); debugger; if (token && refreshToken) { console.log("Token :" + token); console.log("RefreshToken :" + refreshToken); window.localStorage.setItem("token", token); window.localStorage.setItem("refreshToken", refreshToken); window.localStorage.setItem("createdDate", new Date().toString()); } //YENİ EKLENEN SATIR //this.sendSavePerson(data); // Send SocketIO this.sendSavePerson(response.body); //Send SocketIO //--------------------------------------------------------------- return response.body; }), retry(1), catchError(this.errorHandel) ) } . . } |
src/app/app.component.ts: ngAfterViewInit() methodu aşağıdaki gibi değiştirilir:
- “this.service.savedPerson.subscribe((person: Person) => {” :”Savedperson” olayının dinlendiği method burasıdır. Yeni bir kayıt girildiğinde, Observer<Person> tipinde data yakalanır.
- “this.peopleList.unshift(person)” : Yakalanan yeni girilmiş data, “unshift()” methodu ile peopleList[ ]’in en başına eklenir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
. . ngAfterViewInit() { this.service.updatedPerson.subscribe((person: Person) => { debugger; let updateIndex = this.peopleList.findIndex(per => per.username === person.username) this.peopleList[updateIndex] = person; //alert(person.name.first + " " + person.name.last); }); //Yeni EKLENDİ this.service.savedPerson.subscribe((person: Person) => { debugger; this.peopleList.unshift(person); }); //---------------- . . |
Delete person: Şimdi sıra geldi bir kayıt silindiğinde, bunun tüm clientlara bildirilmesine ve herkezden real time silinmesine.
Server Side / app.js:
- “socket.broadcast.on(“sendDeletePerson”, person => {“: Aşağıda görüldüğü gibi seçilen person kaydı silindiğinde, “sendSavePerson” olayı beklenmektedir.
- “socket.broadcast.emit(“Deleteperson”, person)” :Silinen person model geldiğinde, gönderen kişi hariç tüm clientlara , silinen “person” model gönderilir.
1 2 3 4 5 6 |
// Yeni EKLENDİ Delete Person socket.broadcast.on("sendDeletePerson", person => { console.log("Deleted Person:" + person.name.first + ' ' + person.name.last); socket.broadcast.emit("Deletedperson", person); }); //------------------------------- |
Client Side : src/app/personService.ts: Kayıt silindiği zaman, yapılacak işler için değiştirilen yerler aşağıda belirtilmiştir.
- “sendDeletePerson(person: Person) {“: Kayıt silindiği zaman, silinen kayıdın tüm clientlara real time gönderilmesi için çağrılan methoddur.
- “this.socket.emit(‘sendDeletePerson’, person)” : “sendDeletePerson” olayı yaratılır ve SocketIO’ya gönderilir.
- “deletedPerson = this.socket.fromEvent<Person>(‘Deletedperson’)”: SocketIO’dan “Deletedperson” olayı beklenir. Kayıt silindiğinde, SocketIO’dan gönderilen “Person” model burada yakalanır.
- “deletePeople() {.. this.sendDeletePerson(data);..} // Send SocketIO” : deletePeople() methodunda diğer clientlara, silinen”person” datanın, real time gönderilmesi için yukarıda tanımlanan “this.sendDeletePerson()” methodu çağrılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
. . //Yeni Eklenen Delete Person sendDeletePerson(person: Person) { this.socket.emit('sendDeletePerson', person); } deletedPerson = this.socket.fromEvent<Person>('Deletedperson'); //------------------------------ . . public deletePeople(data: Person): Observable<any> { var refreshToken = (window.localStorage.getItem("refreshToken") != null && this.checkTokenTime()) ? window.localStorage.getItem("refreshToken") : ""; let httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + this.GetToken(), 'RefreshToken': refreshToken, }), observe: 'response' as 'body', } return this.httpClient.post<any>(this.deleteUrl, JSON.stringify(data), httpOptions) .pipe( map(response => { var token = response.headers.get('token'); var refreshToken = response.headers.get('refreshToken'); debugger; if (token && refreshToken) { console.log("Token :" + token); console.log("RefreshToken :" + refreshToken); window.localStorage.setItem("token", token); window.localStorage.setItem("refreshToken", refreshToken); window.localStorage.setItem("createdDate", new Date().toString()); } //YENİ EKLENEN SATIR this.sendDeletePerson(data); // Send SocketIO //------------------- return response.body; }), retry(1), catchError(this.errorHandel) ) } |
src/app/app.component.ts: ngAfterViewInit() methodu aşağıdaki gibi değiştirilir:
- “this.service.deletedPerson.subscribe((person: Person) => {” :”Deletedperson” olayının dinlendiği method burasıdır. Kayıt silindiğinde, Observer<Person> tipinde data yakalanır.
- “let deleteIndex = this.peopleList.findIndex(per => per.username === person.username)” : Kayıt silindiğinde, SocketIO “Deletedperson” olayını oluşturur ve personService’de yakanan person’datası buraya düşer. “findIndex()” methodu ile, elimizde olan “peopleList” dizisinden ilgili silinen person datanın index’i bulunur.
- “this.peopleList.splice(deleteIndex, 1);” : Yakalanan yeni silinmiş data, peopleList dizisindeki index ile, “splice()” methodu kullanılarak diziden çıkarılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
. . ngAfterViewInit() { this.service.updatedPerson.subscribe((person: Person) => { debugger; let updateIndex = this.peopleList.findIndex(per => per.username === person.username) this.peopleList[updateIndex] = person; //alert(person.name.first + " " + person.name.last); }); this.service.savedPerson.subscribe((person: Person) => { debugger; this.peopleList.unshift(person); }); //Yeni EKLENDİ this.service.deletedPerson.subscribe((person: Person) => { debugger; let deleteIndex = this.peopleList.findIndex(per => per.username === person.username) this.peopleList.splice(deleteIndex, 1); }); //----------------------- . . |
Bu makalede Angular ve NodeJs’üzerinde güncelenen, kaydedilen ve silinen kayıtların, diğer tüm clientlarda SocketIO kullanılarak bildirilmesi ve onlarda da aynı kayıt değişikliğinin olması sağlanmıştır.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hoşçakalın.
Kaynaklar:
Source Code : https://github.com/borakasmer/SocketIO-Angular8-Redis-NodeJS/tree/master/mongoDb
Merhaba,
ben mobil uygulama geliştiriyorum, server kısmında nodejs uygulamada flutter socket client kullanıyorum. Şöyle bir sorunum var bazı cihazlar sockete bağlanamıyor, çok istisnai bi durum 100 cihazdan belkide 2-3 tanesi bağlanamıyor. Test ederken aynı model aynı ios versiyonu olan iki farklı telefonda biri bağlandı ancak biri bağlanamadı. bu konu hakkında bilginiz var ise bana yardım edebilir misiniz?