SignalR实现在线聊天室功能
一、在线聊天室
1、新建解决方案 SignalROnlineChatDemo
2、新建MVC项目 SignalROnlineChatDemo.Web
(无身份验证)

3、安装SignalR
PM> install-package Microsoft.AspNet.SignalR
4、 创建一个称为 Startup.cs 的新类
public class Startup
{
public void Configuration(IAppBuilder app)
{
// 有关如何配置应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=316888
app.MapSignalR();
}
}
5、添加Hubs
public class ChatHub : Hub
{
public void Hello()
{
Clients.All.hello();
}
}
6、Action/View
/// <summary>
/// 在线聊天室
/// </summary>
/// <returns></returns>
public ActionResult Chat(string groupName)
{
if (string.IsNullOrWhiteSpace(groupName))
{
return Content("groupName is nllOrWhiteSpace");
}
return View((object)groupName);
}
@model string
@{
ViewBag.Title = "Chat";
} <style>
.chat-container div {
margin: 10px 0;
}
.dN {
display: none;
}
</style> <h2>Chat <span id="chatRoomName"></span></h2> <div class="chat-container">
<ul id="discussion"></ul>
<div class="sendto-wrap dN">
发送给:
<span></span>
</div>
<div class="sendcontent-wrap">
<input type="text" name="message" />
<input type="button" id="btnSendMessage" value="Send" />
</div>
</div> @section scripts{
<script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
<!-- Reference the autogenerated SignalR hub script. -->
<script src="~/signalr/hubs"></script>
<script type="text/javascript">
$(function () {
//聊天室编号
var groupName = '@Model';
//成员昵称
var nickName = '';
//成员聊天Id
var connectionId = ''; //Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub; $("#chatRoomName").html(groupName); //Get the user name and store it to prepend to message
while (nickName == '' || $.trim(nickName) == '') {
nickName = prompt('Enter your name:', '')
$('#displayname').val(nickName);
} $('#message').focus(); }); </script>
}
7、功能1:新成员加入,群发欢迎
/// <summary>
/// newcomer 进入聊天室
/// </summary>
/// <param name="groupName"></param>
public void JoinGroup(string groupName, string userNickName)
{
//对聊天室成员群发‘新成员加入’
var conId = Context.ConnectionId; Groups.Add(conId, groupName); var psn = new ChatPerson()
{
ConnectionId = conId,
NickName = userNickName,
GroupName = groupName,
}; Clients.Caller.setCallerInfo(psn);
//Clients.Group(groupName).welcome(psn); //不能广播给自己,所以分成了两句
Clients.Caller.welcome(psn);
Clients.Group(groupName, conId).welcome(psn); }
//新成员身份信息(connectionId)
chat.client.setCallerInfo = function (psn) {
connectionId = psn.ConnectionId;
groupName = psn.groupName;
};
//welcome newcomer
chat.client.welcome = function (psn) {
$("#discussion").append('<li><a href="javascript:;" data-conId="' + psn.ConnectionId + '">' + psn.NickName + '</a>加入了聊天室</li>');
};
结果截图:

