一、引言

  在前一篇文章中,我向大家介绍了如何实现实现端对端聊天的功能的,在这一篇文章中将像大家如何使用SignalR实现群聊这样的功能。

二、实现思路

  要想实现群聊的功能,首先我们需要创建一个房间,然后每个在线用户可以加入这个房间里面进行群聊,我们可以为房间设置一个唯一的名字来作为标识。那SignalR类库里面是否有这样现有的方法呢?答案是肯定的。

  1. // IGroupManager接口提供如下方法
  2. // 作用:将连接ID加入某个组
  3. // Context.ConnectionId 连接ID,每个页面连接集线器即会产生唯一ID
  4. // roomName分组的名称
  5. Groups.Add(Context.ConnectionId, roomName);
  6.  
  7. // 作用:将连接ID从某个分组移除
  8. Groups.Remove(Context.ConnectionId, roomName);
  9.  
  10. // IHubConnectionContext接口提供了如下方法
  11. // 调用客户端方法向房间内所有用户群发消息
  12. // Room:分组名称
  13. // new string[0]:过滤(不发送)的连接ID数组
  14. Clients.Group(Room, new string[]).clientMethod

  上面的代码也就是实现群聊的核心方法。Groups对象说白了也就是SignalR类库维护的一个列表对象而已,其实我们完全可以自己来维护一个Dictionary<string, List<string>>这个对象,创建一个房间的时候,我们将房间名称和进入房间的客户端的ConnectionId加入到这个字典里面,然后在聊天室里面点发送消息的时候,我们根据房间名查找到所有加入群聊的ConnectionId,然后调用Clients.Clients(IList<string> connectionIds)方法来将消息群发到每个客户端。以上也就是实现聊天室的原理。

