-
Notifications
You must be signed in to change notification settings - Fork 58
/
Copy pathSocketServer.cs
328 lines (284 loc) · 13.2 KB
/
SocketServer.cs
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
using DevelopmentInProgress.Socket.Messages;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DevelopmentInProgress.Socket.Server
{
/// <summary>
/// The abstract <see cref="SocketServer"/> base class.
/// </summary>
public abstract class SocketServer
{
private readonly ConnectionManager connectionManager;
private readonly ChannelManager channelManager;
/// <summary>
/// Creates a new instance of the <see cref="SocketServer"/> base class.
/// </summary>
protected SocketServer()
{
connectionManager = new ConnectionManager();
channelManager = new ChannelManager();
}
/// <summary>
/// Creates a new instance of the <see cref="SocketServer"/>.
/// Use when injecting singleton <see cref="ConnectionManager"/>
/// and <see cref="ChannelManager"/> is preferred.
/// </summary>
/// <param name="connectionManager">An instance of the <see cref="ConnectionManager"/>.</param>
/// <param name="channelManager">An instance of the <see cref="ChannelManager"/>.</param>
protected SocketServer(ConnectionManager connectionManager, ChannelManager channelManager)
{
this.connectionManager = connectionManager;
this.channelManager = channelManager;
}
/// <summary>
/// Receive a message from a <see cref="WebSocket"/>.
/// </summary>
/// <param name="webSocket">The <see cref="WebSocket"/>.</param>
/// <param name="message">The <see cref="Message"/>.</param>
/// <returns>A <see="Task"/>.</returns>
public abstract Task ReceiveAsync(WebSocket webSocket, Message message);
/// <summary>
/// Handle a <see cref="WebSocket"/> client connection.
/// </summary>
/// <param name="websocket">The <see cref="WebSocket"/>.</param>
/// <param name="clientId">The identifier of the <see cref="WebSocket"/>.</param>
/// <param name="data">The data accompanying by the <see cref="WebSocket"/>.</param>
/// <returns>A <see="Task"/>.</returns>
public abstract Task OnClientConnectAsync(WebSocket websocket, string clientId, string data);
/// <summary>
/// Removes the <see cref="WebSocket"/> <see cref="ConnectionManager"/>'s web sockets dictionary,
/// calls the web sockets CloseAsync method and then disposes it.
/// </summary>
/// <param name="webSocket">The <see cref="WebSocket"/> to remove.</param>
/// <returns>The <see cref="Connection"/> for the <see cref="WebSocket"/>.</returns>
public virtual async Task<Connection> OnClientDisonnectAsync(WebSocket webSocket)
{
if (webSocket == null)
{
throw new ArgumentNullException(nameof(webSocket));
}
if (connectionManager.TryRemoveWebSocketConnection(webSocket, out Connection connection))
{
connection.Channels.All(c => c.Value.Connections.TryRemove(connection.ConnectionId, out Connection removedConnection));
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure,
$"WebSocket {connection.ConnectionId} closed by {typeof(SocketServer).Name}",
CancellationToken.None).ConfigureAwait(false);
webSocket.Dispose();
return connection;
}
return null;
}
/// <summary>
/// Close and dispose all websocket connections.
/// </summary>
/// <returns>Returns a list of tasks. Each task will close and dispose a websocket.</returns>
public virtual Task[] CloseAndDisposeWebSockets()
{
var connections = connectionManager.GetConnections();
var webSockets = connections.Select(c => c.WebSocket).ToList();
static async Task CloseAndDispose(WebSocket ws)
{
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed", new CancellationToken()).ConfigureAwait(false);
ws.Dispose();
};
return webSockets.Select(CloseAndDispose).ToArray();
}
/// <summary>
/// Adds the <see cref="WebSocket"/> to the <see cref="ConnectionManager"/>'s web sockets dictionary.
/// </summary>
/// <param name="websocket">The <see cref="WebSocket"/> to add.</param>
/// <returns>The <see cref="Connection"/> for the <see cref="WebSocket"/>.</returns>
public virtual Task<Connection> AddWebSocketAsync(WebSocket websocket)
{
if (connectionManager.TryAddWebSocketConnection(websocket, out Connection connection))
{
return Task.FromResult<Connection>(connection);
}
return null;
}
/// <summary>
/// Send a message to a <see cref="WebSocket"/> client.
/// </summary>
/// <param name="connectionId">The connection Id of the <see cref="WebSocket"/> client.</param>
/// <param name="message">The message to send.</param>
/// <returns>A <see cref="Task"/>.</returns>
public async Task SendMessageAsync(string connectionId, Message message)
{
var connection = connectionManager.GetConnection(connectionId);
if (connection != null)
{
await SendMessageAsync(connection.WebSocket, message).ConfigureAwait(false);
}
}
/// <summary>
/// Send messages to a list of connections where the message for each connection is returned by a delegate.
/// </summary>
/// <param name="connections">The connections to send messages to.</param>
/// <param name="getMessage">The delegate to get the message for each connection.</param>
/// <returns>A <see cref="Task"/>.</returns>
public static async Task SendMessageAsync(List<Connection> connections, Func<Connection, Message> getMessage)
{
var webSockets = (from connection in connections select SendMessageAsync(connection.WebSocket, getMessage(connection))).ToList();
await Task.WhenAll(webSockets.ToArray()).ConfigureAwait(false);
}
/// <summary>
/// Send a message to all <see cref="WebSocket"/> clients.
/// </summary>
/// <param name="message">The message to send.</param>
/// <returns>A <see cref="Task"/>.</returns>
public async Task SendMessageToAllAsync(Message message)
{
var json = JsonConvert.SerializeObject(message);
var connections = connectionManager.GetConnections();
var webSockets = from connection in connections select SendMessageAsync(connection.WebSocket, json);
await Task.WhenAll(webSockets.ToArray()).ConfigureAwait(false);
}
/// <summary>
/// Send a message to all <see cref="WebSocket"/> clients.
/// </summary>
/// <param name="channelName">The channel name.</param>
/// <param name="message">The message to send.</param>
/// <returns>A <see cref="Task"/>.</returns>
public async Task SendMessageToChannelAsync(string channelName, Message message)
{
var channel = channelManager.GetChannel(channelName);
if(channel == null)
{
return;
}
await SendMessageToChannelAsync(channel, message).ConfigureAwait(false);
}
/// <summary>
/// Send a message to all <see cref="WebSocket"/> clients.
/// </summary>
/// <param name="channelName">The channel name.</param>
/// <param name="message">The message to send.</param>
/// <returns>A <see cref="Task"/>.</returns>
public static async Task SendMessageToChannelAsync(Channel channel, Message message)
{
if (channel == null)
{
return;
}
var json = JsonConvert.SerializeObject(message);
var webSockets = from connection in channel.Connections.Values.ToArray() select SendMessageAsync(connection.WebSocket, json);
await Task.WhenAll(webSockets.ToArray()).ConfigureAwait(false);
}
/// <summary>
/// Send a message to a <see cref="WebSocket"/> client.
/// </summary>
/// <param name="webSocket">The <see cref="WebSocket"/> to send the message to.</param>
/// <param name="message">The message to send.</param>
/// <returns>A <see cref="Task"/>.</returns>
public static async Task SendMessageAsync(WebSocket webSocket, Message message)
{
var json = JsonConvert.SerializeObject(message);
await SendMessageAsync(webSocket, json).ConfigureAwait(false);
}
/// <summary>
/// Send a message to a <see cref="WebSocket"/> client.
/// </summary>
/// <param name="webSocket">The <see cref="WebSocket"/> to send the message to.</param>
/// <param name="message">The message to send.</param>
/// <returns>A <see cref="Task"/>.</returns>
public static async Task SendMessageAsync(WebSocket webSocket, string message)
{
if(webSocket == null)
{
throw new ArgumentNullException(nameof(webSocket));
}
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
if (!webSocket.State.Equals(WebSocketState.Open))
{
return;
}
await webSocket.SendAsync(
new ArraySegment<byte>(Encoding.UTF8.GetBytes(message), 0, message.Length),
WebSocketMessageType.Text, true, CancellationToken.None)
.ConfigureAwait(false);
}
/// <summary>
/// Subscribe to a <see cref="Channel"/>. If the <see cref="Channel"/>
/// doesn't exist then create one.
/// </summary>
/// <param name="channelName">The channel to subscribe to.</param>
/// <param name="webSocket">The connection subscribing to the channel.</param>
/// <returns>The <see cref="Channel"/>.</returns>
public Channel SubscribeToChannel(string channelName, WebSocket webSocket)
{
var connection = connectionManager.GetConnection(webSocket);
return channelManager.SubscribeToChannel(channelName, connection);
}
/// <summary>
/// Unsubscribe from a <see cref="Channel"/>. If the
/// <see cref="Channel"/> no longer has any <see cref="Connection"/>'s
/// then remove the <see cref="Channel"/>.
/// </summary>
/// <param name="channelName">The channel to unsubscribe from.</param>
/// <param name="webSocket">The connection unsubscribing to the channel.</param>
/// <returns>The <see cref="Channel"/>.</returns>
public Channel UnsubscribeFromChannel(string channelName, WebSocket webSocket)
{
var connection = connectionManager.GetConnection(webSocket);
return channelManager.UnsubscribeFromChannel(channelName, connection);
}
/// <summary>
/// Get a <see cref="Channel"/>.
/// </summary>
/// <param name="channelName">The <see cref="Channel"/> to get.</param>
/// <returns>The <see cref="Channel"/>.</returns>
public Channel GetChannel(string channelName)
{
return channelManager.GetChannel(channelName);
}
/// <summary>
/// Remove a <see cref="Channel"/>.
/// </summary>
/// <param name="channelName">The <see cref="Channel"/> to remove.</param>
/// <param name="channel">The removed channel.</param>
/// <returns>True if successful, else false.</returns>
public bool TryRemoveChannel(string channelName, out Channel channel)
{
return channelManager.TryRemoveChannel(channelName, out channel);
}
/// <summary>
/// Get information about active <see cref="SocketServer"/> connections and channels.
/// </summary>
/// <returns></returns>
public ServerInfo GetServerInfo()
{
var serverInfo = new ServerInfo();
serverInfo.Channels.AddRange(GetChannelInfos());
serverInfo.Connections.AddRange(GetConnectionInfos());
return serverInfo;
}
/// <summary>
/// Gets a <see cref="Connection"/>.
/// </summary>
/// <param name="connectionId">The connection id.</param>
/// <returns>The <see cref="Connection"/>.</returns>
public Connection GetConnection(string connectionId)
{
return connectionManager.GetConnection(connectionId);
}
private List<ChannelInfo> GetChannelInfos()
{
var channelInfos = channelManager.GetChannelInfos();
return channelInfos;
}
private List<ConnectionInfo> GetConnectionInfos()
{
var connectionInfos = connectionManager.GetConnectionInfos();
return connectionInfos;
}
}
}