一、简介

上一次,我们写了个简单的聊天室,接下来,我们来整一个可以私聊的聊天室。

SignalR 官方 API 文档

需求简单分析:

1.私聊功能,那么要记录用户名或用户ID,用于发送消息。

2.怎么向单人发消息,查看 文档,得知 SignalR 的推送方式 有组推、ID 推等等(参考 Calling client methods 这一节 ).

3.怎么在推送消息的方法里面取得 cookie 、querystirng ?查看文档,得知在其推送方法中应该怎么取参数。

4.用户连接上聊天室,应该有广播:xx用户上线了,用户列表更新成最新的,用户关闭浏览器后,也应该有广播:XX用户下线了。查看文档,可知 在哪里处理用户重连接、连接、断开连接的事件。

二、Demo

接下来,我们来一步步实现这个简单的 Demo .

1.0 创建一个新的聊天集线器.名字叫:GroupChatHub ,并给它起个别名,叫 groupChatHub

1.1考虑到用户会有用户名,这儿以简单的方式处理下,让用户一进入系统,先输入名称,之后再跳转到聊天室,以 Get 方式传递用户名.并把用户名保存进 cookie 中。代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System.Threading.Tasks; using System.Collections.Concurrent; namespace TestSignalR.Models
{
[HubName("groupChatHub")]
public class GroupChatHub : Hub
{ private string userName
{
get
{
var ck_userName = Context.RequestCookies["userName"];
return ck_userName == null ? "" : ck_userName.Value;
}
}
}
}

1.2 既然我们要有用户列表,我们应该保存用户名 和 用户的连接ID,添加一个静态的线程安全字典 _onlineDic .

        private static ConcurrentDictionary<string, string> _onlineDic = new ConcurrentDictionary<string, string>();

1.3 接下来,我们处理一下用户连接上的 OnConnected 事件,把用户ID、用户名保存起来。方便等会推送用。

        public override Task OnConnected()
{ if (!_onlineDic.ContainsKey(userName))
{
_onlineDic.AddOrUpdate(userName, Context.ConnectionId, (k, ov) => ov = Context.ConnectionId);
}
else
{
_onlineDic.AddOrUpdate(userName, Context.ConnectionId, (k, ov) => ov = Context.ConnectionId); }
return base.OnConnected();
}

1.4 忘记处理广播 XX用户上线通知了,还有用户列表推送信息。那么在 OnConnected 事件中修改下 :

        public override Task OnConnected()
{ if (!_onlineDic.ContainsKey(userName))
{
_onlineDic.AddOrUpdate(userName, Context.ConnectionId, (k, ov) => ov = Context.ConnectionId);
          Clients.All.publishMsg(new {sender="系统通知",receiver= "所有人", msg="系统消息" + userName + "加入聊天",msgTime=DateTime.Now});
}
else
{
_onlineDic.AddOrUpdate(userName, Context.ConnectionId, (k, ov) => ov = Context.ConnectionId);
}
            Clients.All.publishUser(_onlineDic.Select(c => new { key = c.Key, value = c.Value }));
return base.OnConnected();
}

1.5 处理一下用户重连接,当用户断线重连时,要广播用户上线,加入静态缓存中。其实重连的逻辑处理应该和连接时的逻辑是一样的,所以直接提成一个方法去调用。

    public override Task OnConnected()
{
Connect();
return base.OnConnected();
} public override Task OnReconnected()
{
Connect();
return base.OnReconnected();
} public void Connect()
{
if (!_onlineDic.ContainsKey(userName))
{
_onlineDic.AddOrUpdate(userName, Context.ConnectionId, (k, ov) => ov = Context.ConnectionId); Clients.All.publishMsg(FormateMsg("系统通知", "所有人", "系统消息" + userName + "加入聊天"));
}
else
{
_onlineDic.AddOrUpdate(userName, Context.ConnectionId, (k, ov) => ov = Context.ConnectionId);
}
Clients.All.publishUser(_onlineDic.Select(c => new { key = c.Key, value = c.Value }));
} public dynamic FormateMsg(string sender, string receiver, string msg)
{
return new
{
sender = sender,
receiver = receiver,
msgTime = DateTime.Now,
msg = msg
};
}

