一、简介

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

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. Redis集群模式(Cluster)部署

    1. 安装依赖包 注意:本节需要使用root用户操作 1.1 安装ruby yum install ruby -y yum install ruby-devel.x86_64 -y 1.2 安装rub ...

  2. Java基础一篇过(四)List这篇就够了

    文章更新时间:2020/08/03 一.List介绍 list是Java的一个接口,继承了Collection,常用到的有3个子类实现: ArrayList 底层数据结构是数组.线程不安全 Linke ...

  3. Kafka索引设计的亮点

    前言 其实这篇文章只是从Kafka索引入手,来讲述算法在工程上基于场景的灵活运用.单单是因为看源码的时候有感而写之. 索引的重要性 索引对于我们来说并不陌生,每一本书籍的目录就是索引在现实生活中的应用 ...

  4. RabbitMQ小记(三)

    1.RabbitMQ中mandatory和immediate以及备份交换机 (1)mandatory为true时,若交换机无法根据自身类型和路由键找到符合条件的对列,那么RabbitMQ会回调Basi ...

  5. Shiro入门学习---使用自定义Realm完成认证|练气中期

    写在前面 在上一篇文章<shiro认证流程源码分析--练气初期>当中,我们简单分析了一下shiro的认证流程.不难发现,如果我们需要使用其他数据源的信息完成认证操作,我们需要自定义Real ...

  6. 2020Java程序员架构师面试宝典,学习后面试必过,震惊,本人通过这篇教程,拿到了0个offer

    1. 引言 Java后端学习路线 <吐血整理>顶级程序员工具集 https://github.com/AobingJava/JavaFamily 跟上Java8 经历阿里.头条.腾讯等知名 ...

  7. 9.Android-读写SD卡案例

    1.效果如下所示: 2.读写SD卡时,需要给APP添加读写外部存储设备权限,修改AndroidManifest.xml,添加: <uses-permission android:name=&qu ...

  8. GML与KML的区别

    1.GML是基于XML的地理信息的传输.存储编码,它包括空间的和非空间的地理特征和地理范畴.GML是空 间数据编码.传输.存储.发布的国际标准KML是一个OGC标准 2.GML专注于地理信息的结构与内 ...

  9. 多测师讲解html _表格标签007_高级讲师肖sir

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>表 ...

  10. Rust之路(3)——数据类型 下篇

    [未经书面同意,严禁转载] -- 2020-10-14 -- 架构是道,数据是术.道可道,非常道:术不名,不成术!道无常形,术却可循规. 学习与分析数据类型,最基本的方法就是搞清楚其存储原理,变量和对 ...