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

avatar
作者
猴君
阅读量:0

一、 后端服务器

        后端集成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的回调,所以还是用请求的方式进行断开,没什么问题。

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

        

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!