1.6 接下来,处理一下断开连接的时候的事件:

        public override Task OnDisconnected(bool stopCalled)
{
string Temp = "";
_onlineDic.TryRemove(userName, out Temp);
Clients.All.publishUser(_onlineDic.Select(c => new { key = c.Key, value = c.Value })); Clients.All.publishMsg(FormateMsg("系统通知", "所有人", "系统消息" + userName + "离开聊天")); return base.OnDisconnected(stopCalled);
}

1.7 用户推送消息的事件,我们定义为 Send 方法:

        /// <summary>
/// 发送消息
/// </summary>
/// <param name="sendToClientID">接收者ID</param>
/// <param name="msg">消息</param>
public void Send(string sendToClientID,string msg)
{
if (sendToClientID == "0")
{
Clients.All.publishMsg(FormateMsg(userName,"所有人",msg));
}
else
{
string sendToUsername=""; var sendToUserItem= _onlineDic.FirstOrDefault(c => c.Value == sendToClientID);
if (sendToUserItem.Key != null)
{
sendToUsername = sendToUserItem.Key;
} Clients.Clients(new List<string> { sendToClientID, Context.ConnectionId }).publishMsg(FormateMsg(userName, sendToUsername, msg));
}
}

1.8 OK,我们集线器的事件就这样写完了,准备写前端的界面和事件了。GroupChatHub 的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System.Threading.Tasks; using System.Collections.Concurrent; namespace TestSignalR.Models
{
[HubName("groupChatHub")]
public class GroupChatHub : Hub
{ private string userName
{
get
{
var ck_userName = Context.RequestCookies["userName"];
return ck_userName == null ? "" : ck_userName.Value;
}
} private static ConcurrentDictionary<string, string> _onlineDic = new ConcurrentDictionary<string, string>(); public override Task OnConnected()
{
Connect();
return base.OnConnected();
} public override Task OnReconnected()
{
Connect();
return base.OnReconnected();
} public void Connect()
{
if (!_onlineDic.ContainsKey(userName))
{
_onlineDic.AddOrUpdate(userName, Context.ConnectionId, (k, ov) => ov = Context.ConnectionId); Clients.All.publishMsg(FormateMsg("系统通知", "所有人", "系统消息" + userName + "加入聊天"));
}
else
{
_onlineDic.AddOrUpdate(userName, Context.ConnectionId, (k, ov) => ov = Context.ConnectionId);
}
Clients.All.publishUser(_onlineDic.Select(c => new { key = c.Key, value = c.Value }));
} public dynamic FormateMsg(string sender, string receiver, string msg)
{
return new
{
sender = sender,
receiver = receiver,
msgTime = DateTime.Now,
msg = msg
};
} public override Task OnDisconnected(bool stopCalled)
{
string Temp = "";
_onlineDic.TryRemove(userName, out Temp);
Clients.All.publishUser(_onlineDic.Select(c => new { key = c.Key, value = c.Value })); Clients.All.publishMsg(FormateMsg("系统通知", "所有人", "系统消息" + userName + "离开聊天")); return base.OnDisconnected(stopCalled);
} /// <summary>
/// 发送消息
/// </summary>
/// <param name="sendToClientID">接收者ID</param>
/// <param name="msg">消息</param>
public void Send(string sendToClientID,string msg)
{
if (sendToClientID == "0")
{
Clients.All.publishMsg(FormateMsg(userName,"所有人",msg));
}
else
{
string sendToUsername=""; var sendToUserItem= _onlineDic.FirstOrDefault(c => c.Value == sendToClientID);
if (sendToUserItem.Key != null)
{
sendToUsername = sendToUserItem.Key;
} Clients.Clients(new List<string> { sendToClientID, Context.ConnectionId }).publishMsg(FormateMsg(userName, sendToUsername, msg));
}
} }
}

2.0 创建控制器 HomeController ,增加 Index ,  ChatRoom 方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc; namespace TestSignalR.Controllers
{
public class HomeController : Controller
{ public ActionResult Index()
{
//ViewBag.ClientName = "用户-" + new Random().Next(10000, 99999);
return View();
} public ActionResult ChatRoom()
{
string userName= Request.QueryString["txt_name"];
Response.Cookies.Add(new HttpCookie("userName",userName));
ViewBag.userName = userName;
return View();
} }
}

2.1 首先创建一个输入用户名的视图,Index 文件,先不做用任何户名合法判断处理。

