欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

史上最全面的SignalR系列教程-6、SignalR 实现聊天室

程序员文章站 2022-05-25 10:32:20
...

1、概述


通过前面几篇文章对SignalR的详细介绍。我们知道Asp.net SignalR是微软为实现实时通信的一个类库。一般情况下,SignalR会使用JavaScript的长轮询(long polling)的方式来实现客户端和服务器通信,随着Html5中WebSockets出现,SignalR也支持WebSockets通信。另外SignalR开发的程序不仅仅限制于宿主在IIS中,也可以宿主在任何应用程序,包括控制台,客户端程序和Windows服务等,另外还支持Mono,这意味着它可以实现跨平台部署在Linux环境下。

SignalR内部有两类对象:

  1. Http持久连接(Persisten Connection)对象:用来解决长时间连接的功能。还可以由客户端主动向服务器要求数据,而服务器端不需要实现太多细节,只需要处理PersistentConnection 内所提供的五个事件:OnConnected, OnReconnected, OnReceived, OnError 和 OnDisconnect 即可。
  2. Hub(集线器)对象:用来解决实时(realtime)信息交换的功能,服务端可以利用URL来注册一个或多个Hub,只要连接到这个Hub,就能与所有的客户端共享发送到服务器上的信息,同时服务端可以调用客户端的脚本。

SignalR将整个信息的交换封装起来,客户端和服务器都是使用JSON来沟通的,在服务端声明的所有Hub信息,都会生成JavaScript输出到客户端,.NET则依赖Proxy来生成代理对象,而Proxy的内部则是将JSON转换成对象。

 

2、SignalR实现聊天室(群聊)功能


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

// 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对象,创建一个房间的时候,我们将房间名称和进入房间的客户端的ConnectionId加入到这个字典里面,然后在聊天室里面点发送消息的时候,我们根据房间名查找到所有加入群聊的ConnectionId,然后调用Clients.Clients(IList connectionIds)方法来将消息群发到每个客户端。以上也就是实现聊天室的原理。

 

2.1、 创建ASP.NET Mvc项目

新建一个空的ASP.NET Mvc项目,取名为:SignalRGroupChat。

2.2、安装Nuget包

创建好项目后,要使用SignalR,需要先安装SignalR包,可以通过程序包管理控制台输入包安装命令进行安装。

Install-Package Microsoft.AspNet.SignalR
Install-Package Microsoft.Owin.Cors

2.3、聊天室后台代码实现

