一、引言
在前一篇文章《Asp.net使用SignalR实现酷炫端对端聊天功能》中,我向大家介绍了如何实现实现端对端聊天的功能的,在这一篇文章中将像大家如何使用SignalR实现群聊这样的功能。
二、实现思路
要想实现群聊的功能,首先我们需要创建一个房间,然后每个在线用户可以加入这个房间里面进行群聊,我们可以为房间设置一个唯一的名字来作为标识。那SignalR类库里面是否有这样现有的方法呢?答案是肯定的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// IGroupManager接口提供如下方法 // 作用:将连接ID加入某个组 // Context.ConnectionId 连接ID,每个页面连接集线器即会产生唯一ID // roomName分组的名称 Groups.Add(Context.ConnectionId, roomName); // 作用:将连接ID从某个分组移除 Groups.Remove(Context.ConnectionId, roomName); // IHubConnectionContext接口提供了如下方法 // 调用客户端方法向房间内所有用户群发消息 // Room:分组名称 // new string[0]:过滤(不发送)的连接ID数组 Clients.Group(Room, new string [0]).clientMethod |
上面的代码也就是实现群聊的核心方法。Groups对象说白了也就是SignalR类库维护的一个列表对象而已,其实我们完全可以自己来维护一个Dictionary<string, List<string>>这个对象,创建一个房间的时候,我们将房间名称和进入房间的客户端的ConnectionId加入到这个字典里面,然后在聊天室里面点发送消息的时候,我们根据房间名查找到所有加入群聊的ConnectionId,然后调用Clients.Clients(IList<string> connectionIds)方法来将消息群发到每个客户端。以上也就是实现聊天室的原理。
三、使用SignalR实现聊天室的功能
理清楚了实现思路之后,接下来我们就看下具体的实现代码,同时大家也可以对照代码来对照前面的实现思路。
首先看下聊天室功能所涉及实体类的实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
/// <summary> /// 用户类 /// </summary> public class User { /// <summary> /// 用户Id /// </summary> public string UserId { get ; set ; } /// <summary> /// 用户的连接集合 /// </summary> public List<Connection> Connections { get ; set ; } /// <summary> /// 用户房间集合,一个用户可以加入多个房间 /// </summary> public List<ChatRoom> Rooms { get ; set ; } public User() { Connections = new List<Connection>(); Rooms = new List<ChatRoom>(); } } public class Connection { //连接ID public string ConnectionId { get ; set ; } //用户代理 public string UserAgent { get ; set ; } //是否连接 public bool Connected { get ; set ; } } /// <summary> /// 房间类 /// </summary> public class ChatRoom { // 房间名称 public string RoomName { get ; set ; } // 用户集合 public List<User> Users { get ; set ; } public ChatRoom() { Users = new List<User>(); } } /// <summary> /// 上下文类,用来模拟EF中的DbContext /// </summary> public class ChatContext { public List<User> Users { get ; set ; } public List<Connection> Connections { get ; set ; } public List<ChatRoom> Rooms { get ; set ; } public ChatContext() { Users = new List<User>(); Connections = new List<Connection>(); Rooms = new List<ChatRoom>(); } } |
2. 接下来,让我们来看到集线器的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
[HubName( "chatRoomHub" )] public class GroupsHub : Hub { public static ChatContext DbContext = new ChatContext(); #region IHub Members // 重写Hub连接事件 public override Task OnConnected() { // 查询用户 var user = DbContext.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId); if (user == null ) { user = new User { UserId = Context.ConnectionId }; DbContext.Users.Add(user); } // 发送房间列表 var items = DbContext.Rooms.Select(p => new {p.RoomName}); Clients.Client( this .Context.ConnectionId).getRoomList(JsonHelper.ToJsonString(items.ToList())); return base .OnConnected(); } // 重写Hub连接断开的事件 public override Task OnDisconnected( bool stopCalled) { // 查询用户 var user = DbContext.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId); if (user != null ) { // 删除用户 DbContext.Users.Remove(user); // 从房间中移除用户 foreach (var item in user.Rooms) { RemoveUserFromRoom(item.RoomName); } } return base .OnDisconnected(stopCalled); } #endregion #region Public Methods // 为所有用户更新房间列表 public void UpdateRoomList() { var itme = DbContext.Rooms.Select(p => new {p.RoomName}); var jsondata = JsonHelper.ToJsonString(itme.ToList()); Clients.All.getRoomlist(jsondata); } /// <summary> /// 加入聊天室 /// </summary> public void JoinRoom( string roomName) { // 查询聊天室 var room = DbContext.Rooms.Find(p => p.RoomName == roomName); // 存在则加入 if (room == null ) return ; // 查找房间中是否存在此用户 var isExistUser = room.Users.FirstOrDefault(u => u.UserId == Context.ConnectionId); // 不存在则加入 if (isExistUser == null ) { var user = DbContext.Users.Find(u => u.UserId == Context.ConnectionId); user.Rooms.Add(room); room.Users.Add(user); // 将客户端的连接ID加入到组里面 Groups.Add(Context.ConnectionId, roomName); //调用此连接用户的本地JS(显示房间) Clients.Client(Context.ConnectionId).joinRoom(roomName); } else { Clients.Client(Context.ConnectionId).showMessage( "请勿重复加入房间!" ); } } /// <summary> /// 创建聊天室 /// </summary> /// <param name="roomName"></param> public void CreateRoom( string roomName) { var room = DbContext.Rooms.Find(a => a.RoomName == roomName); if (room == null ) { var cr = new ChatRoom { RoomName = roomName }; //将房间加入列表 DbContext.Rooms.Add(cr); // 本人加入聊天室 JoinRoom(roomName); UpdateRoomList(); } else { Clients.Client(Context.ConnectionId).showMessage( "房间名重复!" ); } } public void RemoveUserFromRoom( string roomName) { //查找房间是否存在 var room = DbContext.Rooms.Find(a => a.RoomName == roomName); //存在则进入删除 if (room == null ) { Clients.Client(Context.ConnectionId).showMessage( "房间名不存在!" ); return ; } // 查找要删除的用户 var user = room.Users.FirstOrDefault(a => a.UserId == Context.ConnectionId); // 移除此用户 room.Users.Remove(user); //如果房间人数为0,则删除房间 if (room.Users.Count <= 0) { DbContext.Rooms.Remove(room); } Groups.Remove(Context.ConnectionId, roomName); //提示客户端 Clients.Client(Context.ConnectionId).removeRoom( "退出成功!" ); } /// <summary> /// 给房间内所有的用户发送消息 /// </summary> /// <param name="room">房间名</param> /// <param name="message">信息</param> public void SendMessage( string room, string message) { // 调用房间内所有客户端的sendMessage方法 // 因为在加入房间的时候,已经将客户端的ConnectionId添加到Groups对象中了,所有可以根据房间名找到房间内的所有连接Id // 其实我们也可以自己实现Group方法,我们只需要用List记录所有加入房间的ConnectionId // 然后调用Clients.Clients(connectionIdList),参数为我们记录的连接Id数组。 Clients.Group(room, new string [0]).sendMessage(room, message + " " + DateTime.Now); } #endregion } |
3. 上面SignalR服务端的代码实现已经完成,接下来就让我们一起看看客户端视图的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
@{ Layout = null; } <!DOCTYPE html> < html > < head > < meta name = "viewport" content = "width=device-width" /> < title >Index</ title > < script src = "~/Scripts/jquery-2.2.2.min.js" ></ script > < script src = "~/Scripts/jquery.signalR-2.2.0.min.js" ></ script > < script src = "~/Scripts/layer/layer.min.js" ></ script > <!--这里要注意,这是虚拟目录,也就是你在OWIN Startup中注册的地址--> < script src = "/signalr/hubs" ></ script > < script type = "text/javascript" > var chat; var roomcount = 0; $(function() { chat = $.connection.chatRoomHub; chat.client.showMessage = function(message) { alert(message); }; chat.client.sendMessage = function(roomname, message) { $("#" + roomname).find("ul").each(function() { $(this).append('< li >' + message + '</ li >'); }); }; chat.client.removeRoom = function(data) { alert(data); }; chat.client.joinRoom = function (roomname) { var html = '< div style = "float:left; margin-left:360px; border:double; height:528px;width:493px" id = "' + roomname + '" roomname = "' + roomname + '" >< button onclick = "RemoveRoom(this)" >退出</ button >\ ' + roomname + '房间\ 聊天记录如下:< ul >\ </ ul >\ < textarea class = "ChatCore_write" id = "ChatCore_write" style = "width:400px" ></ textarea > < button onclick = "SendMessage(this)" >发送</ button >\ </ div >'; $("#RoomList").append(html); }; //注册查询房间列表的方法 chat.client.getRoomlist = function(data) { if (data) { var jsondata = $.parseJSON(data); $("#roomlist").html(" "); for (var i = 0; i < jsondata.length ; i++) { var html = ' <li>房间名:' + jsondata[i].RoomName + '< button roomname = "' + jsondata[i].RoomName + '" onclick = "AddRoom(this)" >加入</ button ></ li >'; $("#roomlist").append(html); } } }; // 获取用户名称。 $('#username').html(prompt('请输入您的名称:', '')); $.connection.hub.start().done(function() { $('#CreatRoom').click(function() { chat.server.createRoom($("#Roomname").val()); }); }); }); function SendMessage(btn) { var message = $(btn).prev().val(); var room = $(btn).parent(); var username = $("#username").html(); message = username + ":" + message; var roomname = $(room).attr("roomname"); chat.server.sendMessage(roomname, message); $(btn).prev().val('').focus(); } function RemoveRoom(btn) { var room = $(btn).parent(); var roomname = $(room).attr("roomname"); chat.server.removeUserFromRoom(roomname); } function AddRoom(roomname) { var data =$(roomname).attr("roomname"); chat.server.joinRoom(data); } </ script > </ head > < body > < div > < div >名称:< p id = "username" ></ p ></ div > 输入房间名: < input type = "text" value = "聊天室1" id = "Roomname" /> < button id = "CreatRoom" >创建聊天室</ button > </ div > < div style = "float:left;border:double" > < div >房间列表</ div > < ul id = "roomlist" ></ ul > </ div > < div id = "RoomList" > </ div > </ body > </ html > |
4. 经过上面3步,聊天室的功能就已经完成了,在看具体效果之前,这里附加一个帮助类的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/// <summary> /// JSON 帮助类 /// </summary> public class JsonHelper { /// <summary> /// 从一个对象信息生成Json字符串 /// </summary> /// <param name="obj"></param> /// <returns></returns> public static string ToJsonString( object obj) { return JsonConvert.SerializeObject(obj); } /// <summary> /// 从Json字符串生成对象 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="jsonString"></param> /// <returns></returns> public static T ToObject<T>( string jsonString) { return JsonConvert.DeserializeObject<T>(jsonString); } } |
四、运行结果
接下来,就具体看看聊天室功能的运行效果,具体运行效果如下图所示:
源码下载:SignalRChatRoom
到这里,本篇的所有内容都介绍完了,接下来我一篇文章将实现如何使用SignalR来实现发图片的功能。