一、理解SignalR

ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息及调用方法),SignalR有三种传输模式:LongLooping(长轮询)、WebSocket(HTML5的WEB套接字)、Forever Frame(隐藏框架的长请求连接),可以在WEB客户端显式指定一种或几种,也可以采取默认(推荐),若采取默认,SignalR会根据浏览器的环境自动选择合适的传输方式。

二、SignalR的三种实现方式

第一种:采用集线器类(Hub)+非自动生成代理模式:服务端与客户端分别定义的相对应的方法,客户端通过代理对象调用服务端的方法,服务端通过IHubConnectionContext回调客户端的方法,客户端通过回调方法接收结果。

之前我写过一篇文章《分享一个基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室》,是通过长轮询+长连接的方式来实现的在线多人聊天室功能,从代码量来看就知道实现起来并不简单,而如今有了SignalR,会简单很多,我这里使用SignalR再来写一个简单的在线多人聊天室示例,以便大家快速掌握SignalR。

DEMO - 1 示例代码如下:

服务端:

//Startup类文件

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
using Microsoft.AspNet.SignalR; [assembly: OwinStartup(typeof(TestWebApp.Models.Startup))] namespace TestWebApp.Models
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
} //ChatHub类文件 using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web; namespace TestWebApp.Models
{
[HubName("chat")]
public class ChatHub : Hub
{
public static ConcurrentDictionary<string, string> OnLineUsers = new ConcurrentDictionary<string, string>(); [HubMethodName("send")]
public void Send(string message)
{
string clientName = OnLineUsers[Context.ConnectionId];
message = HttpUtility.HtmlEncode(message).Replace("\r\n", "<br/>").Replace("\n", "<br/>");
Clients.All.receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), clientName, message);
} [HubMethodName("sendOne")]
public void Send(string toUserId, string message)
{
string clientName = OnLineUsers[Context.ConnectionId];
message = HttpUtility.HtmlEncode(message).Replace("\r\n", "<br/>").Replace("\n", "<br/>");
Clients.Caller.receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("您对 {1}", clientName, OnLineUsers[toUserId]), message);
Clients.Client(toUserId).receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 对您", clientName), message);
} public override System.Threading.Tasks.Task OnConnected()
{
string clientName = Context.QueryString["clientName"].ToString();
OnLineUsers.AddOrUpdate(Context.ConnectionId, clientName, (key, value) => clientName);
Clients.All.userChange(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 加入了。", clientName), OnLineUsers.ToArray());
return base.OnConnected();
} public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
{
string clientName = Context.QueryString["clientName"].ToString();
Clients.All.userChange(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 离开了。", clientName), OnLineUsers.ToArray());
OnLineUsers.TryRemove(Context.ConnectionId, out clientName);
return base.OnDisconnected(stopCalled);
} }
}
        public ActionResult Index()
{
ViewBag.ClientName = "聊客-" + Guid.NewGuid().ToString("N");
var onLineUserList = ChatHub.OnLineUsers.Select(u => new SelectListItem() { Text = u.Value, Value = u.Key }).ToList();
onLineUserList.Insert(0, new SelectListItem() { Text = "-所有人-", Value = "" });
ViewBag.OnLineUsers = onLineUserList;
return View();
}

