第二个 SignalR,可以私聊的聊天室
一、简介
上一次,我们写了个简单的聊天室,接下来,我们来整一个可以私聊的聊天室。
需求简单分析:
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>: ' + 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> <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,可以私聊的聊天室的更多相关文章
- [SignalR]一个简单的聊天室
原文:[SignalR]一个简单的聊天室 1.说明 开发环境:Microsoft Visual Studio 2010 以及需要安装NuGet. 2.添加SignalR所需要的类库以及脚本文件: 3. ...
- SignalR 入门 .netCore实现聊天室
SignalR 入门 .netCore实现聊天室 本文根据微软SignalR 简介 | Microsoft Docs 和 ASP.NET Core SignalR 简介 | Microsoft Doc ...
- Asp.net MVC + Signalr 实现多人聊天室
Asp.net SignalR 简介: 首先简单介绍一下Signalr ,我也是刚接触,觉得挺好玩的,然后写了一个多人聊天室. Asp.net SignalR 是为Asp.net 开发人员提供的一个库 ...
- Vue3 + Socket.io + Knex + TypeScript 实现可以私聊的聊天室
前言 下文只在介绍实现的核心代码,没有涉及到具体的实现细节,如果感兴趣可以往下看,在文章最后贴上了仓库地址.项目采用前后端模式,前端使用 Vite + Vue3 + TS:后端使用 Knex + Ex ...
- Asp.NET MVC 使用 SignalR 实现推送功能二(Hubs 在线聊天室 获取保存用户信息)
简单介绍 关于SignalR的简单实用 请参考 Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室) 在上一篇中,我们只是介绍了简单的消息推送,今天我们来修改一下,实现 ...
- Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室)
简介 ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信.什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端 ...
- ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(一) 之 基层数据搭建,让数据活起来(数据获取)
大家好,本篇是接上一篇 ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(零) 前言 ASP.NET SignalR WebIM系列第二篇.本篇会带领大家将 LayIM ...
- ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(四) 之 用户搜索(Elasticsearch),加好友流程(1)。
前面几篇基本已经实现了大部分即时通讯功能:聊天,群聊,发送文件,图片,消息.不过这些业务都是比较粗犷的.下面我们就把业务细化,之前用的是死数据,那我们就从加好友开始吧.加好友,首先你得知道你要加谁.L ...
- ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(十一) 代码重构使用反射工厂解耦
前言 自从此博客发表以及代码开源以来,得到了许多人的关注.也没许多吧,反正在我意料之外的.包括几位大牛帮我做订阅号推广,真的很感谢他们.另外,还有几个高手给我提了一些架构上的问题.其实本身这个项目是没 ...
随机推荐
- ES6重度学习 demo实例
let 与 const // 并非真正的常量 // const 的本质: const 定义的变量并非常量,并非不可变, // 它定义了一个常量引用一个值.使用 const 定义的对象或者数组,其实是可 ...
- 首发!Alibaba内部:Spring源码培训课件笔记曝光!
本文包含的内容 Spring占有的市场 阅读源码的重要性 阅读源码困难度 课件笔记的主要内容 Spring占有的市场 先来简单说一下现在各个企业常用并与Spring相关的: ssh = spring ...
- hystrix文档翻译之如何使用
Hello World! 使用HystrixCommand实现“Hello World”. public class CommandHelloWorld extends HystrixCommand& ...
- java.lang.NoSuchMethodError: org.springframework.util.Assert.isTrue(ZLjava/util/function/Supplier;)V
spring-data-redis 2的版本只支持spring5和spring boot2+,建议降低spring-data-redis版本 <!-- redis --> <depe ...
- 关于struts1与struts2
1.Action方面 Action是整个Struts框架的核心内容,Struts1.x与Struts2的Action模型很大的区别.Struts2是一个 pull-MVC架构 ,从开发者角度看,就是说 ...
- 基于python的webUI自动化-小白基础篇
最近打算研究一下基于python的webUI自动化,先自学了一下相关基础知识,大概用了一个多月的时间,主要是找视频,一边看视频或者文档一边对照着敲代码运行. 重点强调:一定要一边看一边对照着敲代码运行 ...
- python3 连接数据库~
~目前记录的是针对python3写的数据库连接,不适用于pyhon2.python3如果想要与数据库进行连接,则需要先下载对应各数据库的插件包,然后导入包.python3的插件下载地址:https:/ ...
- 中心极限定理(为什么y服从高斯分布)
因为每一条数据都服从IID原则: 根据中心极限定理,当数据增加的时候,样本均值的分布慢慢变成正态分布 不管分布式什么分布,累加起来都是高斯分布 As sum increases, sum of non ...
- 基于COCA词频表的文本词汇分布测试工具v0.1
美国语言协会对美国人日常使用的英语单词做了一份详细的统计,按照日常使用的频率做成了一张表,称为COCA词频表.排名越低的单词使用频率越高,该表可以用来统计词汇量. 如果你的词汇量约为6000,那么这张 ...
- Linux就该这么学28期——开篇
2020.10.03 正式开始系统学习Linux之旅,希望能在老刘的带领下,掌握操作要领. 现将所学记录在此. 学习环境如下: VmwareWorkStation 15 --虚拟机软件 versio ...