天天看点

ASP.NET SingalR 点对点聊天实现思路总结

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

多学点,总不会吃亏的。