三、使用SignalR实现聊天室的功能

  理清楚了实现思路之后,接下来我们就看下具体的实现代码,同时大家也可以对照代码来对照前面的实现思路。

  1. 首先看下聊天室功能所涉及实体类的实现代码:
  1. /// <summary>
  2. /// 用户类
  3. /// </summary>
  4. public class User
  5. {
  6. /// <summary>
  7. /// 用户Id
  8. /// </summary>
  9. public string UserId { get; set; }
  10.  
  11. /// <summary>
  12. /// 用户的连接集合
  13. /// </summary>
  14. public List<Connection> Connections { get; set; }
  15.  
  16. /// <summary>
  17. /// 用户房间集合,一个用户可以加入多个房间
  18. /// </summary>
  19. public List<ChatRoom> Rooms { get; set; }
  20.  
  21. public User()
  22. {
  23. Connections = new List<Connection>();
  24. Rooms = new List<ChatRoom>();
  25. }
  26. }
  27.  
  28. public class Connection
  29. {
  30. //连接ID
  31. public string ConnectionId { get; set; }
  32.  
  33. //用户代理
  34. public string UserAgent { get; set; }
  35. //是否连接
  36. public bool Connected { get; set; }
  37. }
  38.  
  39. /// <summary>
  40. /// 房间类
  41. /// </summary>
  42. public class ChatRoom
  43. {
  44. // 房间名称
  45. public string RoomName { get; set; }
  46.  
  47. // 用户集合
  48. public List<User> Users { get; set; }
  49.  
  50. public ChatRoom()
  51. {
  52. Users = new List<User>();
  53. }
  54. }
  55.  
  56. /// <summary>
  57. /// 上下文类,用来模拟EF中的DbContext
  58. /// </summary>
  59. public class ChatContext
  60. {
  61. public List<User> Users { get; set; }
  62.  
  63. public List<Connection> Connections { get; set; }
  64.  
  65. public List<ChatRoom> Rooms { get; set; }
  66.  
  67. public ChatContext()
  68. {
  69. Users = new List<User>();
  70. Connections = new List<Connection>();
  71. Rooms = new List<ChatRoom>();
  72. }
  73. }

  2. 接下来,让我们来看到集线器的实现:

  1. [HubName("chatRoomHub")]
  2. public class GroupsHub : Hub
  3. {
  4. public static ChatContext DbContext = new ChatContext();
  5.  
  6. #region IHub Members
  7. // 重写Hub连接事件
  8. public override Task OnConnected()
  9. {
  10. // 查询用户
  11. var user = DbContext.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId);
  12.  
  13. if (user == null)
  14. {
  15. user = new User
  16. {
  17. UserId = Context.ConnectionId
  18. };
  19.  
  20. DbContext.Users.Add(user);
  21. }
  22.  
  23. // 发送房间列表
  24. var items = DbContext.Rooms.Select(p => new {p.RoomName});
  25. Clients.Client(this.Context.ConnectionId).getRoomList(JsonHelper.ToJsonString(items.ToList()));
  26. return base.OnConnected();
  27. }
  28.  
  29. // 重写Hub连接断开的事件
  30. public override Task OnDisconnected(bool stopCalled)
  31. {
  32. // 查询用户
  33. var user = DbContext.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId);
  34.  
  35. if (user != null)
  36. {
  37. // 删除用户
  38. DbContext.Users.Remove(user);
  39.  
  40. // 从房间中移除用户
  41. foreach (var item in user.Rooms)
  42. {
  43. RemoveUserFromRoom(item.RoomName);
  44. }
  45. }
  46. return base.OnDisconnected(stopCalled);
  47. }
  48.  
  49. #endregion
  50.  
  51. #region Public Methods
  52.  
  53. // 为所有用户更新房间列表
  54. public void UpdateRoomList()
  55. {
  56. var itme = DbContext.Rooms.Select(p => new {p.RoomName});
  57. var jsondata = JsonHelper.ToJsonString(itme.ToList());
  58. Clients.All.getRoomlist(jsondata);
  59. }
  60.  
  61. /// <summary>
  62. /// 加入聊天室
  63. /// </summary>
  64. public void JoinRoom(string roomName)
  65. {
  66. // 查询聊天室
  67. var room = DbContext.Rooms.Find(p => p.RoomName == roomName);
  68.  
  69. // 存在则加入
  70. if (room == null) return;
  71.  
  72. // 查找房间中是否存在此用户
  73. var isExistUser = room.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId);
  74.  
  75. // 不存在则加入
  76. if (isExistUser == null)
  77. {
  78. var user = DbContext.Users.Find(u => u.UserId == Context.ConnectionId);
  79. user.Rooms.Add(room);
  80. room.Users.Add(user);
  81.  
  82. // 将客户端的连接ID加入到组里面
  83. Groups.Add(Context.ConnectionId, roomName);
  84.  
  85. //调用此连接用户的本地JS(显示房间)
  86. Clients.Client(Context.ConnectionId).joinRoom(roomName);
  87. }
  88. else
  89. {
  90. Clients.Client(Context.ConnectionId).showMessage("请勿重复加入房间!");
  91. }
  92. }
  93.  
  94. /// <summary>
  95. /// 创建聊天室
  96. /// </summary>
  97. /// <param name="roomName"></param>
  98. public void CreateRoom(string roomName)
  99. {
  100. var room = DbContext.Rooms.Find(a => a.RoomName == roomName);
  101. if (room == null)
  102. {
  103. var cr = new ChatRoom
  104. {
  105. RoomName = roomName
  106. };
  107.  
  108. //将房间加入列表
  109. DbContext.Rooms.Add(cr);
  110.  
  111. // 本人加入聊天室
  112. JoinRoom(roomName);
  113. UpdateRoomList();
  114. }
  115. else
  116. {
  117. Clients.Client(Context.ConnectionId).showMessage("房间名重复!");
  118. }
  119. }
  120.  
  121. public void RemoveUserFromRoom(string roomName)
  122. {
  123. //查找房间是否存在
  124. var room = DbContext.Rooms.Find(a => a.RoomName == roomName);
  125.  
  126. //存在则进入删除
  127. if (room == null)
  128. {
  129. Clients.Client(Context.ConnectionId).showMessage("房间名不存在!");
  130. return;
  131. }
  132.  
  133. // 查找要删除的用户
  134. var user = room.Users.FirstOrDefault(a => a.UserId == Context.ConnectionId);
  135. // 移除此用户
  136. room.Users.Remove(user);
  137. //如果房间人数为0,则删除房间
  138. if (room.Users.Count <= )
  139. {
  140. DbContext.Rooms.Remove(room);
  141. }
  142.  
  143. Groups.Remove(Context.ConnectionId, roomName);
  144.  
  145. //提示客户端
  146. Clients.Client(Context.ConnectionId).removeRoom("退出成功!");
  147. }
  148.  
  149. /// <summary>
  150. /// 给房间内所有的用户发送消息
  151. /// </summary>
  152. /// <param name="room">房间名</param>
  153. /// <param name="message">信息</param>
  154. public void SendMessage(string room, string message)
  155. {
  156. // 调用房间内所有客户端的sendMessage方法
  157. // 因为在加入房间的时候,已经将客户端的ConnectionId添加到Groups对象中了,所有可以根据房间名找到房间内的所有连接Id
  158. // 其实我们也可以自己实现Group方法,我们只需要用List记录所有加入房间的ConnectionId
  159. // 然后调用Clients.Clients(connectionIdList),参数为我们记录的连接Id数组。
  160. Clients.Group(room, new string[]).sendMessage(room, message + " " + DateTime.Now);
  161. }
  162. #endregion
  163. }

  3. 上面SignalR服务端的代码实现已经完成,接下来就让我们一起看看客户端视图的实现:

  1. @{
  2. Layout = null;
  3. }
  4.  
  5. <!DOCTYPE html>
  6.  
  7. <html>
  8. <head>
  9. <meta name="viewport" content="width=device-width" />
  10. <title>Index</title>
  11. <script src="~/Scripts/jquery-2.2.2.min.js"></script>
  12. <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
  13. <script src="~/Scripts/layer/layer.min.js"></script>
  14. <!--这里要注意,这是虚拟目录,也就是你在OWIN Startup中注册的地址-->
  15. <script src="/signalr/hubs"></script>
  16.  
  17. <script type="text/javascript">
  18. var chat;
  19. var roomcount = 0;
  20.  
  21. $(function() {
  22. chat = $.connection.chatRoomHub;
  23. chat.client.showMessage = function(message) {
  24. alert(message);
  25. };
  26. chat.client.sendMessage = function(roomname, message) {
  27. $("#" + roomname).find("ul").each(function() {
  28. $(this).append('<li>' + message + '</li>');
  29. });
  30. };
  31. chat.client.removeRoom = function(data) {
  32. alert(data);
  33. };
  34. chat.client.joinRoom = function (roomname) {
  35. var html = '<div style="float:left; margin-left:360px; border:double; height:528px;width:493px" id="' + roomname + '" roomname="' + roomname + '"><button onclick="RemoveRoom(this)">退出</button>\
  36. ' + roomname + '房间\
  37. 聊天记录如下:<ul>\
  38. </ul>\
  39. <textarea class="ChatCore_write" id="ChatCore_write" style="width:400px"></textarea> <button onclick="SendMessage(this)">发送</button>\
  40. </div>';
  41. $("#RoomList").append(html);
  42. };
  43.  
  44. //注册查询房间列表的方法
  45. chat.client.getRoomlist = function(data) {
  46. if (data) {
  47. var jsondata = $.parseJSON(data);
  48. $("#roomlist").html(" ");
  49. for (var i = 0; i < jsondata.length; i++) {
  50. var html = ' <li>房间名:' + jsondata[i].RoomName + '<button roomname="' + jsondata[i].RoomName + '" onclick="AddRoom(this)">加入</button></li>';
  51. $("#roomlist").append(html);
  52. }
  53. }
  54. };
  55. // 获取用户名称。
  56. $('#username').html(prompt('请输入您的名称:', ''));
  57.  
  58. $.connection.hub.start().done(function() {
  59. $('#CreatRoom').click(function() {
  60. chat.server.createRoom($("#Roomname").val());
  61. });
  62. });
  63. });
  64.  
  65. function SendMessage(btn) {
  66. var message = $(btn).prev().val();
  67. var room = $(btn).parent();
  68. var username = $("#username").html();
  69. message = username + ":" + message;
  70. var roomname = $(room).attr("roomname");
  71. chat.server.sendMessage(roomname, message);
  72. $(btn).prev().val('').focus();
  73. }
  74.  
  75. function RemoveRoom(btn) {
  76. var room = $(btn).parent();
  77. var roomname = $(room).attr("roomname");
  78. chat.server.removeUserFromRoom(roomname);
  79. }
  80.  
  81. function AddRoom(roomname) {
  82. var data =$(roomname).attr("roomname");
  83. chat.server.joinRoom(data);
  84. }
  85.  
  86. </script>
  87. </head>
  88. <body>
  89. <div>
  90. <div>名称:<p id="username"></p></div>
  91. 输入房间名:
  92. <input type="text" value="聊天室1" id="Roomname" />
  93. <button id="CreatRoom">创建聊天室</button>
  94. </div>
  95. <div style="float:left;border:double">
  96. <div>房间列表</div>
  97. <ul id="roomlist"></ul>
  98. </div>
  99. <div id="RoomList">
  100. </div>
  101. </body>
  102. </html>

  4. 经过上面3步,聊天室的功能就已经完成了,在看具体效果之前,这里附加一个帮助类的代码:

  1. /// <summary>
  2. /// JSON 帮助类
  3. /// </summary>
  4. public class JsonHelper
  5. {
  6. /// <summary>
  7. /// 从一个对象信息生成Json字符串
  8. /// </summary>
  9. /// <param name="obj"></param>
  10. /// <returns></returns>
  11. public static string ToJsonString(object obj)
  12. {
  13. return JsonConvert.SerializeObject(obj);
  14. }
  15.  
  16. /// <summary>
  17. /// 从Json字符串生成对象
  18. /// </summary>
  19. /// <typeparam name="T"></typeparam>
  20. /// <param name="jsonString"></param>
  21. /// <returns></returns>
  22. public static T ToObject<T>(string jsonString)
  23. {
  24. return JsonConvert.DeserializeObject<T>(jsonString);
  25. }
  26. }

