[ASP.NET][SignalR] 悠閒 Coding 系列(二) - 認識 SignalR 實作上線清單、對特定對象傳訊(以聊天室為例)


我們的第一彈「悠閒系列」了解到如何利用 SignalR 為我們建立 real-time 的應用程式,而那篇的範例是以聊天室來帶大家入門認識 SignalR ,但我們一般的聊天室當中還會有的功能像是聊天列表、密語、或是建立群組聊天,就像我們都熟悉的 Facebook 那樣,可以對個別使用者發送訊息,或是建立多人聊天,而在右邊也有上線列表....等等,所以第二彈我們就來看看如何利用 SignalR 的特性做到上述的功能。

前言

我們的第一彈「悠閒系列」了解到如何利用 SignalR  為我們建立 real-time 的應用程式,而那篇的範例是以聊天室來帶大家入門認識 SignalR ,但我們一般的聊天室當中還會有的功能像是聊天列表、密語、或是建立群組聊天,就像我們都熟悉的 Facebook 那樣,可以對個別使用者發送訊息,或是建立多人聊天,而在右邊也有上線列表....等等,所以第二彈我們就來看看如何利用 SignalR 的特性做到上述的功能。

前篇回顧

如果沒看過第一篇入門 SignalR 的讀者,建議你可以先看一下「悠閒 Coding 系列(一) - 認識 SignalR 建立 realtime 網頁(含完整程式碼下載)」,透過 Client 的 Javascript 與 Server 端的 Hub 進行連線來示範如何利用 SignalR 來寫出一個簡單的聊天室,不過在進入這篇主題之前我們先回顧一下我們上一篇的程式碼部分。

Hub:


public class codingChatHub : Hub
{
    public void Hello(string name)
    {
        //這邊會傳入name參數
        //呼叫所有連線狀態中頁面上的 javascript function => hello
        //透過server端呼叫client的javascript function

        string message = "歡迎使用者" + name + "加入聊天室";
        Clients.All.hello(message);
    }

    public void SendMessage(string name, string message)
    {
        //這邊會傳入name和message參數
        //並且會呼叫所有連線狀態中頁面上的 javascript function => sendAllMessage
        //透過server端呼叫client的javascript function

        message = name + ":" + message;
        Clients.All.sendAllMessge(message);
    }
}

Javascript:


<script type="text/javascript">
    var userID = "";

    $(function () {

        while (userID.length == 0) {
            userID = window.prompt("請輸入使用者名稱");
            if (!userID)
                userID = "";
        }
        $("#userName").append(userID).show();

        //建立與Server端的Hub的物件,注意Hub的開頭字母一定要為小寫
        var chat = $.connection.codingChatHub;

        //建立連線後,我們接著來定義client端的function來讓Server端的hub呼叫。

        chat.client.hello = function (message) {
            $("#messageList").append("<li>" + message + "</li");
        }

        chat.client.sendAllMessge = function (message) {
            $("#messageList").append("<li>" + message + "</li");
            $("#message").val('');
        }

        //將連線打開
        $.connection.hub.start().done(function () {
            //當連線完成後,呼叫Server端的hello方法,並傳送使用者姓名給Server
            chat.server.hello(userID);
        });;

        $("#send").click(function () {
            //呼叫Server端的sendMessage方法,並傳送使用者姓名及訊息內容給Server
            chat.server.sendMessage(userID, $("#message").val());
        });

    });
</script>

從這邊可以看到,當使用者連線完成後 $.connction.hub.done 會去呼叫 Hub 的 hello 這個 function 並傳入 userID,而 Server 端接收後會廣播至全體連線的使用者告知說 xxx 加入聊天室。而當 Clinet 端發送訊息時,也會利用 chat.server.sendMessage() 的方法來呼叫 Server 端的 sendMessage function,最後一樣以廣播的方式將訊息傳送給所有使用者。

不過這樣的功能還是太單調,我們先想像一下 Facebook 右邊會有上線列表,並且可以針對個別使用者發送訊息,或是建立群組的方式來進行群體聊天,因為礙於篇幅關係所以此篇先完成如何取得上線列表,並且針對個別使用者發送訊息兩項功能,至於群組聊天將拉到下一個篇幅在做詳細的範例 ~

越來越完善的聊天室

這邊在提醒一次讀者,因為這篇的內容是由第二篇延伸過來,對於 SignalR 概念不熟的讀者可以先閱讀「悠閒 Coding 系列(一) - 認識 SignalR 建立 realtime 網頁(含完整程式碼下載)」這篇文章 ,另外想練習這篇範例的朋友可以先下載第一篇的程式碼來逐步進行修改,連結在此 http://sdrv.ms/ZmkANZ ~

1.首先我們先修改一下畫面的部份,因為我們這邊要擴充上線列表以及密語的功能,所以畫面當然會長的不太一樣。

HTML:


<head runat="server">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>SignalR 聊天室</title>
    <style>
        #userName {
            display: none;
            color: red;
        }

        #messageBox, #chatList {
            float: left;
            height: 300px;
            width: 300px;
            overflow: auto;
        }

        #messageBox {
            border: 1px solid #000;
        }

        #chatList {
            width: 150px;
            overflow: scroll;
        }

        #list li {
            cursor: pointer;
        }

        #bar {
            clear: both;
        }

        p {
            margin: 0;
        }
    </style>