WEB客户端:

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta charset="utf-8" />
<title>聊天室</title>
<script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
<style type="text/css">
#chatbox {
width: 100%;
height: 500px;
border: 2px solid blue;
padding: 5px;
margin: 5px 0px;
overflow-x: hidden;
overflow-y: auto;
} .linfo {
} .rinfo {
text-align: right;
}
</style>
<script type="text/javascript">
$(function () { var clientName = $("#clientname").val();
var eChatBox = $("#chatbox");
var eUsers = $("#users"); var conn = $.hubConnection();
conn.qs = { "clientName": clientName }; conn.start().done(function () { $("#btnSend").click(function () {
var toUserId = eUsers.val();
if (toUserId != "") {
chat.invoke("sendOne", toUserId, $("#message").val())
.done(function () {
//alert("发送成功!");
$("#message").val("").focus();
})
.fail(function (e) {
alert(e);
$("#message").focus();
});
}
else {
chat.invoke("send", $("#message").val())
.done(function () {
//alert("发送成功!");
$("#message").val("").focus();
})
.fail(function (e) {
alert(e);
$("#message").focus();
});
}
}); }); var chat = conn.createHubProxy("chat"); chat.on("receiveMessage", function (dt, cn, msg) {
var clsName = "linfo";
if (cn == clientName || cn.indexOf("您对") >= 0) clsName = "rinfo";
eChatBox.append("<p class='" + clsName + "'>" + dt + " <strong>" + cn + "</strong> 说:<br/>" + msg + "</p>");
eChatBox.scrollTop(eChatBox[0].scrollHeight);
}); chat.on("userChange", function (dt, msg, users) {
eChatBox.append("<p>" + dt + " " + msg + "</p>");
eUsers.find("option[value!='']").remove();
for (var i = 0; i < users.length; i++) {
if (users[i].Value == clientName) continue;
eUsers.append("<option value='" + users[i].Key + "'>" + users[i].Value + "</option>")
}
}); });
</script>
</head>
<body>
<h3>大众聊天室</h3>
<div id="chatbox">
</div>
<div>
<span>聊天名称:</span>
@Html.TextBox("clientname", ViewBag.ClientName as string, new { @readonly = "readonly", style = "width:300px;" })
<span>聊天对象:</span>
@Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable<SelectListItem>)
</div>
<div>
@Html.TextArea("message", new { rows = 5, style = "width:500px;" })
<input type="button" value="发送消息" id="btnSend" />
</div>
</body>
</html>

服务端与客户端代码都比较简单,网上相关的说明也有,这里就不再解说了,只说一下这种方式JS端调用服务端方法采用:chat.invoke,而被服务端回调的方法则采用:chat.on (这里的chat是createHubProxy创建得来的)

第二种:采用集线器类(Hub)+自动生成代理模式

DEMO - 2 示例代码如下:

服务端与DEMO 1相同,无需改变

客户端:

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta charset="utf-8" />
<title>聊天室</title>
<script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
<script src="~/signalr/hubs" type="text/javascript"></script>
<style type="text/css">
#chatbox {
width: 100%;
height: 500px;
border: 2px solid blue;
padding: 5px;
margin: 5px 0px;
overflow-x: hidden;
overflow-y: auto;
} .linfo {
} .rinfo {
text-align: right;
}
</style>
<script type="text/javascript">
$(function () { var clientName = $("#clientname").val();
var eChatBox = $("#chatbox");
var eUsers = $("#users"); var chat = $.connection.chat;
$.connection.hub.qs = { "clientName": clientName };
chat.state.test = "test"; chat.client.receiveMessage = function (dt, cn, msg) {
var clsName = "linfo";
if (cn == clientName || cn.indexOf("您对")>=0) clsName = "rinfo";
eChatBox.append("<p class='" + clsName + "'>" + dt + " <strong>" + cn + "</strong> 说:<br/>" + msg + "</p>");
eChatBox.scrollTop(eChatBox[0].scrollHeight);
} chat.client.userChange = function (dt, msg, users) {
eChatBox.append("<p>" + dt + " " + msg + "</p>");
eUsers.find("option[value!='']").remove();
for (var i = 0; i < users.length; i++) {
if (users[i].Value == clientName) continue;
eUsers.append("<option value='" + users[i].Key + "'>" + users[i].Value + "</option>")
}
} $.connection.hub.start().done(function () { $("#btnSend").click(function () {
var toUserId = eUsers.val();
if (toUserId != "") {
chat.server.sendOne(toUserId, $("#message").val())
.done(function () {
//alert("发送成功!");
$("#message").val("").focus();
})
.fail(function (e) {
alert(e);
$("#message").focus();
});
}
else {
chat.server.send($("#message").val())
.done(function () {
//alert("发送成功!");
$("#message").val("").focus();
})
.fail(function (e) {
alert(e);
$("#message").focus();
});
}
}); }); });
</script>
</head>
<body>
<h3>大众聊天室</h3>
<div id="chatbox">
</div>
<div>
<span>聊天名称:</span>
@Html.TextBox("clientname", ViewBag.ClientName as string, new { @readonly = "readonly", style = "width:300px;" })
<span>聊天对象:</span>
@Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable<SelectListItem>)
</div>
<div>
@Html.TextArea("message", new { rows = 5, style = "width:500px;" })
<input type="button" value="发送消息" id="btnSend" />
</div>
</body>
</html>

