SignalR ile Canlı Kullanıcı Sayısını Bulma ve Redis ile Loglama Bölüm 2
Selamlar,
Bugün bir önceki makalede bahsettiğimiz console application’ı yazıp, Redis Pub/Sub özelliği ile log bilgisini MsSql bir server’a yazacağız. Ayrıca toplam kullanıcı sayısını ve her bir server’a göre kırılımının gösterildiği bir sayfayı, performansı da göz önüne alarak oluşturacağız.
Öncelikle “UserCount” tablosu aşağıdaki gibi oluşturulur: Bu tabloda server bazından gelen kullanıcı sayısı tutulacaktır.
Şimdi sıra geldi Redis’den Pub/Sub ile dataların publish edildiği “UserCountService“‘i yazmaya. Bunun için “RedisSqlService” adında bir console application oluşturulur.
Program.cs: Öncelikle RedisEndPoint oluşturulur. RedisClient‘a connect işlemi yapıldıktan sonra “CreateSubscription()” methodu ile 6379 portdan ilgili method dinlenmeye başlanır. Mesaj geldiği zaman yani “sub.OnMessage” durumunda gelen “message” split ile “æ” işaretine göre ayrılıp server ismi ve kullanıcı sayıları bilgileri ayrıştırılır.
Projeye “UserCountContext” adında CodeFirst nesnesi eklenir. İlgili tablo olarak”UserCount” tanımlanır. Daha sonra aşağıda görüldüğü gibi “SaveData()” methodu oluşturulur. Gelen server ismi ve kullanıcı sayısı bu oluşturulan tabloya yazılır. Burada dikkat edilecek nokta “async” olan method, static Main() methodu tarafında çağrılırken sonuna Wait() methodu eklenerek var olan Main() methodunun genel yapısı asenkron olarak değiştirilmemiştir. Zaten denenirse de hata alınılması kaçınılmazdır:) Böylece gelen tüm data yani server adı, anlık toplam kullanıcı sayısı ve kaydedilen tarih ve saat bilgisi MsSql serverda “UserLog” database’ine kaydedilmiş olunur.
Main() methodunun sonuna dinlenecek Redis kanalının “UserCountService” olacağı “SubscribeToChannels()” methodu ile tanımlanmaktadı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 44 45 46 47 48 49 50 51 52 53 54 |
using ServiceStack.Redis; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace RedisSqlService { class Program { static RedisEndpoint conf = new RedisEndpoint() { Host = "127.0.0.1", Port = 6379 }; static void Main(string[] args) { using (IRedisClient client = new RedisClient(conf)) { IRedisSubscription sub = null; using (sub = client.CreateSubscription()) { sub.OnMessage += (channel, message) => { try { string serverName = message.Split('æ')[0]; string userCount = message.Split('æ')[1]; Console.WriteLine(DateTime.Now.ToString()+" : "+ serverName +" " +userCount); SaveData(serverName, int.Parse(userCount)).Wait(); } catch (Exception ex) { Console.WriteLine(ex.Message); } }; } sub.SubscribeToChannels(new string[] { "UserCountService" }); } Console.ReadLine(); } public static async Task SaveData(string serverName, int userCount) { using (UserCountContext dbContext = new UserCountContext()) { UserCount data = new UserCount(); data.DateTime = DateTime.Now; data.ServerName = serverName; data.UserCount1 = userCount; dbContext.UserCounts.Add(data); await dbContext.SaveChangesAsync(); } } } } |
Uygulama çalıştırılıp loglama işlemine başlanınca, aşağıdaki gibi bir kayıt kümesi ilgili tabloda saklanır.
Aşağıda DB katmanın, CodeFirst tarafında karşılığı olan poco sınıflarının tanımı yapılmıştır.
UserCount.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
namespace RedisSqlService { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Spatial; [Table("UserCount")] public partial class UserCount { public int ID { get; set; } [StringLength(50)] public string ServerName { get; set; } [Column("UserCount")] public int? UserCount1 { get; set; } public DateTime? DateTime { get; set; } } } |
UserCountContext.cd:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
namespace RedisSqlService { using System; using System.Data.Entity; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; public partial class UserCountContext : DbContext { public UserCountContext() : base("name=UserCountContext") { } public virtual DbSet<UserCount> UserCounts { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<UserCount>() .Property(e => e.ServerName) .IsUnicode(false); } } } |
- http://localhost:3453/
- http://localhost:78/
Şimdi sıra geldi farklı serverlara ait kullanıcı sayılarının toplamını göstermeye. Bunun için öncelikle farklı bir sunucudan Index.cshtml sayfayı yayınlanması gerekmektedir. Bu örnekte proje bir de IIS serverdan “http://localhost:78/” portundan yayımlanmıştır. Böylece elde bir IIS Express’den bir de IIS’den yayımlanmış 2 farklı sunucu olmuştur. Artık farklı yerlerden yayımlanan Index.cshtml sayfasına gelen anlık kullanıcı sayısı ölçülebilir.
Global.cshtml:
Şimdi sıra geldi en önemli kısma, 2 farklı sunucudaki toplam kullanıcı sayısını almaya : Burada performans konusu çok önemlidir. Örneğin Redis veya Azure üzerinde Scale yapılabilir. (Birden fazla sunucu üzerine signalR tek bir sunucu gibi yayımlanabilir.) Tüm sunucular bu signalR sınıfına bağlanıp toplam kullanıcı sayısını tutulabilir. Ben burada daha basit ve pratik bir yol kullandım. Tüm sunuculardaki “Admin.cshtml” sayfaları “Global.cshtml” bir sayfa altında IFrame olarak yukarıda görüldüğü gibi koydum.
Global.cshtml: Aşağıda görüldüğü gibi farklı kaynaklardan yayımlanan “Admin” sayfaları “IFrame” olarak “Global” sayfasına eklenmiştir. Bir de toplam kullanıcı sayısının yazılacağı “message” id’li bir span oluşturulmuştur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Global</title> <link href="~/Content/bootstrap.min.css" rel="stylesheet" /> </head> <body> <div class="container"> <div class="jumbotron"> <h1><font color="red"><span id="message"></span></font></h1> </div> </div> <div class="text-center"> <iframe src="http://localhost:3453/Home/Admin" width="380" height="380" id="messageFrame" scrolling="no"></iframe> <iframe src="http://localhost:78/Home/Admin" width="380" height="380" id="messageFrame" scrolling="no"></iframe> </div> </body> </html> |
Eğer önceki makalem olam IFrame’de Cross Domain‘i okursanız bundan sonra anlatılacak konuyu daha iyi anlayabilirsiniz. Yapılması gereken IFrameler içerisinde kullanıcı sayısı değiştiğinde, parent yani Global.cshtml sayfadaki bir function’ın, IFrame’den gönderilen server ismi ve kullanıcı sayısı ile tetiklenmesi ve böylece ana sayfadaki Toplam Kullanıcı sayısının değişmesinin sağlanmasıdır. Buradaki zorluk tabiki cross domain problemidir. Bunu aşmanın yolu, IFrameler içinde “parent.postMessage()” ile parent yani Global.cshtml sayfaya ilgili dataların gönderilmesi ve Parent yani Global.cshtm tarafında da “addEventListener()” ya da browser desteğine göre “attachEvent()” methodları ile IFrame’den gelen datanın dinlenmesinin sağlanmasıdır.
Admin.cshtm: Kodlar aşağıdaki gibi değiştirilir. Önceki makaleden hatırlayacağınız gibi Client, “Index.cshtml” sayfaya gelince ya da ayrılınca, Admin sayfasındaki “refreshUserCount()” function’ı tetiklenmekte idi. Değişen UserCount servername’e göre ilgili tablonun içine basılır. Bundan sonra ilgili datalar “parent.postMessage(data, “http://localhost:3453”)” methodu ile Parent’a gönderilir. İlk parametre gönderilen server adı ve kullanıcı sayısı bilgileridir. 2. parametre ise Parent yani Global.cshtml sayfanın bulunduğu sunucu adresidir.
Image Source: https://i.stack.imgur.com/W2g09.jpg
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 62 63 |
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Admin</title> <script src="~/scripts/jquery-2.2.2.min.js"></script> <script src="~/scripts/jquery.signalR-2.2.0.min.js"></script> <script src="~/signalr/hubs"></script> <link href="~/Content/bootstrap.min.css" rel="stylesheet" /> <script> var hubProxy = $.connection.product; $.connection.hub.logging = true; $.connection.hub.start().done(function () { console.log("hub.start.done"); }).fail(function (error) { console.log(error); }); hubProxy.client.refreshUserCount = function (data) { console.log(data); var serverName = data.split('æ')[0]; var userCount = data.split('æ')[1]; $("#Sunucu").html(serverName); //var bodyHtml = "<tr><td>" + serverName + "</td><td>" + userCount + "</td></tr>"; //$('#tbody').html(bodyHtml); $('#' + serverName).html(userCount); parent.postMessage(data, "http://localhost:3453"); }; </script> </head> <body> <div class="container"> <div class="jumbotron"> <h1>Sunuculara Gore Online User Sayisi </h1> </div> <h2><font color="red"> Sunucu: <span id="Sunucu"></span></font></h2> </div> <table class="table"> <thead> <tr> <th>Sunucu Ismi</th> <th>Anlik User Sayisi</th> </tr> </thead> <tbody id="tbody"> <tr> <td>Bora Server 1</td> <td id="BoraServer1">0</td> </tr> <tr> <td>Bora Server 2</td> <td id="BoraServer2">0</td> </tr> </tbody> </table> </body> </html> |
Image Source: https://static.zinoui.com/img/blog/cross-domain-iframe-resize.png
Global.cshtml: İlgili kodlar aşağıdaki gibi değiştirilmiştir. Bu örnekte 2 sunucudan gelecek kullanıcı sayılarına bakılmaktadır. “addEventListener(“message”, listener, false)” methodu ile IFrame’den gelen mesajlar dinlenip “listenar()” function’ı tetiklenmiştir. İlgili function’da datanın geldiği sayfanın url’i “evet.origin” ile kontrol edilmekte ve dışarıdan gelebilecek ataklar bu şekilde bir nebzede olsa önlenebilmektedir. Gelen data parse edilip, server adı ve kullanıcı sayıları bilgileri alındıktan sonra ilgili toplan kullanıcı sayısı yeniden bulunup ekrana bası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 44 45 46 47 48 49 |
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Global</title> <link href="~/Content/bootstrap.min.css" rel="stylesheet" /> <script> var server1 = 0; var server2 = 0; function listener(event) { if (event.origin !== "http://localhost:3453" && event.origin !== "http://localhost:78") { return; } if (event.data.trim() != "") { var serverName = event.data.split('æ')[0]; var userCount = parseInt(event.data.split('æ')[1]); if (serverName == "BoraServer1") { server1 = userCount; } else if (serverName == "BoraServer2") { server2 = userCount; } console.log(event.data); console.log("Server1:" + server1) console.log("Server2:" + server2) document.getElementById("message").innerHTML = (server1+server2); } } if (window.addEventListener) { addEventListener("message", listener, false) } else { attachEvent("onmessage", listener) } </script> </head> <body> <div class="container"> <div class="jumbotron"> <h1><font color="red"><span id="message"></span></font></h1> </div> </div> <div class="text-center"> <iframe src="http://localhost:3453/Home/Admin" width="380" height="380" id="messageFrame" scrolling="no"></iframe> <iframe src="http://localhost:78/Home/Admin" width="380" height="380" id="messageFrame" scrolling="no"></iframe> </div> </body> </html> |
Bu makalede farklı serverlardaki anık kullanıcı sayısını real time olarak signalR web socket ile saydık. Redis Cache kullanarak timeout zamanında, pub/sub özelliği ile console bir application’a server ismi ve kullanıcı sayısını gönderdik. Daha sonra bu data’yi MsSql bir DB’ye asenkron olarak kaydettik. Ayrıca bir monitor sayfası yaptık. Ve diğer admin sayfaları bu sayfaya IFrame olarak koyduk. Daha sonra “parent.post()” ve “windows.addEventListener()” functionları ile toplam kullancısı sayısını tek bir “Global” sayfasında göstermeyi başardık.
Böylece geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hoşçakalın.
Source:
- https://docs.microsoft.com/tr-tr/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections
- https://github.com/ServiceStack/ServiceStack.Redis/blob/master/README.md
- https://docs.microsoft.com/tr-tr/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-clien
- https://www.codeproject.com/Articles/400499/How-To-Safeguard-Your-Site-With-HTML-Sandbox
Son Yorumlar