后端 C# .net服务器 前端 原生网页、微信小程序、uni-app 集成 SignalR 做 即时通讯

就让往事随风都随风 2024-07-10 12:33:02 阅读 78

一、 后端服务器

        后端集成SignalR比较简单,首先要在解决方案中依赖几个SignalR的库,SignalR的库就是做即时通讯的主要库,我们建立连接、收发信息都需要用这个库来进行。

        除了这几个库以外,还要还有几个依赖库要一并依赖进来。

        Owin库的作用主要是为了在项目StartUp时将SignalR库的几个Hub文件注册起来,Hub文件就相当于是我们接口中的Controller文件。

        然后在项目下创建一个文件夹Hubs,Hubs下创建文件ChatHub

using Common.Log;

using Microsoft.Ajax.Utilities;

using Microsoft.AspNet.SignalR;

using Newtonsoft.Json;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using System.Web;

using WebGrease;

using WinexHK.Hubs.ConectionOperate;

namespace WinexHK.Hubs

{

    /// <summary>

    /// ChatHub

    /// </summary>

    public class ChatHub : PersistentConnection

    {

        private ConnectionManagement summaryTableCon = new ConnectionManagement();

        /// <summary>

        /// 连接成功后调用

        /// </summary>

        /// <param name="request"></param>

        /// <param name="connectionId"></param>

        /// <returns></returns>

        protected override Task OnConnected(IRequest request, string connectionId)

        {

            //获得SignalR的连接id

            var connid = connectionId;

            //获得用户id

            var userid = "123321";

            Console.Write($"【{connid}】:已建立连接!");

            //判断一下用户是不是已经链接了

            var checkUserConn = summaryTableCon.IsConn(connid, userid);

            if (!checkUserConn)

            {

                //添加一个新的连接

                summaryTableCon.AddConnInfo(new SignalRConn()

                {

                    UserId = userid,

                    ConnectionId = connid

                });

            }

            //更新连接

            else

            {

                summaryTableCon.UpdateConnInfo(userid, connid);

            }

            return Connection.Send(connectionId, $"【用户:{connectionId}】真正连接成功!");

            //return base.OnConnected(request, connectionId);

        }

        /// <summary>

        /// 接收到请求的时候调用

        /// </summary>

        /// <param name="request"></param>

        /// <param name="connectionId"></param>

        /// <param name="data"></param>

        /// <returns></returns>

        protected override async Task OnReceived(IRequest request, string connectionId, string data)

        {

            var keys = new Dictionary<string, string>

            {

                { "葡萄酒", "wine" },

                { "红酒", "wine" },

                { "白酒", "wine" },

                { "霞多丽", "grapery" },

                { "雷司令", "grapery" }

            };

            var values = new Dictionary<string, string>

            {

                { "wine", "这是一款葡萄酒" },

                { "grapery", "这是一串葡萄" }

            };

            //获得用户id

            var userid = "123321";

            await Task.Factory.StartNew(async () =>

            {

                var key = "";

                for (int i = 0; i < keys.Keys.Count; i++)

                {

                    if (data.Contains(keys.Keys.ToList()[i]))

                    {

                        key = keys.Values.ToList()[i];

                        break;

                    }

                }

                var value = "未找到匹配条目";

                if (values.ContainsKey(key))

                {

                    value = values[key];

                }

                await Connection.Send(connectionId, value);

            }, TaskCreationOptions.LongRunning);

        }

        /// <summary>

        /// 连接中断的时候调用

        /// </summary>

        /// <param name="request"></param>

        /// <param name="connectionId"></param>

        /// <param name="stopCalled"></param>

        /// <returns></returns>

        protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)

        {

            Console.Write($"【{connectionId}】:已断开连接!");

            //获得SignalR的连接id

            var connid = connectionId;

            //关闭连接

            summaryTableCon.DelConnInfo(connid);

            return base.OnDisconnected(request, connectionId, stopCalled);

        }

        /// <summary>

        /// 连接超时重新连接的时候调用

        /// </summary>

        /// <param name="request"></param>

        /// <param name="connectionId"></param>

        /// <returns></returns>

        protected override Task OnReconnected(IRequest request, string connectionId)

        {

            return base.OnReconnected(request, connectionId);

        }

    }

}

        文件里有几个回调方法,OnConnected用户连接时,OnReceived收到信息时、OnDisconnected用户断开连接,OnReconnected用户重连,也可以通过Connection.Send方法给连接用户发送消息。

        下面是Hub里用到的用到的ConnectionManagement的代码,用来保存并管理用户的连接状态。

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

namespace WinexHK.Hubs.ConectionOperate

{

    /// <summary>

    /// 连接管理

    /// </summary>

    public class ConnectionManagement

    {

        /// <summary>

        /// 用户连接集合

        /// </summary>

        public static List<SignalRConn> SignalRConns { get; set; } = new List<SignalRConn>();

        /// <summary>

        /// 添加连接

        /// </summary>

        /// <param name="conn"></param>

        public void AddConnInfo(SignalRConn conn)

        {

            SignalRConns.Add(conn);

        }

        /// <summary>

        /// 删除连接

        /// </summary>

        /// <param name="connid"></param>