<form method="get" action="/Home/ChatRoom">
用户名:
<input type="text" id="txt_name" name="txt_name" value="Frank" /> <button type="submit">进入</button>
</form>

2.2 创建聊天室视图 ChatRoom ,引入 JQ包, SignalR 的文件,虚拟的 集线器文件。

@{
ViewBag.Title = "ChatRoom";
Layout = "~/Views/Shared/_Layout.cshtml";
} <script src="~/Scripts/jquery-1.8.2.min.js"></script> <script src="~/Scripts/jquery.signalR-2.2.3.min.js"></script> <script src="@Url.Content("~/signalr/hubs")"></script>
<h2>聊天室(当前<div id="userCount">1</div>人)</h2> <div>
你是:@ViewBag.userName
</div>
<div>
<select id="ddl_userList">
<option value="0">所有人</option>
</select>
<input type="text" id="txt_msg" />
<input type="button" onclick="SendMsg()" value="发送" />
</div> <div>
聊天记录:
<div id="div_msg"> </div>
</div>

2.3 开始写对应的JS事件:

<script type="text/javascript">

    var chat = $.connection.groupChatHub;//创建聊天的集线器,告诉客户端,你用的就是我们刚才创建的 GroupChatHub
$(function () {
chat.client.broadcastMessage = function (name, message) {//当客户端收到广播所要操作的事件
var encodedName = $('<div />').text(name).html();
var encodedMsg = $('<div />').text(message).html();
$('#div_msg').append('<li><strong>' + encodedName
+ '</strong>:&nbsp;&nbsp;' + encodedMsg + '</li>');
}; chat.client.publishMsg = function (data) {//收到新信息的处理事件
writeMsg(data.sender,data.receiver, data.msg);
}; chat.client.publishUser = function (data) {//当客户端收到用户列表(就是我们自定义的 publishUser 的消息),处理的事件
var _html = "<option value='0'>所有人</option>";
for (var item in data) {
_html+= "<option value='"+data[item].value+"'>" +data[item].key + "</option>";
}
$("#userCount").text(data.length);
$("#ddl_userList").html(_html);
} $.connection.hub.start().done(function () {//初始化集线器
console.log("connect ok.");
}); function writeMsg(sender,receiver,eventLog) {//输出记录
var now = new Date();
var nowStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
$('#div_msg').prepend('<div><b>' + nowStr + '</b>&nbsp;&nbsp;<b>' + sender + '</b>对 <b>' + receiver + '</b> ' + eventLog + '.</div>');
} }); function SendMsg() {
chat.server.send($("#ddl_userList").val(), $("#txt_msg").val()).done(function () {//调用服务端的 Send 方法
console.log("send OK");
});
} </script>

运行起来,看下效果:

消息广播结果:

消息单独发送结果:

简单的可广播、可私聊的聊天室就这样完成了。

三、结束语

总结一下:

1.多看官方文档,英文不好可以看下机翻。

2.写代码之前先理清你要做什么,想做什么,打算怎么做。

3.从最简单的开始写起,如上个 DEMO 的,只能全体聊天的那种。一步步做下去。

4.注意 JQ 包版本,好像低版本的 JQ 包,前端 SignalR 会报错。

感谢大家的阅读。

