前一段時間寫了一個簡單的聊天室,是群聊的方式。部落格地址:http://www.cnblogs.com/panzi/p/4980346.html。還有一種需求就是常見的尤其是培訓機構的主頁面,經常會有1對1諮詢聊天視窗。那麼用singalR如何實現1對1聊天呢。
其實很簡單。我們先看看SingalR裡的IHubConnectionContext介面程式碼:
public interface IHubConnectionContext<T> {
//所有連線伺服器的使用者 T All { get; } //除了一部分使用者 T AllExcept(params string[] excludeConnectionIds);
//這個就是我們要用的點對點,針對單個使用者傳送訊息 T Client(string connectionId);
//群發訊息 T Clients(IList<string> connectionIds);
//按組群發訊息 T Group(string groupName, params string[] excludeConnectionIds);
//多組群發訊息 T Groups(IList<string> groupNames, params string[] excludeConnectionIds);
//使用者 T User(string userId); T Users(IList<string> userIds); }
這裡我們就用 T Client(string connectionId); 這個方法。呼叫方式為 Clients.Client("connectionId").clientFun(msg); //(clientFun為自定義客戶端接收訊息方法名)具體細節不在描述,這裡比較關鍵的就是,如何知道對方的ConnectionId,因為ConnectionId是自動生成的而且,每次重新整理頁面都會變,SingalR本身又不帶統計線上使用者的方法,所以,這個需要自己去實現。思路很清晰,這裡先用 靜態List做使用者線上列表資訊儲存。程式碼如下:
/// <summary> /// 簡單使用者統計model /// </summary> public class HubUser { /// <summary> /// 連線伺服器之後,自動生成的connectionId /// </summary> public string ConnectionId { get; set; } /// <summary> /// 客戶端使用者的主鍵ID /// 一般和業務相關的使用者ID /// </summary> public string ClientUserId { get; set; } /// <summary> /// 聊天所在組 /// </summary> public string GroupId { get; set; } }
public sealed class OnlineUserPool { private static Lazy<List<HubUser>> _onlineUser = new Lazy<List<HubUser>>(); public static List<HubUser> OnlineUser { get { return _onlineUser.Value; } } /// <summary> /// 新增使用者,一般在使用者 連線伺服器或者使用者重新連線的時候 /// </summary> /// <param name="user"></param> public static void AddUser(HubUser user) { DeleteUser(user); _onlineUser.Value.Add(user); } /// <summary> /// 刪除某個線上使用者 /// </summary> /// <param name="clientUserId"></param> /// <param name="connectionId"></param> public static void DeleteUser(HubUser user, bool unConnected = true) { var onlineUser = IsOnline(user); if (onlineUser != null) { _onlineUser.Value.Remove(onlineUser); } } public static HubUser IsOnline(HubUser user) { if (user == null) { throw new ArgumentNullException(); } string clientUserId = user.ClientUserId; string connectionId = user.ConnectionId; if (!string.IsNullOrEmpty(clientUserId)) { return _onlineUser.Value.FirstOrDefault(x => x.ClientUserId == clientUserId); } else { return _onlineUser.Value.FirstOrDefault(x => x.ConnectionId == connectionId); } } /// <summary> /// 獲取線上總數 /// </summary> /// <returns></returns> public static long GetUserCount() { return _onlineUser.Value.Count; } }
可以看到 OnlineUserPool 類實現了往靜態列表新增使用者,刪除使用者等一系列操作。
新增使用者操作需要,在使用者接入到聊天室的時候執行:
public Task Join(ZjMessage message) { message.connectionId = Context.ConnectionId; //就是使用者加入的時候 OnlineUserPool.AddUser(new HubUser { ClientUserId = message.userid, ConnectionId = Context.ConnectionId }); message.msg = "當前已經有:" + OnlineUserPool.GetUserCount() + " 人線上"; return Clients.All.receiveMessage(new { type = "join", msg = message }); }
刪除使用者操作就在重寫OnDisconnect方法裡執行,需要根據ConnectionId刪除
public override Task OnDisconnected(bool stopCalled) { ZjMessage message = new ZjMessage(Context.ConnectionId); //使用者離開 //使用者斷線,需要將該使用者從列表中刪除,(應該考慮短暫失去連線的可能性,不能直接從列表刪除。) OnlineUserPool.DeleteUser(new HubUser { ConnectionId = Context.ConnectionId }); return Clients.All.receiveMessage(new { type = "left", msg = message }); }
所以,當你想點對點傳送訊息的時候,將對方userId傳送到伺服器,然後伺服器從線上列表裡面查詢出相應的connectionID,然後將訊息推送到該connectionID的使用者,就實現了線上兩個人聊天了。當然,用靜態列表的方式也不是很好,如果使用者量龐大,會不會出什麼問題呢,我具體沒研究過。一般的方案,是放在專門的快取伺服器儲存,或者NOSQL資料庫儲存也可以吧,方案有很多,由於沒有具體做過也不敢多費口舌。這個思路是沒問題的,當然也會有更好的方法吧。