我們在開發Web應用時,有時候需要將Server端的的資訊Push到用戶端。常見的一個情境就是微博應用,需要將一個使用者的收聽即時訊息推送到Web端,也就是使用者的更新使用者的Timeline。
對此通用的解決方案就是Long Polling——支援XMLHttpRequest的瀏覽器都可以使用,使得其適用範圍廣。對此需要注意的就是Server端的處理能力,最好能用類似Node.js的Non-Block式的並發。GitHub有個項目SignalR使得的在asp.net中實現Server Side的Push變得簡單。為了使用SignalR,我們需要在NuGet中搜尋SignalR並安裝。
安裝完成後我們便可以使用SignalR了,我們先看Client端的實現:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head runat="server"> <title>Welcome to Microblog</title> <script src="../Scripts/jquery-1.6.4.js" type="text/javascript"></script> <script src="../Scripts/jquery.signalR.js" type="text/javascript"></script> <script src="../signalr/hubs"></script></head><body> <form id="form1" runat="server"> <div> <div> <span>MicroBlog ID:</span><input type="text" id="uid" name="uid" value=" " /> <input type="button" name="refreshTimeline" id="refreshTimeline" value="Refresh Timeline" /> </div> <ul id="messages"></ul> </div> </form> <script type="text/javascript"> $(function () { // Proxy created on the fly var conn = $.connection.microBlogServer; // Declare a function so the server can invoke it conn.newMessage = function (msg) { $('#messages').append('<li>' + msg + '</li>'); }; conn.notify = function(notice) { $('#messages').append('<li>[system notice]:\t' + notice + '</li>'); } // Start the connection $.connection.hub.start(); //call server side refresh when all things ready $("#refreshTimeline").click(function() { conn.refreshTimeline($('#uid').val()); }); }); </script></body></html>
伺服器端也十分簡單:
public class MicroBlogServer:Hub, IDisconnect{ private static Dictionary<string, bool> cancelQueue = new Dictionary<string, bool>(); public void RefreshTimeline(string uid) { cancelQueue.Add(this.Context.ConnectionId, false); var msgTask = new Task(new Action<object>(PushUserMessage), uid); msgTask.Start(); } private void PushUserMessage(object uid) { while (true) { if (cancelQueue[this.Context.ConnectionId]) { cancelQueue.Remove(this.Context.ConnectionId); return; } string msg = GetNewMessage(uid.ToString()); Caller.newMessage(msg); //push new message to client string notice = GetSystemNotification(); if (!string.IsNullOrEmpty(notice)) Clients.notify(notice); } } public System.Threading.Tasks.Task Disconnect() { if (cancelQueue.Keys.Contains(this.Context.ConnectionId)) { cancelQueue[this.Context.ConnectionId] = true; } return null; } #region User Message Generator private string GetNewMessage(string uid) { Thread.Sleep(3000); return string.Format("message from xxx to {0} ({1})", uid, DateTime.Now); } private string GetSystemNotification() { if (new Random().Next(6) >= 5) return "system will shutdown for maintenance"; else return string.Empty; } #endregion}