上述代码中特别需要注意的是,需要引用一个“不存在的JS目录”:<script src="~/signalr/hubs" type="text/javascript"></script>,为什么要打引号,是因为我们在写代码的时候是不存在的,而当运行后就会自动生成signalr的代理脚本,这就是与非自动生成代理脚本最根本的区别,也正是因为这个自动生成的脚本,我们可以在JS中更加方便的调用服务端方法及定义回调方法,调用服务端方法采用:chat.server.XXX,而被服务端回调的客户端方法则采用:chat.client.XXX

看一下上述两种的运行效果截图吧:

第三种:采用持久化连接类(PersistentConnection)

DEMO - 3 示例代码如下:

服务端:

//Startup类:

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
using Microsoft.AspNet.SignalR; [assembly: OwinStartup(typeof(TestWebApp.Models.Startup))] namespace TestWebApp.Models
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR<MyConnection>("/MyConnection");
}
}
} //MyConnection类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.SignalR; namespace TestWebApp.Models
{
public class MyConnection : PersistentConnection
{
private static List<string> monitoringIdList = new List<string>();
protected override Task OnConnected(IRequest request, string connectionId)
{
bool IsMonitoring = (request.QueryString["Monitoring"] ?? "").ToString() == "Y";
if (IsMonitoring)
{
if (!monitoringIdList.Contains(connectionId))
{
monitoringIdList.Add(connectionId);
}
return Connection.Send(connectionId, "ready");
}
else
{
if (monitoringIdList.Count > 0)
{
return Connection.Send(monitoringIdList, "in_" + connectionId);
}
else
{
return Connection.Send(connectionId, "nobody");
}
}
} protected override Task OnReceived(IRequest request, string connectionId, string data)
{
if (monitoringIdList.Contains(connectionId))
{
return Connection.Send(data, "pass");
}
return null;
} protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
{
if (!monitoringIdList.Contains(connectionId))
{
return Connection.Send(monitoringIdList, "out_" + connectionId);
}
return null;
}
}
}

WEB客户端:

