[Asp.net 开发系列之SignalR篇]专题三:使用SignalR实现聊天室的功能

一、引言

  在前一篇文章中,我向大家介绍了如何实现实现端对端聊天的功能的,在这一篇文章中将像大家如何使用SignalR实现群聊这样的功能。

二、实现思路

  要想实现群聊的功能,首先我们需要创建一个房间,然后每个在线用户可以加入这个房间里面进行群聊,我们可以为房间设置一个唯一的名字来作为标识。那SignalR类库里面是否有这样现有的方法呢?答案是肯定的。

// IGroupManager接口提供如下方法
// 作用:将连接ID加入某个组
// Context.ConnectionId 连接ID,每个页面连接集线器即会产生唯一ID
// roomName分组的名称
Groups.Add(Context.ConnectionId, roomName);

// 作用:将连接ID从某个分组移除
Groups.Remove(Context.ConnectionId, roomName);

// IHubConnectionContext接口提供了如下方法
// 调用客户端方法向房间内所有用户群发消息 
// Room:分组名称
// new string[0]:过滤(不发送)的连接ID数组
 Clients.Group(Room, new string[0]).clientMethod

  上面的代码也就是实现群聊的核心方法。Groups对象说白了也就是SignalR类库维护的一个列表对象而已,其实我们完全可以自己来维护一个Dictionary<string, List<string>>这个对象,创建一个房间的时候,我们将房间名称和进入房间的客户端的ConnectionId加入到这个字典里面,然后在聊天室里面点发送消息的时候,我们根据房间名查找到所有加入群聊的ConnectionId,然后调用Clients.Clients(IList<string> connectionIds)方法来将消息群发到每个客户端。以上也就是实现聊天室的原理。

三、使用SignalR实现聊天室的功能

  理清楚了实现思路之后,接下来我们就看下具体的实现代码,同时大家也可以对照代码来对照前面的实现思路。

  1. 首先看下聊天室功能所涉及实体类的实现代码:
/// <summary>
    /// 用户类
    /// </summary>
    public class User
    {
        /// <summary>
        /// 用户Id
        /// </summary>
        public string UserId { get; set; }

        /// <summary>
        /// 用户的连接集合
        /// </summary>
        public List<Connection> Connections { get; set; }

        /// <summary>
        /// 用户房间集合,一个用户可以加入多个房间
        /// </summary>
        public List<ChatRoom> Rooms { get; set; }

        public User()
        {
            Connections = new List<Connection>();
            Rooms = new List<ChatRoom>();
        }
    }

    public class Connection
    {
        //连接ID
        public string ConnectionId { get; set; }

        //用户代理
        public string UserAgent { get; set; }
        //是否连接
        public bool Connected { get; set; } 
    }

     /// <summary>
    /// 房间类
    /// </summary>
    public class ChatRoom
    {
        // 房间名称
        public string RoomName { get; set; }

        // 用户集合
        public List<User> Users { get; set; }

        public ChatRoom()
        {
            Users = new List<User>();
        }
    }

    /// <summary>
    /// 上下文类,用来模拟EF中的DbContext
    /// </summary>
    public class ChatContext
    {
        public List<User> Users { get; set; }

        public List<Connection> Connections { get; set; }

        public List<ChatRoom> Rooms { get; set; }

        public ChatContext()
        {
            Users = new List<User>();
            Connections = new List<Connection>();
            Rooms = new List<ChatRoom>();
        }
    }

  2. 接下来,让我们来看到集线器的实现:

