一、 后端服务器
后端集成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的回调,所以还是用请求的方式进行断开,没什么问题。
到这基本就全通了,不通你再来问我。