8、功能2:群发
/// <summary>
/// newcomer进入聊天室,对聊天室成员群发‘新成员加入’
/// </summary>
/// <param name="groupName"></param>
public void JoinGroup(string groupName, string userNickName)
{
var conId = Context.ConnectionId;
var psn = new ChatPerson()
{
ConnectionId = conId,
NickName = userNickName,
GroupName = groupName,
}; //成员信息计入Redis中
//var redisClient = RedisManager.GetClient();
//if (redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId)) != null)
//{
// //connected
// return;
//}
//redisClient.Set<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId), psn);
//redisClient.SaveAsync();
using (var redisClient = RedisManager.GetClient())
{
IRedisTypedClient<ChatPerson> psns = redisClient.As<ChatPerson>();
if (psns.GetValue(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId)) != null)
{
//connected
return;
}
psns.SetValue(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId), psn);
} Groups.Add(conId, groupName);
Clients.Caller.setCallerInfo(psn);
//Clients.Group(groupName).welcome(psn); //不能广播给自己,所以分成了两句
Clients.Caller.welcome(psn);
Clients.Group(groupName, conId).welcome(psn);
} /// <summary>
/// 群发内容
/// </summary>
public void SendMessage(string message)
{
if (string.IsNullOrWhiteSpace(message))
{
return;
}
var conId = Context.ConnectionId;
ChatPerson psn = null;
var redisClient = RedisManager.GetClient();
if ((psn = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, conId))) == null
|| string.IsNullOrWhiteSpace(psn.GroupName) || string.IsNullOrWhiteSpace(psn.NickName)
)
{
//invalid ConnectionId
return;
}
Clients.Group(psn.GroupName).sendMessage(conId, psn.NickName, message);
}
//群发内容
chat.client.sendMessage = function (sendFromConnectionId, sendFromNickName, message) {
$("#discussion").append('<li><a href="javascript:;" data-conId="' + sendFromConnectionId + '">' + sendFromNickName + '</a>:' + message + '</li>');
};
结果截图:

9、功能3:回复
/// <summary>
/// 回复(@)
/// </summary>
/// <param name="sendTo"></param>
/// <param name="message"></param>
public void SendMessageTo(string sendTo, string message)
{
if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(sendTo))
{
return;
}
var connId = Context.ConnectionId;
if (connId == sendTo)
{
return;
}
ChatPerson curPerson = null;
ChatPerson desPerson = null;
var redisClient = RedisManager.GetClient();
if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null
|| string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)
)
{
//invalid ConnectionId
return;
}
if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, sendTo))) == null
|| string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)
)
{
//invalid ConnectionId
return;
}
if (curPerson.GroupName != desPerson.GroupName)
{
return;
} Clients.Group(curPerson.GroupName).sendMessageTo(curPerson.ConnectionId, curPerson.NickName, desPerson.ConnectionId, desPerson.NickName, message);
}
//at回复
chat.client.sendMessageTo = function (fromConnId, fromNickName, toConnId, toNickName, message) {
$("#discussion").append('<li data-connId="' + fromConnId + '" data-nickName="' + fromNickName + '"><a href="javascript:;">' + fromNickName + '</a>对<a href="javascript:;">' + toNickName + '</a> 说:' + message + ' '
+ (fromConnId == connectionId ? '' : ' <a href="javascript:;" action="at">@他</a>') + '' + (fromConnId == connectionId ? '' : ' <a title="屏蔽其发言" href="javascript:;" action="shielding">屏蔽</a>') + '</li>');
};
//发送
$("#btnSendMessage").click(function () {
var desConnId = $('.sendto-wrap a').attr('data-desConnId');
if (!desConnId || desConnId.length == 0) {
//群发
chat.server.sendMessage($('[name=message]').val());
}
else {
//回复
chat.server.sendMessageTo(desConnId, $('[name=message]').val());
}
$('.sendto-wrap').addClass('dN');
$('.sendto-wrap a').attr('data-desConnId', '');
$('[name=message]').val('').focus();
});
//at
$('#discussion').on('click', '[action=at]', function () {
var desConnId = $(this).closest('li').attr('data-connId');
var desNickName = $(this).closest('li').attr('data-nickName');
$('.sendto-wrap').removeClass('dN');
$('.sendto-wrap a').attr('data-desConnId', desConnId).html(desNickName);
});
结果截图:

