前一段时间写了一个简单的聊天室,是群聊的方式。博客地址: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数据库存储也可以吧,方案有很多,由于没有具体做过也不敢多费口舌。这个思路是没问题的,当然也会有更好的方法吧。