[HubName("chatRoomHub")]
    public class GroupsHub : Hub
    {
        public static ChatContext DbContext = new ChatContext();

        #region IHub Members
        // 重写Hub连接事件
        public override Task OnConnected()
        {
            // 查询用户
            var user = DbContext.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId);

            if (user == null)
            {
                user = new User
                {
                    UserId = Context.ConnectionId
                };

                DbContext.Users.Add(user);
            }

            // 发送房间列表
            var items = DbContext.Rooms.Select(p => new {p.RoomName});
            Clients.Client(this.Context.ConnectionId).getRoomList(JsonHelper.ToJsonString(items.ToList()));
            return base.OnConnected();
        }

        // 重写Hub连接断开的事件
        public override Task OnDisconnected(bool stopCalled)
        {
            // 查询用户
            var user = DbContext.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId);

            if (user != null)
            {
               // 删除用户
                DbContext.Users.Remove(user);

                // 从房间中移除用户
                foreach (var item in user.Rooms)
                {
                    RemoveUserFromRoom(item.RoomName);
                }
            }
            return base.OnDisconnected(stopCalled);
        }

        #endregion 

        #region Public Methods

        // 为所有用户更新房间列表
        public void UpdateRoomList()
        {
            var itme = DbContext.Rooms.Select(p => new {p.RoomName});
            var jsondata = JsonHelper.ToJsonString(itme.ToList());
            Clients.All.getRoomlist(jsondata);
        }

        /// <summary>
        /// 加入聊天室
        /// </summary>
        public void JoinRoom(string roomName)
        {
            // 查询聊天室
            var room = DbContext.Rooms.Find(p => p.RoomName == roomName);

            // 存在则加入
            if (room == null) return;

            // 查找房间中是否存在此用户
            var isExistUser = room.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId);

            // 不存在则加入
            if (isExistUser == null)
            {
                var user = DbContext.Users.Find(u => u.UserId == Context.ConnectionId);
                user.Rooms.Add(room);
                room.Users.Add(user);
               
                // 将客户端的连接ID加入到组里面
                Groups.Add(Context.ConnectionId, roomName);

                //调用此连接用户的本地JS(显示房间)
                Clients.Client(Context.ConnectionId).joinRoom(roomName);
            }
            else
            {
                Clients.Client(Context.ConnectionId).showMessage("请勿重复加入房间!");
            }
        }

        /// <summary>
        /// 创建聊天室
        /// </summary>
        /// <param name="roomName"></param>
        public void CreateRoom(string roomName)
        {
            var room = DbContext.Rooms.Find(a => a.RoomName == roomName);
            if (room == null)
            {
                var cr = new ChatRoom
                {
                    RoomName = roomName
                };

                //将房间加入列表
                DbContext.Rooms.Add(cr);

                // 本人加入聊天室
                JoinRoom(roomName);
                UpdateRoomList();
            }
            else
            {
                Clients.Client(Context.ConnectionId).showMessage("房间名重复!");
            }
        }

        public void RemoveUserFromRoom(string roomName)
        {
            //查找房间是否存在
            var room = DbContext.Rooms.Find(a => a.RoomName == roomName);

            //存在则进入删除
            if (room == null)
            {
                Clients.Client(Context.ConnectionId).showMessage("房间名不存在!");
                return;
            }

            // 查找要删除的用户
            var user = room.Users.FirstOrDefault(a => a.UserId == Context.ConnectionId);
            // 移除此用户
            room.Users.Remove(user);
            //如果房间人数为0,则删除房间
            if (room.Users.Count <= 0)
            {
                DbContext.Rooms.Remove(room);
            }

            Groups.Remove(Context.ConnectionId, roomName);

            //提示客户端
            Clients.Client(Context.ConnectionId).removeRoom("退出成功!");
        }

        /// <summary>
        /// 给房间内所有的用户发送消息
        /// </summary>
        /// <param name="room">房间名</param>
        /// <param name="message">信息</param>
        public void SendMessage(string room, string message)
        {
            // 调用房间内所有客户端的sendMessage方法
            // 因为在加入房间的时候,已经将客户端的ConnectionId添加到Groups对象中了,所有可以根据房间名找到房间内的所有连接Id
            // 其实我们也可以自己实现Group方法,我们只需要用List记录所有加入房间的ConnectionId
            // 然后调用Clients.Clients(connectionIdList),参数为我们记录的连接Id数组。
            Clients.Group(room, new string[0]).sendMessage(room, message + " " + DateTime.Now);
        }
        #endregion 
    }

  3. 上面SignalR服务端的代码实现已经完成,接下来就让我们一起看看客户端视图的实现:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <script src="~/Scripts/jquery-2.2.2.min.js"></script>
    <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
    <script src="~/Scripts/layer/layer.min.js"></script>
    <!--这里要注意,这是虚拟目录,也就是你在OWIN Startup中注册的地址-->
    <script src="/signalr/hubs"></script>

    <script type="text/javascript">
        var chat;
        var roomcount = 0;
        
        $(function() {
            chat = $.connection.chatRoomHub;
            chat.client.showMessage = function(message) {
                alert(message);
            };
            chat.client.sendMessage = function(roomname, message) {
                $("#" + roomname).find("ul").each(function() {
                    $(this).append('<li>' + message + '</li>');
                });
            };
            chat.client.removeRoom = function(data) {
                alert(data);
            };
            chat.client.joinRoom = function (roomname) {
                var html = '<div style="float:left; margin-left:360px; border:double; height:528px;width:493px" id="' + roomname + '" roomname="' + roomname + '"><button onclick="RemoveRoom(this)">退出</button>\
                                    ' + roomname + '房间\
                                                聊天记录如下:<ul>\
                                                </ul>\
                                    <textarea class="ChatCore_write" id="ChatCore_write" style="width:400px"></textarea> <button onclick="SendMessage(this)">发送</button>\
                                    </div>';
                $("#RoomList").append(html);
            };

            //注册查询房间列表的方法
            chat.client.getRoomlist = function(data) {
                if (data) {
                    var jsondata = $.parseJSON(data);
                    $("#roomlist").html(" ");
                    for (var i = 0; i < jsondata.length; i++) {
                        var html = ' <li>房间名:' + jsondata[i].RoomName + '<button roomname="' + jsondata[i].RoomName + '" onclick="AddRoom(this)">加入</button></li>';
                        $("#roomlist").append(html);
                    }
                }
            };
            // 获取用户名称。
            $('#username').html(prompt('请输入您的名称:', ''));

            $.connection.hub.start().done(function() {
                $('#CreatRoom').click(function() {
                    chat.server.createRoom($("#Roomname").val());
                });
            });
        });
        
        function SendMessage(btn) {
            var message = $(btn).prev().val();
            var room = $(btn).parent();
            var username = $("#username").html();
            message = username + ":" + message;
            var roomname = $(room).attr("roomname");
            chat.server.sendMessage(roomname, message);
            $(btn).prev().val('').focus();
        }
        
        function RemoveRoom(btn) {
            var room = $(btn).parent();
            var roomname = $(room).attr("roomname");
            chat.server.removeUserFromRoom(roomname);
        }
        
        function AddRoom(roomname) {
            var data =$(roomname).attr("roomname");
            chat.server.joinRoom(data);
        }

    </script>