10、功能4:私信
/// <summary>
/// 私信给
/// </summary>
/// <param name="sendTo"></param>
/// <param name="message"></param>
public void PrivateMessageTo(string sendTo, string message)
{
if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(sendTo))
{
return;
}
var connId = Context.ConnectionId;
if (connId == sendTo)
{
return;
}
ChatPerson curPerson = null;
ChatPerson desPerson = null;
var redisClient = RedisManager.GetClient();
if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null
|| string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)
)
{
//invalid ConnectionId
return;
}
if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, sendTo))) == null
|| string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)
)
{
//invalid ConnectionId
return;
}
if (curPerson.GroupName != desPerson.GroupName)
{
return;
}
Clients.Caller.myPrivateMessageTo(desPerson.ConnectionId, desPerson.NickName, message);
Clients.Client(sendTo).bePrivateMessageTo(curPerson.ConnectionId, curPerson.NickName, message);
}
//私信
$('#discussion').on('click', '[action=privateAt]', function () {
var desConnId = $(this).closest('li').attr('data-connId');
var desNickName = $(this).closest('li').attr('data-nickName');
$('.sendto-wrap span.sendto-to').html('私信给');
$('.sendto-wrap').removeClass('dN');
$('.sendto-wrap a').attr('data-desConnId', desConnId).attr('data-desAction', 'privateAt').html(desNickName);
});
//privateAt私信
//我发的
chat.client.myPrivateMessageTo = function (toConnId, toNickName, message) {
$("#discussion").append('<li data-connId="' + connectionId + '" data-nickName="' + nickName + '">我对<a href="javascript:;">' + toNickName + '</a> 说:' + message + ' '
//+ getActionBlockHtml(connectionId)
);
};
//发给我的
chat.client.bePrivateMessageTo = function (fromConnId, fromNickName, message) {
$("#discussion").append('<li data-connId="' + fromConnId + '" data-nickName="' + fromNickName + '"><a href="javascript:;">' + fromNickName + '</a>对我私信说:' + message + ' '
+ getActionBlockHtml(fromConnId)
);
};
结果截图:

11、功能5:屏蔽
/// <summary>
///
/// </summary>
public class PersonShielding
{
/// <summary>
/// 成员的ConnectionId
/// </summary>
public string ConnectionId { get; set; } /// <summary>
/// 被屏蔽 我被哪些人屏蔽(这样设计似乎不合理,但好用)
/// </summary>
public string[] BeShieldingByConnIdArr { get; set; }
}
/// <summary>
/// 屏蔽某人的发言
/// </summary>
/// <param name="desConId"></param>
public void Shielding(string desConnId)
{
if (string.IsNullOrWhiteSpace(desConnId))
{
return;
}
var connId = Context.ConnectionId;
if (connId == desConnId)
{
return;
}
var redisClient = RedisManager.GetClient();
ChatPerson curPerson = null;
ChatPerson desPerson = null;
if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null
|| string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)
)
{
//invalid ConnectionId
return;
}
if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, desConnId))) == null
|| string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)
)
{
//invalid ConnectionId
return;
}
var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, desConnId));
if (personShielding == null)
{
personShielding = new PersonShielding()
{
ConnectionId = desConnId,
BeShieldingByConnIdArr = new string[] { connId }
};
}
else
{
if (personShielding.BeShieldingByConnIdArr == null)
{
personShielding.BeShieldingByConnIdArr = new string[] { connId };
}
else if (!personShielding.BeShieldingByConnIdArr.Contains(connId))
{
personShielding.BeShieldingByConnIdArr = personShielding.BeShieldingByConnIdArr.Union(new string[] { connId }).ToArray();
}
}
redisClient.Set<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, desConnId), personShielding);
redisClient.SaveAsync(); Clients.Caller.shieldingSuccess(desConnId, desPerson.NickName);
}
var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId));
if (personShielding != null && personShielding.BeShieldingByConnIdArr != null && personShielding.BeShieldingByConnIdArr.Length > )
{
//屏蔽我的 不发
Clients.Group(curPerson.GroupName, personShielding.BeShieldingByConnIdArr).sendMessage(connId, curPerson.NickName, message);
}
else
{
Clients.Group(curPerson.GroupName).sendMessage(connId, curPerson.NickName, message);
}
var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId));
if (personShielding == null || personShielding.BeShieldingByConnIdArr == null || !personShielding.BeShieldingByConnIdArr.Contains(sendTo))
{
//sendTo没有屏蔽我,那我就发
Clients.Group(curPerson.GroupName).sendMessageTo(curPerson.ConnectionId, curPerson.NickName, desPerson.ConnectionId, desPerson.NickName, message);
}
//屏蔽
$('#discussion').on('click', '[action=shielding]', function () {
if (confirm('确定屏蔽其发言么!')) {
var desConnId = $(this).closest('li').attr('data-connId');
chat.server.shielding(desConnId);
}
});
//屏蔽成功 callback
chat.client.shieldingSuccess = function (desConnId, desNickName) {
$("#discussion").append('<li data-connId="' + connectionId + '" data-nickName="' + nickName + '">你成功屏蔽了<a href="javascript:;">' + desNickName + '</a>的发言'
+ '</li>'
);
};
结果截图:

