首页 学海无涯 .NET Core 使用ASP.NET Core SignalR搭建聊天室(本站聊天室)
使用ASP.NET Core SignalR搭建聊天室(本站聊天室)
摘要 SignalR是.NET的一个开源实时框架,早在.NET Framework就已经存在,如今也有了相应的.NET Core版本。本站初期就已经完成的聊天室(参考留言页面),就是使用的SignalR搭建。本文也将分享该聊天室是如何搭建的。

SignalR介绍

有关SignalR其实我知道的很少,只知道它可以实现实时聊天。

下面一段摘抄自微软官方文档:

ASP.NET Core SignalR 是一个开源代码库,它简化了向应用添加实时 Web 功能的过程。 实时 Web 功能使服务器端代码能够即时将内容推送到客户端。

SignalR 提供了一个用于创建服务器到客户端远程过程调用(RPC)的 API。 RPC 通过服务器端 .NET Core 代码调用客户端上的 JavaScript 函数。

以下是 ASP.NET Core SignalR 的一些功能:

        1.自动管理连接。

        2.同时向所有连接的客户端发送消息。例如:聊天室。

        3.将消息发送到特定的客户端或客户组。

        4.扩展以处理增加的流量。

SignalR使用

一、前言

本博客开发之时,.NET Core才刚刚2.0版本,而SignalR更是只有预览版本,所以本篇文章所说的都是在.NET Core 2.0+SignalR1.0.0 -preview2的环境下,但是相信和.NET Core 2.2+SignalR 1.1.0差别不大。

二、创建ASP.NET Core项目

此处不要问,问就是省略......

三、获取和添加SignalR客户端库

以前忘记我是从哪里找的客户端文件了,其实就是一个signalr.js的文件。现在使用VS可以直接获取,步骤如下:

1.在“解决方案资源管理器”中,右键单击项目,然后选择“添加” > “客户端库”。

2.在“添加客户端库”对话框中,对于“提供程序”,选择“unpkg”

3.在“库”那一行,输入 @aspnet/signalr@版本号,如:@aspnet/signalr@1.1.0。

4.你可以选择包括所有库文件,也可以选择特定文件,实际上我们只需要“dist/browser”文件夹下的“signalr.js”或“signalr.min.js”

5.最后目标位置可以改,也可以不改,然后点安装,这时候我们项目文件就添加了SignalR客户端库“signalr.js”或“signalr.min.js”。

四、添加服务端代码

1.在项目文件夹下创建一个Hubs文件夹,用来存放SignalR相关的类

2.在Hubs文件夹下创建一个ConnectionList类(用于管理聊天室在线用户),继承自IReadOnlyCollection<T>,T是我们系统的用户类