四、运行效果

  接下来,就具体看看聊天室功能的运行效果,具体运行效果如下图所示:

五、总结

  到这里,本篇的所有内容都介绍完了,接下来我一篇文章将实现如何使用SignalR来实现发图片的功能。

  本文所有源码下载地址:SignalRChatRoom

[Asp.net 开发系列之SignalR篇]专题三:使用SignalR实现聊天室的功能的更多相关文章

  1. [Asp.net 开发系列之SignalR篇]专题五:SignalR支持的平台

    SignalR支持多种服务器和客户端配置.此外,每种传输方式都有自身的要求限制:如果某种传输方式不被系统支持,SignalR能够优雅地将故障转移到其他类型的传输方式.关于SignalR所支持的传输方式 ...

  2. [Asp.net 开发系列之SignalR篇]专题二:使用SignalR实现酷炫端对端聊天功能

    一.引言 在前一篇文章已经详细介绍了SignalR了,并且简单介绍它在Asp.net MVC 和WPF中的应用.在上篇博文介绍的都是群发消息的实现,然而,对于SignalR是为了实时聊天而生的,自然少 ...

  3. [Asp.net 开发系列之SignalR篇]专题四:使用SignalR实现发送图片

    一.引言 在前一篇博文已经介绍了如何使用SignalR来实现聊天室的功能,在这篇文章中,将实现如何使用SignalR来实现发送图片的功能. 二.实现发送图片的思路 我还是按照之前的方式来讲述这篇文章, ...

  4. 【Windows10 IoT开发系列】配置篇

    原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry ...

  5. openlayers5-webpack 入门开发系列一初探篇(附源码下载)

    前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...

  6. leaflet-webpack 入门开发系列一初探篇(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...

  7. SignalR学习笔记(一) 简单聊天室

    什么是ASP.NET SignalR? ASP.NET SignalR是一个方便程序员添加实时网络通信功能的类库.所谓的实时网络通信功能(Real-time Web Functionality)就是需 ...

  8. [Asp.net 开发系列之SignalR篇]专题六:使用SignalR实现消息提醒

    一.引言 前面一篇文章我介绍了如何使用SignalR实现图片的传输,然后对于即时通讯应用来说,消息提醒是必不可少的.现在很多网站的都有新消息的提醒功能.自然对于SignalR系列也少不了这个功能的实现 ...

  9. [Asp.net 开发系列之SignalR篇]专题一:Asp.net SignalR快速入门

    一.前言 之前半年时间感觉自己有点浮躁,导致停顿了半年多的时间没有更新博客,今天重新开始记录博文,希望自己可以找回初心,继续沉淀.由于最近做的项目中用到SignalR技术,所以打算总结下Asp.net ...

随机推荐

  1. Getting Started With Hazelcast 读书笔记(第五章,第六章)

    第五章 监听 本章应该是Hazelcast的核心机制了,Hazelcast通过注册各种监听器获悉集群中其他应用对数据的修改,成员的加入,退出等. 分为3个层次. 1.EntryListener(对数据 ...

  2. ASP.NET MVC+Entity Framework 访问数据库

    Entity Framework 4.1支持代码优先(code first)编程模式:即可以先创建模型类,然后通过配置在EF4.1下动态生成数据库. 下面演示两种情形: 1.代码优先模式下,asp.n ...

  3. Ceph剖析:消息处理

    作者:吴香伟 发表于 2014/10/9 版权声明:可以任意转载,转载时务必以超链接形式标明文章原始出处和作者信息以及版权声明 总体上,Ceph的消息处理框架是发布者订阅者的设计结构.Messenge ...

  4. MyTtcp 测试网络带宽

    网络编程学习 注意的指标MB/S 带宽每秒处理的信息 查询等 messages/s queries/s transaction/s延时cpu使用率 ttcp测试网络 读写读写 循环 测试网络带宽 正确 ...

  5. tmp

    Hello 大家好,这次给大家带来的是Gear VR4代,首先我得感谢下我们的虎友Hide兄弟友情提供Gear给我们测评,感谢 感谢.之前我录的前哨战也说过,这次Gear VR 4代较3代变化并不是很 ...

  6. 长轮询(long polling)

    HTTP请求不是持续的连接,你请求一次,服务器响应一次,然后就完了.长轮训是一种利用HTTP模拟持续连接的技巧.具体来说,只要页面载入了,不管你需不需要服务器给你响应信息,你都会给服务器发一个Ajax ...

  7. the fifth class

      1.实际比背景长,怎么做到的? 2个父级一个做头背景一个做尾背景 2.2层,每次自带背景上下是覆盖关系,如何做到 2层?,子浮动 3.标签 4.border可覆盖:margin-bottom 为负 ...

  8. JS、C#及SQL中的DateTime

    一:SQL中的DataTime 1.       between and 相当于>= and <= 2.       常用的将DataTime查询成字符串的方法 Select CONVER ...

  9. 上传App Store成功后,无法构建版本解决方法

    最近iOS10出来了,Xcode也跟着升级到了8,想着App做个更新,于是修改好了代码打算上传新包,无奈总是发现构建不了新版本.这种情况是因为苹果更重视用户的隐私,知道原因就能想到对策了,就是在pli ...

  10. FibonacciSequence

    import java.util.Scanner; public class Fibonacci { public static void main(String[] args) { int n; f ...