实际项目讲解 Signalr(.net core3.x)使用
最近认真研究了一个微软最新的.net core3.x版本中的signalr技术,并使用之开发了一个相对稳定的聊天系统。在此作一些记录,希望能给需要的同志作一些有益的提醒。
一、系统功能界面

二、主要功能代码
1.用户登陆(Cookie方式)
public IActionResult OnPost(string InputTime, string UserId, string Password)
{
if (!string.IsNullOrWhiteSpace(UserId) && !string.IsNullOrWhiteSpace(Password))
{
string tKey = (DateTime.Parse(InputTime).Second - 2).ToString();
ModelUser user = GlobalVars.AllChatUsers.Where(e => e.Id.Equals(UserId) && (e.Pwd + tKey).Equals(Password)).FirstOrDefault();
if (user != null)
{
Claim[] Claims = new Claim[] { new Claim(ClaimTypes.Name, user.Id) };
ClaimsIdentity ClaimsIdentity = new ClaimsIdentity(Claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal LoginUser = new ClaimsPrincipal(ClaimsIdentity);
Task.Run(async () =>
{
//登录用户
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, LoginUser);
}).Wait();
return RedirectToPage("./WebChat", new { Login = "First" });
}
}
//"用户名或密码错误!";
return Page();
}
2.客户端连接服务器代码
function ConnectionStart() {
if (g_ReconnectCount >= g_ReconnectMaxTimes) {
AddMsgToChatList.AddMessage('<span style="color:red">已重连 ' + g_ReconnectCount.toString() + ' 次,仍无法连接。请检查网络,然后刷新重试!</span>');
return;
}
connection.start().then(function () {
g_divInputMsg.setInputState(true);
//connection.state === signalR.HubConnectionState.Connected
//AddMsgToChatList.AddMessage('<span style="color:red">已连接到服务器</span>');
}).catch(function (err) {
//connection.state === signalR.HubConnectionState.Disconnecte
//AddMsgToChatList.AddMessage('<span style="color:red">连接已断开,正在重连....(' + g_ReconnectCount.toString() + ')</span>');
g_ReconnectCount++;
setTimeout(() => ConnectionStart(), 500);
});
}
//首次连接服务器。
ConnectionStart();
3. 客户端发信息到服务器(使用流传输方式,带发送进度显示,以防止客户端发大图时会卡死或无法发送的现象)
客户端主要代码(Javascript):
async function sendMsgToSever(willSendMsg, processElement) {
processElement.innerHTML = '已发送(0/' + willSendMsg.length + ')';
//客户端到服务器的流式处理
var startIndex = 0;
var stopIndex = 0;
var fixLength = 1024 * 20; //此大小影响数据发送速度。4096=8407,5120=6714,10240=3348,20480=1882
var msgSubStr = '';
//var beginTime = (new Date()).valueOf();
console.log(new Date().toDateString)
const subject = new signalR.Subject();
connection.send("UploadStream", subject);
if (willSendMsg.length <= fixLength) { stopIndex = willSendMsg.length; } else { stopIndex = fixLength; }
var sendFun = function () {
if (stopIndex <= willSendMsg.length) {
msgSubStr = willSendMsg.substring(startIndex, stopIndex);
subject.next(msgSubStr);
processElement.innerHTML = '已发送(' + stopIndex.toString() + '/' + willSendMsg.length + ')'; //显示发送到服务器的进度
startIndex = stopIndex;
if (willSendMsg.length === stopIndex) {
subject.complete();
//var endTime = (new Date()).valueOf();
//console.log(fixLength.toString() + '=' + (endTime - beginTime).toString());
return;
}
if (willSendMsg.length >= (stopIndex + fixLength)) { stopIndex += fixLength; } else { stopIndex += willSendMsg.length - stopIndex; }
}
window.setTimeout(sendFun, 0);
}
sendFun();
}
服务器端主要代码(C#):
public async Task UploadStream(IAsyncEnumerable<string> stream)
{
//接收客户端发来的信息
System.Text.StringBuilder ClientNewMsg = new System.Text.StringBuilder();
await foreach (var item in stream)
{
ClientNewMsg.Append(item);
}
string message = ClientNewMsg.ToString();
//发给指定用户
await Clients.Client(RUser.ConnectionId).SendAsync("ReceiveMessage", message);
}
4.服务器端推送信息到客户端
客户端主要代码(Javascript):
connection.on("ReceiveMessage", async function (MsgObj) {
webChatLock.Reset();//锁屏重计时
if (g_PlaySound) {
var SoundFile = "/images/dingdong.wav";
var SoundObj = new Audio(SoundFile);
SoundObj.play();
}
var MsgJson = JSON.parse(MsgObj);
//....进一步处理收到的数据
}
服务器端主要代码(C#):
await Clients.Client(RUser.ConnectionId).SendAsync("ReceiveMessage", message);
5.需要注意的几个问题。
js读取图片内容生成DataBase64 格式数据时,<input type="file"/> 控件不能仅使用声明,而必须添加到页面上,否则苹果手机 safari 浏览器可以打开文件选择框,但将无应响应控件的 change事件。
更新页面数据显示进度时,需使用 window.setTimeout(funname,0),在funname()中发送并更新页度进度值,否则将看不到数据变化。
在处理用户的离线消息时,应使用线程安全的ConcurrentDictionary<string, ModeMsgServer> NeedSendMsgs 等类。
在收到或发送信息时,让聊天窗口自动滚动到最底部,使用 DivChatElement.scrollIntoView(false);代码,需注意的是,信息中有图片,则上述代码应在图片的 load 事件中执行。
采用客户端收到服务器信息后立即回调signalr的Hub中指定方法的方式,以便使服务器得以确认客户端已收到信息,从而将已发送的信息删除(否则将适时重新发送或在下次该用户登陆时再发送,以确保用户收到该信息)。
关于客户端与服务器端“心跳”配置。(注意客户端与服务器端的时间要相匹配)
服务器端(Startup.cs):
services.AddSignalR(options =>
{
//客户端发保持连接请求到服务端最长间隔,默认30秒
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
//服务端发保持连接请求到客户端间隔,默认15秒
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.EnableDetailedErrors = true;
options.MaximumReceiveMessageSize = 1024 * 1024 * 1024;
});
客户端(js):
//设置连接对象的相关属性
connection.serverTimeoutInMilliseconds = 30e3; //等待服务器端发送过来的心跳包最长等待时间30s(如该时间段时收到不服务器发送的“心跳”数据,即认为链接丢失)
connection.keepAliveIntervalInMilliseconds = 15e3; //客户端向服务器发送心跳包频率15s