using Blog.Entity;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Blog.Web.Hubs
{
    public class ConnectionList : IReadOnlyCollection<User>
    {
        /// <summary>
        /// 在线连接的集合
        /// </summary>
        private readonly ConcurrentDictionary<int, User> _connections = new ConcurrentDictionary<int, User>();

        /// <summary>
        /// 用户Id对应的用户对象
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <returns></returns>
        public User this[int userId]
        {
            get
            {
                if (_connections.TryGetValue(userId, out var connection))
                {
                    return connection;
                }
                return null;
            }
        }

        /// <summary>
        /// 连接数量
        /// </summary>
        public int Count => _connections.Count;

        /// <summary>
        /// 加入用户
        /// </summary>
        /// <param name="user">用户对象</param>
        public void Add(User user)
        {
            _connections.TryAdd(user.Id, user);
        }
        /// <summary>
        /// 移除用户
        /// </summary>
        /// <param name="userId">用户Id</param>
        public void Remove(int userId)
        {
            _connections.TryRemove(userId, out var dummy);
        }
        /// <summary>
        /// 实现接口
        /// </summary>
        /// <returns></returns>
        public IEnumerator<User> GetEnumerator()
        {
            foreach (var item in _connections)
            {
                yield return item.Value;
            }
        }
        /// <summary>
        /// 实现接口
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

3.通过依赖注入将ConnectionList添加到Services

        代码参考第5步。

4.在Hubs文件夹下创建一个ChatRoom类,并继承自Hub类

using Blog.Web.AppSupport;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;

namespace Blog.Web.Hubs
{
    [Authorize]
    public class ChatRoom : Hub
    {
        private ConnectionList Connections
        {
            get
            {
                return Context.GetHttpContext().RequestServices.GetService(typeof(ConnectionList)) as ConnectionList;
            }
        }

        private ISessionManager SessionManager
        {
            get
            {
                return Context.GetHttpContext().RequestServices.GetService(typeof(ISessionManager)) as ISessionManager;
            }
        }

        /// <summary>
        /// 客户端连接时触发
        /// </summary>
        /// <returns></returns>
        public override async Task OnConnectedAsync()
        {
            var currUser = SessionManager.CurrUser;

            Connections.Add(currUser);
            //发送在线人数给当前客户端
            await Clients.Caller.SendAsync("OnlineCount", Connections.Count);
            //通知其他客户端有人上线
            await Clients.Others.SendAsync("UserOnline", currUser.Name);
        }
        /// <summary>
        /// 客户端断开连接时触发
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        public override async Task OnDisconnectedAsync(Exception ex)
        {
            var currUser = SessionManager.CurrUser;

            Connections.Remove(currUser.Id);
            //通知其他客户端有人下线
            await Clients.Others.SendAsync("UserOffline", currUser.Name);
        }
        /// <summary>
        /// 客户端发送消息
        /// </summary>
        /// <param name="message"></param>
        public void Send(string message)
        {
            if (string.IsNullOrEmpty(message))
                return;
            var currUser = SessionManager.CurrUser;
            //发送给除自己以外的所有客户端
            Clients.Others.SendAsync("Receive", new { Msg = message, UserAvatar = currUser.Avatar, UserName = currUser.Name, Mine = false });
        }
    }
}

        在Hub类中使用Context.GetHttpContext().RequestServices.GetService()方法,可以获取到之前通过依赖注入的ConnnectionList类,以及其他服务。

        代码中的SessionManager类是我自己封装的一个管理应用程序的Session的类,他的作用就是从Session中获取当前用户信息,以便用作聊天室使用,这里就不展示了。大家可以用最原始的Session来获取用户信息即可。

        可以看到ChatRoom重写了Hub类的两个方法OnConnectedOnDisconnected方法,分别是当客户端连接和断开的时候执行。当客户端连接时,我们通过Session获取到用户信息,然后向Connections中添加用户对象,这样就保存了当前连接的用户,同时还要调用客户端方法OnlineCount和UserOnline(这两个方法可以合并),以告知其他在线的客户端某某某上线、当前在线人数等信息。同理,当客户端断开时,移除用户,并通知其他客户端。

        此外还有一个Send方法,这个方法是用于向其他客户端发送消息的。当一个客户端调用这个方法时,我们获取到调用这个方法的用户对象,然后给调用其他客户端的Receive方法,传入消息,发送人等信息。

5.注入SignalR服务和中间件

namespace Blog.Web
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }


        public void ConfigureServices(IServiceCollection services)
        {
            //SignalR服务
            services.AddSignalR(options =>
            {
                // Faster pings for testing
                //options.KeepAliveInterval = TimeSpan.FromSeconds(5);
            });
            //ConnectionList
            services.AddSingleton(typeof(ConnectionList));
        }


        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseSignalR(routes =>
            {
                routes.MapHub<ChatRoom>("/chatroom");
            });
        }
    }
}

6.以上代码仅供参考,看懂才是最重要的,切勿复制粘贴

五、添加客户端代码

由于本站聊天室的界面使用了layui的编辑器等,涉及到layui的使用,这里就不谈界面了,只聊聊Js代码。

1.引入signalr.js

<script src="~/lib/signalr/dist/browser/signalr.js"></script>

         路径根据自己实际情况。

2.编写聊天代码

        本站聊天室首先是需要用户登录网站后利用登录的用户直接作为聊天用户。以此为前提,代码的实现如下:

        ①创建SignalR连接对象

var url = '/chatroom'   //Hub地址
    , onlineCount = 0;  //在线人数
//创建signalR连接
window.hubConnection = new signalR.HubConnection(url);

        ②编写用户连接与断开的代码

