• SignalR实现在线聊天室功能


    一、在线聊天室

    1、新建解决方案 SignalROnlineChatDemo

    2、新建MVC项目 SignalROnlineChatDemo.Web

     (无身份验证)

     

    3、安装SignalR

    PM> install-package Microsoft.AspNet.SignalR

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

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

    5、添加Hubs

    1     public class ChatHub : Hub
    2     {
    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             //成员聊天Id
    40             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 message
    48             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 newcomer
    7             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             //    //connected
    20             //    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                     //connected
    30                     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))) == null
    55                 || string.IsNullOrWhiteSpace(psn.GroupName) || string.IsNullOrWhiteSpace(psn.NickName)
    56                 )
    57             {
    58                 //invalid ConnectionId
    59                 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))) == null
    21                 || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)
    22                 )
    23             {
    24                 //invalid ConnectionId
    25                 return;
    26             }
    27             if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, sendTo))) == null
    28                || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)
    29                )
    30             {
    31                 //invalid ConnectionId
    32                 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                 //at
    17                 $('#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))) == null
    21                 || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)
    22                 )
    23             {
    24                 //invalid ConnectionId
    25                 return;
    26             }
    27             if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, sendTo))) == null
    28                || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)
    29                )
    30             {
    31                 //invalid ConnectionId
    32                 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))) == null
    20                 || string.IsNullOrWhiteSpace(curPerson.GroupName) || string.IsNullOrWhiteSpace(curPerson.NickName)
    21                 )
    22             {
    23                 //invalid ConnectionId
    24                 return;
    25             }
    26             if ((desPerson = redisClient.Get<ChatPerson>(string.Format(RedisKey.SignalROnlineChatDemoWebModels_ChatPerson, desConnId))) == null
    27                || string.IsNullOrWhiteSpace(desPerson.GroupName) || string.IsNullOrWhiteSpace(desPerson.NickName)
    28                )
    29             {
    30                 //invalid ConnectionId
    31                 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             else
    43             {
    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             //屏蔽成功 callback
    2             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         }

    附:源码下载

  • 相关阅读:
    申通服务恶劣,开始忘本
    基础知识学习外部排序
    ToString()格式和用法大全
    Web.Config Transformation ASP.NET 4.0 新特性
    拒绝try.catch泛滥,学习委托有感
    Oracle Job定时任务的使用详解
    数据库和索引设计简要笔记
    Redis实际应用场景
    线程如何按照自己指定的顺序执行
    WCF 4.0 进阶系列 随笔汇总
  • 原文地址:https://www.cnblogs.com/frozenzhang/p/5406773.html
Copyright © 2020-2023  润新知