要实现聊天室功能,我们需要一些基础实体,如:用户类、房间类等,直接上代码:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace SignalRGroupChat
{
    public class UserContext
    {
        public UserContext()
        {
            Users = new List<User>();
            Connections = new List<Connection>();
            Rooms = new List<ConversationRoom>();
        }

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

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

        /// <summary>
        /// 房间集合
        /// </summary>
        public List<ConversationRoom> Rooms { get; set; }
    }

    public class User
    {
        /// <summary>
        /// 用户名
        /// </summary>
        [Key]        
        public string UserName { get; set; }

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

        /// <summary>
        /// 用户房间集合
        /// </summary>
        public virtual List<ConversationRoom> Rooms { get; set; }

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

    public class Connection
    {
        /// <summary>
        /// 连接ID
        /// </summary>
        public string ConnectionID { get; set; }

        /// <summary>
        /// 用户代理
        /// </summary>
        public string UserAgent { get; set; }

        /// <summary>
        /// 是否连接
        /// </summary>
        public bool Connected { get; set; }
    }

    /// <summary>
    /// 房间类
    /// </summary>
    public class ConversationRoom
    {
        /// <summary>
        /// 房间名称
        /// </summary>
        [Key]
        public string RoomName { get; set; }

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

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

 

实现聊天室的SignalR Hub代码:

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace SignalRGroupChat.Hubs
{
    /// <summary>
    /// 聊天室(群聊)
    /// </summary>
    [HubName("groupHub")]
    public class GroupHub : Hub
    {
        public static UserContext db = new UserContext();
        public void Hello()
        {
            Clients.All.hello();
        }

        /// <summary>
        /// 重写Hub连接事件
        /// </summary>
        /// <returns></returns>
        public override Task OnConnected()
        {
            // 查询用户。
            var user = db.Users.SingleOrDefault(u => u.UserName == Context.ConnectionId);

            //判断用户是否存在,否则添加
            if (user == null)
            {
                user = new User()
                {
                    UserName = Context.ConnectionId
                };
                db.Users.Add(user);
            }
            //发送房间列表
            var itme = from a in db.Rooms
                       select new { a.RoomName };
            Clients.Client(this.Context.ConnectionId).getRoomlist(JsonConvert.SerializeObject(itme.ToList()));
            return base.OnConnected();
        }

        /// <summary>
        /// 更新所有用户的房间列表
        /// </summary>
        private void GetRoomList()
        {
            var itme = from a in db.Rooms
                       select new { a.RoomName };
            string jsondata = JsonConvert.SerializeObject(itme.ToList());
            Clients.All.getRoomlist(jsondata);
        }

        // 重写Hub连接断开的事件 
        public override Task OnDisconnected(bool stopCalled)
        {
            // 查询用户 
            var user = db.Users.FirstOrDefault(u => u.UserName == Context.ConnectionId);
            if (user != null)
            {
                // 删除用户 
                db.Users.Remove(user);
                // 从房间中移除用户 
                foreach (var item in user.Rooms)
                {
                    RemoveFromRoom(item.RoomName);
                }
            }
            return base.OnDisconnected(stopCalled);
        }

        /// <summary>
        /// 加入聊天室
        /// </summary>
        /// <param name="roomName"></param>
        public void AddToRoom(string roomName)
        {
            //查询聊天室
            var room = db.Rooms.Find(a => a.RoomName == roomName);
            //存在则加入
            if (room != null)
            {
                //查找房间中是否存在此用户
                var isuser = room.Users.Where(a => a.UserName == Context.ConnectionId).FirstOrDefault();
                //不存在则加入
                if (isuser == null)
                {
                    var user = db.Users.Find(a => a.UserName == Context.ConnectionId);
                    user.Rooms.Add(room);
                    room.Users.Add(user);
                    Groups.Add(Context.ConnectionId, roomName);
                    //调用此连接用户的本地JS(显示房间)
                    Clients.Client(Context.ConnectionId).addRoom(roomName);                   
                }
                else
                {
                    Clients.Client(Context.ConnectionId).showMessage("请勿重复加入房间!");
                }
            }
        }

        /// <summary>
        /// 创建聊天室
        /// </summary>
        /// <param name="roomName"></param>
        public void CreatRoom(string roomName)
        {
            var room = db.Rooms.Find(a => a.RoomName == roomName);
            if (room == null)
            {
                ConversationRoom cr = new ConversationRoom()
                {
                    RoomName = roomName
                };
                //将房间加入列表
                db.Rooms.Add(cr);
                AddToRoom(roomName);
                Clients.Client(Context.ConnectionId).showMessage("房间创建完成!");
                GetRoomList();
            }
            else
            {
                Clients.Client(Context.ConnectionId).showMessage("房间名重复!");
            }
        }

        /// <summary>
        /// 退出聊天室
        /// </summary>
        /// <param name="roomName"></param>
        public void RemoveFromRoom(string roomName)
        {

            //查找房间是否存在
            var room = db.Rooms.Find(a => a.RoomName == roomName);
            //存在则进入删除
            if (room != null)
            {
                //查找要删除的用户
                var user = room.Users.Where(a => a.UserName == Context.ConnectionId).FirstOrDefault();
                //移除此用户
                room.Users.Remove(user);
                //如果房间人数为0,则删除房间
                if (room.Users.Count <= 0)
                {
                    db.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)
        {
            Clients.Group(Room, new string[0]).sendMessage(Room, Message + " " + DateTime.Now.ToString("HH:mm:ss"));
        }
    }
}

 

2.4、页面部分代码参考

@{
    ViewBag.Title = "GroupChat";
}

<h2>聊天室(群聊)实例</h2>
<div class="row">
    当前用户:<label id="username"></label> 
</div>
<div class="row">
    输入房间名:<input type="text" class="form-control" style="display: initial;" value="技术交流1" id="Roomname" /><button id="CreatRoom" class="btn btn-success">创建聊天室</button>     
</div>
<div class="row">
    <div class="col-md-3">
        <div style="float:left;border:1px solid #ff0000;margin:5px;">
            <div>房间列表</div>               
            <ul id="roomlist">
            </ul>
        </div> 
    </div>
    <div class="col-md-9">
         <div id="RoomList">
    </div>
    </div>  
</div>
<div class="row">
    <div class="col-md-4"></div>
    <ul id="UserList"></ul>
    <div class="col-md-8"></div>  
</div>

<br />

@section scripts {
    <script src="~/Scripts/jquery-3.3.1.min.js"></script>
    <script src="~/Scripts/jquery.signalR-2.4.1.min.js"></script>
    <script src="~/signalr/hubs"></script>
    <script type="text/javascript">
        var chat
        var roomcount = 0;
        $(function () {
            chat = $.connection.groupHub;
            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.addRoom = function (roomname) {
                var html = '<table class="table"><tr><td><div style="width: 80%;margin:5px;border:1px solid #ff0000;" id="' + roomname + '" roomname="' + roomname + '"><button οnclick="RemoveRoom(this)"  class="btn-danger">退出</button>\
                                <label>' + roomname + '</label>房间\
                                            聊天记录如下:<ul>\
                                            </ul>\
                                <input type="text" /> <button class="btn btn-success" οnclick="SendMessage(this)">发送</button>\
                                </div></td></tr></table>'
                $("#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+'"  class="btn-sm btn-info" οnclick="AddRoom(this)">加入</button></li>';
                        $("#roomlist").append(html);
                    }
                }
            }

            // 获取用户名称。
            $('#username').html(prompt('请输入您的名称:', ''));

            $.connection.hub.start().done(function () {
                $('#CreatRoom').click(function () {
                    if (roomcount < 2) {
                        chat.server.creatRoom($("#Roomname").val());
                        roomcount++;
                    } else {
                        alert("聊天窗口只允许有2个")
                    }
                })
            });
        });

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

        function RemoveRoom(btn) {
            var room = $(btn).parent();
            var roomname = $(room).attr("roomname");
            $(room).remove();
            chat.server.removeFromRoom(roomname);
        }

        function AddRoom(roomname) {
             var data =$(roomname).attr("roomname");
             chat.server.addToRoom(data);
        }
    </script>
}

 

3、效果展示

史上最全面的SignalR系列教程-6、SignalR 实现聊天室

 

4、代码下载


实例源码可以移步github下载,地址:https://github.com/yonghu86/SignalRTestProj

 

 

 

 

 

 

 

 

 

 

 

 

相关标签: Dotnet技术文章