Asp.net SignalR应用并实现群聊功能
asp.net signalr 是为 asp.net 开发人员提供的一个库,可以简化开发人员将实时 web 功能添加到应用程序的过程。实时 web 功能是指这样一种功能:当所连接的客户端变得可用时服务器代码可以立即向其推送内容,而不是让服务器等待客户端请求新的数据。(来自官方介绍。)
-1、写这篇的原因
在上篇文章b/s(web)实时通讯解决方案中,并没有详情介绍signalr,所以另起一篇专门介绍signalr,本文的侧重点是hub功能。
0、先看最终实现效果
github:https://github.com/emrys5/signalrgroupchatdemo
在线演示:
1、准备工作
1.1、在nuget上首先下载signalr的包。
1.2、配置owin与signalr
1.2.1、新建startup类,注册signalr
public class startup { public void configuration(iappbuilder app) { app.mapsignalr(); } }
然后在web.config配置startup类,在configuration=>appsettings节点中添加
<add key="owin:appstartup" value="signalrchat.app_start.startup"/>
1.2.2、在页面引入signalr的js
1、由于signalr前端是基于jquery的,所以页面需引入jquery。
2、引入signalr的js 。
3、引入最重要的hubs js,这个js其实并不存在,signalr会反射获取所有供客户端调用的方法放入hubs js中。
<script src="~/scripts/jquery-1.10.2.js"></script> <script src="~/scripts/jquery.signalr-2.2.1.min.js"></script> <script src="~/signalr/hubs"></script>
1.2.3、新建groupchathub类,并继承hub抽象类
在hub类中的方法就是提供给客户端调用的js方法。
在js中就可以用signalr调用sendmsg。
[hubname("simplehub")] public class simplehub : hub { public void sendmsg(string msg) { } }
这样基本上前期准备工作就做完了,后面就是具体的操作。
2、原理与简单的编程
其实原理如果简单点理解就很简单,因为http是无状态的,所以每次请求以后都会与服务器断开链接,那就是说客户端可以很容易找到服务器,但是服务器如果想给你客户端发送消息就比较麻烦,如果不明白的可以参考上一篇文章b/s(web)实时通讯解决方案。
signalr就很好的解决了这个问题,也就说实现了实现了浏览器与服务器的全双工通信。
2.1、客户端至服务端(b=>s)
客户端代码
<script type="text/javascript"> var ticker = $.connection.simplehub; $.connection.hub.start(); $("#btn").click(function () { // 链接完成以后,可以发送消息至服务端 ticker.server.sendmsg("需要发送的消息"); }); </script>
服务端代码
[hubname("simplehub")] public class simplehub : hub { public void sendmsg(string msg) { // 获取链接id var connectionid = context.connectionid; // 获取cookie var cookie = context.requestcookies; } }
其中simplehub就是我们定义的继承hub类simplehub,然后我们可以用特性hubname进行重命名。
然后开始链接。
在链接完成以后,我们就可以调用在simplehub类中调用的方法。这就就很简单的实现了客户端至服务端发送消息。
我们还可以在context中获取我们想要的东西,比如链接id,cookie等。
2.2、服务端至客户端(s=>b)
服务端代码
[hubname("simplehub")] public class simplehub : hub { public void sendmsg(string msg) { clients.all.msg("发送给客户端的消息"); } }
客户端代码
<script type="text/javascript"> var ticker = $.connection.groupchathub; $.connection.hub.start(); ticker.client.msg = function (data) { console.log(data); } </script>
这里演示了怎么发送消息至客户端,也是signalr比较重要的功能,这里有两个问题需要解决。
问题一、这里是发送消息给所有连着的客户端,如果是单个客户端或者是一批客户端应该怎么发送。
问题二、我们在调用msg给个客户端发送消息时是在接收消息以后做的反馈,然后发送消息给客户端,这样就很类似ajax了,服务端并没有主动给客户端发送消息。
解决:
问题一、clients可以给特性的一群或者一个客户端发送消息
// 所有人 clients.all.msg("发送给客户端的消息"); // 特定 cooectionid clients.client("connectionid").msg("发送给客户端的消息"); // 特定 group clients.group("groupname").msg("发送给客户端的消息");
这是比较常用的三个,当然还有很多,比如allexcept,clients。
在signalr2.0中还添加了others,othersingroup,othersingroups等等。
问题二、我们可以在需要发送消息的地方调用globalhost.connectionmanager.gethubcontext<simplehub>().clients中获取clients。获取clients并发送消息我们最好写成单例模式,因为这种需求很符合单例,群聊中有详细的代码。
3、signalr实现群聊
以上的介绍和代码已经可以实现b=>s和s=>b了,那实现群聊和单独聊天就比较简单了。
由于功能比较简单,所有我把用户名存到了cookie里,也就说第一次进来时需要设置cookie。
还有就是在hub中要实现onconnected、ondisconnected和onreconnected,然后在方法中设置用户和connectionid和统计在线用户,以便聊天使用。
hub代码
/// <summary> /// signalr hub 群聊类 /// </summary> [hubname("groupchathub")] // 标记名称供js调用 public class groupchathub : hub { /// <summary> /// 用户名 /// </summary> private string username { get { var username = context.requestcookies["username"]; return username == null ? "" : httputility.urldecode(username.value); } } /// <summary> /// 在线用户 /// </summary> private static dictionary<string, int> _onlineuser = new dictionary<string, int>(); /// <summary> /// 开始连接 /// </summary> /// <returns></returns> public override task onconnected() { connected(); return base.onconnected(); } /// <summary> /// 重新链接 /// </summary> /// <returns></returns> public override task onreconnected() { connected(); return base.onreconnected(); } private void connected() { // 处理在线人员 if (!_onlineuser.containskey(username)) // 如果名称不存在,则是新用户 { // 加入在线人员 _onlineuser.add(username, 1); // 向客户端发送在线人员 clients.all.publshuser(_onlineuser.select(i => i.key)); // 向客户端发送加入聊天消息 clients.all.publshmsg(formatmsg("系统消息", username + "加入聊天")); } else { // 如果是已经存在的用户,则把在线链接的个数+1 _onlineuser[username] = _onlineuser[username] + 1; } // 加入hub group,为了发送单独消息 groups.add(context.connectionid, "group-" + username); } /// <summary> /// 结束连接 /// </summary> /// <param name="stopcalled"></param> /// <returns></returns> public override task ondisconnected(bool stopcalled) { // 人员链接数-1 _onlineuser[username] = _onlineuser[username] - 1; // 判断是否断开了所有的链接 if (_onlineuser[username] == 0) { // 移除在线人员 _onlineuser.remove(username); // 向客户端发送在线人员 clients.all.publshuser(_onlineuser.select(i => i.key)); // 向客户端发送退出聊天消息 clients.all.publshmsg(formatmsg("系统消息", username + "退出聊天")); } // 移除hub group groups.remove(context.connectionid, "group-" + username); return base.ondisconnected(stopcalled); } /// <summary> /// 发送消息,供客户端调用 /// </summary> /// <param name="user">用户名,如果为0,则是发送给所有人</param> /// <param name="msg">消息</param> public void sendmsg(string user, string msg) { if (user == "0") { // 发送给所有用户消息 clients.all.publshmsg(formatmsg(username, msg)); } else { //// 发送给自己消息 //clients.group("group-" + username).publshmsg(formatmsg(username, msg)); //// 发送给选择的人员 //clients.group("group-" + user).publshmsg(formatmsg(username, msg)); // 发送给自己消息 clients.groups(new list<string> { "group-" + username, "group-" + user }).publshmsg(formatmsg(username, msg)); } } /// <summary> /// 格式化发送的消息 /// </summary> /// <param name="name"></param> /// <param name="msg"></param> /// <returns></returns> private dynamic formatmsg(string name, string msg) { return new { name = name, msg = httputility.htmlencode(msg), time = datetime.now.tostring("yyyy-mm-dd hh:mm:ss") }; } }
js代码
<script type="text/javascript"> $(function () { // 链接hub var ticker = $.connection.groupchathub; $.connection.hub.start(); // 接收服务端发送的消息 $.extend(ticker.client, { // 接收聊天消息 publshmsg: function (data) { $("#msg").append("<li><span class='p'>" + data.name + ":</span>" + data.msg + " <span class='time'>" + data.time + "</span></li>") $("#msg").parents("div")[0].scrolltop = $("#msg").parents("div")[0].scrollheight; }, // 接收在线人员,然后加入select,以供单独聊天选中 publshuser: function (data) { $("#count").text(data.length); $("#users").empty(); $("#users").append('<option value="0">所有人</option>'); for (var i = 0; i < data.length; i++) { $("#users").append('<option value="' + data[i] + '">' + data[i] + '</option>') } } }); // 发送消息按钮 $("#btn-send").click(function () { var msg = $("#txt-msg").val(); if (!msg) { alert('请输入内容!'); return false; } $("#txt-msg").val(''); // 主动发送消息,传入发送给谁,和发送的内容。 ticker.server.sendmsg($("#users").val(), msg); }); }); </script>
html代码
<h2> 群聊系统(<span id="count">1</span>人在线):@viewbag.username </h2> <div style="overflow:auto;height:300px"> <ul id="msg"></ul> </div> <select id="users" class="form-control" style="max-width:150px;"> <option value="0">所有人</option> </select> <input type="text" onkeydown='if (event.keycode == 13) { $("#btn-send").click() }' class="form-control" id="txt-msg" placeholder="内容" style="max-width:400px;" /> <br /> <button type="button" id="btn-send">发送</button>
这样就消息了群聊和发送给特定的人聊天功能。
3.1、封装主动发送消息的单例
/// <summary> /// 主动发送给用户消息,单例模式 /// </summary> public class groupchat { /// <summary> /// clients,用来主动发送消息 /// </summary> private ihubconnectioncontext<dynamic> clients { get; set; } private readonly static groupchat _instance = new groupchat(globalhost.connectionmanager.gethubcontext<groupchathub>().clients); private groupchat(ihubconnectioncontext<dynamic> clients) { clients = clients; } public static groupchat instance { get { return _instance; } } /// <summary> /// 主动给所有人发送消息,系统直接调用 /// </summary> /// <param name="msg"></param> public void sendsystemmsg(string msg) { clients.all.publshmsg(new { name = "系统消息", msg = msg, time = datetime.now.tostring("yyyy-mm-dd hh:mm:ss") }); } }
如果需要发送消息,直接调用sendsystemmsg即可。
groupchat.instance.sendsystemmsg("消息");
4、结语
啥也不说了直接源码
github:https://github.com/emrys5/signalrgroupchatdemo
在线演示:
最后望对各位有所帮助,本文原创,欢迎拍砖和推荐。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。