</head>
<body>
    <p id="userName">Hi! </p>
    <div id="messageBox">
        <p>聊天室內容</p>
        <ul id="messageList"></ul>
    </div>
    <div id="chatList">
        <p>上線清單</p>
        <ul id="list">
        </ul>
    </div>
    <div id="bar">
        <select id="box">
            <option value="all">所有人</option>
        </select>
        <input type="text" id="message" />
        <input type="button" id="send" value="發送" />
    </div>
</body>

樣板畫面:

好吧 ~ 很陽春的一個聊天室畫面,不過麻雀雖小五臟俱全,右邊就是稍後為了顯示上線人數的地方,而在下面輸入訊息旁邊也多了個下拉式選單方便我們稍後來對某個使用者發送密語。

2.接著我們來為我們 Server 端的 Hub 進行調整:


 public class codingChatHub : Hub
 {

     //宣告靜態類別,來儲存上線清單
     public static class UserHandler
     {
         public static Dictionary<string, string> ConnectedIds = new Dictionary<string, string>();
     }

     //使用者連現時呼叫
     public void userConnected(string name)
     {
         //進行編碼,防止XSS攻擊
         name = HttpUtility.HtmlEncode(name);
         string message = "歡迎使用者 " + name + " 加入聊天室";

         //發送訊息給除了自己的其他使用者
         Clients.Others.addList(Context.ConnectionId, name);
         Clients.Others.hello(message);

         //發送訊息至自己,並且取得上線清單
         Clients.Caller.getList(UserHandler.ConnectedIds.Select(p => new { id = p.Key, name = p.Value }).ToList());

         //新增目前使用者至上線清單
         UserHandler.ConnectedIds.Add(Context.ConnectionId, name);
     }

     //發送訊息給所有人
     public void sendAllMessage(string message)
     {
         message = HttpUtility.HtmlEncode(message);
         var name = UserHandler.ConnectedIds.Where(p => p.Key == Context.ConnectionId).FirstOrDefault().Value;
         message = name + "說:" + message;
         Clients.All.sendAllMessge(message);
     }


     //發送訊息至特定使用者
     public void sendMessage(string ToId, string message)
     {
         message = HttpUtility.HtmlEncode(message);
         var fromName = UserHandler.ConnectedIds.Where(p => p.Key == Context.ConnectionId).FirstOrDefault().Value;
         message = fromName + " <span style='color:red'>悄悄的對你說</span>:" + message;
         Clients.Client(ToId).sendMessage(message);
     }

     //當使用者斷線時呼叫
     public override Task OnDisconnected()
     {
         //當使用者離開時,移除在清單內的 ConnectionId
         Clients.All.removeList(Context.ConnectionId);
         UserHandler.ConnectedIds.Remove(Context.ConnectionId);
         return base.OnDisconnected();
     }
}

程式碼說明:

  • 宣告一個靜態類別,並建立一個字典來存放所有使用者的 ConnectionId 和 Name
  • UserConnected 會在使用者連線成功後執行,使用 Clients.Caller().getList() 來取得上線清單給自己,並利用 Clinets.Other.addList() 發送訊息給除了自己以外的使用者。
  • sendAllMessage 使用 Clients.All.sendAllMessage() 將訊息發送至所有人
  • sendMessage 使用 Clinets.Cliendt(ConnectionId).sendMessage() 發送至特定使用者
  • 當使用者離現時會出發 OnDisconnected 此方法,並使用 Clients.All.removeList() 呼叫 Client 端 Javascript 將此離線人員從清單上清除。

3.而前端 Javascript 修改後如下:


<%-- 引用 jQuery 的參考--%>
<%--引用 SignalR 的參考--%>

<%--這邊滿重要的,這個參考是動態產生的,當我們build之後才會動態建立這個資料夾,且需引用在jQuery和signalR之後--%>

重點整理:

  • 前端 Javascript 利用 chat.server.method 來呼叫 Hub 的應用程式,而 chat.client.method 則為建立給 Hub 呼叫的 function
  • 前端 Javascript 可利用 $.connection.hub.id 來取得自己的 ConnectionId
  • 另外這邊也整理出在 Hub 裡的幾種呼叫方式:

// 呼叫所有使用者
Clients.All.send(message);

// 呼叫除了自己的使用者
Clients.Others.send(message);

// 呼叫所有使用者除了某個 ConnectionId 的使用者
Clients.AllExcept(Context.ConnectionId).send(message);

// 呼叫自己
Clients.Caller.send(message);

// 呼叫特定使用者
Clients.Client(Context.ConnectionId).send(message);

完成畫面

程式碼下載

SignalR_20130530

總結

現在我們聊天室的功能越來越完善了,除了原本單純發送廣播訊息以外,現在又多了可以對特定使用者發送訊息並且有上線清單可以查看,當然這都只是應用的一部分除此之外我們也可以運用在類似建立黑名單的功能或是只有群組內的使用者收的到訊息等功能。SignalR 整個底層核心非常大,如果搭配前端 MVVM 像是 Angular 或是 knockout 等 Javascript Framework 更可以把即時型更添一層樓,後面會以一個篇幅來介紹 SignalR 建立群組的功能以及接下來也會介紹 SignalR 如何搭配 Javascript  MVVM 的架構來達到互動性超高的即時網頁。

新手發文,如有錯誤煩請告知,感謝。
如果喜歡我的文章請按推薦,有任何問題歡迎下面留言~~~

 

 

簽名:

學習這條路很廣,喜歡什麼技術不重要,重要的是你肯花時間去學習