二、其他工作
1、退出后重新接入,能确定唯一身份么?
关于这个问题,通常的解决方案是:使用UserId,以UserId作为主线。当时是想到一点就写一点的!
如果能在页面加载$.connection.hub.start()的时候带入上次使用的connectionId就好了,但暂时我还没发现能这么做
(如果谁知道怎么解决,分享给我下,谢谢!!!)
2、退出聊天室时从Redis清理用户的相关数据
先来看看Hub的这三个方法:Hub.OnConnected 、Hub.OnDisconnected 、 Hub.OnReconnected
/// <summary>
/// Called when the connection connects to this hub instance.
/// </summary>
/// <returns></returns>
public override Task OnConnected()
{
DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnConnected, ConnectionId:{0}", Context.ConnectionId);
return base.OnConnected();
} /// <summary>
/// Called when a connection disconnects from this hub gracefully or due to a timeout.
/// </summary>
/// <param name="stopCalled"></param>
/// <returns></returns>
public override Task OnDisconnected(bool stopCalled)
{
DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnDisconnected, ConnectionId:{0}", Context.ConnectionId);
return base.OnDisconnected(stopCalled);
} /// <summary>
/// Called when the connection reconnects to this hub instance.
/// </summary>
/// <returns></returns>
public override Task OnReconnected()
{
DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnReconnected, ConnectionId:{0}", Context.ConnectionId);
return base.OnReconnected();
}
从查看日志可以得出结论:
Onconnected 是在加载页面/刷新页面重新加载的时候触发($.connection.hub.start())。
OnDisConnected 在离开(关闭选项卡/刷新页面)的时候触发($.connection.hub.stop())。
OnReconnected 仅会在生成网站的时候触发。重启网站不会触发。(暂不知所以然)
所以可以这样
public override Task OnDisconnected(bool stopCalled)
{
var connId = Context.ConnectionId; DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnDisconnected, ConnectionId:{0}, stopCalled:{1}", connId, stopCalled); using (var redisClient = RedisManager.GetClient())
{
redisClient.Remove(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId));
redisClient.Remove(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId));
redisClient.SaveAsync();
} return base.OnDisconnected(stopCalled);
}
附:源码下载
SignalR实现在线聊天室功能的更多相关文章
- Asp.NET MVC 使用 SignalR 实现推送功能二(Hubs 在线聊天室 获取保存用户信息)
简单介绍 关于SignalR的简单实用 请参考 Asp.NET MVC 使用 SignalR 实现推送功能一(Hubs 在线聊天室) 在上一篇中,我们只是介绍了简单的消息推送,今天我们来修改一下,实现 ...
- 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。
基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍.最后我们将会实现一个基于S ...
- 基于Server-Sent Event的简单在线聊天室
Web即时通信 所谓Web即时通信,就是说我们可以通过一种机制在网页上立即通知用户一件事情的发生,是不需要用户刷新网页的.Web即时通信的用途有很多,比如实时聊天,即时推送等.如当我们在登陆浏览知乎时 ...
- 三、jQuery--jQuery基础--jQuery基础课程--第12章 jQuery在线聊天室
在线聊天室案例 一.功能简介: 1.用户需要登录后才能进入聊天室交流 2.已无刷新的方式,动态展示交流后的内容和在线人员的基本信息 3.登录后的用户可以提交文字和表情图标 技术重点:利用ajax的无刷 ...
- 基于JQuery+JSP的无数据库无刷新多人在线聊天室
JQuery是一款非常强大的javascript插件,本文就针对Ajax前台和JSP后台来实现一个无刷新的多人在线聊天室,该实现的数据全部存储在服务端内存里,没有用到数据库,本文会提供所有源程序,需要 ...
- 百度前端面试题-类似slack的在线聊天室
别人国庆出去玩,我在家写代码的感觉也是很不错哒. 首先介绍一下技术架构吧! 使用了js框架:FFF,zepto,jquery,md5.min.js 前端框架:Bootstrap 后端:野狗,部分PHP ...
- AngularJS+Node.js+socket.io 开发在线聊天室
所有文章搬运自我的个人主页:sheilasun.me 不得不说,上手AngularJS比我想象得难多了,把官网提供的PhoneCat例子看完,又跑到慕课网把大漠穷秋的AngularJS实战系列看了一遍 ...
- 基于Java的在线聊天室
概述 Java socket编程,实现一个在线聊天室, 实现在线用户群聊,私聊,发送文件等功能. 详细 代码下载:http://www.demodashi.com/demo/13623.html 一. ...
- 在线聊天室的实现(1)--websocket协议和javascript版的api
前言: 大家刚学socket编程的时候, 往往以聊天室作为学习DEMO, 实现简单且上手容易. 该Demo被不同语言实现和演绎, 网上相关资料亦不胜枚举. 以至于很多技术书籍在讲解网络相关的编程时, ...
随机推荐
- warning: incompatible implicit declaration of built-in function 'exit'
warning: incompatible implicit declaration of built-in function 'exit' 解决方法: 在头文件里 引入 stdlib 文件, #i ...
- iOS彩票项目--第二天,自定义蒙版、封装活动菜单、自定义pop菜单
一.自定义蒙版--封装控件,先想好外界怎么来调用,根据外界调用的方法,然后进入内部实现 在外部,调用蒙版的方法--[ChaosCover show]; [ChaosCover hide]; 内部实现 ...
- 给border在加上图片
.div_top .div_menu li a:hover{ border:2px; height:24px; border-image:url(../img/bg-line-1.png) 0 0 7 ...
- 【转】MFC WM_CTLCOLOR 消息
WM_CTLCOLOR消息用来完成对EDIT, STATIC, BUTTON等控件设置背景和字体颜色, 其用法如下: 1.首先在自己需要设置界面的对话框上点击右键-->建立类向导-->加入 ...
- wamp5多站点配置教程
wamp5多站点配置教程 第一要做的是安装第二个apache服务一.找到Apache2的htppd.conf文件.例如:我的wamp是安装在G盘的,我的就是G:\wamp\Apache2\conf目录 ...
- Xenocode Postbuild 2010 for .NET 混淆工具的详细使用步骤【转】
1,首先我们需要去下载这个工具去,我这里倒是有一个下载的网址,已经被破解了,而且有序列号 http://download.csdn.net/tag/Xenocode+Postbuild+2010+fo ...
- R语言低级绘图函数-arrows
arrows 函数用来在一张图表上添加箭头,只需要分别指定起始坐标和终止坐标,就可以添加箭头了,还可以通过一些属性对箭头的形状,大小进行调整 基本用法: xo, yo 指定起始点的x和y坐标,x1, ...
- 解决 Comparison method violates its general contract!
问题:Comparison method violates its general contract!报错 Collections.sort(list, new Comparator<Integ ...
- 【ML】scikit-learn-book
http://nbviewer.ipython.org/github/gmonce/scikit-learn-book/tree/master/
- asp.net DropDownList实现二级联动效果
1.在aspx页面中,拖入两个DroDownList控件,代码如下: <div> <asp:DropDownList ID="s1" runat=" ...