第二个 SignalR,可以私聊的聊天室的更多相关文章

  1. [SignalR]一个简单的聊天室

    原文:[SignalR]一个简单的聊天室 1.说明 开发环境:Microsoft Visual Studio 2010 以及需要安装NuGet. 2.添加SignalR所需要的类库以及脚本文件: 3. ...

  2. SignalR 入门 .netCore实现聊天室

    SignalR 入门 .netCore实现聊天室 本文根据微软SignalR 简介 | Microsoft Docs 和 ASP.NET Core SignalR 简介 | Microsoft Doc ...

  3. Asp.net MVC + Signalr 实现多人聊天室

    Asp.net SignalR 简介: 首先简单介绍一下Signalr ,我也是刚接触,觉得挺好玩的,然后写了一个多人聊天室. Asp.net SignalR 是为Asp.net 开发人员提供的一个库 ...

  4. Vue3 + Socket.io + Knex + TypeScript 实现可以私聊的聊天室

    前言 下文只在介绍实现的核心代码,没有涉及到具体的实现细节,如果感兴趣可以往下看,在文章最后贴上了仓库地址.项目采用前后端模式,前端使用 Vite + Vue3 + TS:后端使用 Knex + Ex ...

  5. Asp.NET MVC 使用 SignalR 实现推送功能二(Hubs 在线聊天室 获取保存用户信息)

    简单介绍 关于SignalR的简单实用 请参考 Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室) 在上一篇中,我们只是介绍了简单的消息推送,今天我们来修改一下,实现 ...

  6. Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室)

    简介       ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信.什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端 ...

  7. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(一) 之 基层数据搭建,让数据活起来(数据获取)

    大家好,本篇是接上一篇 ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(零) 前言  ASP.NET SignalR WebIM系列第二篇.本篇会带领大家将 LayIM ...

  8. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(四) 之 用户搜索(Elasticsearch),加好友流程(1)。

    前面几篇基本已经实现了大部分即时通讯功能:聊天,群聊,发送文件,图片,消息.不过这些业务都是比较粗犷的.下面我们就把业务细化,之前用的是死数据,那我们就从加好友开始吧.加好友,首先你得知道你要加谁.L ...

  9. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(十一) 代码重构使用反射工厂解耦

    前言 自从此博客发表以及代码开源以来,得到了许多人的关注.也没许多吧,反正在我意料之外的.包括几位大牛帮我做订阅号推广,真的很感谢他们.另外,还有几个高手给我提了一些架构上的问题.其实本身这个项目是没 ...

随机推荐

  1. DNS递归解析和迭代解析

    DNS解析流程分为递归查询和迭代查询,递归查询是以本地名称服务器为中心查询, 递归查询是默认方式,迭代查询是以DNS客户端,也就是客户机器为中心查询.其实DNS客户端和本地名称服务器是递归,而本地名称 ...

  2. 在CentOS 7服务器中使用Jexus发布.net core webapi

    环境: 服务器:CentOS 7 64位 .net core 2.1 Jexus独立版 官网:https://www.jexus.org/ 按照官网安装独立版命令:curl https://jexus ...

  3. Docker网络(五)

    本章内容 1.dokcer默认自带的几种网络介绍 2. 自定义网络 3. 容器间通信 4. 容器与外界交互 docker网络分为单个主机上的容器网络和多个主机上的哇网络,本文主要讲解单个主机上的容器网 ...

  4. (专题四)06 matlab绘图选项卡

    绘图选项卡 例子1--选择已有变量,绘制图形 都是按照选中的先后顺序依次确定坐标, 如果要修改绘制图形 法一,利用绘图工具和停靠图形按钮 法二,命令行窗口中输入命令 >>plottools ...

  5. 【漏洞复现篇】CVE-2020-1472-微软NetLogon权限提升-手把手教学-简单域环境搭建与Exp执行

    一.漏洞简介 NetLogon 远程协议是一种在 Windows 域控上使用的 RPC 接口,被用于各种与用户和机器认证相关的任务.最常用于让用户使用 NTLM 协议登录服务器,也用于 NTP 响应认

  6. Module build failed: TypeError: this.getResolve is not a function at Object.loader 使用vue-cli 创建项目 使用sass时报错 -- 等其他sass 报错 ./node_modules/css-loader?{"sourceMap":true}!./node_modules/vue-loader/lib

    已经安装了 sass相关依赖包 npm  install sass-loader --save-devnpm install node-sass --sava-dev 并且在build文件下webpa ...

  7. 跟我一起学.NetCore之Swagger让前后端不再烦恼及界面自定义

    前言 随着前后端分离开发模式的流行,接口对接.联调成为常事,前端同事会经常问:我需要调哪个接口?这个接口数据格式是啥?条件都传啥? 对于一些紧急接口可能会采取沟通对接,然后补文档,其他的都会回一句:看 ...

  8. Zookeeper 笔记小结

    转自: https://www.cnblogs.com/raphael5200/p/5285583.html  1.Zookeeper的角色 » 领导者(leader),负责进行投票的发起和决议,更新 ...

  9. requests和正则表达式爬取猫眼电影Top100练习

    1 import requests 2 import re 3 from multiprocessing import Pool 4 from requests.exceptions import R ...

  10. MySQL的简单实用 手把手教学

    ------------恢复内容开始------------ MySQL的使用 1.登陆数据库 打开terminal 在终端根文件目录下输入/usr/local/mysql/bin/mysql -u ...