</head>
<body>
    <div>
        <div>名称:<p id="username"></p></div>
        输入房间名:
        <input type="text" value="聊天室1" id="Roomname" />
        <button id="CreatRoom">创建聊天室</button>
    </div>
    <div style="float:left;border:double">
        <div>房间列表</div>
        <ul id="roomlist"></ul>
    </div>
    <div id="RoomList">
    </div>
</body>
</html>

  4. 经过上面3步,聊天室的功能就已经完成了,在看具体效果之前,这里附加一个帮助类的代码:

/// <summary>
    /// JSON 帮助类
    /// </summary>
    public class JsonHelper
    {
        /// <summary>
        /// 从一个对象信息生成Json字符串
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string ToJsonString(object obj)
        {
            return JsonConvert.SerializeObject(obj);
        }

        /// <summary>
        /// 从Json字符串生成对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsonString"></param>
        /// <returns></returns>
        public static T ToObject<T>(string jsonString)
        {
            return JsonConvert.DeserializeObject<T>(jsonString);
        }
    }
View Code

四、运行效果

  接下来,就具体看看聊天室功能的运行效果,具体运行效果如下图所示:

五、总结

  到这里,本篇的所有内容都介绍完了,接下来我一篇文章将实现如何使用SignalR来实现发图片的功能。

  本文所有源码下载地址:SignalRChatRoom

 

posted @ 2016-04-09 22:25 Learning hard 阅读(...) 评论(...) 编辑 收藏