一、简介

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

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. js中数组Array对象的方法sort()的应用

    一. sort()方法的介绍 //给一组数据排序 var arrNum = [12,1,9,23,56,100,88,66]; console.log("排序前的数组:"+arrN ...

  2. (数据科学学习手札95)elyra——jupyter lab平台最强插件集

    本文示例文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 jupyter lab是我最喜欢的编辑器,在过往 ...

  3. 最全总结 | 聊聊 Python 数据处理全家桶(Sqlite篇)

    1. 前言 上篇文章 聊到 Python 处理 Mysql 数据库最常见的两种方式,本篇文章继续说另外一种比较常用的数据库:Sqlite Sqlite 是一种 嵌入式数据库,数据库就是一个文件,体积很 ...

  4. CVE-2018-8045

    Joomla!Core SQL注入漏洞 CVE-2018-8045 受影响版本:joomla!3.5.0-3.8.5 漏洞描述: joomla!3.5.0-3.8.5版本对sql语句内的变量缺少类型转 ...

  5. 小白也能看懂的Redis教学基础篇——redis神秘的数据结构

    各位看官大大们,周末好! 作为一个Java后端开发,要想获得比较可观的工资,Redis基本上是必会的(不要问我为什么知道,问就是被问过无数次).那么Redis是什么,它到底拥有什么神秘的力量,能获得众 ...

  6. linux系统内存爆满的快速解决办法!

    首先用free工具检查一下内存的使用情况: $ free -m total used free shared buff/cache available Mem: 15884 207 573 145 1 ...

  7. Spring Boot 第三弹,一文带你了解日志如何配置?

    前言 日志通常不会在需求阶段作为一个功能单独提出来,也不会在产品方案中看到它的细节.但是,这丝毫不影响它在任何一个系统中的重要的地位. 今天就来介绍一下Spring Boot中的日志如何配置. Spr ...

  8. Tomcat 8.5集群配置

    示例 <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions= ...

  9. c++ 在window下创建窗口的基本步骤

    1. WinMain 函数的定义: 2. 创建一个窗口 2.1 设计一个窗口类 WNDCLASS 2.2 注册窗口类 2.3 创建窗口 2.4 显示及更新窗口 3. 进行消息循环 BOOL GetMe ...

  10. error C2065: “uint8_t”: 未声明的标识符

    转载:https://blog.csdn.net/lys07962000/article/details/12052571 参考: http://blog.csdn.net/chenxin_130/a ...