<!-- MonitoringPage.cshtml 监控管理页面-->

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>MonitoringPage</title>
<script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
<style type="text/css">
table {
border:1px solid #808080;
width:600px;
}
td {
border:1px solid #808080;
padding:3px;
}
.odd{ background-color: #bbf;}
.even{ background-color:#ffc; }
.non-temptr {
display:none;
}
</style>
<script type="text/javascript">
$(function () {
$("#userstable tbody tr:odd").addClass("odd");
$("#userstable tbody tr:even").addClass("even"); var conn = $.connection("/MyConnection", {"Monitoring":"Y"}); conn.start().done(function () {
$("#userstable").delegate("button.pass", "click", function () {
var rid = $(this).parent("td").prev().attr("data-rid");
conn.send(rid);
var tr = $(this).parents("tr");
tr.remove();
}); }).fail(function (msg) {
alert(msg);
}); conn.received(function (msg) {
if (msg == "ready")
{
$("#spstatus").html("监控服务已就绪");
return;
}
else if (msg.indexOf("in_") == 0) {
var tr = $(".non-temptr").clone(true);
tr.removeClass("non-temptr");
var td = tr.children().first();
var rid = msg.toString().substr("in_".length);
td.html(rid + "进入被监控页面,是否允许?");
td.attr("data-rid", rid);
$("#userstable tbody").append(tr);
}
else
{
var rid = msg.toString().substr("out_".length);
$("td[data-rid=" + rid + "]").parent("tr").remove();
}
}); });
</script>
</head>
<body>
<div>
以下是实时监控到进入EnterPage页面的用户情况:(服务状况:<strong><span id="spstatus"></span></strong>)
</div>
<table id="userstable">
<tr>
<td>用户进入消息</td>
<td>授 权</td>
</tr>
<tr class="non-temptr">
<td></td>
<td style="width:100px"><button class="pass">允许</button></td>
</tr>
</table>
</body>
</html> <!-- EnterPage.cshtml 监控受限页面-->
<!DOCTYPE html> <html>
<head>
<meta name="viewport" content="width=device-width" />
<title>EnterPage</title>
<script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script>
</head>
<body>
<script type="text/javascript">
$(function () {
var conn = $.connection("/MyConnection"); conn.start().fail(function (msg) {
alert(msg);
}); conn.received(function (data) {
if (data == "pass") {
$("#msg").html("管理员已审核通过,可以进入浏览详情。");
setTimeout(function () {
self.location = "http://www.zuowenjun.cn";
}, 3000);
}
else
{
$("#msg").html("无管理员在线,请稍候再重新进入该页面。");
}
});
});
</script>
<div id="msg">
该页面浏览受限,已自动将您的浏览请求发给管理员,请稍候。。。
</div>
</body>
</html>

上述代码可以看出与采用Hub(集线器类)的不同之处,一是:Startup.Configuration中是需要指定app.MapSignalR<MyConnection>("/MyConnection"),二是需实现继承自PersistentConnection类的自定义的持久化连接类,在这个连接中可以重写:OnConnected、OnDisconnected、OnReceived、OnReconnected、ProcessRequest方法,同时有几个重要的属性成员Connection、Groups,服务端发消息给客户端采用:Connection.Broadcast(广播,所有客户端都可以收到消息),Connection.Send(发送给指定的客户端)

运行效果如下截图示:

SignalR支持额外附加:QueryString、Cookie、State,具体的客户端设置与服务端接收请见上面的代码,同时也可以参见如下其它博主总结的表格(SignalR的Javascript客户端API使用方式整理):

通过三个DEMO学会SignalR的三种实现方式的更多相关文章

  1. SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论 SignalR 简单示例 通过三个DEMO学会SignalR的三种实现方式 SignalR推送框架两个项目永久连接通讯使用 SignalR 集线器简单实例2 用SignalR创建实时永久长连接异步网络应用程序

    SignalR代理对象异常:Uncaught TypeError: Cannot read property 'client' of undefined 推出的结论   异常汇总:http://www ...

  2. 通过三个DEMO学会SignalR的三种实现方式 转载https://www.cnblogs.com/zuowj/p/5674615.html

    一.理解SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息 ...

  3. SignalR的三个Demo

    一.理解SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息 ...

  4. 10天学会phpWeChat——第三天:从数据库读取数据到视图

    在第二天,我们创建了我们的第一个phpWeChat功能模块,但是比较简单.实际生产环境中,我们不可能有如此简单的需求.更多的情况是数据存储在MySql数据库中,我们开发功能模块的作用就是将这些数据从M ...

  5. 并发编程系列小结(线程安全,synchronized,脏读,线程间的通信wait/notify,线程的三种实现方式Demo,可替代wait/notify的方法)

    线程安全: 当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法就是线程安全的) synchronized: 可以在任意对象或方法上加锁,而加锁的这段代码称为 ...

  6. 漫画:一招学会TCP的三次握手和四次挥手

    TCP三次握手和四次挥手的问题在面试中是最为常见的考点之一.很多读者都知道三次和四次,但是如果问深入一点,他们往往都无法作出准确回答. 本篇尝试使用动画来对这个知识点进行讲解,期望读者们可以更加简单地 ...

  7. [置顶] 一个demo学会css

    全栈工程师开发手册 (作者:栾鹏) 一个demo学会css css选择器全解 css操作语法全解 学习了css权威指南这本书,自己喜欢边学边总结边写demo,所以写了这篇文章,包含了大部分的css编程 ...

  8. AutoLayout的三种设置方式之——NSLayoutConstraint代码篇

    AutoLayout是从IOS 6开始苹果引入来取代autoresizing的新的布局技术,该技术有三种设置方式,等下我来为大家一一叙述一下. 在说三种设置方式前,我们先简单的说一下autolayou ...

  9. 瀑布流的三种实现方式(原生js+jquery+css3)

    前言 项目需求要弄个瀑布流的页面,用的是waterfall这个插件,感觉还是可以的,项目赶就没自己的动手写.最近闲来没事,就自己写个.大致思路理清楚,还是挺好实现的... 原生javascript版 ...

随机推荐

  1. hibernate多对一双向关联

    关联是类(类的实例)之间的关系,表示有意义和值得关注的连接. 本系列将介绍Hibernate中主要的几种关联映射 Hibernate一对一主键单向关联Hibernate一对一主键双向关联Hiberna ...

  2. 06.LoT.UI 前后台通用框架分解系列之——浮夸的图片上传

    LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...

  3. mybatis_映射查询

    一.一对一映射查询: 第一种方式(手动映射):借助resultType属性,定义专门的pojo类作为输出类型,其中该po类中封装了查询结果集中所有的字段.此方法较为简单,企业中使用普遍. <!- ...

  4. 免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)

    很多的软件项目中都会使用到定时任务.定时轮询数据库同步,定时邮件通知等功能..NET Framework具有“内置”定时器功能,通过System.Timers.Timer类.在使用Timer类需要面对 ...

  5. ASP.NET Core 中文文档 第四章 MVC(4.6)Areas(区域)

    原文:Areas 作者:Dhananjay Kumar 和 Rick Anderson 翻译:耿晓亮(Blue) 校对:许登洋(Seay) Areas 是 ASP.NET MVC 用来将相关功能组织成 ...

  6. RabbitMQ + PHP (一)入门与安装

    RabbitMQ: 1.是实现AMQP(高级消息队列协议)的消息中间件的一种. 2.主要是为了实现系统之间的双向解耦而实现的.当生产者大量产生数据时,消费者无法快速消费,那么需要一个中间层.保存这个数 ...

  7. iOS 原生地图地理编码与反地理编码

    当我们要在App实现功能:输入地名,编码为经纬度,实现导航功能. 那么,我需要用到原生地图中的地理编码功能,而在Core Location中主要包含了定位.地理编码(包括反编码)功能. 在文件中导入 ...

  8. [转载]敏捷开发之Scrum扫盲篇

    现在敏捷开发是越来越火了,人人都在谈敏捷,人人都在学习Scrum和XP...      为了不落后他人,于是我也开始学习Scrum,今天主要是对我最近阅读的相关资料,根据自己的理解,用自己的话来讲述S ...

  9. 如何理解MySQL中auto_increment?

    1.auto_increment用于主键自动增长.比如从1开始增长,当把第一条数据删除,再插入第二条数据时,主键值为2,不是1.

  10. MSSQL 事务,视图,索引,存储过程,触发器

    事务 事务是一种机制.是一种操作序列,它包含了一组数据库操作命令,这组命令要么全部执行,要么全部不执行. 在数据库系统上执行并发操作时事务是作为最小的控制单元来使用的.这特别适用于多用户同时操作的数据 ...