NodeJS Üzerinde SocketIO ile Peer To Peer Resimleşme
Selamlar,
https://youtu.be/TCyoS1jj5rs
Bu makalede, NodeJs – SocketIO ve Angular 8 ile Resim açma oyunu yazacağız. Amaç belirlenen bir client’a, doğrudan bir bildiri göndermektir.
Öncelikle 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 |
app.js:
- “socketList = [];” SocketIO’ya connect olan tüm clientların SocketID’si, bu dizi içeriside tutulur.
- “server.listen(1923); io.on(‘connection’, (socket) => {” : En başta 1923 portundan dinleyen Socket IO, ayağa kaldırılır.
- “io.on(‘connection’, (socket) => { console.log(
User Socket Connected - ${socket.id}
) ” Connect ve disconnect olan client’ın, socketID’si, konsola yazdırılır.- “socketList.push(socket.id)”: Yeni connect olan client’ın SocketID’si, global bir listeye eklenir.
- “io.sockets.to(socket.id).emit(“currentSocketID”, socket.id)” : Connect olan client’ın kendisi, “socket.id“‘sinden bulunup, sadece kendisine parametre olarak gönderilir.
- “io.emit(“getSocketID”, socketList)” : En güncel connect olan Client listesi, tüm clientlara gönderilir. İleride gönderilen bu liste, bir combobox’a doldurulacaktır.
- “socket.on(“disconnect”, () => { console.log(
${socket.id} User disconnected.
)”: Disconnect olan tüm clientlar, bu function’a düşerler. Disconnect olan client’ın “scoketID“‘si, konsola yazdırılır.-
- “const index = socketList.indexOf(socket.id)”: Disconnect olan client’ın index numarası, “socketList” içerisinde bulunur.
- “if (index !== -1) { socketList.splice(index, 1); }” : Listede, çıkan client’ın socketID’si silinir.
- “io.emit(“leaveSocketID”, socket.id)” : Son çıkan client’ın SocketID’si, tüm clientlara gönderilir. Böylece front end tarafda, resmin gönderileceği client’ın doldurulduğu combo yenilenir. Ve giden client’ın socketID’si, listeden çıkarılır.
-
- “socket.on(“sendUpdatePicture”, picture => {” : Front End tarafta belirlenen client için seçilen resim, server side NodeJS tarafında “sendUpdatePicture()” function’ı ile karşılanır.
- “io.sockets.to(picture.socketID).emit(“changePicture”, picture)” : Client side tarafta ilgili socketID ile belirtilen client’ın, “changePicture()” methodu server side taraftan tetiklenir. Ve gösterilmesi istenen resim, ilgili client’ın ekranında gösterilir.
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 |
const app = require('express')(); const server = require('http').Server(app); const io = require('socket.io')(server); socketList = []; server.listen(1923); io.on('connection', (socket) => { console.log(`User Socket Connected - ${socket.id}`); socketList.push(socket.id); io.sockets.to(socket.id).emit("currentSocketID", socket.id); io.emit("getSocketID", socketList); socket.on("disconnect", () => { console.log(`${socket.id} User disconnected.`) const index = socketList.indexOf(socket.id); if (index !== -1) { socketList.splice(index, 1); } io.emit("leaveSocketID", socket.id); }); socket.on("sendUpdatePicture", function (picture) { console.log("New Picture:" + picture.Name + ' ' + picture.Url); io.sockets.to(picture.socketID).emit("changePicture", picture); }); }); |
Şimdi sıra geldi Client Side projeyi oluşturmaya. Aşağıdaki komut ile yeni bir Angular projesi oluşturulur.
1 |
ng new Socket game |
Bu projede Angular ve NodeJS için kullanılan paketler, aşağıdaki gibi belirtilmiştir.
Client Side Angular/ Socket.IO :
Front end tarafında Angular projesine, Socket.IO kütüphanesini aşağıdaki komut ile eklenir.
1 |
npm i ngx-socket-io --save |
app/app.module.ts: Module dosyasına, aşağıdaki kütüphaneler eklenir. “SocketIoConfig“‘de tanımlanan, “http://localhost:1923” adresi, SocketIO’nun çalıştığı url’dir. Daha sonra farklı bir sunucu için, ilgili Url adresi değiştirilecektir. Uygulama içinde yazılacak ImageService, yine bu modülde tanımlanır. Son olarak “FormsModule” tanımlaması ile img “[src]” ve “[hidden]” gibi tanımlamaların proje genelinde yapılabilmesi sağlanmaktadır.
1 2 3 4 5 6 7 |
// ...diğer import kütüphaneleri import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io'; import { ImageService } from 'src/Service/imageService'; import { FormsModule } from '@angular/forms'; const config: SocketIoConfig = { url: 'http://localhost:1923', options: {} }; |
app.module.ts / imports [ ]: “SocketIoModule.forRoot(config)” İlgili module, import dizisine config ayarı ile birlikte eklenir.
1 2 3 4 |
imports: [ BrowserModule, SocketIoModule.forRoot(config) ], |
Model/Picture.ts: Clientlara gönderilecek olan modeldir.
1 2 3 4 5 6 |
export class Picture { url: String; socketID: String; name: String; constructor() { } } |
Service/imageService.ts:
- “constructor(private socket: Socket)” : SocketIO, dependency injection ile service içine dahil edilmiştir.
- “sendPictureUrl(picture: Picture, socketID: string) { this.socket.emit(‘changePicture’, picture); }” : Button tıklandığı zaman, “sendPictureUrl()” metodu çağrılır. Burada seçilen client’a ait resmin değişmesi için, SocketIO’nın “sendUpdatePicture()” methodu, seçilen picture objesi ile birlikte tetiklenir.
- “let picture: Picture = new Picture()” : Client’a gönderilecek picture nesnesi, tüm propertyleri ile oluşturulur.
- “updatedPicture = this.socket.fromEvent<Picture>(‘changePicture’)” : Bir resim seçildiği zaman SocketIO’dan, client side tarafındaki “changePicture()” function’ı tetiklenir. Kısaca bir başka client’dan gönderilen resim nesnesi, SocketIO’dan gönderilen client’a, bu “updatedPicture” nesnesi ile karşılanmaktadır.
- “socketList = this.socket.fromEvent<Array<string>>(‘getSocketID’)” : Ilk connect olunma durumunda, online olan tüm clientların SocketID listesi, server side taraftan buraya gönderilerek bu değişkene atanır. Amaç resmin gönderileceği client’ın, seçildiği combonun socketList modelinden doldurulmasıdır.
- “currentSocketID = this.socket.fromEvent<string>(‘currentSocketID’)” : Ilk connect olunma durumunda, connect olan client’ın SocketID’sinin server side taraftan gönderilip, client side tarafta karşılandığı yerdir.
- “leaveSocketID = this.socket.fromEvent<string>(‘leaveSocketID’)” : Herhangi bir client ayrıldığı zaman, yani disconnect olduğunda ait olduğu SocketID, diğer online olan tüm clientlara gönderilir. Amaç, makalenin devamında anlatılacak olan resmin gönderileceği client, combodan SocketID’si ile seçilir. Online client’ın ayrılma durumunda, ilgili client’ın SocketID’si bu combodan çı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 |
import { Socket } from 'ngx-socket-io'; import { Injectable } from '@angular/core'; import { Picture } from 'src/Model/Picture'; @Injectable({ providedIn: 'root' }) export class ImageService { constructor(private socket: Socket) { } sendPictureUrl(pictureUrl: string, socketID: string) { let picture: Picture = new Picture(); picture.name = pictureUrl; picture.url = `../assets/images/${pictureUrl}.jpg`; picture.socketID = socketID; this.socket.emit('sendUpdatePicture', picture); } updatedPicture = this.socket.fromEvent<Picture>('changePicture'); socketList = this.socket.fromEvent<Array<string>>('getSocketID'); currentSocketID = this.socket.fromEvent<string>('currentSocketID'); leaveSocketID = this.socket.fromEvent<string>('leaveSocketID'); } |
1 2 3 4 5 6 7 8 9 10 |
button:hover{ background:red !important; } span { display:inline-block; width: 13em; font-weight: normal; text-align: center } |
app.component.ts: Amaç ekrana basılan 4 resimden birinin, combodan seçilen online olan bir client’a gönderilmesidir.
- “bigImage: string = “””: İlgili client’ın ekranına basılacak olan büyük resimdir.
- “socketList = []” : Comboyu dolduran, online clientların SocketID listesidir.
- “selectedSocket” : Combodan seçilen client’ın, socketID’sidir.
- “socketID”: Client’ın kendi SocketID’sidir. Sayfanın başlığına, client’ın tanımlaması amaçlı, yukarıda görüldüğü gibi yazdırılmıştır.
- “hasBigImage”: İlgili client’a gönderilen herhangi bir resim var mı yok mu kontrolu, bu değişkene bağlı olarak yapılır.
- “constructor(public service: ImageService) { }” : Constructor, dependency injection ile ImageService’i sayfaya dahil edilmiştir.
- “ngAfterViewInit() {” : Sayfadaki tüm elementlerin yüklenmesinden sonra, çalıştırılan bir methoddur.
- “this.service.updatedPicture.subscribe((picture: Picture) => {” : Başka bir client tarafından bu client’a bir resim objesi gönderildiğinde ==>”updatedPicture” nesnesi imageService’de tanımlanan “changePicture()” function’ı ile “observer” bir değer alacak şekilde doldurulur. “subscribe” ile de gelen picture nesnesi, “bigImage” değişkenine atanır. Ayrıca resmin gözükmesi için, “hasBigImage” değişkenine “true” değeri atanır.
- “this.service.currentSocketID.subscribe((currentSocketID: string) => {” : Connect olan client’ın, Socket ID’si, burada yakalanır. Server Side taraftan tetiklenen “currentSocketID()” function ile, currentSocketID değişkenine atanır.
- “this.service.socketList.subscribe((socketList: Array<string>) => {” : Connect ve disconnect olan tüm clientların, kendilerine ait bir socketID’si vardır. Connect olunma durumunda, gelen yeni socketID, server side tarafta bulunan socketList’e eklenir. Disconnect durumunda ayrılan socketID, yine server side tarafda bulunan socketList’den çıkarılır. Bir client’ın ilk kez connect olması durumunda, bağlı olan tüm clientların socketID listesi, server side taraftan ==> client side tarafda bulunan “getSocketID()” function’ı ile tetiklenerek, “socketList[]” dizisine atanır.
- “this.selectedSocket = socketList[0];” : Default olarak, en baştaki client’ın SocketID’si seçilir.
- “this.service.leaveSocketID.subscribe((leaveSocketID: string) => {” : Clientlardan biri disconnect olduğu zaman, server side taraftan ==> client side taraftaki “leaveSocketID()” function’ı tetiklenir.
- “const index: number = this.socketList.indexOf(leaveSocketID);” : Böylece “socketList” içerisinde bulunun, ayrılan client’ın socketID’si bulunur ve çıkarılır.
- “this.socketList.splice(index, 1)” : Silinecek socketID’nin index’i bulunduktan sonra, listeden çıkarılır.
- “public changePicture(imgName) {” : Seçilen resimin hem var olan ekranda gösterilmesi, hem de combodan seçilen client’a gönderilmesi işlemleri, bu function altında yapılır.
- “this.bigImage =
../assets/images/${imgName}.jpg
” : Sayfanın ortasında, yukarıda görülen resim ekrana basılır. - “this.hasBigImage=false” : Başka bir client’dan gönderilmiş, ortada büyük bir resim var ise gizlenir.
- “this.service.sendPictureUrl(imgName, this.selectedSocket)” : Combodan seçilen client’a ait socketID ve tıklanan resmin ismi ile, servise ait sendPictureUrl() methodu çağrılır. Ve seçilen resim, ilgili client’a gönderilmiş olunur. Yani gerçek anlamda gönderilme işlemi olmasa da, seçilen resmin istenen client’a gösterilmesi socketIO aracılı ile sağlanmış olunur.
- “this.bigImage =
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 52 53 54 |
import { Component, AfterViewInit } from '@angular/core'; import { ImageService } from 'src/Service/imageService'; import { Picture } from 'src/Model/Picture'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements AfterViewInit { title = 'Socketgame'; bigImage: string = ""; socketList = []; selectedSocket: string; socketID: string; hasBigImage: boolean = false; constructor(public service: ImageService) { } ngAfterViewInit() { this.service.updatedPicture.subscribe((picture: Picture) => { console.log(`Url: ${picture.url} - SocketID ${picture.socketID}`) this.bigImage = picture.url; this.hasBigImage = true; }); this.service.currentSocketID.subscribe((currentSocketID: string) => { this.socketID = currentSocketID; console.log(`Your SocketID :${this.socketID}`) }); this.service.socketList.subscribe((socketList: Array<string>) => { this.socketList = socketList; this.selectedSocket = socketList[0]; console.log(`Your SocketID :${this.socketID}`) }); this.service.leaveSocketID.subscribe((leaveSocketID: string) => { console.log(`Leave SocketID :${leaveSocketID}`) const index: number = this.socketList.indexOf(leaveSocketID); if (index !== -1) { this.socketList.splice(index, 1); } }); } public changePicture(imgName) { console.log(`Sending image:${imgName} to ${this.selectedSocket}`); this.bigImage = `../assets/images/${imgName}.jpg`; this.hasBigImage=false; this.service.sendPictureUrl(imgName, this.selectedSocket); } } |
component.html: Sıra geldi Angular 8 Html sayfaya. Sayfada Css amaçlı bootstrap kullanılmıştır.
- “<div class=”jumbotron”> <h2 style=”text-align:center”>coderBora® Profil Sayfası<br> <font color=”red”> <h4>SocketID: {{socketID}}</h4> </font> </h2> </div>” : Her sayfanın bir çeşit kimliği olan SocketID’si, (socketID) sayfanını en üstüne bastırılır.
- “<h3>Gönderilecek Kişi:</h3> <select [(ngModel)]=”selectedSocket”> <option *ngFor=”let socket of socketList” [value]=”socket”> {{ socket }} </option> </select>” : Resmin göndereleceği clientlara ait socketID’ler “socketList”‘e atanır. İlgili liste gezilerek, ekrandaki combobox doldurulur.
- “<button type=”button” class=”btn btn-primary”><img src=”../assets/images/beach.jpg” class=”img-thumbnail img-check” width=”500″ height=”50″ alt=”painting” (click)=”changePicture(‘beach’)”> </button>” : Her bir resim aynı zamanda bir buttondur. Seçilen client’a, gönderilecek resim bu listelenen resimlerden biri olacaktır. “changePicture(‘beach’)” ilgili buttonun tıklanması durumunda, changePicture() methodu çağrılmaktadır. Bu da hem sayfanın altına, seçilen resmin küçük halinin gösterilmesine hem de servis tarafında, “sendPictureUrl()” methodunun çağrılarak ilgili resim adının, seçilen client’a gönderilmesine neden olmaktadır. Bunun için backend tarafında, “sendUpdatePicture()” methoduna oluşturulacak picture nesnesi parametre olarak gönderilmektedir.
- “<td *ngIf=”hasBigImage”> <img [src]=”bigImage” width=”1000px” height=”500px” [hidden]=”bigImage==””> </td>” : Client’ın kendisine başka bir client’dan resim gelmiş ise, hasBigImage true değerini alır. Böylece tablonun tanımlı satırı gözükür.
- “<td *ngIf=”!hasBigImage”> <img src=”./assets/images/green_arrows.gif” width=”250px” *ngIf=”bigImage!=”” style=”padding-left: 150px;”> <span *ngIf=”bigImage!=””>Sending.. <b>{{selectedSocket}}</b></span> <img [src]=” bigImage” width=”400″ height=”200px” [hidden]=”bigImage==””> </td>” : Bu bölüm, tamamen bilgilendirme amaçlıdır. Burada, başka bir client’a resim gönderme amaçlı tıklanma işleminden sonra “hasBigImage” false değerini alır. Böylece, ilgili satır gözükür hale gelir. Burada resmin adının gönderileceği Client’ın SocketID’si ve gönderilen resmin küçük hali gösterilmektedir.
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 52 53 54 55 56 57 58 59 60 61 |
<style> .check { opacity: 0.5; color: #996; } select { font-size: 25px; } </style> <div class="container"> <div class="jumbotron"> <h2 style="text-align:center">coderBora® Profil Sayfası<br> <font color="red"> <h4>SocketID: {{socketID}}</h4> </font> </h2> </div> <hr> <table> <tr> <td> <h3>Gönderilecek Kişi:</h3> <select [(ngModel)]="selectedSocket"> <option *ngFor="let socket of socketList" [value]="socket"> {{ socket }} </option> </select> </td> <td style="padding-left: 100px"> <h3>Arka Resimler :</h3> <div class="btn-group" role="group" aria-label="Picture items"> <button type="button" class="btn btn-primary"><img src="../assets/images/beach.jpg" class="img-thumbnail img-check" width="500" height="50" alt="painting" (click)="changePicture('beach')"> </button> <button type="button" class="btn btn-primary"><img src="../assets/images/love.jpg" class="img-thumbnail img-check" width="500" height="50" alt="oldmans" (click)="changePicture('love')"></button> <button type="button" class="btn btn-primary"><img src="../assets/images/worker.jpg" class="img-thumbnail img-check" width="500" height="50" alt="seeView" (click)="changePicture('worker')"></button> <button type="button" class="btn btn-primary"><img src="../assets/images/motor.jpg" class="img-thumbnail img-check" width="500" height="50" alt="picnic" (click)="changePicture('motor')"></button> </div> </td> </tr> <tr> <td></td> <td *ngIf="hasBigImage"> <img [src]="bigImage" width="1000px" height="500px" [hidden]="bigImage==''"> </td> <td *ngIf="!hasBigImage"> <img src="./assets/images/green_arrows.gif" width="250px" *ngIf="bigImage!=''" style="padding-left: 150px;"> <span *ngIf="bigImage!=''">Sending.. <b>{{selectedSocket}}</b></span> <img [src]=" bigImage" width="400" height="200px" [hidden]="bigImage==''"> </td> </tr> </table> </div> |
Image Source: https://user-images.githubusercontent.com/13397845/65087951-e83c0180-d9b7-11e9-9a6f-08e75ea13847.png
Bu makalede NodeJS – SocketIO üzerinden Clientların, bire bir nasıl haberleştiklerini, örnek olarak birbirlerine nasıl resim gönderdiklerini, ayrıca belli durumlarda nasıl toplu yayın yaptıklarını yine örnek olarak, her connect ve disconnect olan clientların SocketID’lerinin tüm sayfalardaki comboya, eklenmesi ya da çıkarılması durumlarını, Angular 8 üzerinde eğlenceli bir örnek ile inceledik.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source: https://socket.io/docs/emit-cheatsheet/
Source Code: https://github.com/borakasmer/SocketIOPeerToPeerGame
Son Yorumlar