        public void DelConnInfo(string connid)

        {

            var signalRConns = SignalRConns.FirstOrDefault(u => u.ConnectionId == connid);

            if (signalRConns != null)

            {

                SignalRConns.Remove(signalRConns);

            }

        }

        /// <summary>

        /// 更新链接(老的链接不起作用了)

        /// 场景:客户端重连了,userid没变,但是connid变了

        /// </summary>

        /// <param name="userId">用户id</param>

        /// <param name="newConnsId">新的链接id</param>

        public void UpdateConnInfo(string userId, string newConnsId)

        {

            var signalRConns = SignalRConns.FirstOrDefault(u => u.UserId.ToLower() == userId.ToLower());

            if (signalRConns != null)

            {

                signalRConns.ConnectionId = newConnsId;

            }

        }

        /// <summary>

        /// 判断用户是否已经链接

        /// </summary>

        /// <param name="connid">连接id</param>

        /// <param name="userid">用户id</param>

        /// <returns></returns>

        public bool IsConn(string connid, string userid)

        {

            var userConn = SignalRConns.FirstOrDefault(u => u.ConnectionId.ToLower() == connid.ToLower() && u.UserId.ToLower() == userid.ToLower());

            return userConn == null ? false : true;

        }

    }

}

        最后是用到的一个类,保存用户id及连接id

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

namespace WinexHK.Hubs.ConectionOperate

{

    /// <summary>

    /// 连接

    /// </summary>

    public class SignalRConn

    {

        /// <summary>

        /// 系统用户id

        /// </summary>

        public string UserId { get; set; }

        /// <summary>

        /// SignleR链接Id(每次链接SignalR都会分配一个id)

        /// </summary>

        public string ConnectionId { get; set; }

    }

}

二、 PC版原生网页

        这几个类定义好,基本上后端的代码就完成了,然后开始做前端部分。前端部分分两个版本,一个是PC版的网页,一个是uni-app版的网页,由于uni-app对于SignalR框架的适配性不好,我们暂时先不开发微信小程序及app版的SignalR,等后面技术成熟了在进行开发。

        PC版跟uni-app网页版比较类似,基本上依赖3个js文件就可以搞定了,页面自己看着改改。

        

        一个是js的库,版本应该都ok,不挑。一个是signalR的库,还有一个是做聊天要用的调用方法的库。下面是chat_hub.js的代码

      

// 连接服务,xxxx里直接填后端的地址即可

var connection = $.connection("http://xxxx/chathub");

// 建立链接

connection.start(function () {

    //连接成功

    console.log("【" + new Date().toLocaleString() + "】连接成功!");

    //发送消息

    connection.send("给我一杯葡萄酒吧");

});

// 连接断开

connection.disconnected(function () {

    console.log("【" + new Date().toLocaleString() + "】连接断开!");

});

// 接收服务器发来的消息

connection.received(function (data) {

    console.log("【" + new Date().toLocaleString() + "】接收服务器发来的消息:");

    console.log(data);

});

三、uni-app版web网页

        uni-app版的web端跟PC版有些不一样的,因为uni-app不是实时编译运行的,所以PC的模式对uni-app并不奏效,配进去后发现JQuery加载不出来,SignalR的js会报错。

        解决办法有两步,首先在main.js中就将JQuery直接依赖上,并配进window对象中

        其次要对SignalR中所有用到JQuery的地方进行修改,所有对$的使用都要从window对象中获取

好比说这个地方,改之前是上面这样的,改之后是下面这样的

        这么改的原因是因为SignalR的js对于uni-app的项目来说属于外部文件,你在uni-app项目内全局引入的JQuery在外部js中并不生效,但window对象是通用的,所以可以在uni-app项目初始化时把JQuery对象保存在window对象中,使用的时候调window中的$即可。

四、uni-app版微信小程序与App(微信小程序原生同理可用)

        uni-app版的微信小程序与App版更不一样了,因为这两个版本不支持jQuery,所以我们要放弃JQuery版本的signalR,改用websocket来进行连接

        整体的逻辑代码没有太大的变动,只在SignalR的连接、发送、断开三处做平台适配即可。

        首先通过negotiate接口获取到ConnectionToken,这个token很关键,需要在一个会话周期内保存,后续发送信息、断开连接都跟这个token有关,后端的connectionId就保存在这里面。 

        获取到token后通过connect方法进行SignalR连接,需要用到uni提供的connectSocket方法通过ws协议进行连接,*****处直接写域名或ip地址即可。uni还提供了需要与websocket连接相关的回调,例如onSocketOpen,onSocketMessage等等,这些可以阅读uni提供的官方文档了解。

        最重要的就是信息的发送与接收了,发送信息通过uni提供的sendSocketMessage方法进行处理,这边不像网页版可以直接传对象,需要把对象序列化一下。

        接收信息就通过onSocketMessage回调来处理。这边层级比较深,需要取M列表下的第一位。

        最后是断开连接的操作,尝试了一下uni官方提供的closeSocket方法,效果不是很好,后端接收不到OnDisconnected的回调,所以还是用请求的方式进行断开,没什么问题。

        到这基本就全通了,不通你再来问我。

        



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。