[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);
        }
    }

四、运行效果

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

五、总结

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

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

时间: 2024-10-25 09:28:01

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

[Asp.net 开发系列之SignalR篇]专题二:使用SignalR实现酷炫端对端聊天功能

一.引言 在前一篇文章已经详细介绍了SignalR了,并且简单介绍它在Asp.net MVC 和WPF中的应用.在上篇博文介绍的都是群发消息的实现,然而,对于SignalR是为了实时聊天而生的,自然少了不像QQ一样的端对端的聊天了.本篇博文将介绍如何使用SignalR来实现类似QQ聊天的功能. 二.使用SignalR实现端对端聊天的思路 在介绍具体实现之前,我先来介绍了使用SignalR实现端对端聊天的思路.相信大家在前篇文章已经看到过Clients.All.sendMessage(name,

【Windows10&nbsp;IoT开发系列】配置篇

原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry Pi.MinnowBoard和Galileo平台上的核心版.本文重点针对Raspberry Pi平台的Windwos10 IoT配置做介绍. Windows 10 IoT Editions ​一:设置你的电脑. 注:​开发Windows10 IoT的电脑需要Visual Studio 2015.

leaflet-webpack 入门开发系列一初探篇(附源码下载)

前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 webpack 配置介绍文档详细的 webpack 文档配置介绍,适合新手查看,我也是边看边学 vscode 安装包下载,我这边用 vscode工具编译开发前端项目,个人觉的这款工具还不错 leaflet api文档介绍,详细介绍 leaflet 每个类的函数以及属性等等 leaflet 在线例子 l

openlayers5-webpack 入门开发系列一初探篇(附源码下载)

前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 webpack 配置介绍文档详细的 webpack 文档配置介绍,适合新手查看,我也是边看边学 vscode 安装包下载,我这边用 vscode工具编译开发前端项目,个人觉的这款工具还不错 openlayers5 api文档介绍,详细介绍 openlayers5 每个类的函数以及属性等等 op

[Asp.net 开发系列之SignalR篇]专题六:使用SignalR实现消息提醒

一.引言 前面一篇文章我介绍了如何使用SignalR实现图片的传输,然后对于即时通讯应用来说,消息提醒是必不可少的.现在很多网站的都有新消息的提醒功能.自然对于SignalR系列也少不了这个功能的实现了.在这篇文章中将介绍如何使用SignalR+iNotify库来实现新消息的声音和弹框提醒. 二.消息提醒的实现思路 消息提醒也就是当客户有新消息来时,在客户端的右下角进行弹框提醒.要实现这个功能的思路是: SignalR服务端推送消息到客户端的实现方式为调用客户端的receiveMessage方法

微信商城开发系列第四篇 不写代码玩转微信公众号

本系列文章属作者原创文章,请尊重作者的劳动成果,转载请注明出处:walkingmanc的专栏 , 谢谢! 同时欢迎大家加入微信商城开发QQ群:364072602,共同探讨进步.  为什么叫不写代码玩转微信公众号呢? 我们大家都知道,微信公众号有两种模式,一种是编辑模式,一种是开发模式.所谓的不写代码玩转微信公众号,其实就是在编辑模式下如何使用微信公众号的意思,呵呵,是不是有种恍然大悟的感觉. 其实,如果你关注的微信公众号比较多的话,你会发现有很多有名的公众号,它们没有菜单,每天都会发布4到5篇文

chromium浏览器开发系列第四篇:如何调试最新chromium源码

附上上几篇文章地址,方便大家查看: 下载源码 编译源码 目录结构 接二连三的事情,时间比较紧张,但是还是没有把这个系列的文章丢掉,因为这也是对自己知识的总结吧.提倡大家多写写,以后再看的时候会有种莫名的小激动. 上周写的是chromium的目录结构,好像大家不太感兴趣,在我看来这部分很重要.开头有链接地址,大家想看可以再看看. 从源码下载到编译,到目录结构,今天终于到了重要的环节,调试. 在windows上,调试工具都是使用微软自家的产品:Visual Studio或者winDBG(windeb

chromium浏览器开发系列第四篇:如何调试最新chromium

接二连三的事情,时间比较紧张,但是还是没有把这个系列的文章丢掉,因为这也是对自己知识的总结吧.提倡大家多写写,以后再看的时候会有种莫名的小激动. 上周写的是chromium的目录结构,好像大家不太感兴趣,在我看来这部分很重要.开头有链接地址,大家想看可以再看看. 从源码下载到编译,到目录结构,今天终于到了重要的环节,调试. 在windows上,调试工具都是使用微软自家的产品:Visual Studio或者winDBG(windebug).Chromium也是与时俱进,现在主要使用Visual S

chromium浏览器开发系列第五篇:Debugging with WinDBG

Windbg相信windows开发的人都知道,有些人用的溜儿溜儿的,有个crash,直接拿这个工具一分析,就定位出来了.非常好用.以前有个同事,做sdk开发的,会各种命令.来北京后,还去过微软面试(不过当时是做外包,挣得也不少),问的问题就包括会不会用windbg定位问题.当时就会几个简单的命令,最后还是没面上(不堪回首). 使用windbg调试windows下的程序,只要有符号文件,问题定位分分钟的事.下面主要讲一下使用windbg调试chromium.有些是从官网上对翻过来的,如果大家看不明