前一段時間寫了一個簡單的聊天室,是群聊的方式。博客地址: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數據庫存儲也可以吧,方案有很多,由於沒有具體做過也不敢多費口舌。這個思路是沒問題的,當然也會有更好的方法吧。