├── .gitignore ├── SocketServer.Tests ├── ConcurrentLL.Tests.cs ├── EchoSocketServerSession.cs ├── Properties │ └── AssemblyInfo.cs ├── SocketServer.Tests.cs ├── TaskScheduler.Tests.cs ├── WebSocketServer.Tests.csproj └── packages.config ├── SocketServer ├── Ext.cs ├── IWebSocketSession.cs ├── IWebSocketSessionManager.cs ├── OwinExt.cs ├── Properties │ └── AssemblyInfo.cs ├── SocketCloseStatus.cs ├── SocketMessageType.cs ├── SocketServerOwinMW.cs ├── WebSocketServer.csproj ├── WebSocketSessionBase.cs ├── WebSocketSessionManager.cs └── packages.config ├── TestApp ├── ApplicationManifest.xml ├── PublishProfiles │ └── Local.xml ├── Scripts │ ├── Create-FabricApplication.ps1 │ ├── Deploy-FabricApplication.ps1 │ ├── Get-FabricApplicationStatus.ps1 │ └── Utilities.psm1 └── TestApp.sfproj ├── TestClient ├── App.config ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TestClient.csproj └── packages.config ├── TestStatefulSvc ├── App.config ├── PackageRoot │ ├── Config │ │ └── Settings.xml │ └── ServiceManifest.xml ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── ServiceEventSource.cs ├── SocketSessionTypes.cs ├── TestStatefulSvc.cs ├── TestStatefulSvc.csproj └── packages.config ├── Utils ├── ILeveledTask.cs ├── LeveledMultiQTaskScheduler - Copy.cs ├── LeveledTask-T.cs ├── LeveledTask.cs ├── Properties │ └── AssemblyInfo.cs ├── SequentialLeveledTaskScheduler.cs └── WebSocketServer.Utils.csproj ├── WebSocketServer.ServiceFabric.Clients ├── Properties │ └── AssemblyInfo.cs ├── ServiceFabricWebSocketClient.cs ├── ServiceFabricWebSocketClientFactory.cs ├── WebSocketServer.ServiceFabric.Clients.csproj └── packages.config ├── WebSocketServer.ServiceFabric.Services ├── MultiTypeWebSocketManager.cs ├── Properties │ └── AssemblyInfo.cs ├── ServiceFabricSocketSessionBase.cs ├── WebSocketCommunicationListener.cs ├── WebSocketServer.ServiceFabric.Services.csproj └── packages.config ├── WebSocketServer.sln └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | -------------------------------------------------------------------------------- /SocketServer.Tests/EchoSocketServerSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using WebSocketServer; 8 | using Microsoft.Owin; 9 | using System.Threading; 10 | using System.Diagnostics; 11 | 12 | namespace WebSocketServer.Tests 13 | { 14 | 15 | 16 | 17 | 18 | // custom factory and session implementation 19 | // they do nothing other than exposing a delegate which can be set by test 20 | // classes to get whatever was recieved by the web socket on the server side. 21 | 22 | 23 | // in your implementation you don't have to implement a custom factory 24 | // but you will have to implement a custom session (base class is abstract); 25 | class TestWebSocketSession : WebSocketSessionBase 26 | { 27 | public TestWebSocketSession(IOwinContext context, 28 | TestWebSocketSessionFactory factory, 29 | CancellationToken cancelToken) : base(context, factory, cancelToken) 30 | { 31 | //no op just init base 32 | } 33 | 34 | 35 | 36 | // we home to the factory and call a call back on it. 37 | // tests will override the call back for testing purposes. 38 | public override Task OnReceiveAsync(ArraySegment buffer, Tuple received) 39 | { 40 | var factory = m_factory as TestWebSocketSessionFactory; 41 | return factory.OnReceiveAsyncCallBack(this, buffer, received); 42 | } 43 | } 44 | 45 | class TestWebSocketSessionFactory : WebSocketSessionManager 46 | { 47 | 48 | 49 | 50 | // tests should set this delegate to get whatever ever recieved by the socket. 51 | public Func, Tuple, Task> OnReceiveAsyncCallBack = 52 | (session, buffer, tuple) => 53 | { 54 | 55 | Trace.WriteLine("Default recieved called"); 56 | return Task.FromResult(0); 57 | }; 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SocketServer.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SocketServer.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SocketServer.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6fbeffc2-dbc2-414b-80c2-8da72b4eb604")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SocketServer.Tests/SocketServer.Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Microsoft.Owin.Hosting; 6 | using System.Diagnostics; 7 | 8 | using WebSocketServer; 9 | using System.Threading; 10 | 11 | using System.Net.WebSockets; 12 | using System.Threading.Tasks; 13 | using WebSocketServer.Utils; 14 | using System.Collections.Concurrent; 15 | 16 | namespace WebSocketServer.Tests 17 | { 18 | [TestClass] 19 | public class SocketServerTests 20 | { 21 | 22 | #region context management 23 | 24 | private TestContext m_testContext; 25 | 26 | public TestContext TestContext 27 | { 28 | get 29 | { 30 | return m_testContext; 31 | } 32 | set 33 | { 34 | m_testContext = value; 35 | } 36 | } 37 | #endregion 38 | 39 | 40 | private static IDisposable webServer = null; 41 | private readonly static string _serverAddress = "http://localhost:9001/websocket"; 42 | private readonly static string _serverAddressPublish = "ws://localhost:9001/websocket"; 43 | 44 | 45 | private static TestWebSocketSessionFactory _echoFactory; 46 | 47 | private static IDisposable startServer() 48 | { 49 | Trace.WriteLine(string.Format("Web server is starting on {0}", _serverAddress)); 50 | 51 | _echoFactory = new TestWebSocketSessionFactory(); 52 | 53 | IDisposable server = WebApp.Start(_serverAddress, app => 54 | { 55 | app.MapWebSocket, TestWebSocketSession>(_echoFactory); 56 | } 57 | ); 58 | 59 | return server; 60 | } 61 | 62 | 63 | 64 | 65 | 66 | [ClassInitialize()] 67 | public static void init(TestContext testContext) 68 | { 69 | webServer = startServer(); 70 | 71 | 72 | 73 | } 74 | 75 | 76 | [ClassCleanup()] 77 | public static void end() 78 | { 79 | 80 | if (null != webServer) 81 | webServer.Dispose(); 82 | 83 | } 84 | 85 | 86 | 87 | 88 | [TestMethod] 89 | [TestCategory("SocketServer.Basic")] 90 | public void BasicListen() 91 | { 92 | Thread.Sleep(100); 93 | } 94 | 95 | [TestMethod] 96 | [TestCategory("SocketServer.Basic")] 97 | public async Task MessageVerification() 98 | { 99 | var snd = "Hello, World!"; 100 | var rcv = string.Empty; 101 | _echoFactory.OnReceiveAsyncCallBack = (session, buffer, tuple) => 102 | { 103 | var messageLength = tuple.Item3; 104 | var actual = buffer.Actualize(messageLength); 105 | 106 | 107 | rcv = Encoding.UTF8.GetString(actual); 108 | Assert.AreEqual(snd, rcv); 109 | Trace.WriteLine(string.Format("Message:{0} received on session Id{1}", rcv, session.SessionId), "info"); 110 | return Task.FromResult(0); 111 | }; 112 | 113 | 114 | ClientWebSocket clientWSocket = new ClientWebSocket(); 115 | await clientWSocket.ConnectAsync(new Uri(_serverAddressPublish), CancellationToken.None); 116 | await clientWSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(snd)), 117 | WebSocketMessageType.Text, 118 | true, 119 | CancellationToken.None); 120 | 121 | 122 | 123 | await Task.Delay(100); // = console.read(); 124 | } 125 | 126 | 127 | 128 | 129 | [TestMethod] 130 | [TestCategory("SocketServer.Basic")] 131 | public async Task Echo() 132 | { 133 | var snd = "Hello, World!"; 134 | var rcv = string.Empty; 135 | 136 | _echoFactory.OnReceiveAsyncCallBack = (session, buffer, tuple) => 137 | { 138 | var messageLength = tuple.Item3; 139 | var actual = buffer.Actualize(messageLength); 140 | 141 | 142 | rcv = Encoding.UTF8.GetString(actual); 143 | Assert.AreEqual(snd, rcv); 144 | 145 | // send it back 146 | session.Send(rcv); 147 | return Task.FromResult(0); 148 | }; 149 | 150 | 151 | ClientWebSocket clientWSocket = new ClientWebSocket(); 152 | await clientWSocket.ConnectAsync(new Uri(_serverAddressPublish), CancellationToken.None); 153 | 154 | // send 155 | await clientWSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(snd)), 156 | WebSocketMessageType.Text, 157 | true, 158 | CancellationToken.None); 159 | 160 | 161 | 162 | await Task.Delay(100); 163 | var socketRecieved = await ReceiveFromSocket(clientWSocket); 164 | 165 | Assert.AreEqual(snd, socketRecieved); 166 | await clientWSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "close",CancellationToken.None); 167 | } 168 | 169 | 170 | [TestMethod] 171 | [TestCategory("SocketServer.Advanced")] 172 | public async Task BroadcastViaSend() 173 | { 174 | 175 | 176 | var numOfSessions = 100; 177 | var snd = "Hello, World!"; 178 | var receiveTask = new List>(); 179 | 180 | _echoFactory.OnReceiveAsyncCallBack = (session, buffer, tuple) => 181 | { 182 | var messageLength = tuple.Item3; 183 | var actual = buffer.Actualize(messageLength); 184 | 185 | var rcv = Encoding.UTF8.GetString(actual); 186 | Assert.AreEqual(snd, rcv); 187 | 188 | // send it back 189 | session.Send(rcv); 190 | return Task.FromResult(0); 191 | }; 192 | 193 | 194 | var sessions = new ClientWebSocket[numOfSessions]; 195 | for (var i = 0; i < numOfSessions; i++) 196 | { 197 | sessions[i] = new ClientWebSocket(); 198 | await sessions[i].ConnectAsync(new Uri(_serverAddressPublish), CancellationToken.None); 199 | } 200 | 201 | await _echoFactory.PostToAll(snd); 202 | 203 | for (var i = 0; i < numOfSessions; i++) 204 | receiveTask.Add(ReceiveFromSocket(sessions[i])); 205 | 206 | 207 | 208 | await Task.WhenAll(receiveTask); 209 | 210 | foreach (var task in receiveTask) 211 | Assert.AreEqual(snd, task.Result); 212 | 213 | 214 | for (var i = 0; i < numOfSessions; i++) 215 | await sessions[i].CloseAsync(WebSocketCloseStatus.NormalClosure, "close..", CancellationToken.None); 216 | } 217 | 218 | 219 | 220 | 221 | [TestMethod] 222 | [TestCategory("SocketServer.Advanced")] 223 | public async Task BroadcastViaPost() 224 | { 225 | 226 | 227 | var numOfSessions = 100; 228 | var snd = "Hello, World!"; 229 | var receiveTask = new List>(); 230 | 231 | _echoFactory.OnReceiveAsyncCallBack = (session, buffer, tuple) => 232 | { 233 | var messageLength = tuple.Item3; 234 | var actual = buffer.Actualize(messageLength); 235 | 236 | var rcv = Encoding.UTF8.GetString(actual); 237 | Assert.AreEqual(snd, rcv); 238 | 239 | // send it back 240 | session.Post(rcv); 241 | return Task.FromResult(0); 242 | }; 243 | 244 | 245 | var sessions = new ClientWebSocket[numOfSessions]; 246 | for (var i = 0; i < numOfSessions; i++) 247 | { 248 | sessions[i] = new ClientWebSocket(); 249 | await sessions[i].ConnectAsync(new Uri(_serverAddressPublish), CancellationToken.None); 250 | } 251 | 252 | await _echoFactory.PostToAll(snd); 253 | 254 | for (var i = 0; i < numOfSessions; i++) 255 | receiveTask.Add(ReceiveFromSocket(sessions[i])); 256 | 257 | 258 | 259 | await Task.WhenAll(receiveTask); 260 | 261 | foreach (var task in receiveTask) 262 | Assert.AreEqual(snd, task.Result); 263 | 264 | 265 | for (var i = 0; i < numOfSessions; i++) 266 | await sessions[i].CloseAsync(WebSocketCloseStatus.NormalClosure, "close..", CancellationToken.None); 267 | } 268 | 269 | 270 | 271 | 272 | 273 | [TestMethod] 274 | [TestCategory("SocketServer.Advanced")] 275 | public async Task HighFrequencyMessaging_LONGRUNNING() 276 | { 277 | 278 | 279 | var numOfSessions = 100; 280 | var numOfMessages = 10000; 281 | 282 | var receiveTask = new List>(); 283 | var sendTasks = new List(); 284 | 285 | var clients = new ConcurrentDictionary(); 286 | _echoFactory.OnReceiveAsyncCallBack = (session, buffer, tuple) => 287 | { 288 | var messageLength = tuple.Item3; 289 | var actual = buffer.Actualize(messageLength); 290 | 291 | var rcv = Encoding.UTF8.GetString(actual); 292 | 293 | // send it back 294 | session.Post(rcv); 295 | return Task.FromResult(0); 296 | }; 297 | 298 | Trace.AutoFlush = true; 299 | 300 | 301 | int j = 0; 302 | do 303 | { 304 | 305 | var t = Task.Run(async () => 306 | { 307 | var client = new ClientWebSocket(); 308 | await client.ConnectAsync(new Uri(_serverAddressPublish), CancellationToken.None); 309 | int k = 0; 310 | while (k < numOfMessages / numOfSessions) 311 | { 312 | Trace.WriteLine(string.Format("Message on client {0}", j)); 313 | await client.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(j.ToString())), 314 | WebSocketMessageType.Text, 315 | true, 316 | CancellationToken.None); 317 | k++; 318 | } 319 | // this exception should never be thrown 320 | clients.AddOrUpdate(Guid.NewGuid().ToString(), client, (key, c) => { throw new Exception("duplicate client!"); }); 321 | }); 322 | 323 | sendTasks.Add(t); 324 | j++; 325 | } while (j < numOfSessions - 1); 326 | 327 | await Task.WhenAll(sendTasks); 328 | 329 | foreach (var client in clients.Values) 330 | receiveTask.Add(ReceiveFromSocket(client)); 331 | 332 | 333 | 334 | 335 | await Task.WhenAll(receiveTask); 336 | 337 | 338 | 339 | /* 340 | uncomment this if you plan to drain the socket befoire closing., 341 | foreach (var client in clients.Values) // be kind rewind 342 | await client.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "close..", CancellationToken.None); 343 | 344 | */ 345 | } 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | private async Task ReceiveFromSocket(ClientWebSocket theSocket) 355 | { 356 | 357 | // receive 358 | 359 | var arraySegment = new ArraySegment(new Byte[1024 * 256]); 360 | var res = await theSocket.ReceiveAsync(arraySegment, CancellationToken.None); 361 | 362 | return Encoding.UTF8.GetString(arraySegment.Actualize(res.Count)); 363 | } 364 | 365 | 366 | 367 | [TestMethod] 368 | [TestCategory("SocketServer.Common")] 369 | public async Task ServerSideCloseCounter() 370 | { 371 | var numOfSessions = 100; 372 | 373 | var sessions = new ClientWebSocket[numOfSessions]; 374 | for (var i = 0; i < numOfSessions; i++) 375 | { 376 | sessions[i] = new ClientWebSocket(); 377 | await sessions[i].ConnectAsync(new Uri(_serverAddressPublish), CancellationToken.None); 378 | } 379 | 380 | Assert.AreEqual(numOfSessions, _echoFactory.Count); 381 | await _echoFactory.CloseAll(); 382 | Assert.AreEqual(0, _echoFactory.Count); // we closed everything, we should be clean 383 | } 384 | 385 | 386 | 387 | [TestMethod] 388 | [TestCategory("SocketServer.Common")] 389 | public async Task ClientSideCloseCounter() 390 | { 391 | var numOfSessions = 100; 392 | 393 | var sessions = new ClientWebSocket[numOfSessions]; 394 | for (var i = 0; i < numOfSessions; i++) 395 | { 396 | sessions[i] = new ClientWebSocket(); 397 | await sessions[i].ConnectAsync(new Uri(_serverAddressPublish), CancellationToken.None); 398 | } 399 | 400 | Assert.AreEqual(numOfSessions, _echoFactory.Count); 401 | for (var i = 0; i < numOfSessions; i++) 402 | await sessions[i].CloseAsync(WebSocketCloseStatus.NormalClosure, 403 | "closing..", 404 | CancellationToken.None); 405 | 406 | Assert.AreEqual(0, _echoFactory.Count); // we closed everything, we should be clean 407 | } 408 | 409 | 410 | 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /SocketServer.Tests/WebSocketServer.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604} 7 | Library 8 | Properties 9 | WebSocketServer.Tests 10 | WebSocketServer.Tests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | AnyCPU 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 41 | True 42 | 43 | 44 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 45 | True 46 | 47 | 48 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 49 | True 50 | 51 | 52 | ..\packages\Owin.1.0\lib\net40\Owin.dll 53 | True 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | False 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {6d3b3575-d199-4c47-b01e-121b023b7342} 81 | WebSocketServer 82 | 83 | 84 | {aa3697d5-4421-4540-ad17-261975a9908e} 85 | WebSocketServer.Utils 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | False 96 | 97 | 98 | False 99 | 100 | 101 | False 102 | 103 | 104 | False 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | -------------------------------------------------------------------------------- /SocketServer.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SocketServer/Ext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebSocketServer 8 | { 9 | public static class Ext 10 | { 11 | public static byte[] Actualize(this ArraySegment buffer, int ActualLength) 12 | { 13 | 14 | var actual = new Byte[ActualLength]; 15 | 16 | for (var i = 0; i < ActualLength; i++) 17 | actual[i] = buffer.Array[i]; 18 | 19 | return actual; 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SocketServer/IWebSocketSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebSocketServer 8 | { 9 | public interface IWebSocketSession 10 | { 11 | uint MaxMessageSize 12 | { 13 | get; 14 | set; 15 | } 16 | string SessionId 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | Task SocketLoop(IDictionary websocketContext); 23 | 24 | 25 | Task Close(); 26 | Task DrainThenClose(); 27 | 28 | 29 | void Abort(); 30 | Task OnReceiveAsync(ArraySegment buffer,Tuple received); 31 | 32 | 33 | Task Send(ArraySegment data, 34 | SocketMessageType messageType, 35 | bool EndOfMessage); 36 | Task Send(string sMessage); 37 | 38 | Task Send(byte[] data, SocketMessageType messageType); 39 | Task Send(T O); 40 | Task Post(ArraySegment data, 41 | SocketMessageType messageType, 42 | bool EndOfMessage); 43 | Task Post(string sMessage); 44 | Task Post(byte[] data, SocketMessageType messageType); 45 | 46 | Task Post(T O); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SocketServer/IWebSocketSessionManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Owin; 2 | using WebSocketServer.Utils; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace WebSocketServer 11 | { 12 | public interface IWebSocketSessionManager where T : IWebSocketSession 13 | { 14 | SequentialLeveledTaskScheduler Scheduler { get; } 15 | int Count { get; } 16 | Task AcceptSocket(IOwinContext context); 17 | void AbortAll(); 18 | Task PostToAll(byte[] buffer, SocketMessageType messageType); 19 | Task PostToAll(string message); 20 | void SockedClosed(string SessionId); 21 | 22 | Task CloseAll(); 23 | Task CloseAll(CancellationToken cancellationToken); 24 | IEnumerable GetSession(Func Predicate); 25 | 26 | Task Close(string SessionId); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SocketServer/OwinExt.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Owin; 2 | using Owin; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace WebSocketServer 10 | { 11 | public static class OwinExt 12 | { 13 | #region OWIN USE Helpers 14 | public static void MapWebSocketRoute(this IAppBuilder app, 15 | string route, 16 | F factory) 17 | where F : WebSocketSessionManager 18 | where S : WebSocketSessionBase 19 | 20 | { 21 | app.Map(route, builder => builder.Use>(factory)); 22 | } 23 | 24 | public static void MapWebSocket(this IAppBuilder app, 25 | F factory) 26 | where F : WebSocketSessionManager 27 | where S : WebSocketSessionBase 28 | { 29 | app.Use>(factory); 30 | } 31 | 32 | 33 | 34 | public static void ExeclusiveMapWebSocketRoute(this IAppBuilder app, 35 | string route, 36 | F factory) 37 | where F : WebSocketSessionManager 38 | where S : WebSocketSessionBase 39 | { 40 | app.Map(route, builder => builder.Use>(factory, true)); 41 | } 42 | 43 | 44 | public static void ExeclusiveMapWebSocket(this IAppBuilder app, 45 | F factory) 46 | where F : WebSocketSessionManager 47 | where S : WebSocketSessionBase 48 | { 49 | app.Use>(factory, true); 50 | } 51 | 52 | 53 | #endregion 54 | 55 | public static void Fault(this IOwinContext context, int StatusCdoe, string error) 56 | { 57 | context.Response.StatusCode = StatusCdoe; 58 | context.Response.Write(error); 59 | } 60 | public static void FaultNotaSocket(this IOwinContext context) 61 | { 62 | context.Fault(500, "Not a socket request, server expects a web socket"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SocketServer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SocketServer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SocketServer")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6d3b3575-d199-4c47-b01e-121b023b7342")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SocketServer/SocketCloseStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebSocketServer 8 | { 9 | public enum SocketCloseStatus 10 | { 11 | EndpointUnavailable = 1001, 12 | InvalidMessageType = 1003, 13 | InvalidPayloadData = 1007, 14 | MandatoryExtension = 1010, 15 | MessageTooBig = 1004, 16 | NormalClosure = 1000, 17 | PolicyViolation = 1008, 18 | ProtocolError = 1002 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SocketServer/SocketMessageType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebSocketServer 8 | { 9 | public enum SocketMessageType : int 10 | { 11 | Text = 0x1, 12 | Binary = 0x2, 13 | Close = 0x8 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SocketServer/SocketServerOwinMW.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Net.WebSockets; 8 | 9 | using Microsoft.Owin; 10 | using Owin; 11 | 12 | 13 | namespace WebSocketServer 14 | { 15 | public class WebSocketServerOwinMW : OwinMiddleware 16 | where F : WebSocketSessionManager 17 | where S : WebSocketSessionBase 18 | { 19 | private WebSocketSessionManager m_factory; 20 | private bool m_IsExeclusive = false; 21 | 22 | public WebSocketServerOwinMW(OwinMiddleware next, 23 | WebSocketSessionManager factory, 24 | bool IsExeclusive) : base(next) 25 | { 26 | m_factory = factory; 27 | m_IsExeclusive = IsExeclusive; 28 | } 29 | 30 | 31 | 32 | public WebSocketServerOwinMW(OwinMiddleware next, 33 | WebSocketSessionManager factory) : base(next) 34 | { 35 | m_factory = factory; 36 | 37 | } 38 | 39 | public override async Task Invoke(IOwinContext context) 40 | { 41 | if (!await m_factory.AcceptSocket(context) && m_IsExeclusive) 42 | { 43 | context.FaultNotaSocket(); 44 | return; 45 | } 46 | 47 | await Next.Invoke(context); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /SocketServer/WebSocketServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6D3B3575-D199-4C47-B01E-121B023B7342} 8 | Library 9 | Properties 10 | WebSocketServer 11 | WebSocketServer 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | x64 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | true 35 | bin\x64\Debug\ 36 | DEBUG;TRACE 37 | full 38 | x64 39 | prompt 40 | MinimumRecommendedRules.ruleset 41 | 42 | 43 | bin\x64\Release\ 44 | TRACE 45 | true 46 | pdbonly 47 | x64 48 | prompt 49 | MinimumRecommendedRules.ruleset 50 | 51 | 52 | 53 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 54 | True 55 | 56 | 57 | ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll 58 | True 59 | 60 | 61 | ..\packages\Owin.1.0\lib\net40\Owin.dll 62 | True 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 | {aa3697d5-4421-4540-ad17-261975a9908e} 91 | WebSocketServer.Utils 92 | 93 | 94 | 95 | 102 | -------------------------------------------------------------------------------- /SocketServer/WebSocketSessionBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Owin; 8 | using WebSocketServer.Utils; 9 | using Newtonsoft.Json; 10 | using System.Diagnostics; 11 | 12 | 13 | // TODO: 14 | // Modify abort and cancel to maintan status and handle reentryance 15 | // create a sepraete cancelation token for abort and cancel. 16 | 17 | 18 | namespace WebSocketServer 19 | { 20 | using WebSocketSendAsync = 21 | Func 22 | < 23 | ArraySegment /* data */, 24 | int /* messageType */, 25 | bool /* endOfMessage */, 26 | CancellationToken /* cancel */, 27 | Task 28 | >; 29 | 30 | using WebSocketReceiveAsync = 31 | Func 32 | < 33 | ArraySegment /* data */, 34 | CancellationToken /* cancel */, 35 | Task 36 | < 37 | Tuple 38 | < 39 | int /* messageType */, 40 | bool /* endOfMessage */, 41 | int /* count */ 42 | > 43 | > 44 | >; 45 | 46 | using WebSocketCloseAsync = 47 | Func 48 | < 49 | int /* closeStatus */, 50 | string /* closeDescription */, 51 | CancellationToken /* cancel */, 52 | Task 53 | >; 54 | 55 | public abstract class WebSocketSessionBase : IWebSocketSession, IDisposable 56 | { 57 | 58 | 59 | 60 | protected readonly IOwinContext m_context; 61 | protected WebSocketSendAsync m_SendAsync; 62 | protected WebSocketReceiveAsync m_ReceiveAsync; 63 | protected WebSocketCloseAsync m_CloseAsync; 64 | protected readonly IWebSocketSessionManager m_factory; 65 | protected readonly CancellationToken m_root_factory_working_token; 66 | protected readonly SequentialLeveledTaskScheduler m_Scheduler; 67 | protected string m_SessionId = string.Empty; 68 | 69 | 70 | private uint m_MaxMessageSize = 1024 * 256; 71 | 72 | 73 | public uint MaxMessageSize 74 | { 75 | get { return m_MaxMessageSize; } 76 | set 77 | { 78 | m_MaxMessageSize = value; 79 | } 80 | } 81 | 82 | 83 | public string SessionId 84 | { 85 | get { return m_SessionId; } 86 | set { 87 | // todo: validate if we are connected, fail if we are 88 | m_SessionId = value; 89 | } 90 | } 91 | 92 | public WebSocketSessionBase(IOwinContext context, 93 | IWebSocketSessionManager factory, 94 | CancellationToken cancelToken) 95 | { 96 | m_context = context; 97 | m_factory = factory; 98 | m_SessionId = Guid.NewGuid().ToString(); 99 | m_root_factory_working_token = cancelToken; 100 | m_Scheduler = factory.Scheduler; 101 | } 102 | 103 | 104 | 105 | public async Task SocketLoop(IDictionary websocketContext) 106 | { 107 | m_SendAsync = (WebSocketSendAsync)websocketContext["websocket.SendAsync"]; 108 | m_ReceiveAsync = (WebSocketReceiveAsync)websocketContext["websocket.ReceiveAsync"]; 109 | m_CloseAsync = (WebSocketCloseAsync)websocketContext["websocket.CloseAsync"]; 110 | 111 | var bytes = new byte[MaxMessageSize]; 112 | bool bClose = true; 113 | Tuple received = null; 114 | try 115 | { 116 | do 117 | { 118 | // keep track of item3 (size). the rest of the buffer 119 | // will evantually become garbage as messages received. 120 | 121 | ArraySegment buffer = new ArraySegment(bytes); 122 | received = await m_ReceiveAsync(buffer, m_root_factory_working_token); 123 | 124 | if (received.Item3 > MaxMessageSize) 125 | { 126 | await Close(SocketCloseStatus.MessageTooBig); 127 | break; 128 | } 129 | 130 | if (received.Item3 > 0) 131 | await OnReceiveAsync(buffer, received); 132 | } 133 | while (received.Item1 != (int)SocketMessageType.Close && 134 | !m_root_factory_working_token.IsCancellationRequested); 135 | } 136 | catch (AggregateException ae) 137 | { 138 | var flat = ae.Flatten(); 139 | Trace.WriteLine(string.Format("socket {0} encountered {1} error during receiving and is aborting", m_SessionId, ae.Message), "error"); 140 | await Close(SocketCloseStatus.EndpointUnavailable); 141 | bClose = false; 142 | } 143 | catch (Exception E) 144 | { 145 | Trace.WriteLine(string.Format("socket {0} encountered {1} error dropped by client, aborting", m_SessionId, E.Message), "warning"); 146 | await Close(SocketCloseStatus.EndpointUnavailable); 147 | bClose = false; 148 | } 149 | 150 | if(bClose) 151 | await Close(SocketCloseStatus.NormalClosure); 152 | } 153 | 154 | public virtual Task DrainThenClose() 155 | { 156 | // this posts a task at the end of 157 | // low priority task queue. 158 | 159 | // if a sender tries to send now 160 | // s/he might get a socket closing error 161 | // from the underlying OWIN 162 | var lt = new LeveledTask( 163 | () => 164 | { 165 | Close().Wait(); 166 | } 167 | ); 168 | 169 | lt.QueueId = m_SessionId; 170 | lt.IsHighPriority = false; 171 | lt.Start(m_Scheduler); 172 | return lt; 173 | 174 | } 175 | 176 | public virtual async Task Close() 177 | { 178 | await Close(SocketCloseStatus.NormalClosure); 179 | } 180 | 181 | protected virtual async Task Close(SocketCloseStatus closeStatus) 182 | { 183 | // this will close the socket even if there are messages 184 | // in flight waiting on the down stream. 185 | await m_CloseAsync((int)closeStatus, closeStatus.ToString(), this.m_root_factory_working_token); 186 | m_Scheduler.RemoveQueue(m_SessionId); 187 | m_factory.SockedClosed(m_SessionId); 188 | 189 | 190 | } 191 | 192 | public virtual void Abort() 193 | { 194 | // no wait. 195 | var state = SocketCloseStatus.EndpointUnavailable; 196 | m_CloseAsync((int)state, state.ToString(), this.m_root_factory_working_token); 197 | m_Scheduler.RemoveQueue(m_SessionId); 198 | m_factory.SockedClosed(m_SessionId); 199 | } 200 | 201 | 202 | 203 | public abstract Task OnReceiveAsync(ArraySegment buffer, 204 | Tuple received); 205 | 206 | protected Task GetFromBufferAsJson(ArraySegment buffer, Tuple received) where O: new() 207 | { 208 | return Task.Factory.StartNew( function: ()=> 209 | { 210 | var messageLength = received.Item3; 211 | var actual = buffer.Actualize(messageLength); 212 | return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(actual)); 213 | } 214 | ); 215 | } 216 | 217 | protected Task GetFromBufferAsString(ArraySegment buffer, Tuple received) 218 | { 219 | return Task.Factory.StartNew(function: () => 220 | { 221 | var bytes = buffer.Actualize(received.Item3); 222 | var message = Encoding.UTF8.GetString(bytes); 223 | return message; 224 | } 225 | ); 226 | } 227 | 228 | public virtual Task Send(ArraySegment data, 229 | SocketMessageType messageType, 230 | bool EndOfMessage) 231 | { 232 | var bIsHighPrority = true; // puts it at the begining of the Q 233 | return makeSendTask(data, messageType, EndOfMessage, bIsHighPrority); 234 | } 235 | public virtual Task Send(string sMessage) 236 | { 237 | var bytes = UTF8Encoding.UTF8.GetBytes(sMessage); 238 | return Send(new ArraySegment(bytes), SocketMessageType.Text, true); 239 | } 240 | 241 | public virtual Task Send(byte[] data, SocketMessageType messageType) 242 | { 243 | var arraySegement = new ArraySegment(data); 244 | return Send(arraySegement, messageType, true); 245 | } 246 | public virtual Task Send(T O) 247 | { 248 | var sJson = JsonConvert.SerializeObject(O); 249 | return Send(sJson); 250 | } 251 | 252 | public virtual Task Post(ArraySegment data, 253 | SocketMessageType messageType, 254 | bool EndOfMessage) 255 | { 256 | var bIsHighPrority = false; // puts it at the end of the Q 257 | return makeSendTask(data, messageType, EndOfMessage, bIsHighPrority); 258 | } 259 | public virtual Task Post(string sMessage) 260 | { 261 | var bytes = UTF8Encoding.UTF8.GetBytes(sMessage); 262 | return Post(new ArraySegment(bytes), SocketMessageType.Text, true); 263 | } 264 | 265 | public virtual Task Post(T O) 266 | { 267 | var sJson = JsonConvert.SerializeObject(O); 268 | return Post(sJson); 269 | } 270 | 271 | 272 | public virtual Task Post(byte[] data, SocketMessageType messageType) 273 | { 274 | var arraySegement = new ArraySegment(data); 275 | return Post(arraySegement, messageType, true); 276 | } 277 | 278 | protected virtual Task makeSendTask(ArraySegment data, 279 | SocketMessageType messageType, 280 | bool EndOfMessage, 281 | bool isHighPriority) 282 | { 283 | var lt = new LeveledTask(() => m_SendAsync(data, (int)messageType, EndOfMessage, m_root_factory_working_token).Wait()); 284 | lt.QueueId = m_SessionId; 285 | lt.IsHighPriority = isHighPriority; 286 | lt.Start(m_Scheduler); 287 | return lt; 288 | } 289 | 290 | #region IDisposable Support 291 | private bool disposedValue = false; // To detect redundant calls 292 | 293 | protected virtual void Dispose(bool disposing) 294 | { 295 | if (!disposedValue) 296 | { 297 | if (disposing) 298 | Abort(); 299 | 300 | disposedValue = true; 301 | } 302 | } 303 | 304 | 305 | 306 | // This code added to correctly implement the disposable pattern. 307 | public void Dispose() 308 | { 309 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above. 310 | Dispose(true); 311 | // TODO: uncomment the following line if the finalizer is overridden above. 312 | // GC.SuppressFinalize(this); 313 | } 314 | #endregion 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /SocketServer/WebSocketSessionManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Owin; 2 | using WebSocketServer.Utils; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | 12 | 13 | 14 | namespace WebSocketServer 15 | { 16 | 17 | 18 | public class WebSocketSessionManager : IWebSocketSessionManager, IDisposable where T : IWebSocketSession 19 | { 20 | 21 | protected CancellationTokenSource m_working_cts = new CancellationTokenSource(); 22 | 23 | protected readonly ConcurrentDictionary m_sessions = 24 | new ConcurrentDictionary(); 25 | 26 | 27 | protected readonly SequentialLeveledTaskScheduler m_scheduler = new SequentialLeveledTaskScheduler(); 28 | public SequentialLeveledTaskScheduler Scheduler{get { return m_scheduler; }} 29 | public int Count { get { return m_sessions.Count; } } 30 | public WebSocketSessionManager(){} 31 | 32 | 33 | 34 | protected virtual bool IsSocket(IOwinContext context, ref Action, Func, Task>> accept) 35 | { 36 | accept = context.Environment["websocket.Accept"] as 37 | Action, Func, Task>>; 38 | 39 | if (null == accept) 40 | return false; 41 | 42 | return true; 43 | } 44 | 45 | protected virtual void AddSocket(T newSocket) 46 | { 47 | m_sessions.AddOrUpdate(newSocket.SessionId, 48 | newSocket, 49 | (key, socket) => // don't allow duplicates 50 | { 51 | throw new InvalidOperationException("a socket with the same session id is already connected"); 52 | } 53 | ); 54 | } 55 | 56 | 57 | public virtual Task AcceptSocket(IOwinContext context) 58 | { 59 | Action, Func, Task>> accept = null; 60 | 61 | if (!IsSocket(context, ref accept)) 62 | return Task.FromResult(false); 63 | 64 | 65 | var newSocket = (T)Activator.CreateInstance(typeof(T), 66 | context, 67 | this, 68 | m_working_cts.Token); 69 | accept(null, newSocket.SocketLoop); 70 | 71 | AddSocket(newSocket); 72 | 73 | return Task.FromResult(true); 74 | } 75 | 76 | 77 | public virtual void AbortAll() 78 | { 79 | foreach (var session in m_sessions.Values) 80 | session.Abort(); 81 | } 82 | 83 | public virtual void SockedClosed(string SessionId) 84 | { 85 | // todo handle false returns. 86 | T current; 87 | m_sessions.TryRemove(SessionId, out current); 88 | 89 | } 90 | public virtual Task PostToAll(string message) 91 | { 92 | return PostToAll(Encoding.UTF8.GetBytes(message), 93 | SocketMessageType.Text); 94 | 95 | } 96 | public virtual Task PostToAll(byte[] buffer, SocketMessageType messageType) 97 | { 98 | var postAllTasks = new List(); 99 | foreach (var session in m_sessions.Values) 100 | postAllTasks.Add(Task.Factory.StartNew(() => { session.Post(buffer, messageType); })); 101 | 102 | return Task.WhenAll(postAllTasks); 103 | } 104 | 105 | public virtual Task CloseAll() 106 | { 107 | return CloseAll(CancellationToken.None); 108 | } 109 | 110 | public virtual Task CloseAll(CancellationToken cancellationToken) 111 | { 112 | var closeAllTasks = new List(); 113 | 114 | foreach (var session in m_sessions.Values) 115 | closeAllTasks.Add( Task.Factory.StartNew(() => { session.Close(); } , cancellationToken)); 116 | 117 | return Task.WhenAll(closeAllTasks); 118 | } 119 | 120 | 121 | public virtual async Task Close(string SessionId) 122 | { 123 | T session; 124 | var bFound = m_sessions.TryGetValue(SessionId, out session); 125 | if (!bFound) return bFound; 126 | 127 | await session.Close(); 128 | 129 | return bFound; 130 | } 131 | 132 | public T this[string SessionId] 133 | { 134 | get { return m_sessions[SessionId]; } 135 | } 136 | 137 | public IEnumerable GetSession(Func Predicate) 138 | { 139 | return m_sessions.Values.Where(session => Predicate(session)); 140 | } 141 | 142 | #region IDisposable Support 143 | private bool disposedValue = false; // To detect redundant calls 144 | 145 | protected virtual void Dispose(bool disposing) 146 | { 147 | if (!disposedValue) 148 | { 149 | if (disposing) 150 | { 151 | AbortAll(); 152 | } 153 | 154 | disposedValue = true; 155 | } 156 | } 157 | 158 | public void Dispose() 159 | { 160 | 161 | Dispose(true); 162 | // GC.SuppressFinalize(this); 163 | } 164 | #endregion 165 | 166 | } 167 | } -------------------------------------------------------------------------------- /SocketServer/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TestApp/ApplicationManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TestApp/PublishProfiles/Local.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /TestApp/Scripts/Create-FabricApplication.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Creates an instance of a Service Fabric application type. 4 | 5 | .DESCRIPTION 6 | This script creates an instance of a Service Fabric application type. It is invoked by Visual Studio when executing the "Create Application" command of a Service Fabric Application project. 7 | 8 | .NOTES 9 | WARNING: This script file is invoked by Visual Studio. Its parameters must not be altered but its logic can be customized as necessary. 10 | 11 | .PARAMETER PublishProfileFile 12 | Path to the file containing the publish profile. 13 | 14 | .PARAMETER ApplicationManifestPath 15 | Path to the application manifest of the Service Fabric application. 16 | 17 | .PARAMETER ApplicationParameter 18 | Hashtable of the Service Fabric application parameters to be used for the application. 19 | 20 | .EXAMPLE 21 | . Scripts\Create-FabricApplication.ps1 -ApplicationManifestPath 'ApplicationManifest.xml' 22 | 23 | Create the application using the application manifest file. 24 | 25 | .EXAMPLE 26 | . Scripts\Create-FabricApplication.ps1 -ApplicationManifestPath 'ApplicationManifest.xml' -ApplicationParameter @{CustomParameter1='MyValue'; CustomParameter2='MyValue'} 27 | 28 | Create the application by providing values for parameters that are defined in the application manifest. 29 | #> 30 | 31 | Param 32 | ( 33 | [String] 34 | $PublishProfileFile, 35 | 36 | [String] 37 | $ApplicationManifestPath, 38 | 39 | [Hashtable] 40 | $ApplicationParameter 41 | ) 42 | 43 | $LocalFolder = (Split-Path $MyInvocation.MyCommand.Path) 44 | 45 | if (!$PublishProfileFile) 46 | { 47 | $PublishProfileFile = "$LocalFolder\..\PublishProfiles\Local.xml" 48 | } 49 | 50 | if (!$ApplicationManifestPath) 51 | { 52 | $ApplicationManifestPath = "$LocalFolder\..\ApplicationManifest.xml" 53 | } 54 | 55 | if (!(Test-Path $ApplicationManifestPath)) 56 | { 57 | throw "$ApplicationManifestPath is not found." 58 | } 59 | 60 | $UtilitiesModulePath = "$LocalFolder\Utilities.psm1" 61 | Import-Module $UtilitiesModulePath 62 | 63 | $PublishProfile = Read-PublishProfile $PublishProfileFile 64 | $ClusterConnectionParameters = $PublishProfile.ClusterConnectionParameters 65 | 66 | try 67 | { 68 | [void](Connect-ServiceFabricCluster @ClusterConnectionParameters) 69 | } 70 | catch [System.Fabric.FabricObjectClosedException] 71 | { 72 | Write-Warning "Service Fabric cluster may not be connected." 73 | throw 74 | } 75 | 76 | $names = Get-Names -ApplicationManifestPath $ApplicationManifestPath -PublishProfile $PublishProfile 77 | if (!$names) 78 | { 79 | return 80 | } 81 | 82 | New-ServiceFabricApplication -ApplicationName $names.ApplicationName -ApplicationTypeName $names.ApplicationTypeName -ApplicationTypeVersion $names.ApplicationTypeVersion -ApplicationParameter $ApplicationParameter -------------------------------------------------------------------------------- /TestApp/Scripts/Deploy-FabricApplication.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Deploys a Service Fabric application type to a cluster. 4 | 5 | .DESCRIPTION 6 | This script deploys a Service Fabric application type to a cluster. It is invoked by Visual Studio when deploying a Service Fabric Application project. 7 | 8 | .NOTES 9 | WARNING: This script file is invoked by Visual Studio. Its parameters must not be altered but its logic can be customized as necessary. 10 | 11 | .PARAMETER PublishProfileFile 12 | Path to the file containing the publish profile. 13 | 14 | .PARAMETER ApplicationPackagePath 15 | Path to the folder of the packaged Service Fabric application. 16 | 17 | .PARAMETER DoNotCreateApplication 18 | Indicates that the Service Fabric application should not be created after registering the application type. 19 | 20 | .PARAMETER ApplicationParameter 21 | Hashtable of the Service Fabric application parameters to be used for the application. 22 | 23 | .EXAMPLE 24 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' 25 | 26 | Deploy the application using the default package location for a Debug build. 27 | 28 | .EXAMPLE 29 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -DoNotCreateApplication 30 | 31 | Deploy the application but do not create the application instance. 32 | 33 | .EXAMPLE 34 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -ApplicationParameter @{CustomParameter1='MyValue'; CustomParameter2='MyValue'} 35 | 36 | Deploy the application by providing values for parameters that are defined in the application manifest. 37 | #> 38 | 39 | Param 40 | ( 41 | [String] 42 | $PublishProfileFile, 43 | 44 | [String] 45 | $ApplicationPackagePath, 46 | 47 | [Switch] 48 | $DoNotCreateApplication, 49 | 50 | [Hashtable] 51 | $ApplicationParameter 52 | ) 53 | 54 | $LocalFolder = (Split-Path $MyInvocation.MyCommand.Path) 55 | 56 | $UtilitiesModulePath = "$LocalFolder\Utilities.psm1" 57 | Import-Module $UtilitiesModulePath 58 | 59 | if (!$PublishProfileFile) 60 | { 61 | $PublishProfileFile = "$LocalFolder\..\PublishProfiles\Local.xml" 62 | } 63 | 64 | if (!$ApplicationPackagePath) 65 | { 66 | $ApplicationPackagePath = "$LocalFolder\..\pkg\Release" 67 | } 68 | 69 | $ApplicationPackagePath = Resolve-Path $ApplicationPackagePath 70 | $ApplicationManifestPath = "$ApplicationPackagePath\ApplicationManifest.xml" 71 | 72 | if (!(Test-Path $ApplicationManifestPath)) 73 | { 74 | throw "$ApplicationManifestPath is not found. You may need to create a package by running the 'Package' command in Visual Studio for the desired build configuration (Debug or Release)." 75 | } 76 | 77 | $packageValidationSuccess = (Test-ServiceFabricApplicationPackage $ApplicationPackagePath) 78 | if (!$packageValidationSuccess) 79 | { 80 | throw "Validation failed for package: $ApplicationPackagePath" 81 | } 82 | 83 | Write-Host 'Deploying application...' 84 | 85 | $PublishProfile = Read-PublishProfile $PublishProfileFile 86 | $ClusterConnectionParameters = $PublishProfile.ClusterConnectionParameters 87 | 88 | try 89 | { 90 | Write-Host 'Connecting to the cluster...' 91 | [void](Connect-ServiceFabricCluster @ClusterConnectionParameters) 92 | } 93 | catch [System.Fabric.FabricObjectClosedException] 94 | { 95 | Write-Warning "Service Fabric cluster may not be connected." 96 | throw 97 | } 98 | 99 | # Get image store connection string 100 | $clusterManifestText = Get-ServiceFabricClusterManifest 101 | $imageStoreConnectionString = Get-ImageStoreConnectionString ([xml] $clusterManifestText) 102 | 103 | $names = Get-Names -ApplicationManifestPath $ApplicationManifestPath -PublishProfile $PublishProfile 104 | if (!$names) 105 | { 106 | return 107 | } 108 | 109 | $tmpPackagePath = Copy-Temp $ApplicationPackagePath $names.ApplicationTypeName 110 | $applicationPackagePathInImageStore = $names.ApplicationTypeName 111 | 112 | $app = Get-ServiceFabricApplication -ApplicationName $names.ApplicationName 113 | if ($app) 114 | { 115 | Write-Host 'Removing application instance...' 116 | $app | Remove-ServiceFabricApplication -Force 117 | } 118 | 119 | foreach ($node in Get-ServiceFabricNode) 120 | { 121 | [void](Get-ServiceFabricDeployedReplica -NodeName $node.NodeName -ApplicationName $names.ApplicationName | Remove-ServiceFabricReplica -NodeName $node.NodeName -ForceRemove) 122 | } 123 | 124 | $reg = Get-ServiceFabricApplicationType -ApplicationTypeName $names.ApplicationTypeName 125 | if ($reg) 126 | { 127 | Write-Host 'Unregistering application type...' 128 | $reg | Unregister-ServiceFabricApplicationType -Force 129 | } 130 | 131 | Write-Host 'Copying application package...' 132 | Copy-ServiceFabricApplicationPackage -ApplicationPackagePath $tmpPackagePath -ImageStoreConnectionString $imageStoreConnectionString -ApplicationPackagePathInImageStore $applicationPackagePathInImageStore 133 | 134 | Write-Host 'Registering application type...' 135 | Register-ServiceFabricApplicationType -ApplicationPathInImageStore $applicationPackagePathInImageStore 136 | 137 | Write-Host 'Removing application package...' 138 | Remove-ServiceFabricApplicationPackage -ApplicationPackagePathInImageStore $applicationPackagePathInImageStore -ImageStoreConnectionString $imageStoreConnectionString 139 | 140 | if (!$DoNotCreateApplication) 141 | { 142 | Write-Host 'Creating application...' 143 | [void](New-ServiceFabricApplication -ApplicationName $names.ApplicationName -ApplicationTypeName $names.ApplicationTypeName -ApplicationTypeVersion $names.ApplicationTypeVersion -ApplicationParameter $ApplicationParameter) 144 | Write-Host 'Create application succeeded' 145 | } -------------------------------------------------------------------------------- /TestApp/Scripts/Get-FabricApplicationStatus.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Outputs messages indicating the readiness of a Service Fabric application. 4 | 5 | .DESCRIPTION 6 | This script outputs messages indicating the readiness of a Service Fabric application. It is invoked by Visual Studio after starting a Service Fabric Application project. 7 | 8 | .NOTES 9 | WARNING: This script file is invoked by Visual Studio. Its parameters must not be altered but its logic can be customized as necessary. 10 | 11 | .PARAMETER PublishProfileFile 12 | Path to the file containing the publish profile. 13 | 14 | .PARAMETER ApplicationManifestPath 15 | Path to the application manifest of the Service Fabric application. 16 | 17 | .EXAMPLE 18 | . Scripts\Get-FabricApplicationStatus.ps1 -ApplicationManifestPath 'ApplicationManifest.xml' 19 | 20 | Get the status of a deployed application as described by the application manifest. 21 | #> 22 | 23 | Param 24 | ( 25 | [String] 26 | $PublishProfileFile, 27 | 28 | [String] 29 | $ApplicationManifestPath 30 | ) 31 | 32 | $LocalFolder = (Split-Path $MyInvocation.MyCommand.Path) 33 | 34 | if (!$PublishProfileFile) 35 | { 36 | $PublishProfileFile = "$LocalFolder\..\PublishProfiles\Local.xml" 37 | } 38 | 39 | if (!$ApplicationManifestPath) 40 | { 41 | $ApplicationManifestPath = "$LocalFolder\..\ApplicationManifest.xml" 42 | } 43 | 44 | if (!(Test-Path $ApplicationManifestPath)) 45 | { 46 | throw "$ApplicationManifestPath is not found." 47 | } 48 | 49 | $UtilitiesModulePath = "$LocalFolder\Utilities.psm1" 50 | Import-Module $UtilitiesModulePath 51 | 52 | $PublishProfile = Read-PublishProfile $PublishProfileFile 53 | $ClusterConnectionParameters = $PublishProfile.ClusterConnectionParameters 54 | 55 | try 56 | { 57 | [void](Connect-ServiceFabricCluster @ClusterConnectionParameters) 58 | } 59 | catch [System.Fabric.FabricObjectClosedException] 60 | { 61 | Write-Warning "Service Fabric cluster may not be connected." 62 | throw 63 | } 64 | 65 | $names = Get-Names -ApplicationManifestPath $ApplicationManifestPath -PublishProfile $PublishProfile 66 | if (!$names) 67 | { 68 | return 69 | } 70 | 71 | $started = $false 72 | $ready = $false 73 | $retryCount = 20 74 | do 75 | { 76 | try 77 | { 78 | $app = Get-ServiceFabricApplication -ApplicationName $names.ApplicationName 79 | if ($app) 80 | { 81 | if (!$started) 82 | { 83 | $started = $true 84 | Write-Host "The application has started." 85 | } 86 | 87 | $ready = $true 88 | Write-Host "Service Status:" 89 | $services = $app | Get-ServiceFabricService 90 | foreach($s in $services) 91 | { 92 | $remaining = $s | Get-ServiceFabricPartition | Where-Object {$_.PartitionStatus -ne "Ready"} | Measure 93 | if($remaining.Count -gt 0) 94 | { 95 | $ready = $false 96 | Write-Host "$($s.ServiceName) is not ready, $($remaining.Count) partitions remaining." 97 | } 98 | else 99 | { 100 | Write-Host "$($s.ServiceName) is ready." 101 | } 102 | } 103 | } 104 | else 105 | { 106 | Write-Host "Waiting for the application to start." 107 | } 108 | Write-Host "" 109 | } 110 | finally 111 | { 112 | if(!$ready) 113 | { 114 | Start-Sleep -Seconds 5 115 | } 116 | $retryCount-- 117 | } 118 | } while (!$ready -and $retryCount -gt 0) 119 | 120 | if(!$ready) 121 | { 122 | Write-Host "Something is taking too long, the application is still not ready." 123 | } 124 | else 125 | { 126 | Write-Host "The application is ready." 127 | } -------------------------------------------------------------------------------- /TestApp/Scripts/Utilities.psm1: -------------------------------------------------------------------------------- 1 | function Copy-Temp 2 | { 3 | <# 4 | .SYNOPSIS 5 | Copies files to a temp folder. 6 | 7 | .PARAMETER From 8 | Source location from which to copy files. 9 | 10 | .PARAMETER Name 11 | Folder name within temp location to store the files. 12 | #> 13 | 14 | [CmdletBinding()] 15 | Param 16 | ( 17 | [String] 18 | $From, 19 | 20 | [String] 21 | $Name 22 | ) 23 | 24 | if (!(Test-Path $From)) 25 | { 26 | return $null 27 | } 28 | 29 | $To = $env:Temp + '\' + $Name 30 | 31 | if (Test-Path $To) 32 | { 33 | Remove-Item -Path $To -Recurse -ErrorAction Stop | Out-Null 34 | } 35 | 36 | New-Item $To -ItemType directory | Out-Null 37 | 38 | robocopy "$From" "$To" /E | Out-Null 39 | 40 | # robocopy has non-standard exit values that are documented here: https://support.microsoft.com/en-us/kb/954404 41 | # Exit codes 0-8 are considered success, while all other exit codes indicate at least one failure. 42 | # Some build systems treat all non-0 return values as failures, so we massage the exit code into 43 | # something that they can understand. 44 | if (($LASTEXITCODE -ge 0) -and ($LASTEXITCODE -le 8)) 45 | { 46 | # Simply setting $LASTEXITCODE in this script will not override the script's exit code. 47 | # We need to start a new process and let it exit. 48 | PowerShell -NoProfile -Command "exit 0" 49 | } 50 | 51 | return $env:Temp + '\' + $Name 52 | } 53 | 54 | function Get-Names 55 | { 56 | <# 57 | .SYNOPSIS 58 | Returns an object containing common information from the application manifest. 59 | 60 | .PARAMETER ApplicationManifestPath 61 | Path to the application manifest file. 62 | 63 | .PARAMETER PublishProfile 64 | Hashtable containing the publish profile settings. 65 | #> 66 | 67 | [CmdletBinding()] 68 | Param 69 | ( 70 | [String] 71 | $ApplicationManifestPath, 72 | 73 | [Hashtable] 74 | $PublishProfile 75 | ) 76 | 77 | $appXml = [xml] (Get-Content $ApplicationManifestPath) 78 | if (!$appXml) 79 | { 80 | return 81 | } 82 | 83 | $appMan = $appXml.ApplicationManifest 84 | $FabricNamespace = 'fabric:' 85 | $appTypeSuffix = 'Type' 86 | 87 | $h = @{ 88 | FabricNamespace = $FabricNamespace; 89 | ApplicationTypeName = $appMan.ApplicationTypeName; 90 | ApplicationTypeVersion = $appMan.ApplicationTypeVersion; 91 | } 92 | if ($PublishProfile.ApplicationName) 93 | { 94 | $appName = $PublishProfile.ApplicationName 95 | } 96 | else 97 | { 98 | $shortAppName = $appMan.ApplicationTypeName 99 | if ($shortAppName.EndsWith($appTypeSuffix)) 100 | { 101 | $shortAppName = $shortAppName.Substring(0, $shortAppName.Length - $appTypeSuffix.Length) 102 | } 103 | 104 | $appName = $FabricNamespace + "/" + $shortAppName 105 | } 106 | 107 | $h += @{ 108 | ApplicationName = $appName 109 | } 110 | 111 | Write-Output (New-Object psobject -Property $h) 112 | } 113 | 114 | function Get-ImageStoreConnectionString 115 | { 116 | <# 117 | .SYNOPSIS 118 | Returns the value of the image store connection string from the cluster manifest. 119 | 120 | .PARAMETER ApplicationManifestPath 121 | Path to the application manifest file. 122 | #> 123 | 124 | [CmdletBinding()] 125 | Param 126 | ( 127 | [xml] 128 | $ClusterManifest 129 | ) 130 | 131 | $managementSection = $ClusterManifest.ClusterManifest.FabricSettings.Section | ? { $_.Name -eq "Management" } 132 | return $managementSection.ChildNodes | ? { $_.Name -eq "ImageStoreConnectionString" } | Select-Object -Expand Value 133 | } 134 | 135 | function Read-PublishProfile 136 | { 137 | <# 138 | .SYNOPSIS 139 | Parses the publish profile file and returns a Hashtable containing its state. 140 | 141 | .PARAMETER PublishProfileFilePath 142 | Path to the publish profile file. 143 | #> 144 | 145 | [CmdletBinding()] 146 | Param 147 | ( 148 | [String] 149 | $PublishProfileFilePath 150 | ) 151 | 152 | if (!(Test-Path $PublishProfileFilePath)) 153 | { 154 | throw "$PublishProfileFilePath is not found." 155 | } 156 | 157 | $PublishProfileFolder = (Split-Path $PublishProfileFilePath) 158 | 159 | $PublishProfile = ([xml] (Get-Content $PublishProfileFilePath)).PublishProfile 160 | $PublishProfile = Convert-PublishProfileToHashtable $PublishProfile 161 | 162 | return $PublishProfile; 163 | } 164 | 165 | 166 | function Convert-PublishProfileToHashtable 167 | { 168 | <# 169 | .SYNOPSIS 170 | Converts publish profile XML file to a Hashtable. 171 | 172 | .PARAMETER xml 173 | The XML to convert. 174 | #> 175 | 176 | [CmdletBinding()] 177 | Param 178 | ( 179 | [System.Xml.XmlElement] 180 | $xml 181 | ) 182 | 183 | $hash = @{} 184 | $xml.ChildNodes | foreach { 185 | if ($_.Name -eq 'ClusterConnectionParameters') { 186 | $parameters = @{} 187 | $_.Attributes | foreach { 188 | $boolVal = $null 189 | if ([bool]::TryParse($_.Value, [ref]$boolVal)) { 190 | $parameters[$_.Name] = $boolVal 191 | } 192 | else { 193 | $parameters[$_.Name] = $_.Value 194 | } 195 | } 196 | 197 | $hash[$_.Name] = $parameters 198 | } 199 | else { 200 | $hash[$_.Name] = $_.'#text' 201 | } 202 | } 203 | 204 | return $hash 205 | } -------------------------------------------------------------------------------- /TestApp/TestApp.sfproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 029ccf61-a058-4ddb-8c9d-34bb7a177530 6 | 0.7 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /TestClient/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TestClient/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ServiceFabric.Services; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Fabric; 5 | using System.Linq; 6 | using System.Net.WebSockets; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using WebSocketServer.ServiceFabric.Clients; 11 | 12 | namespace TestClient 13 | { 14 | class Program 15 | { 16 | static readonly string FabricServiceName = "fabric:/TestApp/TestStatefulSvc"; 17 | // basically my sockets are mapped to listening address + this 18 | // a socket is also mapped to the root address (as in publishing address as is) 19 | static readonly string[] socketPath = new string[] { "", "customer", "order" }; 20 | 21 | 22 | // if i have one socket listening on the address i can bypass all this and just connect 23 | // directly to endpoint address. 24 | static readonly object consoleLock = new object(); 25 | static void Main(string[] args) 26 | { 27 | try { 28 | 29 | // UseStandardSocket().Wait(); 30 | UseSFClientInterfaces().Wait(); 31 | 32 | } 33 | catch (AggregateException Ex) 34 | { 35 | Console.WriteLine("error : " + Ex.InnerException.Message); 36 | } 37 | 38 | 39 | Console.Read(); 40 | } 41 | 42 | #region Using Service Fabric Client Interfaces 43 | static async Task UseSFClientInterfaces() 44 | { 45 | var factory = new ServiceFabricWebSocketClientFactory(); 46 | 47 | ServicePartitionClient partitionClient = new ServicePartitionClient(factory, new Uri(FabricServiceName)); 48 | var Tasks = new List(); 49 | await partitionClient.InvokeWithRetryAsync( 50 | async (client) => 51 | { 52 | 53 | 54 | // in here business as usual 55 | // get a socket 56 | var socket = await client.GetWebSocketAsync(); // socket at the base address 57 | Tasks.Add(useSocket(socket)); 58 | 59 | var anotherSocket = await client.GetWebSocketAsync("customer"); // get socket at baseaddress/customer 60 | Tasks.Add(useSocket(anotherSocket)); 61 | 62 | var yetAnotherSocket = await client.GetWebSocketAsync("order"); // get socket at baseaddress/order 63 | Tasks.Add(useSocket(yetAnotherSocket)); 64 | 65 | 66 | // i can also close the socket here 67 | //client.AbortAll(); 68 | }); 69 | 70 | 71 | await Task.WhenAll(Tasks); 72 | 73 | // now when the partition goes out of scope, evantually the dispose on client will be 74 | // called awhich will take care of closing the client 75 | 76 | 77 | } 78 | 79 | 80 | 81 | static async Task useSocket(ClientWebSocket clientWSocket) 82 | { 83 | 84 | 85 | var upStreamMessageCount = 10; 86 | var delayMs = 5 * 1000; // every 5 sec 87 | var rcvWaitCount = 10; 88 | 89 | var UpstreamMessage = string.Format("Hello Server On {0}", socketPath); 90 | var sendTask = Task.Run(async () => 91 | { 92 | for (var i = 1; i <= upStreamMessageCount; i++) 93 | { 94 | lock (consoleLock) 95 | Console.WriteLine(string.Format("sending {0}", UpstreamMessage)); 96 | 97 | await clientWSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(UpstreamMessage)), 98 | WebSocketMessageType.Text, 99 | true, 100 | CancellationToken.None); 101 | 102 | await Task.Delay(delayMs); 103 | } 104 | }); 105 | 106 | 107 | 108 | var rcvTask = Task.Run(async () => 109 | { 110 | var arraySegment = new ArraySegment(new byte[1024 * 256]); 111 | for (var i = 1; i <= rcvWaitCount; i++) 112 | { 113 | var res = await clientWSocket.ReceiveAsync(arraySegment, CancellationToken.None); 114 | var rcv = Encoding.UTF8.GetString(arraySegment.Array, 0, res.Count); 115 | var downstreamMessage = string.Format("Server Message: {0}", rcv); 116 | lock (consoleLock) 117 | Console.WriteLine(downstreamMessage); 118 | 119 | await Task.Delay(delayMs); 120 | 121 | } 122 | }); 123 | 124 | 125 | 126 | await Task.WhenAll(sendTask, rcvTask); 127 | } 128 | 129 | 130 | 131 | 132 | 133 | #endregion 134 | 135 | 136 | 137 | 138 | static async Task UseStandardSocket() 139 | { 140 | 141 | FabricClient fc = new FabricClient(); //local cluster 142 | // my service is singlton partition 143 | var resolvedPartitions = await fc.ServiceManager.ResolveServicePartitionAsync(new Uri(FabricServiceName)); 144 | var ep = resolvedPartitions.Endpoints.SingleOrDefault((endpoint) => endpoint.Role == ServiceEndpointRole.StatefulPrimary); 145 | var baseUri = ep.Address; 146 | 147 | var Tasks = new List(); 148 | 149 | foreach (var path in socketPath) 150 | Tasks.Add(ClientSocketLoop(baseUri, path)); 151 | 152 | 153 | await Task.WhenAll(Tasks); 154 | 155 | } 156 | 157 | static async Task ClientSocketLoop(string targetAddress, string socketPath) 158 | { 159 | var upStreamMessageCount = 10; 160 | var delayMs = 5 * 1000; // every 5 sec 161 | var rcvWaitCount = 10; 162 | 163 | var UpstreamMessage = string.Format("Hello Server On {0}", socketPath); 164 | var socketAddress = new Uri(string.Concat(targetAddress, socketPath)); 165 | 166 | ClientWebSocket clientWSocket = new ClientWebSocket(); 167 | await clientWSocket.ConnectAsync(socketAddress, CancellationToken.None); 168 | var sendTask = Task.Run(async () => 169 | { 170 | for (var i = 1; i <= upStreamMessageCount; i++) 171 | { 172 | lock(consoleLock) 173 | Console.WriteLine(string.Format("sending {0}", UpstreamMessage)); 174 | 175 | await clientWSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(UpstreamMessage)), 176 | WebSocketMessageType.Text, 177 | true, 178 | CancellationToken.None); 179 | 180 | await Task.Delay(delayMs); 181 | } 182 | }); 183 | 184 | 185 | 186 | var rcvTask = Task.Run(async () => 187 | { 188 | var arraySegment = new ArraySegment(new byte[1024 * 256]); 189 | for (var i = 1; i <= rcvWaitCount; i++) 190 | { 191 | var res = await clientWSocket.ReceiveAsync(arraySegment, CancellationToken.None); 192 | var rcv = Encoding.UTF8.GetString(arraySegment.Array, 0, res.Count); 193 | var downstreamMessage = string.Format("Server Message: {0}", rcv); 194 | lock(consoleLock) 195 | Console.WriteLine(downstreamMessage); 196 | 197 | await Task.Delay(delayMs); 198 | 199 | } 200 | }); 201 | 202 | 203 | await Task.WhenAll(sendTask, rcvTask); 204 | 205 | 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /TestClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TestClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft Corporation")] 12 | [assembly: AssemblyProduct("TestClient")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft Corporation 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("076ee734-806e-467f-88ea-3f79e8d23e9a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TestClient/TestClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {076EE734-806E-467F-88EA-3F79E8D23E9A} 8 | Exe 9 | Properties 10 | TestClient 11 | TestClient 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | x64 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Microsoft.ServiceFabric.Data.1.0.328-preview1\lib\net45\Microsoft.ServiceFabric.Data.dll 38 | True 39 | 40 | 41 | ..\packages\Microsoft.ServiceFabric.Services.1.0.328-preview1\lib\net45\Microsoft.ServiceFabric.Services.dll 42 | True 43 | 44 | 45 | 46 | 47 | 48 | ..\packages\Microsoft.ServiceFabric.4.0.1427-preview1\lib\net45\System.Fabric.Common.Internal.dll 49 | True 50 | 51 | 52 | ..\packages\Microsoft.ServiceFabric.4.0.1427-preview1\lib\net45\System.Fabric.Common.Internal.Strings.dll 53 | True 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {a689c4f5-a1fe-4bc3-9f90-a7699f3668e3} 75 | WebSocketServer.ServiceFabric.Clients 76 | 77 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /TestClient/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TestStatefulSvc/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TestStatefulSvc/PackageRoot/Config/Settings.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 |
7 | 8 |
9 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /TestStatefulSvc/PackageRoot/ServiceManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | TestStatefulSvc.exe 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /TestStatefulSvc/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Fabric; 4 | using System.Threading; 5 | 6 | namespace TestStatefulSvc 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | try 13 | { 14 | using (FabricRuntime fabricRuntime = FabricRuntime.Create()) 15 | { 16 | // This is the name of the ServiceType that is registered with FabricRuntime. 17 | // This name must match the name defined in the ServiceManifest. If you change 18 | // this name, please change the name of the ServiceType in the ServiceManifest. 19 | fabricRuntime.RegisterServiceType("TestStatefulSvcType", typeof(TestStatefulSvc)); 20 | 21 | ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(TestStatefulSvc).Name); 22 | 23 | Thread.Sleep(Timeout.Infinite); 24 | } 25 | } 26 | catch (Exception e) 27 | { 28 | ServiceEventSource.Current.ServiceHostInitializationFailed(e); 29 | throw; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TestStatefulSvc/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TestStatefulSvc")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft Corporation")] 12 | [assembly: AssemblyProduct("TestStatefulSvc")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft Corporation 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("cf4c040b-3bd4-4a3e-981e-517724cdf1da")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TestStatefulSvc/ServiceEventSource.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ServiceFabric.Services; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.Tracing; 5 | using System.Fabric; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace TestStatefulSvc 11 | { 12 | [EventSource(Name = "MyCompany-TestApp-TestStatefulSvc")] 13 | internal sealed class ServiceEventSource : EventSource 14 | { 15 | public static ServiceEventSource Current = new ServiceEventSource(); 16 | 17 | [NonEvent] 18 | public void Message(string message, params object[] args) 19 | { 20 | if (this.IsEnabled()) 21 | { 22 | string finalMessage = string.Format(message, args); 23 | Message(finalMessage); 24 | } 25 | } 26 | 27 | [Event(1, Level = EventLevel.Informational, Message = "{0}")] 28 | public void Message(string message) 29 | { 30 | if (this.IsEnabled()) 31 | { 32 | WriteEvent(1, message); 33 | } 34 | } 35 | 36 | [NonEvent] 37 | public void ServiceMessage(StatelessService service, string message, params object[] args) 38 | { 39 | if (this.IsEnabled()) 40 | { 41 | string finalMessage = string.Format(message, args); 42 | ServiceMessage( 43 | service.ServiceInitializationParameters.ServiceName.ToString(), 44 | service.ServiceInitializationParameters.ServiceTypeName, 45 | service.ServiceInitializationParameters.InstanceId, 46 | service.ServiceInitializationParameters.PartitionId, 47 | service.ServiceInitializationParameters.CodePackageActivationContext.ApplicationName, 48 | service.ServiceInitializationParameters.CodePackageActivationContext.ApplicationTypeName, 49 | FabricRuntime.GetNodeContext().NodeName, 50 | finalMessage); 51 | } 52 | } 53 | 54 | [NonEvent] 55 | public void ServiceMessage(StatefulService service, string message, params object[] args) 56 | { 57 | if (this.IsEnabled()) 58 | { 59 | string finalMessage = string.Format(message, args); 60 | ServiceMessage( 61 | service.ServiceInitializationParameters.ServiceName.ToString(), 62 | service.ServiceInitializationParameters.ServiceTypeName, 63 | service.ServiceInitializationParameters.ReplicaId, 64 | service.ServiceInitializationParameters.PartitionId, 65 | service.ServiceInitializationParameters.CodePackageActivationContext.ApplicationName, 66 | service.ServiceInitializationParameters.CodePackageActivationContext.ApplicationTypeName, 67 | FabricRuntime.GetNodeContext().NodeName, 68 | finalMessage); 69 | } 70 | } 71 | 72 | [Event(2, Level = EventLevel.Informational, Message = "{7}")] 73 | private void ServiceMessage( 74 | string serviceName, 75 | string serviceTypeName, 76 | long replicaOrInstanceId, 77 | Guid partitionId, 78 | string applicationName, 79 | string applicationTypeName, 80 | string nodeName, 81 | string message) 82 | { 83 | if (this.IsEnabled()) 84 | { 85 | WriteEvent(2, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); 86 | } 87 | } 88 | 89 | [Event(3, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}")] 90 | public void ServiceTypeRegistered(int hostProcessId, string serviceType) 91 | { 92 | WriteEvent(3, hostProcessId, serviceType); 93 | } 94 | 95 | [NonEvent] 96 | public void ServiceHostInitializationFailed(Exception e) 97 | { 98 | ServiceHostInitializationFailed(e.ToString()); 99 | } 100 | 101 | [Event(4, Level = EventLevel.Error, Message = "Service host initialization failed")] 102 | private void ServiceHostInitializationFailed(string exception) 103 | { 104 | WriteEvent(4, exception); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /TestStatefulSvc/SocketSessionTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.ServiceFabric.Data; 8 | using WebSocketServer; 9 | using WebSocketServer.ServiceFabric.Services; 10 | using Microsoft.ServiceFabric.Data.Collections; 11 | 12 | namespace TestStatefulSvc 13 | { 14 | // these classes are sample socket sessions, 15 | // service can reach all sockets via listner.SessionManager. 16 | // classes can implement common base where core business operations can be defined 17 | // or core serializer/de-serialization behaviour can be implemented 18 | public class CustomerWSSession : ServiceFabricSocketSessionBase 19 | { 20 | public static readonly string DictionaryName = "CustomerMessages"; 21 | 22 | public CustomerWSSession(IReliableStateManager stateManager, 23 | Microsoft.Owin.IOwinContext context, 24 | IWebSocketSessionManager factory, 25 | CancellationToken cancelToken) : 26 | base(stateManager, context, factory, cancelToken) 27 | { 28 | } 29 | 30 | public async Task SayHelloToCustomer(string HelloMessage) 31 | { 32 | await base.Post(string.Format("Server->Customer: {0}", HelloMessage)); 33 | } 34 | public async Task CustomerSaid(string HelloMessage) 35 | { 36 | // just save them in a dictionary 37 | var myDictionary = await this.StateManager.GetOrAddAsync>(DictionaryName); 38 | 39 | using (var tx = this.StateManager.CreateTransaction()) 40 | { 41 | var newKey = string.Format("{0}-{1}", this.SessionId, DateTime.UtcNow.Ticks); 42 | await myDictionary.AddAsync(tx, newKey, HelloMessage); 43 | await tx.CommitAsync(); 44 | } 45 | } 46 | 47 | public override async Task OnReceiveAsync(ArraySegment buffer, Tuple received) 48 | { 49 | await CustomerSaid(await GetFromBufferAsString(buffer, received)); 50 | } 51 | } 52 | 53 | 54 | 55 | 56 | public class OrderWSSession : ServiceFabricSocketSessionBase 57 | { 58 | public static readonly string DictionaryName = "OrderMessages"; 59 | 60 | public OrderWSSession(IReliableStateManager stateManager, 61 | Microsoft.Owin.IOwinContext context, 62 | IWebSocketSessionManager factory, 63 | CancellationToken cancelToken) : 64 | base(stateManager, context, factory, cancelToken) 65 | { 66 | } 67 | 68 | public async Task SayHelloToOrder(string HelloMessage) 69 | { 70 | await base.Post(string.Format("Server->order: {0}", HelloMessage)); 71 | } 72 | public async Task OrderSaid(string HelloMessage) 73 | { 74 | // just save them in a dictionary 75 | var myDictionary = await this.StateManager.GetOrAddAsync>(DictionaryName); 76 | 77 | using (var tx = this.StateManager.CreateTransaction()) 78 | { 79 | var newKey = string.Format("{0}-{1}", this.SessionId, DateTime.UtcNow.Ticks); 80 | await myDictionary.AddAsync(tx, newKey, HelloMessage); 81 | await tx.CommitAsync(); 82 | } 83 | } 84 | 85 | public override async Task OnReceiveAsync(ArraySegment buffer, Tuple received) 86 | { 87 | await OrderSaid(await GetFromBufferAsString(buffer, received)); 88 | } 89 | } 90 | 91 | 92 | public class GeneralWSSession : ServiceFabricSocketSessionBase 93 | { 94 | public static readonly string DictionaryName = "GeneralMessages"; 95 | 96 | public GeneralWSSession(IReliableStateManager stateManager, 97 | Microsoft.Owin.IOwinContext context, 98 | IWebSocketSessionManager factory, 99 | CancellationToken cancelToken) : 100 | base(stateManager, context, factory, cancelToken) 101 | { 102 | } 103 | 104 | public async Task SayHelloToGeneral(string HelloMessage) 105 | { 106 | await base.Post(string.Format("Server->General: {0}", HelloMessage)); 107 | } 108 | public async Task GeneralSaid(string HelloMessage) 109 | { 110 | // just save them in a dictionary 111 | var myDictionary = await this.StateManager.GetOrAddAsync>(DictionaryName); 112 | 113 | using (var tx = this.StateManager.CreateTransaction()) 114 | { 115 | var newKey = string.Format("{0}-{1}", this.SessionId, DateTime.UtcNow.Ticks); 116 | await myDictionary.AddAsync(tx, newKey, HelloMessage); 117 | await tx.CommitAsync(); 118 | } 119 | } 120 | 121 | public override async Task OnReceiveAsync(ArraySegment buffer, Tuple received) 122 | { 123 | await GeneralSaid(await GetFromBufferAsString(buffer, received)); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /TestStatefulSvc/TestStatefulSvc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.ServiceFabric; 7 | using Microsoft.ServiceFabric.Data.Collections; 8 | using Microsoft.ServiceFabric.Services; 9 | using WebSocketServer.ServiceFabric.Services; 10 | using System.Diagnostics; 11 | using Microsoft.Owin; 12 | using Owin; 13 | 14 | namespace TestStatefulSvc 15 | { 16 | 17 | 18 | public class TestStatefulSvc : StatefulService 19 | { 20 | private WebSocketCommunicationListener m_listener; 21 | private Random m_rnd = new Random(); 22 | 23 | protected override ICommunicationListener CreateCommunicationListener() 24 | { 25 | m_listener = new WebSocketCommunicationListener(StateManager); 26 | // map to any type that implements ServiceFabricWebSocketSessionBase 27 | m_listener.Map("customer", typeof(CustomerWSSession)); // mapped to /customer 28 | m_listener.Map("order", typeof(OrderWSSession)); // mapped to /order 29 | m_listener.Map("", typeof(GeneralWSSession)); // mapped to / 30 | 31 | 32 | // you can use the above to filter sockets based on the replica type 33 | // for example primaries can have different socket types than seconaries. 34 | 35 | 36 | // Listening address is what the server actually listen to 37 | // publishing address is what is returned to Fabric runtime (commnunicated as EndPoint.Address on client side) 38 | /* 39 | if you want to control how listening and publishing addresses are created 40 | m_listener.OnCreateListeningAddress = (listener) => { << return my listening address here >>} 41 | m_listener.OnCreatePublishingAddress = (listener) => { << return my Publishing ddress here >>} 42 | */ 43 | 44 | /* 45 | if you want to add more OWIN stuff pre or post web socket stages 46 | m_listener.OnOwinPreMapping = (listener, appbuilder) => { << appbuilder.UseXX >>} 47 | m_listener.OnOwinPostMapping = (listener, appbuilder) => { << appbuilder.UseXX >>} 48 | */ 49 | 50 | 51 | return m_listener; 52 | } 53 | 54 | 55 | protected override async Task RunAsync(CancellationToken cancellationToken) 56 | { 57 | // this a typical case where a service 58 | // needs to send messages to connected client 59 | // in my case i am just filtering by session type. 60 | // you can use the predicate passed to getSession to get your target session 61 | 62 | while (!cancellationToken.IsCancellationRequested) 63 | { 64 | await Task.Delay(5000); // every five secs 65 | var n = m_rnd.Next(1, 3); // not really that random 66 | try 67 | { 68 | switch (n) 69 | { 70 | case 1: // we will send to all "generalWsSession" connected; 71 | { 72 | var clients = m_listener.SessionManager.GetSession((session) => null != (session as GeneralWSSession)); 73 | foreach (var client in clients) 74 | await ((GeneralWSSession)client).SayHelloToGeneral(string.Format("To all general - {0}", DateTime.UtcNow.Ticks)); 75 | break; 76 | } 77 | case 2: // we will send to all "customerWsSession" connected; 78 | { 79 | var clients = m_listener.SessionManager.GetSession((session) => null != (session as CustomerWSSession)); 80 | foreach (var client in clients) 81 | await ((CustomerWSSession)client).SayHelloToCustomer(string.Format("to all customer- {0}", DateTime.UtcNow.Ticks)); 82 | break; 83 | } 84 | case 3: // we will send to all "OrderWsSession" connected; 85 | { 86 | var clients = m_listener.SessionManager.GetSession((session) => null != (session as OrderWSSession)); 87 | foreach (var client in clients) 88 | await ((OrderWSSession)client).SayHelloToOrder(string.Format("to all order - {0}", DateTime.UtcNow.Ticks)); 89 | break; 90 | } 91 | } 92 | } 93 | catch (AggregateException ae) 94 | { 95 | Trace.WriteLine("Failed to send!" + ae.InnerException.ToString()); 96 | } 97 | 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /TestStatefulSvc/TestStatefulSvc.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x64 7 | {CF4C040B-3BD4-4A3E-981E-517724CDF1DA} 8 | Exe 9 | Properties 10 | TestStatefulSvc 11 | TestStatefulSvc 12 | v4.5.1 13 | 512 14 | true 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\x64\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | x64 24 | MinimumRecommendedRules.ruleset 25 | 26 | 27 | pdbonly 28 | true 29 | bin\x64\Release\ 30 | TRACE 31 | prompt 32 | x64 33 | MinimumRecommendedRules.ruleset 34 | 35 | 36 | $(AdditionalFileItemNames);None 37 | 38 | 39 | 40 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 41 | True 42 | 43 | 44 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 45 | True 46 | 47 | 48 | ..\packages\Microsoft.ServiceFabric.Data.1.0.328-preview1\lib\net45\Microsoft.ServiceFabric.Data.dll 49 | True 50 | 51 | 52 | ..\packages\Microsoft.ServiceFabric.Services.1.0.328-preview1\lib\net45\Microsoft.ServiceFabric.Services.dll 53 | True 54 | 55 | 56 | ..\packages\Owin.1.0\lib\net40\Owin.dll 57 | True 58 | 59 | 60 | 61 | 62 | 63 | ..\packages\Microsoft.ServiceFabric.4.0.1427-preview1\lib\net45\System.Fabric.Common.Internal.dll 64 | True 65 | 66 | 67 | ..\packages\Microsoft.ServiceFabric.4.0.1427-preview1\lib\net45\System.Fabric.Common.Internal.Strings.dll 68 | True 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {6D3B3575-D199-4C47-B01E-121B023B7342} 89 | WebSocketServer 90 | 91 | 92 | {937e4754-c655-4d28-b61a-9213b28a61c4} 93 | WebSocketServer.ServiceFabric.Services 94 | 95 | 96 | 97 | 104 | -------------------------------------------------------------------------------- /TestStatefulSvc/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Utils/ILeveledTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebSocketServer.Utils 8 | { 9 | public interface ILeveledTask 10 | { 11 | string QueueId { get; set; } 12 | bool IsHighPriority { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Utils/LeveledMultiQTaskScheduler - Copy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace SocketServer.Utils 11 | { 12 | public class LeveledTask : Task 13 | { 14 | public string QueueId = string.Empty; 15 | public bool IsHighPriority = false; 16 | 17 | #region Ctors 18 | public LeveledTask(Action action) : base(action) 19 | { 20 | } 21 | 22 | public LeveledTask(Action action, object state) : base(action, state) 23 | { 24 | } 25 | 26 | public LeveledTask(Action action, TaskCreationOptions creationOptions) : base(action, creationOptions) 27 | { 28 | } 29 | 30 | public LeveledTask(Action action, CancellationToken cancellationToken) : base(action, cancellationToken) 31 | { 32 | } 33 | 34 | public LeveledTask(Action action, object state, TaskCreationOptions creationOptions) : base(action, state, creationOptions) 35 | { 36 | } 37 | 38 | public LeveledTask(Action action, object state, CancellationToken cancellationToken) : base(action, state, cancellationToken) 39 | { 40 | } 41 | 42 | public LeveledTask(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions) : base(action, cancellationToken, creationOptions) 43 | { 44 | } 45 | 46 | public LeveledTask(Action action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions) : base(action, state, cancellationToken, creationOptions) 47 | { 48 | } 49 | #endregion 50 | } 51 | public class SequentialLeveledMultiQTaskScheduler : TaskScheduler 52 | { 53 | 54 | private ConcurrentDictionary> queues 55 | = new ConcurrentDictionary>(); 56 | 57 | 58 | 59 | 60 | public const int DefaulltMinTaskBeforeYield = 10; 61 | public const int DefaultMaxTaskBeforeYield = 50; 62 | 63 | private int m_MaxTaskBeforeYield = DefaulltMinTaskBeforeYield; 64 | 65 | public int MaxTaskBeforeYield 66 | { 67 | set { 68 | if (value > DefaultMaxTaskBeforeYield) 69 | { 70 | m_MaxTaskBeforeYield = DefaultMaxTaskBeforeYield; 71 | return; 72 | } 73 | if (value < DefaulltMinTaskBeforeYield) 74 | { 75 | m_MaxTaskBeforeYield = DefaulltMinTaskBeforeYield; 76 | return; 77 | } 78 | 79 | m_MaxTaskBeforeYield = value; 80 | } 81 | get { return m_MaxTaskBeforeYield; } 82 | } 83 | 84 | 85 | 86 | 87 | private ConcurrentLinkedList GetOrAddQueue(string QueueId) 88 | { 89 | var linkedlist = queues.GetOrAdd(QueueId, new ConcurrentLinkedList()); 90 | return linkedlist; 91 | } 92 | private bool RemoveQueue(string QueueId, bool cancelTasks = true) 93 | { 94 | ConcurrentLinkedList ll; 95 | var bFound = queues.TryRemove(QueueId, out ll); 96 | throw new NotImplementedException(); 97 | 98 | /* 99 | 100 | if (bFound && cancelTasks) 101 | { 102 | ConcurrentLinkedListNode node = ll.Head; 103 | do 104 | { 105 | var leveledTask = node.Value; 106 | if(!leveledTask.IsCompleted) 107 | 108 | } 109 | while (null != (node = node.Next)); 110 | 111 | } 112 | */ 113 | 114 | 115 | 116 | 117 | return bFound; 118 | } 119 | 120 | 121 | 122 | protected override IEnumerable GetScheduledTasks() 123 | { 124 | var masterList = new List(0); 125 | 126 | foreach (var linkedlist in queues.Values) 127 | masterList.AddRange(linkedlist); 128 | 129 | return masterList; 130 | } 131 | 132 | protected override void QueueTask(Task task) 133 | { 134 | var leveledtask = task as LeveledTask; 135 | if (null == leveledtask) 136 | throw new InvalidOperationException("this leveled sequential scheduler shouldn't be used with regular Task objects"); 137 | 138 | 139 | if (leveledtask.QueueId == null || 140 | leveledtask.QueueId == string.Empty) 141 | throw new InvalidOperationException("Task scheduler received a task that does not have a queue assigned to it"); 142 | 143 | var linkedList = GetOrAddQueue(leveledtask.QueueId); 144 | 145 | if (leveledtask.IsHighPriority) 146 | linkedList.AddHead(leveledtask); 147 | else 148 | linkedList.AddTail(leveledtask); 149 | 150 | ProcessWork(leveledtask.QueueId); 151 | } 152 | 153 | private void ProcessWork(string QueueId) 154 | { 155 | 156 | 157 | var linkedlist = GetOrAddQueue(QueueId); 158 | 159 | if (0 == linkedlist.Count) 160 | return; // was already completed. 161 | 162 | 163 | ThreadPool.UnsafeQueueUserWorkItem(w => { 164 | 165 | 166 | bool bGotLock = false; 167 | Monitor.TryEnter(linkedlist, ref bGotLock); 168 | 169 | if (!bGotLock) // a thread from the thread pool is already looping on the Q. 170 | return; // at any point of time ONLY one thread is allwoed to dequeue on the Q to enable order tasks 171 | 172 | 173 | var ExecutedTasks = 0; 174 | LeveledTask leveledTask = linkedlist.RemoveHead(); 175 | // basically at minimum we pull 2 tasks even if the first is null. 176 | 177 | 178 | do 179 | { 180 | ExecutedTasks++; 181 | 182 | //Trace.WriteLine(string.Format("Q:{0} T:{1} Thread:{2}", QueueId, ExecutedTasks, Thread.CurrentThread.ManagedThreadId)); 183 | //Trace.WriteLine(string.Format("Task Null:{0}", (leveledTask == null) ? "yes" : "no")); 184 | 185 | if (leveledTask != null) 186 | { 187 | try 188 | { 189 | base.TryExecuteTask(leveledTask); 190 | } 191 | catch(Exception e) 192 | { 193 | Trace.WriteLine(string.Format("Task Executer: Error Executing Task {0} {1}", e.Message, e.StackTrace), "error"); 194 | } 195 | } 196 | 197 | } 198 | while ((ExecutedTasks <= m_MaxTaskBeforeYield && queues.Count > 1) && null != (leveledTask = linkedlist.RemoveHead())); 199 | 200 | Monitor.Exit(linkedlist); 201 | 202 | // Yielded, call it back to ensure that remaining tasks will be executed. 203 | 204 | if ((ExecutedTasks > m_MaxTaskBeforeYield && queues.Count > 1)) 205 | { 206 | Trace.WriteLine(string.Format("Queue {0} yielded thread {1} after {2} tasks", 207 | QueueId, 208 | Thread.CurrentThread.ManagedThreadId, 209 | ExecutedTasks - 1)); 210 | Task.Run(() => { 211 | ProcessWork(QueueId); // hopefully by the time this one is called 212 | // the thread has been returned to the pool and picked more 213 | // work from other queues. 214 | }); 215 | } 216 | 217 | 218 | }, null); 219 | 220 | } 221 | 222 | protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) 223 | { 224 | // if tasks got inlined it will lose this position in the queue. 225 | // so we can not afford inline tasks here 226 | return false; 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Utils/LeveledTask-T.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebSocketServer.Utils 9 | { 10 | public class LeveledTask : Task, ILeveledTask 11 | { 12 | public string QueueId { get; set; } 13 | public bool IsHighPriority { get; set; } 14 | 15 | 16 | public LeveledTask(Func function) : base(function) 17 | { 18 | } 19 | 20 | public LeveledTask(Func function, object state) : base(function, state) 21 | { 22 | } 23 | 24 | public LeveledTask(Func function, TaskCreationOptions creationOptions) : base(function, creationOptions) 25 | { 26 | } 27 | 28 | public LeveledTask(Func function, CancellationToken cancellationToken) : base(function, cancellationToken) 29 | { 30 | } 31 | 32 | public LeveledTask(Func function, object state, TaskCreationOptions creationOptions) : base(function, state, creationOptions) 33 | { 34 | } 35 | 36 | public LeveledTask(Func function, object state, CancellationToken cancellationToken) : base(function, state, cancellationToken) 37 | { 38 | } 39 | 40 | public LeveledTask(Func function, CancellationToken cancellationToken, TaskCreationOptions creationOptions) : base(function, cancellationToken, creationOptions) 41 | { 42 | } 43 | 44 | public LeveledTask(Func function, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions) : base(function, state, cancellationToken, creationOptions) 45 | { 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Utils/LeveledTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebSocketServer.Utils 9 | { 10 | public class LeveledTask : Task, ILeveledTask 11 | { 12 | public string QueueId { get; set; } 13 | public bool IsHighPriority { get; set; } 14 | 15 | #region Ctors 16 | public LeveledTask(Action action) : base(action) 17 | { 18 | } 19 | 20 | public LeveledTask(Action action, object state) : base(action, state) 21 | { 22 | } 23 | 24 | public LeveledTask(Action action, TaskCreationOptions creationOptions) : base(action, creationOptions) 25 | { 26 | } 27 | 28 | public LeveledTask(Action action, CancellationToken cancellationToken) : base(action, cancellationToken) 29 | { 30 | } 31 | 32 | public LeveledTask(Action action, object state, TaskCreationOptions creationOptions) : base(action, state, creationOptions) 33 | { 34 | } 35 | 36 | public LeveledTask(Action action, object state, CancellationToken cancellationToken) : base(action, state, cancellationToken) 37 | { 38 | } 39 | 40 | public LeveledTask(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions) : base(action, cancellationToken, creationOptions) 41 | { 42 | } 43 | 44 | public LeveledTask(Action action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions) : base(action, state, cancellationToken, creationOptions) 45 | { 46 | } 47 | #endregion 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Utils/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Utils")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Utils")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("aa3697d5-4421-4540-ad17-261975a9908e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Utils/SequentialLeveledTaskScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace WebSocketServer.Utils 11 | { 12 | 13 | public class SequentialLeveledTaskScheduler : TaskScheduler 14 | { 15 | 16 | private ConcurrentDictionary> m_HiQueues 17 | = new ConcurrentDictionary>(); 18 | 19 | private ConcurrentDictionary> m_LowQueues 20 | = new ConcurrentDictionary>(); 21 | 22 | private ConcurrentDictionary MasterQueueLocks 23 | = new ConcurrentDictionary(); 24 | 25 | 26 | public const int DefaulltMinTaskBeforeYield = 10; 27 | public const int DefaultMaxTaskBeforeYield = 50; 28 | 29 | private int m_MaxTaskBeforeYield = DefaulltMinTaskBeforeYield; 30 | 31 | public int MaxTaskBeforeYield 32 | { 33 | set { 34 | if (value > DefaultMaxTaskBeforeYield) 35 | { 36 | m_MaxTaskBeforeYield = DefaultMaxTaskBeforeYield; 37 | return; 38 | } 39 | if (value < DefaulltMinTaskBeforeYield) 40 | { 41 | m_MaxTaskBeforeYield = DefaulltMinTaskBeforeYield; 42 | return; 43 | } 44 | 45 | m_MaxTaskBeforeYield = value; 46 | } 47 | get { return m_MaxTaskBeforeYield; } 48 | } 49 | 50 | public void AddQueue(string QueueId) 51 | { 52 | var bHighPrority = true; 53 | var bAddIfNotExist = true; 54 | 55 | GetOrAddQueue(QueueId, bHighPrority, bAddIfNotExist); 56 | GetOrAddQueue(QueueId, !bHighPrority, bAddIfNotExist); 57 | } 58 | 59 | 60 | 61 | public IEnumerable GetScheduledTasks(string QueueId) 62 | { 63 | var hQ = GetOrAddQueue(QueueId, true, false); 64 | var lQ = GetOrAddQueue(QueueId, false, false); 65 | 66 | if (null == hQ || null == lQ) 67 | return null; 68 | 69 | var masterList = new List(); 70 | 71 | masterList.AddRange(hQ); 72 | masterList.AddRange(lQ); 73 | 74 | 75 | return masterList; 76 | } 77 | 78 | 79 | private ConcurrentQueue GetOrAddQueue(string QueueId, bool isHighPriority, bool addIfNotExist = true) 80 | { 81 | if (addIfNotExist) 82 | { 83 | var hiQueue = m_HiQueues.GetOrAdd(QueueId, new ConcurrentQueue()); 84 | var lowQueue = m_LowQueues.GetOrAdd(QueueId, new ConcurrentQueue()); 85 | 86 | return (isHighPriority) ? hiQueue : lowQueue; 87 | } 88 | else 89 | { 90 | var qList = (isHighPriority) ? m_HiQueues : m_LowQueues; 91 | return qList[QueueId]; 92 | } 93 | } 94 | 95 | private object GetOrAddLock(string QueueId, bool AddIfNotExist = true) 96 | { 97 | if (AddIfNotExist) 98 | { 99 | object oNewLock = new object(); 100 | var o = MasterQueueLocks.GetOrAdd(QueueId, oNewLock); 101 | return o; 102 | } 103 | else 104 | { 105 | return MasterQueueLocks[QueueId]; 106 | } 107 | 108 | } 109 | 110 | public void RemoveQueue(string QueueId) 111 | { 112 | LeveledTask lt = new LeveledTask(() => 113 | { 114 | 115 | // this will remove the Q from the list of Q 116 | // but will not null it so if we have an going exection it will just keep on going. 117 | // because the list of Q and locks no longer maintain a reference to lQ & HQ and lock 118 | // it will evantually be null 119 | 120 | Trace.WriteLine(string.Format("queue {0} will be removed", QueueId), "info"); 121 | 122 | ConcurrentQueue q; 123 | object oLock; 124 | 125 | 126 | m_HiQueues.TryRemove(QueueId, out q); 127 | m_LowQueues.TryRemove(QueueId, out q); 128 | 129 | MasterQueueLocks.TryRemove(QueueId, out oLock); 130 | 131 | }); 132 | 133 | lt.IsHighPriority = false; 134 | lt.QueueId = QueueId; 135 | lt.Start(this); 136 | 137 | } 138 | 139 | 140 | 141 | protected override IEnumerable GetScheduledTasks() 142 | { 143 | var masterList = new List(); 144 | 145 | foreach (var hqueue in m_HiQueues.Values) 146 | masterList.AddRange(hqueue); 147 | 148 | foreach (var lqueue in m_LowQueues.Values) 149 | masterList.AddRange(lqueue); 150 | return masterList; 151 | } 152 | 153 | 154 | 155 | protected override void QueueTask(Task task) 156 | { 157 | var leveledtask = TaskAsLeveledTask(task); 158 | 159 | /* 160 | 161 | if (null == leveledtask) 162 | throw new InvalidOperationException("this leveled sequential scheduler shouldn't be used with regular Task objects"); // bang! 163 | 164 | TPL favors the same schedules for the child tasks 165 | if a leveled task created another task TPL will attempt to 166 | schedule it here. 167 | */ 168 | 169 | 170 | if (null == leveledtask) 171 | { 172 | Trace.WriteLine("Sequential scheduler encounterd an unlevled task and will execute it as usual"); 173 | ProcessUnleveledTask(task); 174 | return; 175 | } 176 | 177 | 178 | 179 | if (leveledtask.QueueId == null || 180 | leveledtask.QueueId == string.Empty) 181 | throw new InvalidOperationException("Task scheduler received a task that does not have a queue assigned to it"); // bang! 182 | 183 | var Q = GetOrAddQueue(leveledtask.QueueId, leveledtask.IsHighPriority); 184 | Q.Enqueue(task); 185 | 186 | ProcessWork(leveledtask.QueueId); 187 | } 188 | 189 | private void ProcessUnleveledTask(Task task) 190 | { 191 | 192 | // regular tasks go directly to the thread pool 193 | ThreadPool.UnsafeQueueUserWorkItem(w => 194 | { 195 | try 196 | { 197 | base.TryExecuteTask(task); 198 | } 199 | catch (Exception e) 200 | { 201 | Trace.WriteLine(string.Format("Task Executer: Error Executing Unleveled Task {0} {1}", e.Message, e.StackTrace), "error"); 202 | } 203 | 204 | }, null); 205 | } 206 | 207 | private ILeveledTask TaskAsLeveledTask(Task task) 208 | { 209 | return task as ILeveledTask; 210 | } 211 | 212 | private void ProcessWork(string QueueId) 213 | { 214 | ThreadPool.UnsafeQueueUserWorkItem(w => { 215 | var oLock = GetOrAddLock(QueueId); 216 | 217 | bool bGotLock = false; 218 | Monitor.TryEnter(oLock, ref bGotLock); 219 | 220 | var hQ = GetOrAddQueue(QueueId, true); 221 | var lQ = GetOrAddQueue(QueueId, false); 222 | 223 | if (0 == hQ.Count && 0 == lQ.Count) 224 | return; // was already completed. 225 | 226 | if (!bGotLock) // a thread from the thread pool is already looping on the Q. 227 | { 228 | //Trace.WriteLine(string.Format("Scheduler attempt to acquire lock on {0} and failed", QueueId), "info"); 229 | return; // at any point of time ONLY one thread is allwoed to dequeue on the Q to enable order tasks 230 | } 231 | 232 | 233 | var ExecutedTasks = 0; 234 | 235 | 236 | while ( 237 | // should yield 238 | ExecutedTasks <= m_MaxTaskBeforeYield || 239 | // don't yeild if we have only one queue. 240 | (ExecutedTasks > m_MaxTaskBeforeYield && m_HiQueues.Count + m_LowQueues.Count == 2) || 241 | // don't yeild if this queue has been removed, drain it before dropping the reference. 242 | (ExecutedTasks > m_MaxTaskBeforeYield && (!m_HiQueues.ContainsKey(QueueId) && !m_LowQueues.ContainsKey(QueueId) ) ) 243 | ) 244 | 245 | { 246 | 247 | 248 | if (ExecutedTasks > m_MaxTaskBeforeYield && (!m_HiQueues.ContainsKey(QueueId) && !m_LowQueues.ContainsKey(QueueId))) 249 | Trace.WriteLine(string.Format("Queue {0} has been removed. Draining.. (remaining {1} tasks)", QueueId, lQ.Count + hQ.Count), "info"); 250 | 251 | 252 | Task leveledTask = null; 253 | var bFound = false; 254 | 255 | // found in High Priority Queue 256 | bFound = hQ.TryDequeue(out leveledTask); 257 | 258 | // found in Low Priority Queue 259 | if (!bFound && null == leveledTask) 260 | lQ.TryDequeue(out leveledTask); 261 | 262 | if (!bFound && null == leveledTask) //nothing here to work on 263 | break; 264 | 265 | //{ 266 | //Trace.WriteLine(string.Format("faild! count {0}/{1} queue {2}", hQ.Count, lQ.Count, QueueId), "info"); 267 | ///break; 268 | //} 269 | 270 | try 271 | { 272 | base.TryExecuteTask(leveledTask); 273 | } 274 | catch (Exception e) 275 | { 276 | Trace.WriteLine(string.Format("Task Executer: Error Executing Task {0} {1}", e.Message, e.StackTrace), "error"); 277 | } 278 | 279 | ExecutedTasks++; 280 | 281 | } 282 | 283 | if (0 == ExecutedTasks) // we were unsucessfull picking up tasks 284 | Trace.WriteLine(string.Format("Scheduler attempted to execute on queue {0} with count {1} and found 0 tasks", QueueId, lQ.Count + hQ.Count), "info"); 285 | 286 | 287 | Monitor.Exit(oLock); 288 | 289 | 290 | 291 | 292 | if ((ExecutedTasks > m_MaxTaskBeforeYield && hQ.Count + lQ.Count > 0)) 293 | { 294 | 295 | // current thread is about to be released back to the pool (and we still have more as we yielded). 296 | // call it back to ensure that remaining tasks will be executed (even if no more tasks are sent to the scheduler). 297 | Trace.WriteLine(string.Format("Queue {0} yielded thread {1} after {2} tasks", 298 | QueueId, 299 | Thread.CurrentThread.ManagedThreadId, 300 | ExecutedTasks - 1)); 301 | 302 | Task.Run(() => { ProcessWork(QueueId);} ); 303 | } 304 | 305 | 306 | 307 | }, null); 308 | } 309 | 310 | protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) 311 | { 312 | return false; 313 | 314 | // TODO: future enhancement, execute regular tasks here. 315 | } 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /Utils/WebSocketServer.Utils.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AA3697D5-4421-4540-AD17-261975A9908E} 8 | Library 9 | Properties 10 | WebSocketServer.Utils 11 | WebSocketServer.Utils 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | x64 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | true 35 | bin\x64\Debug\ 36 | DEBUG;TRACE 37 | full 38 | x64 39 | prompt 40 | MinimumRecommendedRules.ruleset 41 | 42 | 43 | bin\x64\Release\ 44 | TRACE 45 | true 46 | pdbonly 47 | x64 48 | prompt 49 | MinimumRecommendedRules.ruleset 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 76 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Clients/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WebSocketServer.ServiceFabric.Clients")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft Corporation")] 12 | [assembly: AssemblyProduct("WebSocketServer.ServiceFabric.Clients")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft Corporation 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a689c4f5-a1fe-4bc3-9f90-a7699f3668e3")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Clients/ServiceFabricWebSocketClient.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ServiceFabric.Services; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Fabric; 8 | using System.Collections.Concurrent; 9 | using System.Net.WebSockets; 10 | using System.Threading; 11 | 12 | namespace WebSocketServer.ServiceFabric.Clients 13 | { 14 | public class ServiceFabricWebSocketClient : ICommunicationClient, IDisposable 15 | { 16 | 17 | private ConcurrentDictionary m_sockets 18 | = new ConcurrentDictionary(); 19 | 20 | private bool disposedValue = false; // To detect redundant calls 21 | 22 | public string BaseAddress { get; private set; } 23 | 24 | public ResolvedServicePartition ResolvedServicePartition { get; set; } 25 | 26 | 27 | private string getAddress(string subRoute) 28 | { 29 | return string.Concat(BaseAddress, subRoute); 30 | } 31 | private Task GetAddSocket(string sAddress) 32 | { 33 | return Task.Factory.StartNew((address) => { 34 | var strAddress = (string)address; 35 | 36 | var newSocket = new ClientWebSocket(); 37 | var socket = m_sockets.AddOrUpdate(sAddress, newSocket, (key, val) => { return val; } ); 38 | 39 | // check socket 40 | if (socket.State != WebSocketState.Open) 41 | socket.ConnectAsync(new Uri(strAddress), CancellationToken.None).Wait(); 42 | 43 | return socket; 44 | }, sAddress); 45 | } 46 | 47 | 48 | public ServiceFabricWebSocketClient(string baseAddress) 49 | { 50 | BaseAddress = baseAddress; 51 | } 52 | 53 | public async Task GetWebSocketAsync() 54 | { 55 | return await GetAddSocket(BaseAddress); 56 | } 57 | 58 | public async Task GetWebSocketAsync(string subRoute) 59 | { 60 | return await GetAddSocket(getAddress(subRoute)); 61 | } 62 | 63 | public async Task CloseWebSocketAsync(string subRoute) 64 | { 65 | var socket = m_sockets[getAddress(subRoute)]; 66 | if (null != socket) 67 | await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "close!" , CancellationToken.None); 68 | } 69 | public void AbortAll() 70 | { 71 | foreach (var socket in m_sockets.Values) 72 | socket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "close!", CancellationToken.None).Wait(); 73 | } 74 | 75 | 76 | 77 | 78 | 79 | #region IDisposable Support 80 | 81 | 82 | protected virtual void Dispose(bool disposing) 83 | { 84 | if (!disposedValue) 85 | { 86 | if (disposing) 87 | { 88 | AbortAll(); 89 | } 90 | 91 | disposedValue = true; 92 | } 93 | } 94 | 95 | 96 | public void Dispose() 97 | { 98 | Dispose(true); 99 | } 100 | #endregion 101 | 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Clients/ServiceFabricWebSocketClientFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ServiceFabric.Services; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Fabric; 8 | using System.Threading; 9 | 10 | namespace WebSocketServer.ServiceFabric.Clients 11 | { 12 | public class ServiceFabricWebSocketClientFactory : CommunicationClientFactoryBase 13 | 14 | { 15 | protected override void AbortClient(ServiceFabricWebSocketClient client) 16 | { 17 | client.AbortAll(); 18 | } 19 | 20 | protected override Task CreateClientAsync(ResolvedServiceEndpoint endpoint, CancellationToken cancellationToken) 21 | { 22 | return Task.FromResult(new ServiceFabricWebSocketClient(endpoint.Address)); 23 | } 24 | 25 | protected override bool ValidateClient(ServiceFabricWebSocketClient clientChannel) 26 | { 27 | return true; // clients are valid because they depend on sockets. 28 | } 29 | 30 | protected override bool ValidateClient(ResolvedServiceEndpoint endpoint, ServiceFabricWebSocketClient client) 31 | { 32 | return (client.BaseAddress == endpoint.Address); 33 | 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Clients/WebSocketServer.ServiceFabric.Clients.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3} 8 | Library 9 | Properties 10 | WebSocketServer.ServiceFabric.Clients 11 | WebSocketServer.ServiceFabric.Clients 12 | v4.5.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | x64 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\Microsoft.ServiceFabric.Data.1.0.328-preview1\lib\net45\Microsoft.ServiceFabric.Data.dll 37 | True 38 | 39 | 40 | ..\packages\Microsoft.ServiceFabric.Services.1.0.328-preview1\lib\net45\Microsoft.ServiceFabric.Services.dll 41 | True 42 | 43 | 44 | 45 | 46 | 47 | ..\packages\Microsoft.ServiceFabric.4.0.1427-preview1\lib\net45\System.Fabric.Common.Internal.dll 48 | True 49 | 50 | 51 | ..\packages\Microsoft.ServiceFabric.4.0.1427-preview1\lib\net45\System.Fabric.Common.Internal.Strings.dll 52 | True 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 79 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Clients/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Services/MultiTypeWebSocketManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Owin; 7 | using Microsoft.ServiceFabric.Data; 8 | using System.Fabric; 9 | 10 | namespace WebSocketServer.ServiceFabric.Services 11 | { 12 | public class MultiTypeWebSocketManager : WebSocketSessionManager 13 | { 14 | internal Dictionary MappedSessions = null; 15 | 16 | private IReliableStateManager m_StateManager; 17 | private ServiceInitializationParameters m_ServiceInitializationParameters; 18 | 19 | /// 20 | /// used to match to the current request to identify which socket type 21 | /// Override if you need to match using somethig else 22 | /// (i.e. claims in a security token or a header or a query string) 23 | /// 24 | /// 25 | /// 26 | 27 | protected Type Match(IOwinContext context) 28 | { 29 | var currentRequestUrl = new Uri(string.Concat("http://someserver", 30 | context.Environment["owin.RequestPathBase"].ToString().ToLower())); 31 | var lastSegment = currentRequestUrl.Segments.Last(); 32 | 33 | 34 | 35 | // look for a last segement that matches one of our maps 36 | foreach (var pair in MappedSessions) 37 | if (lastSegment == pair.Key.ToLower()) 38 | return pair.Value; 39 | 40 | // because we are using OwinMap Route we will only 41 | // get here if we have the root address. 42 | 43 | return MappedSessions[""]; //if we are here then we need to look for a key "" or null; 44 | } 45 | 46 | public MultiTypeWebSocketManager(IReliableStateManager StateManager, 47 | ServiceInitializationParameters InitializationParameters) 48 | { 49 | m_StateManager = StateManager; 50 | m_ServiceInitializationParameters = InitializationParameters; 51 | } 52 | public override Task AcceptSocket(IOwinContext context) 53 | { 54 | if (null == MappedSessions || 0 == MappedSessions.Count) 55 | throw new InvalidOperationException("Multi types socket manager has no types"); 56 | 57 | 58 | 59 | // do wehave a socket? 60 | Action, Func, Task>> accept = null; 61 | 62 | if (!IsSocket(context, ref accept)) 63 | return Task.FromResult(false); 64 | 65 | 66 | 67 | var socketType = Match(context); 68 | 69 | if (null == socketType) 70 | return Task.FromResult(false); 71 | 72 | 73 | 74 | 75 | var newSocket = (ServiceFabricSocketSessionBase) Activator.CreateInstance( 76 | socketType, 77 | m_StateManager, 78 | context, 79 | this, 80 | m_working_cts.Token); 81 | accept(null, newSocket.SocketLoop); 82 | 83 | AddSocket(newSocket); 84 | 85 | return Task.FromResult(true); 86 | } 87 | 88 | } 89 | } -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Services/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WebSocketServer.ServiceFabric.Services")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft Corporation")] 12 | [assembly: AssemblyProduct("WebSocketServer.ServiceFabric.Services")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft Corporation 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("937e4754-c655-4d28-b61a-9213b28a61c4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Services/ServiceFabricSocketSessionBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Owin; 8 | using Microsoft.ServiceFabric.Data; 9 | 10 | namespace WebSocketServer.ServiceFabric.Services 11 | { 12 | // services as base class for service fabric sessions 13 | // sessions will need access to State Manager 14 | public abstract class ServiceFabricSocketSessionBase : WebSocketSessionBase 15 | { 16 | protected IReliableStateManager StateManager; 17 | public ServiceFabricSocketSessionBase(IReliableStateManager stateManager, 18 | IOwinContext context, 19 | IWebSocketSessionManager factory, 20 | CancellationToken cancelToken) : 21 | base(context, factory, cancelToken) 22 | { 23 | StateManager = stateManager; 24 | } 25 | 26 | 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Services/WebSocketCommunicationListener.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ServiceFabric.Services; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Fabric; 8 | using System.Threading; 9 | using Microsoft.ServiceFabric.Data; 10 | using Owin; 11 | using System.Globalization; 12 | using Microsoft.Owin.Hosting; 13 | using System.Diagnostics; 14 | 15 | namespace WebSocketServer.ServiceFabric.Services 16 | { 17 | public class WebSocketCommunicationListener : ICommunicationListener 18 | { 19 | private Dictionary m_mappedSessions = 20 | new Dictionary(); 21 | 22 | private IReliableStateManager m_ReliableStateManager; 23 | private IDisposable m_WebServer = null; 24 | private MultiTypeWebSocketManager m_WebSocketSessionsManager; 25 | private ServiceInitializationParameters m_InitializationParameters; 26 | private string m_ListeningAddress = string.Empty; 27 | private string m_PublishingAddress = string.Empty; 28 | 29 | public string ListeningAddress { get { return m_ListeningAddress; } } 30 | public string PublishingAddress { get { return m_PublishingAddress; } } 31 | 32 | public ServiceInitializationParameters InitializationParameters { get { return m_InitializationParameters; } } 33 | public Func OnCreateListeningAddress { get; set; } 34 | public Func OnCreatePublishingAddress { get; set; } 35 | 36 | public Action OnOwinPreMapping { get; set; } 37 | 38 | public Action OnOwinPostMapping { get; set; } 39 | 40 | 41 | private void EnsureStubFuncs() 42 | { 43 | if (null == OnOwinPreMapping) 44 | OnOwinPreMapping = (listener, app) => { }; 45 | 46 | if (null == OnOwinPostMapping) 47 | OnOwinPostMapping = (listener, app) => { }; 48 | 49 | if (null == OnCreateListeningAddress) 50 | OnCreateListeningAddress = (listener) => 51 | { 52 | StatefulServiceInitializationParameters statefulInitParam; 53 | 54 | var bIsStateful = (null != (statefulInitParam = listener.InitializationParameters as StatefulServiceInitializationParameters)); 55 | var port = listener.InitializationParameters.CodePackageActivationContext.GetEndpoint("ServiceEndPoint").Port; 56 | 57 | 58 | if (bIsStateful) 59 | return String.Format( 60 | CultureInfo.InvariantCulture, 61 | "http://{0}:{1}/{2}/{3}/", 62 | FabricRuntime.GetNodeContext().IPAddressOrFQDN, 63 | port, 64 | statefulInitParam.PartitionId, 65 | statefulInitParam.ReplicaId); 66 | else 67 | return String.Format( 68 | CultureInfo.InvariantCulture, 69 | "http://{0}:{1}/", 70 | FabricRuntime.GetNodeContext().IPAddressOrFQDN, 71 | port); 72 | }; 73 | 74 | 75 | if (null == OnCreatePublishingAddress) 76 | OnCreatePublishingAddress = (listener) => 77 | { 78 | // HTTPListener doesn't like WSS while clients will expect it 79 | return listener.m_ListeningAddress.Replace("http://", "ws://"); 80 | 81 | }; 82 | 83 | } 84 | 85 | 86 | public WebSocketCommunicationListener(IReliableStateManager StateManager) 87 | { 88 | m_ReliableStateManager = StateManager; 89 | } 90 | 91 | 92 | public IEnumerable> Maps 93 | { 94 | get { return m_mappedSessions.ToArray(); } 95 | } 96 | 97 | public MultiTypeWebSocketManager SessionManager 98 | { 99 | get { return m_WebSocketSessionsManager; } 100 | } 101 | 102 | public void Map(string subRoute, Type Socket) 103 | { 104 | //TODO: if connected throw 105 | 106 | if (m_mappedSessions.ContainsKey(subRoute)) 107 | throw new InvalidOperationException(string.Format("Sub route {0} is already mapped to {1}", subRoute, m_mappedSessions[subRoute].ToString())); 108 | 109 | if(!Socket.GetInterfaces().Contains(typeof(IWebSocketSession))) 110 | throw new InvalidOperationException(string.Format("Type {0} is not a web socket", Socket.ToString())); 111 | 112 | m_mappedSessions.Add(subRoute, Socket); 113 | } 114 | 115 | public void RemoveMap(string subRoute) 116 | { 117 | //TODO: if connected throw 118 | m_mappedSessions.Remove(subRoute); 119 | } 120 | 121 | 122 | public void Abort() 123 | { 124 | if (m_WebSocketSessionsManager != null) 125 | m_WebSocketSessionsManager.AbortAll(); 126 | 127 | if (null != m_WebServer) m_WebServer.Dispose(); 128 | } 129 | 130 | public async Task CloseAsync(CancellationToken cancellationToken) 131 | { 132 | if(m_WebSocketSessionsManager != null) 133 | await m_WebSocketSessionsManager.CloseAll(cancellationToken); 134 | 135 | if (null!= m_WebServer) m_WebServer.Dispose(); 136 | } 137 | 138 | public void Initialize(ServiceInitializationParameters serviceInitializationParameters) 139 | { 140 | m_WebSocketSessionsManager = new MultiTypeWebSocketManager(m_ReliableStateManager, serviceInitializationParameters); 141 | m_InitializationParameters = serviceInitializationParameters; 142 | } 143 | 144 | public Task OpenAsync(CancellationToken cancellationToken) 145 | { 146 | EnsureStubFuncs(); 147 | 148 | return Task.Factory.StartNew(function:() => 149 | { 150 | m_ListeningAddress = OnCreateListeningAddress(this); 151 | m_PublishingAddress = OnCreatePublishingAddress(this); 152 | //ensure that last char is "/" 153 | if (m_ListeningAddress[m_ListeningAddress.Length - 1] != '/') 154 | m_ListeningAddress = string.Concat(m_ListeningAddress, "/"); 155 | 156 | if (m_PublishingAddress[m_PublishingAddress.Length - 1] != '/') 157 | m_PublishingAddress = string.Concat(m_PublishingAddress, "/"); 158 | 159 | 160 | // pass the map to the session manager 161 | m_WebSocketSessionsManager.MappedSessions = m_mappedSessions; 162 | 163 | Trace.WriteLine(string.Format("Service Fabric WebSocket starting on {0}",m_ListeningAddress)); 164 | 165 | // start web server 166 | m_WebServer = WebApp.Start(m_ListeningAddress, app => 167 | { 168 | // call premap 169 | OnOwinPreMapping(this, app); 170 | 171 | 172 | var path = "/"; // Owin expects path segmenets after 173 | // listening, not at the root path. 174 | 175 | // map route for each of the maps 176 | // the root map (ie maps "") remove trailing / 177 | foreach (var pair in m_mappedSessions) 178 | { 179 | 180 | var mappedPath = string.Empty; 181 | if (string.Empty != pair.Key) 182 | mappedPath = string.Concat(path, pair.Key); 183 | else 184 | mappedPath = path.Substring(0, path.Length - 1); // remove trailing '/' 185 | 186 | app.MapWebSocketRoute 187 | (mappedPath, m_WebSocketSessionsManager); 188 | 189 | Trace.WriteLine(string.Format("{0} mapped to {1}", mappedPath, pair.Value.ToString())); 190 | } 191 | 192 | OnOwinPostMapping(this, app); 193 | // call post map 194 | } 195 | ); 196 | return m_PublishingAddress; 197 | }); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Services/WebSocketServer.ServiceFabric.Services.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {937E4754-C655-4D28-B61A-9213B28A61C4} 8 | Library 9 | Properties 10 | WebSocketServer.ServiceFabric.Services 11 | WebSocketServer.ServiceFabric.Services 12 | v4.5.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | x64 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 37 | True 38 | 39 | 40 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 41 | True 42 | 43 | 44 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 45 | True 46 | 47 | 48 | ..\packages\Microsoft.ServiceFabric.Data.1.0.328-preview1\lib\net45\Microsoft.ServiceFabric.Data.dll 49 | True 50 | 51 | 52 | ..\packages\Microsoft.ServiceFabric.Services.1.0.328-preview1\lib\net45\Microsoft.ServiceFabric.Services.dll 53 | True 54 | 55 | 56 | ..\packages\Owin.1.0\lib\net40\Owin.dll 57 | True 58 | 59 | 60 | 61 | 62 | 63 | ..\packages\Microsoft.ServiceFabric.4.0.1427-preview1\lib\net45\System.Fabric.Common.Internal.dll 64 | True 65 | 66 | 67 | ..\packages\Microsoft.ServiceFabric.4.0.1427-preview1\lib\net45\System.Fabric.Common.Internal.Strings.dll 68 | True 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | {6d3b3575-d199-4c47-b01e-121b023b7342} 91 | WebSocketServer 92 | 93 | 94 | 95 | 102 | -------------------------------------------------------------------------------- /WebSocketServer.ServiceFabric.Services/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /WebSocketServer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketServer.Utils", "Utils\WebSocketServer.Utils.csproj", "{AA3697D5-4421-4540-AD17-261975A9908E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketServer", "SocketServer\WebSocketServer.csproj", "{6D3B3575-D199-4C47-B01E-121B023B7342}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketServer.Tests", "SocketServer.Tests\WebSocketServer.Tests.csproj", "{6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ServiceFabric", "ServiceFabric", "{E1B1967C-F6B8-4999-B620-94B904D7C4DB}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketServer.ServiceFabric.Services", "WebSocketServer.ServiceFabric.Services\WebSocketServer.ServiceFabric.Services.csproj", "{937E4754-C655-4D28-B61A-9213B28A61C4}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ServiceSample", "ServiceSample", "{C1E31623-4A04-4A90-A6C2-071DF7AFF400}" 17 | EndProject 18 | Project("{A07B5EB6-E848-4116-A8D0-A826331D98C6}") = "TestApp", "TestApp\TestApp.sfproj", "{029CCF61-A058-4DDB-8C9D-34BB7A177530}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestStatefulSvc", "TestStatefulSvc\TestStatefulSvc.csproj", "{CF4C040B-3BD4-4A3E-981E-517724CDF1DA}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ClientSample", "ClientSample", "{3C58649E-53D8-4E9E-A431-8F2F1C58C88B}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "TestClient\TestClient.csproj", "{076EE734-806E-467F-88EA-3F79E8D23E9A}" 25 | EndProject 26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketServer.ServiceFabric.Clients", "WebSocketServer.ServiceFabric.Clients\WebSocketServer.ServiceFabric.Clients.csproj", "{A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}" 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Debug|x64 = Debug|x64 32 | Release|Any CPU = Release|Any CPU 33 | Release|x64 = Release|x64 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {AA3697D5-4421-4540-AD17-261975A9908E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {AA3697D5-4421-4540-AD17-261975A9908E}.Debug|x64.ActiveCfg = Debug|x64 38 | {AA3697D5-4421-4540-AD17-261975A9908E}.Debug|x64.Build.0 = Debug|x64 39 | {AA3697D5-4421-4540-AD17-261975A9908E}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {AA3697D5-4421-4540-AD17-261975A9908E}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {AA3697D5-4421-4540-AD17-261975A9908E}.Release|x64.ActiveCfg = Release|x64 42 | {AA3697D5-4421-4540-AD17-261975A9908E}.Release|x64.Build.0 = Release|x64 43 | {6D3B3575-D199-4C47-B01E-121B023B7342}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {6D3B3575-D199-4C47-B01E-121B023B7342}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {6D3B3575-D199-4C47-B01E-121B023B7342}.Debug|x64.ActiveCfg = Debug|x64 46 | {6D3B3575-D199-4C47-B01E-121B023B7342}.Debug|x64.Build.0 = Debug|x64 47 | {6D3B3575-D199-4C47-B01E-121B023B7342}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {6D3B3575-D199-4C47-B01E-121B023B7342}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {6D3B3575-D199-4C47-B01E-121B023B7342}.Release|x64.ActiveCfg = Release|x64 50 | {6D3B3575-D199-4C47-B01E-121B023B7342}.Release|x64.Build.0 = Release|x64 51 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}.Debug|x64.ActiveCfg = Debug|Any CPU 54 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}.Debug|x64.Build.0 = Debug|Any CPU 55 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}.Release|x64.ActiveCfg = Release|Any CPU 58 | {6FBEFFC2-DBC2-414B-80C2-8DA72B4EB604}.Release|x64.Build.0 = Release|Any CPU 59 | {937E4754-C655-4D28-B61A-9213B28A61C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {937E4754-C655-4D28-B61A-9213B28A61C4}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {937E4754-C655-4D28-B61A-9213B28A61C4}.Debug|x64.ActiveCfg = Debug|Any CPU 62 | {937E4754-C655-4D28-B61A-9213B28A61C4}.Debug|x64.Build.0 = Debug|Any CPU 63 | {937E4754-C655-4D28-B61A-9213B28A61C4}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {937E4754-C655-4D28-B61A-9213B28A61C4}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {937E4754-C655-4D28-B61A-9213B28A61C4}.Release|x64.ActiveCfg = Release|Any CPU 66 | {937E4754-C655-4D28-B61A-9213B28A61C4}.Release|x64.Build.0 = Release|Any CPU 67 | {029CCF61-A058-4DDB-8C9D-34BB7A177530}.Debug|Any CPU.ActiveCfg = Debug|x64 68 | {029CCF61-A058-4DDB-8C9D-34BB7A177530}.Debug|x64.ActiveCfg = Debug|x64 69 | {029CCF61-A058-4DDB-8C9D-34BB7A177530}.Debug|x64.Build.0 = Debug|x64 70 | {029CCF61-A058-4DDB-8C9D-34BB7A177530}.Debug|x64.Deploy.0 = Debug|x64 71 | {029CCF61-A058-4DDB-8C9D-34BB7A177530}.Release|Any CPU.ActiveCfg = Release|x64 72 | {029CCF61-A058-4DDB-8C9D-34BB7A177530}.Release|x64.ActiveCfg = Release|x64 73 | {029CCF61-A058-4DDB-8C9D-34BB7A177530}.Release|x64.Build.0 = Release|x64 74 | {029CCF61-A058-4DDB-8C9D-34BB7A177530}.Release|x64.Deploy.0 = Release|x64 75 | {CF4C040B-3BD4-4A3E-981E-517724CDF1DA}.Debug|Any CPU.ActiveCfg = Debug|x64 76 | {CF4C040B-3BD4-4A3E-981E-517724CDF1DA}.Debug|x64.ActiveCfg = Debug|x64 77 | {CF4C040B-3BD4-4A3E-981E-517724CDF1DA}.Debug|x64.Build.0 = Debug|x64 78 | {CF4C040B-3BD4-4A3E-981E-517724CDF1DA}.Release|Any CPU.ActiveCfg = Release|x64 79 | {CF4C040B-3BD4-4A3E-981E-517724CDF1DA}.Release|x64.ActiveCfg = Release|x64 80 | {CF4C040B-3BD4-4A3E-981E-517724CDF1DA}.Release|x64.Build.0 = Release|x64 81 | {076EE734-806E-467F-88EA-3F79E8D23E9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 82 | {076EE734-806E-467F-88EA-3F79E8D23E9A}.Debug|Any CPU.Build.0 = Debug|Any CPU 83 | {076EE734-806E-467F-88EA-3F79E8D23E9A}.Debug|x64.ActiveCfg = Debug|Any CPU 84 | {076EE734-806E-467F-88EA-3F79E8D23E9A}.Debug|x64.Build.0 = Debug|Any CPU 85 | {076EE734-806E-467F-88EA-3F79E8D23E9A}.Release|Any CPU.ActiveCfg = Release|Any CPU 86 | {076EE734-806E-467F-88EA-3F79E8D23E9A}.Release|Any CPU.Build.0 = Release|Any CPU 87 | {076EE734-806E-467F-88EA-3F79E8D23E9A}.Release|x64.ActiveCfg = Release|Any CPU 88 | {076EE734-806E-467F-88EA-3F79E8D23E9A}.Release|x64.Build.0 = Release|Any CPU 89 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 90 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 91 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}.Debug|x64.ActiveCfg = Debug|Any CPU 92 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}.Debug|x64.Build.0 = Debug|Any CPU 93 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 94 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}.Release|Any CPU.Build.0 = Release|Any CPU 95 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}.Release|x64.ActiveCfg = Release|Any CPU 96 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3}.Release|x64.Build.0 = Release|Any CPU 97 | EndGlobalSection 98 | GlobalSection(SolutionProperties) = preSolution 99 | HideSolutionNode = FALSE 100 | EndGlobalSection 101 | GlobalSection(NestedProjects) = preSolution 102 | {937E4754-C655-4D28-B61A-9213B28A61C4} = {E1B1967C-F6B8-4999-B620-94B904D7C4DB} 103 | {C1E31623-4A04-4A90-A6C2-071DF7AFF400} = {E1B1967C-F6B8-4999-B620-94B904D7C4DB} 104 | {029CCF61-A058-4DDB-8C9D-34BB7A177530} = {C1E31623-4A04-4A90-A6C2-071DF7AFF400} 105 | {CF4C040B-3BD4-4A3E-981E-517724CDF1DA} = {C1E31623-4A04-4A90-A6C2-071DF7AFF400} 106 | {3C58649E-53D8-4E9E-A431-8F2F1C58C88B} = {E1B1967C-F6B8-4999-B620-94B904D7C4DB} 107 | {076EE734-806E-467F-88EA-3F79E8D23E9A} = {3C58649E-53D8-4E9E-A431-8F2F1C58C88B} 108 | {A689C4F5-A1FE-4BC3-9F90-A7699F3668E3} = {E1B1967C-F6B8-4999-B620-94B904D7C4DB} 109 | EndGlobalSection 110 | EndGlobal 111 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | #WebSocket Server for Owin (& Azure Service Fabric) # 2 | 3 | This repo contains components that implement a Web Socket Server (.NET) for Owin. The web socket server uses Owin Startup routine, allowing you to have additional Owin stages before or after the web socket server stage. The web socket server contains a session manager that allows hosting process to interact with active web socket sessions (for example server side broadcast messages). The sessions are implemented on top of a sequential queue (per each) that surfaces granular control on messages going to the down stream (details discussed [here](http://henidak.com/2015/08/web-socket-p1/)). 4 | 5 | This is further extended for Azure Service Fabric specific hosting. 6 | 7 | 8 | 9 | ## Using it with Owin## 10 | **Typical Usage Pattern** 11 | 12 | 1. Create your socket types (subclass-ing *WebSocketSessionBase*). Each represents a session type. For example "Customer". They can expose business logic specific methods such as SendPOComplete. Each may result into one or more messages send to the down stream. 13 | 14 | 2. The server instantiate a new instance of socket type for every incoming new connection. 15 | 16 | 3. You can also expose server side messages that *OnReceiveAsync* method can route to. 17 | 18 | 4. Optionally implement a session manager (subclass-ing *WebSocketSessionManager*) or use the out of the box manager *WebSocketSessionManager* (Example: please refer to the code below for a custom manager that maps different socket types to different Owin routes) 19 | 20 | 5. Call one of the many Owin extension methods included in the library. 21 | 22 | The below code uses the web socket server with a self hosted Owin server 23 | 24 | 25 | _echoFactory = new TestWebSocketSessionFactory(); 26 | 27 | IDisposable server = WebApp.Start(_serverAddress, app => 28 | { 29 | app.MapWebSocket, TestWebSocketSession>(_echoFactory); 30 | } 31 | ); 32 | 33 | 34 | 35 | > Check the unit test project for the complete sample code. 36 | 37 | 38 | - The Session Manager (referred to as factory in test project) contains a reference to all active sockets currently connected. Server can interact with the via *GetSession(predicate Func)* method. For example a server process - which hosts the web socket server - can find all active sessions matching a certain criteria then call methods to send messages on the downstream. For further details check the code below. 39 | 40 | - Socket session class implements a Post & a Send methods. Send puts the message at the beginning of the downstream queue while Post puts it at the end. Both depends on the custom task scheduler discussed below. 41 | 42 | - Socket Session & Session Manager implement IDisposable and will close sessions(s) when they are GCed or Disposed. 43 | 44 | - Socket session implement close (async), abort (sync) and DrainAndClose which waits for the messages on the downstream (prior to the drain event) to be sent to connected client before closing the session. 45 | 46 | - The package contains a custom Owin middle-ware and extension methods that allows you to quickly integrate it in your Owin pipeline. 47 | 48 | 49 | ### Interacting with Connected Web Sockets### 50 | 51 | You can use the SessionManager instance to interact with all the connected web sockets that was created by the manager (as a result of Owin pipeline calls) as the following 52 | 53 | 54 | //m_Manager is a session manager attached to Owin as the above code. 55 | // this returns all sockets 56 | m_Manager.GetSession((session) => true); 57 | 58 | // GeneralWSSession is a class that implements WebSocketSessionBase 59 | reach (var client in clients) 60 | await ((GeneralWSSession)client).SayHelloToGeneral(string.Format("To all general - {0}", DateTime.UtcNow.Ticks)); 61 | 62 | 63 | 64 | ## Using it with Service Fabric ## 65 | WebSocketServer.ServiceFabric.Services library contain classes that you need to run the web socket server in context of Service Fabric. The session manager Service Fabric uses enables you manage sockets of different types (mapped to different addresses). 66 | 67 | **Typical Usage Pattern (Services Side):** 68 | 69 | - Create classes that implement your web socket (subclass-ing ServiceFabricSocketSessionBase class). For example i have 3 General, Customer & Order each implements a different socket (the entire code is in sample service and sample client projects). 70 | 71 | 72 | - Use WebSocketCommunicationListener in your service as the following 73 | 74 | 75 | protected override ICommunicationListener CreateCommunicationListener() 76 | { 77 | m_listener = new WebSocketCommunicationListener(StateManager); 78 | // map to any type that implements ServiceFabricWebSocketSessionBase 79 | m_listener.Map("customer", typeof(CustomerWSSession)); // mapped to /customer 80 | m_listener.Map("order", typeof(OrderWSSession)); // mapped to /order 81 | m_listener.Map("", typeof(GeneralWSSession)); // mapped to / 82 | 83 | 84 | // you can use the above to filter sockets based on the replica type 85 | // for example primaries can have different socket types than seconaries. 86 | 87 | 88 | // Listening address is what the server actually listen to 89 | // publishing address is what is returned to Fabric runtime (commnunicated as EndPoint.Address on client side) 90 | /* 91 | if you want to control how listening and publishing addresses are created 92 | m_listener.OnCreateListeningAddress = (listener) => { << return my listening address here >>} 93 | m_listener.OnCreatePublishingAddress = (listener) => { << return my Publishing ddress here >>} 94 | */ 95 | 96 | /* 97 | if you want to add more OWIN stuff pre or post web socket stages 98 | m_listener.OnOwinPreMapping = (listener, appbuilder) => { << appbuilder.UseXX >>} 99 | m_listener.OnOwinPostMapping = (listener, appbuilder) => { << appbuilder.UseXX >>} 100 | */ 101 | 102 | 103 | return m_listener; 104 | } 105 | 106 | 107 | >You can also map just one socket implementation. 108 | 109 | 110 | **Notes** 111 | - The listener injects Service's IReliableStateManager into the sockets. This enables you to call reliable collections in your sockets. 112 | 113 | - Stateless services can use null during listener creation (for reliable state). 114 | 115 | - The listener also exposes Owin Startup to you so you can use *OnOwinPreMapping* and *OnOwinPostMapping* method to wire up any other custom Owin middleware (for example wiring up ADAL, a custom request logger or even other Owin components such as WebApi). 116 | 117 | - The listener maintains a reference to the Session Manager where you can access it from anywhere in your service (for example in OnRunAsync method) as the following: 118 | 119 | 120 | 121 | // m_listener is an instance of WebSocketCommunicationListener 122 | 123 | // the predicate below can be anything that filters sessions 124 | var clients = m_listener.SessionManager.GetSession((session) => null != (session as GeneralWSSession)); 125 | 126 | foreach (var client in clients) 127 | await ((GeneralWSSession)client).SayHelloToGeneral(string.Format("To all general - {0}", DateTime.UtcNow.Ticks)); 128 | 129 | // custom session types can have fields such as CustomerType which cna be used for filtering 130 | 131 | 132 | - Service Fabric extends the default session manager to implement a multi-type session manager(instead of the 1:1 session manager used in the underlying Owin). 133 | 134 | > check the TestStatefulSvc project for the complete sample code. 135 | 136 | 137 | **Typical Usage Pattern (Client Side):** 138 | 139 | On the client side you can use standard ClientWebSocket (of System.Net.WebSockets namespace). You can use them using standard service resolution approach or you can use *ServiceFabricWebSocketClient* & *ServiceFabricWebSocketClientFactory* together they implement Service Fabric *ICommunicationClient* & *CommunicationClientFactoryBase*. Those have the following Characterstics: 140 | 141 | 142 | 1. The client implements IDisposable and closes all the connected sockets when disposing. 143 | 2. The client can switch socket type via GetSocket method. 144 | 3. The client makes sure that only one socket of each type is connected (and will recycle sockets between GetScoket calls, no new sockets will be created). 145 | 146 | ## Of Interest: Sequential Multi Q Task Scheduler ## 147 | In order to allow ordered messages on the down stream. The web socket server sets on top of a custom task scheduler. The task scheduler allows the following: 148 | 149 | 1. Execute Tasks in order per queue. 150 | 2. One scheduler can manage N number of queues. 151 | 3. Put task at the end of the queue or at the Beginning of the queue (low priority vs high priority). 152 | 4. Because it is built using the .NET TPL you can use standard await/When/ContinueWith constructs. 153 | 154 | 155 | 156 | **Typical Usage:** 157 | 158 | 159 | LeveledTask lt = new LeveledTask( () => 160 | { 161 | 162 | return DateTime.Now.Ticks; 163 | }); 164 | 165 | lt.QueueId = "Q01"; // which queue this task belongs to 166 | lt.IsHighPriority = true; // put at the beginning of the q 167 | lt.Start(scheduler); 168 | 169 | await lt; 170 | 171 | 172 | There is also LeveledTask for tasks that does not return results. 173 | 174 | > Please review TaskScheduler.Tests.cs in WebSocketServer.Tests project for further samples. 175 | 176 | 177 | #What is in the Package# 178 | 179 | 1. *WebSocketServer* Project contains the implementation of Web Socket Server on Owin. 180 | 2. *WebSocketServer.Utils* contains the implementation of the Custom Task Scheduler (can be used on its own). 181 | 3. *WebSocketServer.ServiceFabric.Services* extends the web socket server for Service Fabric hosting. 182 | 4. *WebSocketServer.ServiceFabric.Clients* extends Service Fabric client interfaces for web sockets communications (can be used on its own). 183 | 5. *WebSocketServer.Tests* contains the unit tests that verifies the entire thing (except Service Fabric that is verified in the sample). 184 | 6. Additionally: 185 | 1. Sample Service Fabric Service 186 | 2. Sample Service Fabric Client --------------------------------------------------------------------------------