你的位置:首页 > ASP.net教程

[ASP.net教程]SignalR实现在线聊天室功能(欢迎、发送、回复、私信、屏蔽)


一、在线聊天室

1、新建解决方案 SignalROnlineChatDemo

 

2、新建MVC项目 SignalROnlineChatDemo.Web

 (无身份验证)

 

 

3、安装SignalR

PM> install-package Microsoft.AspNet.SignalR

 

4、 创建一个称为 Startup.cs 的新类

1   public class Startup2   {3     public void Configuration(IAppBuilder app)4     {5       // 有关如何配置应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=3168886       app.MapSignalR();7     }8   }

 

5、添加Hubs

1   public class ChatHub : Hub2   {3     public void Hello()4     {5       Clients.All.hello();6     }7   }

 

6、Action/View

 1     /// <summary> 2     /// 在线聊天室 3     /// </summary> 4     /// <returns></returns> 5     public ActionResult Chat(string groupName) 6     { 7       if (string.IsNullOrWhiteSpace(groupName)) 8       { 9         return Content("groupName is nllOrWhiteSpace");10       }11       return View((object)groupName);12     }

 1 @model string 2 @{ 3   ViewBag.Title = "Chat"; 4 } 5  6 <style> 7   .chat-container div { 8     margin: 10px 0; 9   }10   .dN {11     display: none;12   }13 </style>14 15 <h2>Chat <span id="chatRoomName"></span></h2>16 17 <div class="chat-container">18   <ul id="discussion"></ul>19   <div class="sendto-wrap dN">20     发送给:21     <span></span>22   </div>23   <div class="sendcontent-wrap">24     <input type="text" name="message" />25     <input type="button" id="btnSendMessage" value="Send" />26   </div>27 </div>28 29 @section scripts{30   <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>31   <!-- Reference the autogenerated SignalR hub script. -->32   <script src="~/signalr/hubs"></script>33   <script type="text/javascript">34     $(function () {35       //聊天室编号36       var groupName = '@Model';37       //成员昵称38       var nickName = '';39       //成员聊天Id40       var connectionId = '';41 42       //Reference the auto-generated proxy for the hub.43       var chat = $.connection.chatHub;44 45       $("#chatRoomName").html(groupName);46 47       //Get the user name and store it to prepend to message48       while (nickName == '' || $.trim(nickName) == '') {49         nickName = prompt('Enter your name:', '')50         $('#displayname').val(nickName);51       }52 53       $('#message').focus();54 55     });56 57   </script>58 }

 

7、功能1:新成员加入,群发欢迎

 1     /// <summary> 2     /// newcomer 进入聊天室 3     /// </summary> 4     /// <param name="groupName"></param> 5     public void JoinGroup(string groupName, string userNickName) 6     { 7       //对聊天室成员群发‘新成员加入’ 8       var conId = Context.ConnectionId; 9 10       Groups.Add(conId, groupName);11 12       var psn = new ChatPerson()13       {14         ConnectionId = conId,15         NickName = userNickName,16         GroupName = groupName,17       };18       19       Clients.Caller.setCallerInfo(psn);20       //Clients.Group(groupName).welcome(psn); //不能广播给自己,所以分成了两句21       Clients.Caller.welcome(psn);22       Clients.Group(groupName, conId).welcome(psn);23       24     }

1       //新成员身份信息(connectionId)2       chat.client.setCallerInfo = function (psn) {3         connectionId = psn.ConnectionId;4         groupName = psn.groupName;5       };6       //welcome newcomer7       chat.client.welcome = function (psn) {8         $("#discussion").append('<li><a href="javascript:;" data-conId="' + psn.ConnectionId + '">' + psn.NickName + '</a>加入了聊天室</li>');9       };

结果截图:

 

8、功能2:群发

 1 /// <summary> 2     /// newcomer进入聊天室,对聊天室成员群发‘新成员加入’ 3     /// </summary> 4     /// <param name="groupName"></param> 5     public void JoinGroup(string groupName, string userNickName) 6     { 7       var conId = Context.ConnectionId; 8       var psn = new ChatPerson() 9       {10         ConnectionId = conId,11         NickName = userNickName,12         GroupName = groupName,13       };14 15       //成员信息计入Redis中16       //var redisClient = RedisManager.GetClient();17       //if (redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId)) != null)18       //{19       //  //connected20       //  return;21       //}22       //redisClient.Set<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId), psn);23       //redisClient.SaveAsync();24       using (var redisClient = RedisManager.GetClient())25       {26         IRedisTypedClient<ChatPerson> psns = redisClient.As<ChatPerson>();27         if (psns.GetValue(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId)) != null)28         {29           //connected30           return;31         }32         psns.SetValue(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, psn.ConnectionId), psn);33       }34 35       Groups.Add(conId, groupName);36       Clients.Caller.setCallerInfo(psn);37       //Clients.Group(groupName).welcome(psn); //不能广播给自己,所以分成了两句38       Clients.Caller.welcome(psn);39       Clients.Group(groupName, conId).welcome(psn);40     }41 42     /// <summary>43     /// 群发内容44     /// </summary>45     public void SendMessage(string message)46     {47       if (string.IsNullOrWhiteSpace(message))48       {49         return;50       }51       var conId = Context.ConnectionId;52       ChatPerson psn = null;53       var redisClient = RedisManager.GetClient();54       if ((psn = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, conId))) == null55         || string.IsNullOrWhiteSpace(psn.GroupName) || string.IsNullOrWhiteSpace(psn.NickName)56         )57       {58         //invalid ConnectionId59         return;60       }61       Clients.Group(psn.GroupName).sendMessage(conId, psn.NickName, message);62     }

1       //群发内容2       chat.client.sendMessage = function (sendFromConnectionId, sendFromNickName, message) {3         $("#discussion").append('<li><a href="javascript:;" data-conId="' + sendFromConnectionId + '">' + sendFromNickName + '</a>:' + message + '</li>');4       };

结果截图:

 

9、功能3:回复

 1 /// <summary> 2     /// 回复(@) 3     /// </summary> 4     /// <param name="sendTo"></param> 5     /// <param name="message"></param> 6     public void SendMessageTo(string sendTo, string message) 7     { 8       if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(sendTo)) 9       {10         return;11       }12       var connId = Context.ConnectionId;13       if (connId == sendTo)14       {15         return;16       }17       ChatPerson curPerson = null;18       ChatPerson desPerson = null;19       var redisClient = RedisManager.GetClient();20       if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null21         || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)22         )23       {24         //invalid ConnectionId25         return;26       }27       if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, sendTo))) == null28        || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)29         )30       {31         //invalid ConnectionId32         return;33       }34       if (curPerson.GroupName != desPerson.GroupName)35       {36         return;37       }38 39       Clients.Group(curPerson.GroupName).sendMessageTo(curPerson.ConnectionId, curPerson.NickName, desPerson.ConnectionId, desPerson.NickName, message);40     }

1       //at回复2       chat.client.sendMessageTo = function (fromConnId, fromNickName, toConnId, toNickName, message) {3         $("#discussion").append('<li data-connId="' + fromConnId + '" data-nickName="' + fromNickName + '"><a href="javascript:;">' + fromNickName + '</a>对<a href="javascript:;">' + toNickName + '</a>&nbsp;说:' + message + ' '4           + (fromConnId == connectionId ? '' : '&nbsp;&nbsp;&nbsp;<a href="javascript:;" action="at">&#64;他</a>') + '' + (fromConnId == connectionId ? '' : '&nbsp;&nbsp;&nbsp;<a title="屏蔽其发言" href="javascript:;" action="shielding">屏蔽</a>') + '</li>');5       };

 1 //发送 2         $("#btnSendMessage").click(function () { 3           var desConnId = $('.sendto-wrap a').attr('data-desConnId'); 4           if (!desConnId || desConnId.length == 0) { 5             //群发 6             chat.server.sendMessage($('[name=message]').val()); 7           } 8           else { 9             //回复10             chat.server.sendMessageTo(desConnId, $('[name=message]').val());11           }12           $('.sendto-wrap').addClass('dN');13           $('.sendto-wrap a').attr('data-desConnId', '');14           $('[name=message]').val('').focus();15         });16         //at17         $('#discussion').on('click', '[action=at]', function () {18           var desConnId = $(this).closest('li').attr('data-connId');19           var desNickName = $(this).closest('li').attr('data-nickName');20           $('.sendto-wrap').removeClass('dN');21           $('.sendto-wrap a').attr('data-desConnId', desConnId).html(desNickName);22         });

结果截图:

 

10、功能4:私信

 1 /// <summary> 2     /// 私信给 3     /// </summary> 4     /// <param name="sendTo"></param> 5     /// <param name="message"></param> 6     public void PrivateMessageTo(string sendTo, string message) 7     { 8       if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(sendTo)) 9       {10         return;11       }12       var connId = Context.ConnectionId;13       if (connId == sendTo)14       {15         return;16       }17       ChatPerson curPerson = null;18       ChatPerson desPerson = null;19       var redisClient = RedisManager.GetClient();20       if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null21         || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)22         )23       {24         //invalid ConnectionId25         return;26       }27       if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, sendTo))) == null28        || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)29         )30       {31         //invalid ConnectionId32         return;33       }34       if (curPerson.GroupName != desPerson.GroupName)35       {36         return;37       }38       Clients.Caller.myPrivateMessageTo(desPerson.ConnectionId, desPerson.NickName, message);39       Clients.Client(sendTo).bePrivateMessageTo(curPerson.ConnectionId, curPerson.NickName, message);40     }

1         //私信2         $('#discussion').on('click', '[action=privateAt]', function () {3           var desConnId = $(this).closest('li').attr('data-connId');4           var desNickName = $(this).closest('li').attr('data-nickName');5           $('.sendto-wrap span.sendto-to').html('私信给');6           $('.sendto-wrap').removeClass('dN');7           $('.sendto-wrap a').attr('data-desConnId', desConnId).attr('data-desAction', 'privateAt').html(desNickName);8         });

 1       //privateAt私信 2       //我发的 3       chat.client.myPrivateMessageTo = function (toConnId, toNickName, message) { 4         $("#discussion").append('<li data-connId="' + connectionId + '" data-nickName="' + nickName + '">我对<a href="javascript:;">' + toNickName + '</a>&nbsp;说:' + message + ' ' 5           //+ getActionBlockHtml(connectionId) 6           ); 7       }; 8       //发给我的 9       chat.client.bePrivateMessageTo = function (fromConnId, fromNickName, message) {10         $("#discussion").append('<li data-connId="' + fromConnId + '" data-nickName="' + fromNickName + '"><a href="javascript:;">' + fromNickName + '</a>对我私信说:' + message + ' '11           + getActionBlockHtml(fromConnId)12           );13       };

结果截图:

 

11、功能5:屏蔽 

 1   /// <summary> 2   ///  3   /// </summary> 4   public class PersonShielding 5   { 6     /// <summary> 7     /// 成员的ConnectionId 8     /// </summary> 9     public string ConnectionId { get; set; }10 11     /// <summary>12     /// 被屏蔽 我被哪些人屏蔽(这样设计似乎不合理,但好用)13     /// </summary>14     public string[] BeShieldingByConnIdArr { get; set; }15   }

 

 1 /// <summary> 2     /// 屏蔽某人的发言 3     /// </summary> 4     /// <param name="desConId"></param> 5     public void Shielding(string desConnId) 6     { 7       if (string.IsNullOrWhiteSpace(desConnId)) 8       { 9         return;10       }11       var connId = Context.ConnectionId;12       if (connId == desConnId)13       {14         return;15       }16       var redisClient = RedisManager.GetClient();17       ChatPerson curPerson = null;18       ChatPerson desPerson = null;19       if ((curPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId))) == null20         || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)21         )22       {23         //invalid ConnectionId24         return;25       }26       if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, desConnId))) == null27        || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)28         )29       {30         //invalid ConnectionId31         return;32       }33       var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, desConnId));34       if (personShielding == null)35       {36         personShielding = new PersonShielding()37         {38           ConnectionId = desConnId,39           BeShieldingByConnIdArr = new string[] { connId }40         };41       }42       else43       {44         if (personShielding.BeShieldingByConnIdArr == null)45         {46           personShielding.BeShieldingByConnIdArr = new string[] { connId };47         }48         else if (!personShielding.BeShieldingByConnIdArr.Contains(connId)) 49         {50           personShielding.BeShieldingByConnIdArr = personShielding.BeShieldingByConnIdArr.Union(new string[] { connId }).ToArray();51         }52       }53       redisClient.Set<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, desConnId), personShielding);54       redisClient.SaveAsync();55 56       Clients.Caller.shieldingSuccess(desConnId, desPerson.NickName);57     }

 1       var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId)); 2       if (personShielding != null && personShielding.BeShieldingByConnIdArr != null && personShielding.BeShieldingByConnIdArr.Length > 0) 3       { 4         //屏蔽我的 不发 5         Clients.Group(curPerson.GroupName, personShielding.BeShieldingByConnIdArr).sendMessage(connId, curPerson.NickName, message); 6       } 7       else 8       { 9         Clients.Group(curPerson.GroupName).sendMessage(connId, curPerson.NickName, message);10       }

1       var personShielding = redisClient.Get<PersonShielding>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId));2       if (personShielding == null || personShielding.BeShieldingByConnIdArr == null || !personShielding.BeShieldingByConnIdArr.Contains(sendTo))3       {4         //sendTo没有屏蔽我,那我就发5         Clients.Group(curPerson.GroupName).sendMessageTo(curPerson.ConnectionId, curPerson.NickName, desPerson.ConnectionId, desPerson.NickName, message);6       }

1         //屏蔽2         $('#discussion').on('click', '[action=shielding]', function () {3           if (confirm('确定屏蔽其发言么!')) { 4             var desConnId = $(this).closest('li').attr('data-connId');5             chat.server.shielding(desConnId);6           }7         });

1       //屏蔽成功 callback2       chat.client.shieldingSuccess = function (desConnId, desNickName) {3         $("#discussion").append('<li data-connId="' + connectionId + '" data-nickName="' + nickName + '">你成功屏蔽了<a href="javascript:;">' + desNickName + '</a>的发言'4                   + '</li>'5           );6       };

结果截图:

 

二、其他工作

1、退出后重新接入,能确定唯一身份么?

  关于这个问题,通常的解决方案是:使用UserId,以UserId作为主线(代码就不重新码了)。

  如果能在页面加载$.connection.hub.start()的时候带入上次使用的connectionId就好了,但暂时我还没发现能这么做

  (如果谁知道怎么解决,分享给我下,谢谢!!!)

 

2、退出聊天室时从Redis清理用户的相关数据

  先来看看Hub的这三个方法:Hub.OnConnected 、Hub.OnDisconnected 、 Hub.OnReconnected

 1     /// <summary> 2     /// Called when the connection connects to this hub instance. 3     /// </summary> 4     /// <returns></returns> 5     public override Task OnConnected() 6     { 7       DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnConnected, ConnectionId:{0}", Context.ConnectionId); 8       return base.OnConnected(); 9     }10 11     /// <summary>12     /// Called when a connection disconnects from this hub gracefully or due to a timeout.13     /// </summary>14     /// <param name="stopCalled"></param>15     /// <returns></returns>16     public override Task OnDisconnected(bool stopCalled)17     {18       DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnDisconnected, ConnectionId:{0}", Context.ConnectionId);19       return base.OnDisconnected(stopCalled);20     }21 22     /// <summary>23     /// Called when the connection reconnects to this hub instance.24     /// </summary>25     /// <returns></returns>26     public override Task OnReconnected()27     {28       DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnReconnected, ConnectionId:{0}", Context.ConnectionId);29       return base.OnReconnected();30     }

从查看日志可以得出结论:

  Onconnected 是在加载页面/刷新页面重新加载的时候触发($.connection.hub.start())。

  OnDisConnected 在离开(关闭选项卡/刷新页面)的时候触发($.connection.hub.stop())。

  OnReconnected 仅会在生成网站的时候触发。重启网站不会触发。(暂不知所以然)

所以可以这样

 1     public override Task OnDisconnected(bool stopCalled) 2     { 3       var connId = Context.ConnectionId; 4  5       DefaultLoggerProvider.Instance.InfoFormat("ChatHub.OnDisconnected, ConnectionId:{0}, stopCalled:{1}", connId, stopCalled); 6  7       using (var redisClient = RedisManager.GetClient()) 8       { 9         redisClient.Remove(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, connId));10         redisClient.Remove(string.Format(RedisKey.SignalROnlineChatDemoWebModels_PersonShielding, connId));11         redisClient.SaveAsync();12       }13 14       return base.OnDisconnected(stopCalled);15     }

 

附:源码下载