var status = $(this).data('status'); //点击在线(online)or 离线(offline)
if (hubConnection.connection.connectionState === 1 && status === 'offline') {
    hubConnection.stop();   //切断连接
} else if (hubConnection.connection.connectionState !== 1 && status === 'online') {
    var connectLoading = layer.msg('连接聊天服务器中...', { icon: 16 });
    //连接聊天服务器
    hubConnection.start()
        .then(function () {
            //连接成功
            layer.close(connectLoading);
            $(self).addClass('online').attr('title', '在线');
            $chatbody.append('<p class="systemmsg">成功连接聊天室</p>');
            msg_end.scrollIntoView();
        })
        .catch(error => {
            //连接出错
            layer.close(connectLoading);
            if (error.statusCode === 401) {
                layer.msg('登录后方可使用聊天室', { shift: 6, icon: 5 });
            } else {
                console.error(error.message);
                layer.msg('连接聊天服务器失败', { shift: 6, icon: 5 });
            }
        });
}

        备注1:如果用户点击的在线,并且signalr处于未连接状态,则进行连接。反之,如果用户点击离线,并且signalr处于连接状态,则切断连接。

        备注2:核心方法hubConnection.start和stop都会触发服务端对应的OnConnectedOnDisconnected方法。

        ③编写接收服务端消息的事件

//接收用户发送的消息
//C# Code:Clients.Others.SendAsync("Recive", new { Msg = message, UserAvatar = currUser.Avatar, UserName = currUser.Name, Mine = false });
hubConnection.on('Recive', function (data) {
    if (data.mine) {
        data.class = 'msg-mine';
    } else {
        data.class = 'msg-other';
    }
    laytpl(chatMsgTpl.innerHTML).render(data, function (html) {
        $chatbody.append(html);
        msg_end.scrollIntoView();
    });
});

//有人上线
//C# Code:Clients.Others.SendAsync("UserOnline", currUser.Name);
hubConnection.on('UserOnline', function (userName) {
    $chatbody.append('<p class="systemmsg"><span style=\"color:#0094ff;margin-right:5px;\">' + userName + '</span>加入聊天室</p>');
    msg_end.scrollIntoView();

    onlineCount++;
    $('.chatroom .layui-card-header .onlineCount').text('(' + onlineCount + '人在线)');
});

//有人下线
//C# Code:Clients.Others.SendAsync("UserOffline", currUser.Name);
hubConnection.on('UserOffline', function (userName) {
    $chatbody.append('<p class="systemmsg"><span style=\"color:#0094ff;margin-right:5px;\">' + userName + '</span>退出聊天室</p>');
    msg_end.scrollIntoView();

    onlineCount--;
    $('.chatroom .layui-card-header .onlineCount').text('(' + onlineCount + '人在线)');
});

//在线人数推送(首次连接时会收到)
//C# Code:Clients.Caller.SendAsync("OnlineCount", Connections.Count);
hubConnection.on('OnlineCount', function (count) {
    onlineCount = count;
    $('.chatroom .layui-card-header').append('<span class="onlineCount">(' + onlineCount + '人在线)</span>');
});

        备注:每个客户端方法和对应的服务端调用都已经标注,可自行理解,不多做解释,有不懂的地方欢迎留言。

         ④编写发送消息按钮点击事件

//聊天室发送按钮点击
function chatsend() {
    //处于未连接状态则return
    if (hubConnection.connection.connectionState !== 1) {
        $chatbody.append(systemMsg('请先连接聊天室'));
        msg_end.scrollIntoView();
        return;
    }
    //检测内容是否合法
    //调用服务端Send方法
    hubConnection.invoke('Send', content);
    //将自己的消息Html拼接到聊天消息框
}

        备注:看代码注释即可        

        ⑤掉线处理

//连接断开时触发
hubConnection.onclose(function () {
    $('.chatroom .layui-card-header .status').removeClass('online').attr('title', '离线');
    $chatbody.append('<p class="systemmsg">你已与聊天室断开连接</p>');
    msg_end.scrollIntoView();
    $('.chatroom .layui-card-header .onlineCount').remove();
});

        备注:当客户端断开连接时,将在线状态改为离线,在聊天框中提示“已掉线”,并且将不能查看当前在线人数。

        ⑥还是同样一句话:以上代码仅供参考,看懂才是最重要的,切勿复制粘贴

六、查看效果

        1.用户断开与连接

        

        

        2.聊天消息收发

        

        

七、体验效果

        由于本站用户太少,所以聊天室常年为空。

        若想体验,骚年可用两个不同浏览器,打开聊天室页面,用两个不同QQ号登陆,即可进行实时聊天体验。

        传送门:聊天室


版权声明:本文由不落阁原创出品,转载请注明出处!

本文链接:http://www.leo96.com/article/detail/37

本文配乐
来说两句吧
最新评论
  • 鸡你太美
    鸡你太美
    看到博主的网站,我就看到了.net的希望