├── .gitignore ├── GladNet.sln ├── LICENSE ├── MyGet.bat ├── README.md ├── clear-bin.bat ├── lib └── Unity2017 │ └── UnityEngine.dll ├── post-MyGet.ps1 ├── src ├── GladNet.API.AutoFac │ ├── GladNet.API.AutoFac.csproj │ └── Modules │ │ ├── AssemblyMessageHandlerServiceModule.cs │ │ ├── Dispatching │ │ └── InPlaceMessageDispatchingServiceModule.cs │ │ ├── GameMessageHandlerServiceModule.cs │ │ └── GameMessagingServicesModule.cs ├── GladNet.API.Client │ ├── GladNet.API.Client.csproj │ └── Session │ │ └── SessionStarter.cs ├── GladNet.API.Server │ ├── Application │ │ ├── GladNetServerApplication.cs │ │ └── IServerApplicationBase.cs │ ├── Events │ │ └── ManagedSessionContextualEventArgs.cs │ └── GladNet.API.Server.csproj ├── GladNet.API.TCP │ ├── GladNet.API.TCP.csproj │ ├── Message │ │ └── SocketConnectionNetworkMessageInterface.cs │ ├── Network │ │ └── SocketConnectionConnectionServiceAdapter.cs │ └── Session │ │ ├── BaseTcpManagedSession.cs │ │ └── SessionCreationContext.cs ├── GladNet.API.WebSocket.DotNetWebSocket │ ├── Connection │ │ └── DotNetWebSocketConnection.cs │ └── GladNet.API.WebSocket.DotNetWebSocket.csproj ├── GladNet.API.WebSocket.UnityWebGL │ ├── Connection │ │ ├── IUnityWebGLWebSocket.cs │ │ ├── UnityWebGLWebSocketFactory.cs │ │ ├── WebGLMessageSendService.cs │ │ └── WebGLWebSocketConnection.cs │ ├── Events │ │ ├── WebSocketCloseEventHandler.cs │ │ ├── WebSocketErrorEventHandler.cs │ │ ├── WebSocketMessageEventHandler.cs │ │ └── WebSocketOpenEventHandler.cs │ ├── Extensions │ │ └── UnityWebGLWebSocketHelpers.cs │ └── GladNet.API.WebSocket.UnityWebGL.csproj ├── GladNet.API.WebSocket │ ├── Connection │ │ └── IWebSocketConnection.cs │ ├── GladNet.API.WebSocket.csproj │ ├── Message │ │ └── WebSocketConnectionNetworkMessageInterface.cs │ ├── Network │ │ └── WebSocketConnectionConnectionServiceAdapter.cs │ └── Session │ │ ├── BaseClientWebSocketManagedSession.cs │ │ └── SessionCreationContext.cs ├── GladNet.API │ ├── Collections │ │ ├── AsyncExProducerConsumerQueueAsyncMessageQueue.cs │ │ └── IAsyncMessageQueue.cs │ ├── Constants │ │ └── NetworkConnectionOptionsConstants.cs │ ├── Extensions │ │ ├── IDisposableAttachableExtensions.cs │ │ └── IMessageSendServiceExtensions.cs │ ├── GladNet.API.csproj │ ├── Message │ │ ├── Dispatching │ │ │ ├── INetworkMessageDispatchingStrategy.cs │ │ │ └── InPlaceNetworkMessageDispatchingStrategy.cs │ │ ├── Header │ │ │ ├── HeaderlessPacketHeader.cs │ │ │ ├── IPacketHeader.cs │ │ │ ├── IPacketHeaderFactory.cs │ │ │ ├── PacketHeaderCreationContext.cs │ │ │ └── PacketHeaderSerializationContext.cs │ │ ├── INetworkOutgoingMessage.cs │ │ ├── IPeerMessageContext.cs │ │ ├── IPeerSessionMessageContext.cs │ │ ├── NetworkIncomingMessage.cs │ │ ├── SendResult.cs │ │ ├── Serialization │ │ │ ├── IMessageDeserializer.cs │ │ │ └── IMessageSerializer.cs │ │ ├── Services │ │ │ ├── IMessageSendService.cs │ │ │ ├── INetworkMessageInterface.cs │ │ │ ├── INetworkMessageReceivable.cs │ │ │ └── QueueBasedMessageSendService.cs │ │ └── SessionMessageContext.cs │ ├── Network │ │ ├── INetworkConnectable.cs │ │ ├── INetworkDisconnectable.cs │ │ ├── NetworkAddressInfo.cs │ │ └── NetworkConnectionOptions.cs │ ├── Service │ │ └── INetworkConnectionService.cs │ └── Session │ │ ├── IManagedSession.cs │ │ ├── ManagedSession.cs │ │ ├── ManagedSessionGeneric.cs │ │ ├── ServiceContainers │ │ ├── SessionMessageBuildingServiceContext.cs │ │ └── SessionMessageInterfaceServiceContext.cs │ │ └── SessionDetails.cs ├── GladNet.Client.DotNetTcpClient │ ├── GladNet.Client.DotNetTcpClient.csproj │ ├── Memory │ │ └── GladNetPipeMemoryPool.cs │ └── TCPSocketConnectionFactory.cs ├── GladNet.Server.DotNetTcpServer │ ├── GladNet.Server.DotNetTcpServer.csproj │ └── TcpGladNetServerApplication.cs └── GladNet.Server.DotNetWebSocketServer │ ├── GladNet.Server.DotNetWebSocketServer.csproj │ └── WebSocketGladNetServerApplication.cs └── tests ├── GladNet.DotNetTcpClient.EchoTest ├── ConsoleLogger.cs ├── GladNet.DotNetTcpClient.EchoTest.csproj ├── Network │ ├── Handlers │ │ └── DefaultStringMessageHandler.cs │ ├── Session │ │ ├── StringMessagePacketHeaderFactory.cs │ │ ├── StringMessageSerializer.cs │ │ └── StringPacketHeaderSerializer.cs │ └── TCPEchoClientSession.cs └── Program.cs ├── GladNet.DotNetTcpServer.EchoTest ├── ConsoleLogger.cs ├── GladNet.DotNetTcpServer.EchoTest.csproj ├── Network │ ├── Handlers │ │ └── DefaultStringMessageHandler.cs │ ├── Session │ │ ├── StringMessagePacketHeaderFactory.cs │ │ ├── StringMessageSerializer.cs │ │ └── StringPacketHeaderSerializer.cs │ ├── TCPEchoGladNetServerApplication.cs │ └── TCPEchoManagedSession.cs └── Program.cs └── GladNet.WebSocket.EchoTest ├── GladNet.WebSocket.EchoTest.csproj └── Program.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | push-nuget.ps1 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | build/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]ib/Dependency Builds/ 27 | 28 | # Visual Studo 2015 cache/options directory 29 | .vs/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | *_i.c 45 | *_p.c 46 | *_i.h 47 | *.ilk 48 | *.meta 49 | *.obj 50 | *.pch 51 | *.pdb 52 | *.pgc 53 | *.pgd 54 | *.rsp 55 | *.sbr 56 | *.tlb 57 | *.tli 58 | *.tlh 59 | *.tmp 60 | *.tmp_proj 61 | *.log 62 | *.vspscc 63 | *.vssscc 64 | .builds 65 | *.pidb 66 | *.svclog 67 | *.scc 68 | 69 | # Chutzpah Test files 70 | _Chutzpah* 71 | 72 | # Visual C++ cache files 73 | ipch/ 74 | *.aps 75 | *.ncb 76 | *.opensdf 77 | *.sdf 78 | *.cachefile 79 | 80 | # Visual Studio profiler 81 | *.psess 82 | *.vsp 83 | *.vspx 84 | 85 | # TFS 2012 Local Workspace 86 | $tf/ 87 | 88 | # Guidance Automation Toolkit 89 | *.gpState 90 | 91 | # ReSharper is a .NET coding add-in 92 | _ReSharper*/ 93 | *.[Rr]e[Ss]harper 94 | *.DotSettings.user 95 | 96 | # JustCode is a .NET coding addin-in 97 | .JustCode 98 | 99 | # TeamCity is a build add-in 100 | _TeamCity* 101 | 102 | # DotCover is a Code Coverage Tool 103 | *.dotCover 104 | 105 | # NCrunch 106 | _NCrunch_* 107 | .*crunch*.local.xml 108 | *.ncrunch* 109 | nCrunchTemp_* 110 | 111 | # MightyMoose 112 | *.mm.* 113 | AutoTest.Net/ 114 | 115 | # Web workbench (sass) 116 | .sass-cache/ 117 | 118 | # Installshield output folder 119 | [Ee]xpress/ 120 | 121 | # DocProject is a documentation generator add-in 122 | DocProject/buildhelp/ 123 | DocProject/Help/*.HxT 124 | DocProject/Help/*.HxC 125 | DocProject/Help/*.hhc 126 | DocProject/Help/*.hhk 127 | DocProject/Help/*.hhp 128 | DocProject/Help/Html2 129 | DocProject/Help/html 130 | 131 | # Click-Once directory 132 | publish/ 133 | 134 | # Publish Web Output 135 | *.[Pp]ublish.xml 136 | *.azurePubxml 137 | # TODO: Comment the next line if you want to checkin your web deploy settings 138 | # but database connection strings (with potential passwords) will be unencrypted 139 | *.pubxml 140 | *.publishproj 141 | 142 | # NuGet Packages 143 | *.nupkg 144 | # The packages folder can be ignored because of Package Restore 145 | **/packages/* 146 | # except build/, which is used as an MSBuild target. 147 | !**/packages/build/ 148 | # Uncomment if necessary however generally it will be regenerated when needed 149 | #!**/packages/repositories.config 150 | 151 | # Windows Azure Build Output 152 | csx/ 153 | *.build.csdef 154 | 155 | # Windows Store app package directory 156 | AppPackages/ 157 | 158 | # Others 159 | *.[Cc]ache 160 | ClientBin/ 161 | [Ss]tyle[Cc]op.* 162 | ~$* 163 | *~ 164 | *.dbmdl 165 | *.dbproj.schemaview 166 | *.publishsettings 167 | node_modules/ 168 | bower_components/ 169 | 170 | # RIA/Silverlight projects 171 | Generated_Code/ 172 | 173 | # Backup & report files from converting an old project file 174 | # to a newer Visual Studio version. Backup files are not needed, 175 | # because we have git ;-) 176 | _UpgradeReport_Files/ 177 | Backup*/ 178 | UpgradeLog*.XML 179 | UpgradeLog*.htm 180 | 181 | # SQL Server files 182 | *.mdf 183 | *.ldf 184 | 185 | # Business Intelligence projects 186 | *.rdl.data 187 | *.bim.layout 188 | *.bim_*.settings 189 | 190 | # Microsoft Fakes 191 | FakesAssemblies/ 192 | 193 | # Node.js Tools for Visual Studio 194 | .ntvs_analysis.dat 195 | 196 | # Visual Studio 6 build log 197 | *.plg 198 | 199 | # Visual Studio 6 workspace options file 200 | *.opt 201 | -------------------------------------------------------------------------------- /GladNet.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{82223BC2-FD46-45B7-8714-A1BBB6FACC0E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.API", "src\GladNet.API\GladNet.API.csproj", "{1B6D4F01-C4BB-4242-89F9-F17527BF442E}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.API.Server", "src\GladNet.API.Server\GladNet.API.Server.csproj", "{26682A85-BEEC-4D96-A843-0925890E472B}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.Server.DotNetTcpServer", "src\GladNet.Server.DotNetTcpServer\GladNet.Server.DotNetTcpServer.csproj", "{A8243286-3CB8-4B81-8379-A61DEAA39D09}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EE396D2F-98B0-46DC-851E-8A52A4C46E6B}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.DotNetTcpServer.EchoTest", "tests\GladNet.DotNetTcpServer.EchoTest\GladNet.DotNetTcpServer.EchoTest.csproj", "{E6C56850-6930-4863-88AA-7FAC7592E70B}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.API.Client", "src\GladNet.API.Client\GladNet.API.Client.csproj", "{28DB2EDC-FA18-4F55-8AD9-3B5DE543F519}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.API.TCP", "src\GladNet.API.TCP\GladNet.API.TCP.csproj", "{E9F23B76-BCCE-41A3-A4AB-CB6FB36C6B5F}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.Client.DotNetTcpClient", "src\GladNet.Client.DotNetTcpClient\GladNet.Client.DotNetTcpClient.csproj", "{BA324EF9-0A69-42CC-B771-3A4200BEC3CE}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.DotNetTcpClient.EchoTest", "tests\GladNet.DotNetTcpClient.EchoTest\GladNet.DotNetTcpClient.EchoTest.csproj", "{F349568E-02A0-4B46-9AD6-B78C2DBF5240}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.API.AutoFac", "src\GladNet.API.AutoFac\GladNet.API.AutoFac.csproj", "{269BD7A7-3E02-4FE5-AC58-79475DB97E7D}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.API.WebSocket", "src\GladNet.API.WebSocket\GladNet.API.WebSocket.csproj", "{98542DA6-E718-4249-976F-DFAF30537398}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.WebSocket.EchoTest", "tests\GladNet.WebSocket.EchoTest\GladNet.WebSocket.EchoTest.csproj", "{4EB331A8-C2C8-4EA4-BFE9-1C5682FA6266}" 31 | EndProject 32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.Server.DotNetWebSocketServer", "src\GladNet.Server.DotNetWebSocketServer\GladNet.Server.DotNetWebSocketServer.csproj", "{94924166-0227-4884-8313-7D528B958AA4}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.API.WebSocket.DotNetWebSocket", "src\GladNet.API.WebSocket.DotNetWebSocket\GladNet.API.WebSocket.DotNetWebSocket.csproj", "{A329F06E-F71D-4D65-9CBD-C97CA3DEB799}" 35 | EndProject 36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GladNet.API.WebSocket.UnityWebGL", "src\GladNet.API.WebSocket.UnityWebGL\GladNet.API.WebSocket.UnityWebGL.csproj", "{678A2755-69F1-4EE3-AE39-754D812048FF}" 37 | EndProject 38 | Global 39 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 40 | Debug|Any CPU = Debug|Any CPU 41 | Release|Any CPU = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 44 | {1B6D4F01-C4BB-4242-89F9-F17527BF442E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {1B6D4F01-C4BB-4242-89F9-F17527BF442E}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {1B6D4F01-C4BB-4242-89F9-F17527BF442E}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {1B6D4F01-C4BB-4242-89F9-F17527BF442E}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {26682A85-BEEC-4D96-A843-0925890E472B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {26682A85-BEEC-4D96-A843-0925890E472B}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {26682A85-BEEC-4D96-A843-0925890E472B}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {26682A85-BEEC-4D96-A843-0925890E472B}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {A8243286-3CB8-4B81-8379-A61DEAA39D09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {A8243286-3CB8-4B81-8379-A61DEAA39D09}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {A8243286-3CB8-4B81-8379-A61DEAA39D09}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {A8243286-3CB8-4B81-8379-A61DEAA39D09}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {E6C56850-6930-4863-88AA-7FAC7592E70B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {E6C56850-6930-4863-88AA-7FAC7592E70B}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {E6C56850-6930-4863-88AA-7FAC7592E70B}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {E6C56850-6930-4863-88AA-7FAC7592E70B}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {28DB2EDC-FA18-4F55-8AD9-3B5DE543F519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {28DB2EDC-FA18-4F55-8AD9-3B5DE543F519}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {28DB2EDC-FA18-4F55-8AD9-3B5DE543F519}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {28DB2EDC-FA18-4F55-8AD9-3B5DE543F519}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {E9F23B76-BCCE-41A3-A4AB-CB6FB36C6B5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {E9F23B76-BCCE-41A3-A4AB-CB6FB36C6B5F}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {E9F23B76-BCCE-41A3-A4AB-CB6FB36C6B5F}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {E9F23B76-BCCE-41A3-A4AB-CB6FB36C6B5F}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {BA324EF9-0A69-42CC-B771-3A4200BEC3CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {BA324EF9-0A69-42CC-B771-3A4200BEC3CE}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {BA324EF9-0A69-42CC-B771-3A4200BEC3CE}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {BA324EF9-0A69-42CC-B771-3A4200BEC3CE}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {F349568E-02A0-4B46-9AD6-B78C2DBF5240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 73 | {F349568E-02A0-4B46-9AD6-B78C2DBF5240}.Debug|Any CPU.Build.0 = Debug|Any CPU 74 | {F349568E-02A0-4B46-9AD6-B78C2DBF5240}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {F349568E-02A0-4B46-9AD6-B78C2DBF5240}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {269BD7A7-3E02-4FE5-AC58-79475DB97E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {269BD7A7-3E02-4FE5-AC58-79475DB97E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {269BD7A7-3E02-4FE5-AC58-79475DB97E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {269BD7A7-3E02-4FE5-AC58-79475DB97E7D}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {98542DA6-E718-4249-976F-DFAF30537398}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {98542DA6-E718-4249-976F-DFAF30537398}.Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {98542DA6-E718-4249-976F-DFAF30537398}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {98542DA6-E718-4249-976F-DFAF30537398}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {4EB331A8-C2C8-4EA4-BFE9-1C5682FA6266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 85 | {4EB331A8-C2C8-4EA4-BFE9-1C5682FA6266}.Debug|Any CPU.Build.0 = Debug|Any CPU 86 | {4EB331A8-C2C8-4EA4-BFE9-1C5682FA6266}.Release|Any CPU.ActiveCfg = Release|Any CPU 87 | {4EB331A8-C2C8-4EA4-BFE9-1C5682FA6266}.Release|Any CPU.Build.0 = Release|Any CPU 88 | {94924166-0227-4884-8313-7D528B958AA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {94924166-0227-4884-8313-7D528B958AA4}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {94924166-0227-4884-8313-7D528B958AA4}.Release|Any CPU.ActiveCfg = Release|Any CPU 91 | {94924166-0227-4884-8313-7D528B958AA4}.Release|Any CPU.Build.0 = Release|Any CPU 92 | {A329F06E-F71D-4D65-9CBD-C97CA3DEB799}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 93 | {A329F06E-F71D-4D65-9CBD-C97CA3DEB799}.Debug|Any CPU.Build.0 = Debug|Any CPU 94 | {A329F06E-F71D-4D65-9CBD-C97CA3DEB799}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {A329F06E-F71D-4D65-9CBD-C97CA3DEB799}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {678A2755-69F1-4EE3-AE39-754D812048FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 97 | {678A2755-69F1-4EE3-AE39-754D812048FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 98 | {678A2755-69F1-4EE3-AE39-754D812048FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 99 | {678A2755-69F1-4EE3-AE39-754D812048FF}.Release|Any CPU.Build.0 = Release|Any CPU 100 | EndGlobalSection 101 | GlobalSection(SolutionProperties) = preSolution 102 | HideSolutionNode = FALSE 103 | EndGlobalSection 104 | GlobalSection(NestedProjects) = preSolution 105 | {1B6D4F01-C4BB-4242-89F9-F17527BF442E} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 106 | {26682A85-BEEC-4D96-A843-0925890E472B} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 107 | {A8243286-3CB8-4B81-8379-A61DEAA39D09} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 108 | {E6C56850-6930-4863-88AA-7FAC7592E70B} = {EE396D2F-98B0-46DC-851E-8A52A4C46E6B} 109 | {28DB2EDC-FA18-4F55-8AD9-3B5DE543F519} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 110 | {E9F23B76-BCCE-41A3-A4AB-CB6FB36C6B5F} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 111 | {BA324EF9-0A69-42CC-B771-3A4200BEC3CE} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 112 | {F349568E-02A0-4B46-9AD6-B78C2DBF5240} = {EE396D2F-98B0-46DC-851E-8A52A4C46E6B} 113 | {269BD7A7-3E02-4FE5-AC58-79475DB97E7D} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 114 | {98542DA6-E718-4249-976F-DFAF30537398} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 115 | {4EB331A8-C2C8-4EA4-BFE9-1C5682FA6266} = {EE396D2F-98B0-46DC-851E-8A52A4C46E6B} 116 | {94924166-0227-4884-8313-7D528B958AA4} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 117 | {A329F06E-F71D-4D65-9CBD-C97CA3DEB799} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 118 | {678A2755-69F1-4EE3-AE39-754D812048FF} = {82223BC2-FD46-45B7-8714-A1BBB6FACC0E} 119 | EndGlobalSection 120 | GlobalSection(ExtensibilityGlobals) = postSolution 121 | SolutionGuid = {886DD3D7-35E3-42F0-8CE9-74FB5AD7BBD0} 122 | EndGlobalSection 123 | EndGlobal 124 | -------------------------------------------------------------------------------- /MyGet.bat: -------------------------------------------------------------------------------- 1 | nuget restore GladNet.sln 2 | dotnet restore GladNet.sln 3 | dotnet build GladNet.sln -c Release 4 | PAUSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GladNet (Glader's Library of Advanced Development for Network Emulation Technologies) 2 | 3 | Introducing GladNet4, the 4th generation of the .NET/C# GladNet networking library. 4 | Originally designed for the niche of server/client network game emulation, GladNet has evolved into a generalized C#/.NET networking library, with a continued focus on games networking. GladNet4 offers a collection of APIs that enable developers to quickly build client or server software in .NET/C#. The library features an extendable API, supporting multiple cryptography schemes and serializers, making it highly adaptable. 5 | 6 | ## Features 7 | 8 | **Client** 9 | - [x] Async 10 | - [x] TCP 11 | - [x] Websockets 12 | - [x] [FreecraftCore.Serializer](https://www.github.com/FreecraftCore) (Attribute-based custom binary protocol serializer) 13 | - [ ] JSON 14 | - [x] Protobuf 15 | - [ ] XML 16 | 17 | **Server** 18 | 19 | - [x] Async 20 | - [x] TCP 21 | - [x] Websockets 22 | 23 | ## License 24 | 25 | Contributions including pull requests, commits, notes, dumps, gists or anything else in the repository are licensed under the below licensing terms. 26 | 27 | AGPL with the exception that an unrestricted perpetual non-exclusive license is granted to [HelloKitty](https://www.github.com/HelloKitty) (Andrew Blakely) 28 | -------------------------------------------------------------------------------- /clear-bin.bat: -------------------------------------------------------------------------------- 1 | FOR /F "tokens=*" %%G IN ('DIR /B /AD /S bin') DO RMDIR /S /Q "%%G" 2 | FOR /F "tokens=*" %%G IN ('DIR /B /AD /S publish') DO RMDIR /S /Q "%%G" -------------------------------------------------------------------------------- /lib/Unity2017/UnityEngine.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloKitty/GladNet/8b00743190458228c9124430bf63cf872be26670/lib/Unity2017/UnityEngine.dll -------------------------------------------------------------------------------- /post-MyGet.ps1: -------------------------------------------------------------------------------- 1 | ##Looks through the entire src directory and runs nuget pack with dependencies added on each csproj found 2 | ##foreach file in src/* 3 | foreach($f in Get-ChildItem ./src/) 4 | { 5 | ##foreach file in the src/*/ directory that ends with the .csproj format 6 | foreach($ff in (Get-ChildItem (Join-Path ./src/ $f.Name) | Where-Object { $_.Name.EndsWith(".csproj") })) 7 | { 8 | ##Add the project path + the csproj name and add the include referenced projects argument which will 9 | ##force nuget dependencies 10 | $projectArgs = "pack " + (Join-Path (Join-Path src/ $f.Name) $ff.Name)## 11 | $projectArgs = $projectArgs + " -c Release --include-symbols -p:PackageVersion=3.0.48" 12 | Start-Process dotnet $projectArgs -Wait 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/GladNet.API.AutoFac/GladNet.API.AutoFac.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) server API library. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | GladNet.API.AutoFac 16 | 4.9.45 17 | 18 | 19 | 20 | true 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/GladNet.API.AutoFac/Modules/AssemblyMessageHandlerServiceModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using Autofac; 7 | using Glader.Essentials; 8 | using Module = Autofac.Module; 9 | 10 | namespace GladNet 11 | { 12 | /// 13 | /// Service module that registers all message handlers within the provided 14 | /// 15 | public class AssemblyMessageHandlerServiceModule : Module where TMessageReadType : class where TMessageWriteType : class 16 | { 17 | private Assembly TargetAssembly { get; } 18 | 19 | public AssemblyMessageHandlerServiceModule(Assembly targetAssembly) 20 | { 21 | TargetAssembly = targetAssembly ?? throw new ArgumentNullException(nameof(targetAssembly)); 22 | } 23 | 24 | protected override void Load(ContainerBuilder builder) 25 | { 26 | base.Load(builder); 27 | 28 | //Register all handlers in the assembly 29 | foreach(var handler in GetHandlerTypes(TargetAssembly)) 30 | RegisterHandler(builder, handler); 31 | } 32 | 33 | /// 34 | /// Registers a bindable in the provider container. 35 | /// 36 | /// The handler to register. 37 | /// 38 | /// 39 | private ContainerBuilder RegisterHandler(ContainerBuilder builder) 40 | where THandlerType : IMessageHandler>, 41 | ITypeBindable>, TMessageReadType> 42 | { 43 | RegisterHandler(builder, typeof(THandlerType)); 44 | return builder; 45 | } 46 | 47 | /// 48 | /// Registers a bindable in the provider container. 49 | /// 50 | /// 51 | /// Type of handler. 52 | /// Throws if the handler type isn't a message handler. 53 | /// 54 | private static void RegisterHandler(ContainerBuilder builder, Type handlerType) 55 | { 56 | //New design will give a unique handler per session. 57 | //Makes this SO much easier in some cases. 58 | var registrationBuilder = builder.RegisterType(handlerType) 59 | .As>, TMessageReadType>>() 60 | .InstancePerLifetimeScope(); 61 | 62 | //TODO: Assert it is assignable to. 63 | foreach(var additional in handlerType.GetCustomAttributes()) 64 | registrationBuilder = registrationBuilder 65 | .As(additional.ServiceType); 66 | } 67 | 68 | /// 69 | /// Parses the provided to locate all handler types. 70 | /// 71 | /// The assembly to parse. 72 | /// Enumerable of all available message handler types. 73 | private static IEnumerable GetHandlerTypes(Assembly assembly) 74 | { 75 | if(assembly == null) throw new ArgumentNullException(nameof(assembly)); 76 | 77 | return assembly.GetTypes() 78 | .Where(t => t.IsAssignableTo>, TMessageReadType>>()) 79 | .Where(t => !t.IsAbstract) 80 | .Where(t => !t.IsAssignableTo>>()) //not a default handler 81 | .ToArray(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/GladNet.API.AutoFac/Modules/Dispatching/InPlaceMessageDispatchingServiceModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Autofac; 5 | 6 | namespace GladNet 7 | { 8 | //From Booma 9 | /// 10 | /// Service registration module for . 11 | /// Registers for in-place message dispatching. 12 | /// 13 | /// 14 | /// 15 | public sealed class InPlaceMessageDispatchingServiceModule : Module 16 | where TMessageWriteType : class 17 | where TMessageReadType : class 18 | { 19 | protected override void Load(ContainerBuilder builder) 20 | { 21 | base.Load(builder); 22 | 23 | //Register just inplace dispatching which is basically handle on same thread. 24 | builder.RegisterType>() 25 | .AsImplementedInterfaces() 26 | .InstancePerLifetimeScope(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/GladNet.API.AutoFac/Modules/GameMessageHandlerServiceModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using Autofac; 7 | using Glader.Essentials; 8 | using GladNet; 9 | using Module = Autofac.Module; 10 | 11 | namespace GladNet 12 | { 13 | //From Booma 14 | /// 15 | /// Game message handler service module. 16 | /// Registers the services and handling for message handlers. 17 | /// Registers the following services: 18 | /// for 19 | /// 20 | /// for specified message types parameters. 21 | /// 22 | public abstract class GameMessageHandlerServiceModule : Module 23 | where TMessageReadType : class 24 | where TMessageWriteType : class 25 | where TDefaultHandlerType : BaseDefaultMessageHandler> 26 | { 27 | /// 28 | protected sealed override void Load(ContainerBuilder builder) 29 | { 30 | base.Load(builder); 31 | 32 | //We Register the default handler because we'll internally bind it to the handler service 33 | //we create. This simplifies handler discovery abit too. 34 | builder.RegisterType() 35 | .AsSelf() 36 | .As>>() 37 | .SingleInstance(); 38 | 39 | //New Design makes these handlers NOT stateless. It makes certain things WAY easier to deal with. 40 | builder 41 | .RegisterType>>() 42 | .As>>() 43 | .OnActivated(args => 44 | { 45 | //Bind one of the default handlers 46 | var handler = args.Context.Resolve(); 47 | args.Instance.Bind(handler); 48 | 49 | //Now we resolve ALL bindable handlers 50 | //Any handler that is registered will now be bound to this handler service. 51 | foreach (var bindable in args.Context.Resolve>, TMessageReadType>>>()) 52 | { 53 | bindable.BindTo(args.Instance); 54 | } 55 | }) 56 | .InstancePerLifetimeScope(); 57 | 58 | RegisterHandlers(builder); 59 | } 60 | 61 | /// 62 | /// Implementers can register additional handlers here. 63 | /// 64 | /// 65 | protected virtual void RegisterHandlers(ContainerBuilder builder) 66 | { 67 | 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/GladNet.API.AutoFac/Modules/GameMessagingServicesModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Autofac; 5 | 6 | namespace GladNet 7 | { 8 | /// 9 | /// Service module that registers the following services: 10 | /// 11 | /// for outgoing write types 12 | /// . 13 | /// 14 | /// 15 | /// 16 | public sealed class GameMessagingServicesModule : Module 17 | where TMessageWriteType : class 18 | where TMessageReadType : class 19 | { 20 | protected override void Load(ContainerBuilder builder) 21 | { 22 | base.Load(builder); 23 | 24 | //The following registers messaging and message interface dependencies. 25 | builder.RegisterType>() 26 | .As>() 27 | .InstancePerLifetimeScope(); 28 | 29 | builder.RegisterType>() 30 | .As>() 31 | .InstancePerLifetimeScope(); 32 | 33 | builder.RegisterType>() 34 | .AsSelf() 35 | .InstancePerLifetimeScope(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/GladNet.API.Client/GladNet.API.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) server API library. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | GladNet.API.Client 16 | 4.9.45 17 | 18 | 19 | 20 | true 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/GladNet.API.Client/Session/SessionStarter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Common.Logging; 7 | 8 | namespace GladNet 9 | { 10 | //TODO: This is unfinished, not generalized enough to use in Server side yet. 11 | public sealed class SessionStarter 12 | where TSessionType : ManagedSession, IDisposable 13 | { 14 | private ILog Logger { get; } 15 | 16 | public SessionStarter(ILog logger) 17 | { 18 | Logger = logger ?? throw new ArgumentNullException(nameof(logger)); 19 | } 20 | 21 | public async Task StartAsync(TSessionType session, CancellationToken token = default) 22 | { 23 | Task writeTask = Task.Run(async () => await StartSessionNetworkThreadAsync(session.Details, session.StartWritingAsync(token), "Write"), token); 24 | Task readTask = Task.Run(async () => await StartSessionNetworkThreadAsync(session.Details, session.StartListeningAsync(token), "Read"), token); 25 | 26 | await Task.Run(async () => 27 | { 28 | try 29 | { 30 | await Task.WhenAny(writeTask, readTask); 31 | } 32 | catch(Exception e) 33 | { 34 | //Suppress this exception, we have critical deconstruction code to run. 35 | if(Logger.IsErrorEnabled) 36 | Logger.Error($"Session: {session.Details.ConnectionId} encountered critical failure in awaiting network task. Error: {e}"); 37 | } 38 | finally 39 | { 40 | if(Logger.IsDebugEnabled) 41 | Logger.Debug($"Session Stopping. Read State Error: {readTask.IsFaulted} Write State Error: {writeTask.IsFaulted} Read Completed: {readTask.IsCompleted} Write Completed: {writeTask.IsCompleted} Read Cancelled: {readTask.IsCanceled} Write Cancelled: {writeTask.IsCanceled}"); 42 | 43 | if (readTask.IsFaulted) 44 | if(Logger.IsDebugEnabled) 45 | Logger.Debug($"Read Fault: {readTask.Exception}"); 46 | 47 | if(writeTask.IsFaulted) 48 | if(Logger.IsDebugEnabled) 49 | Logger.Debug($"Write Fault: {readTask.Exception}"); 50 | 51 | await TryGracefulDisconnectAsync(session); 52 | } 53 | 54 | if (Logger.IsDebugEnabled) 55 | Logger.Debug($"Session: {session.Details.ConnectionId} Stopped Network Read/Write."); 56 | 57 | try 58 | { 59 | //TODO: Maybe a general disconnection event??? 60 | } 61 | catch(Exception e) 62 | { 63 | if(Logger.IsErrorEnabled) 64 | Logger.Error($"Failed Session: {session.Details.ConnectionId} ended event. Reason: {e}"); 65 | } 66 | finally 67 | { 68 | try 69 | { 70 | session.Dispose(); 71 | } 72 | catch(Exception e) 73 | { 74 | if(Logger.IsErrorEnabled) 75 | Logger.Error($"Encountered error in Client: {session.Details.ConnectionId} session disposal. Error: {e}"); 76 | throw; 77 | } 78 | } 79 | }, token); 80 | } 81 | 82 | private async Task TryGracefulDisconnectAsync(TSessionType session) 83 | { 84 | try 85 | { 86 | await session.ConnectionService.DisconnectAsync(); 87 | } 88 | catch (Exception e) 89 | { 90 | if (Logger.IsErrorEnabled) 91 | Logger.Error($"Session: {session.Details.ConnectionId} was open but failed to disconnect. Reason: {e}"); 92 | } 93 | finally 94 | { 95 | try 96 | { 97 | session.Dispose(); 98 | } 99 | catch (Exception e) 100 | { 101 | if (Logger.IsErrorEnabled) 102 | Logger.Error($"Session: {session.Details.ConnectionId} failed to dispose. Reason: {e}"); 103 | } 104 | } 105 | } 106 | 107 | public void Start(TSessionType session, CancellationToken token = default) 108 | { 109 | //Don't block/await (fire and forget) 110 | #pragma warning disable 4014 111 | StartAsync(session, token); 112 | #pragma warning restore 4014 113 | } 114 | 115 | private async Task StartSessionNetworkThreadAsync(SessionDetails details, Task task, string taskName) 116 | { 117 | if(details == null) throw new ArgumentNullException(nameof(details)); 118 | if(task == null) throw new ArgumentNullException(nameof(task)); 119 | 120 | try 121 | { 122 | await task; 123 | } 124 | catch(Exception e) 125 | { 126 | if(Logger.IsErrorEnabled) 127 | Logger.Error($"Session: {details.ConnectionId} encountered error in network {taskName} thread. Error: {e}"); 128 | } 129 | finally 130 | { 131 | 132 | } 133 | } 134 | 135 | public void Dispose() 136 | { 137 | // WARNING: heed warning in Thread.Abort doc, don't do it 138 | // See: https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.abort?view=net-8.0 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/GladNet.API.Server/Application/GladNetServerApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Common.Logging; 8 | using Glader.Essentials; 9 | 10 | namespace GladNet 11 | { 12 | /// 13 | /// Base type for GladNet server application bases. 14 | /// 15 | /// 16 | /// 17 | public abstract class GladNetServerApplication 18 | : IServerApplicationListenable, IFactoryCreatable 19 | where TManagedSessionType : ManagedSession 20 | { 21 | /// 22 | /// Network address information for the server. 23 | /// 24 | public NetworkAddressInfo ServerAddress { get; } 25 | 26 | /// 27 | /// Server application logger. 28 | /// 29 | public ILog Logger { get; } 30 | 31 | //TODO: We need a better API for exposing this. 32 | /// 33 | /// Collection that maps connection id to the managed session types. 34 | /// 35 | protected ConcurrentDictionary Sessions { get; } = new ConcurrentDictionary(); 36 | 37 | /// 38 | /// Event that is fired when a managed session is ended. 39 | /// This could be caused by disconnection but is not required to be related to disconnection. 40 | /// 41 | public event EventHandler> OnManagedSessionEnded; 42 | 43 | /// 44 | /// Creates a new server application with the specified address. 45 | /// 46 | /// Address for listening. 47 | /// The logger. 48 | protected GladNetServerApplication(NetworkAddressInfo serverAddress, ILog logger) 49 | { 50 | ServerAddress = serverAddress ?? throw new ArgumentNullException(nameof(serverAddress)); 51 | Logger = logger ?? throw new ArgumentNullException(nameof(logger)); 52 | } 53 | 54 | /// 55 | public abstract Task BeginListeningAsync(CancellationToken token = default); 56 | 57 | //Should be overriden by the consumer of the library. 58 | /// 59 | /// Called internally when a session is being created. 60 | /// This method should produce a valid session and is considered the hub of the connection. 61 | /// 62 | /// The context for creating the managed session. 63 | /// A non-null session. 64 | public abstract TManagedSessionType Create(TSessionCreationContextType context); 65 | 66 | /// 67 | /// Starts the read/write network tasks. 68 | /// 69 | /// The network cancel tokens. 70 | /// The session. 71 | protected void StartNetworkSessionTasks(CancellationToken token, TManagedSessionType clientSession) 72 | { 73 | Task writeTask = Task.Run(async () => 74 | { 75 | try 76 | { 77 | await StartSessionNetworkThreadAsync(clientSession.Details, clientSession.StartWritingAsync(token), token, "Write"); 78 | } 79 | catch (Exception e) 80 | { 81 | if(Logger.IsErrorEnabled) 82 | Logger.Error($"Session: {clientSession.Details.ConnectionId} Write thread encountered critical failure. Error: {e}"); 83 | throw; 84 | } 85 | }, token); 86 | 87 | Task readTask = Task.Run(async () => 88 | { 89 | try 90 | { 91 | await StartSessionNetworkThreadAsync(clientSession.Details, clientSession.StartListeningAsync(token), token, "Read"); 92 | } 93 | catch (Exception e) 94 | { 95 | if(Logger.IsErrorEnabled) 96 | Logger.Error($"Session: {clientSession.Details.ConnectionId} Read thread encountered critical failure. Error: {e}"); 97 | throw; 98 | } 99 | }, token); 100 | 101 | Task.Run(async () => 102 | { 103 | try 104 | { 105 | // Important that NO MATTER WHAT even if some cancel logic fails in this call that 106 | // OnManagedSessionEnded is invoked 107 | await AwaitManagedReadWriteTasksAsync(clientSession, readTask, writeTask, token); 108 | await TryGracefulDisconnectionAsync(clientSession); 109 | } 110 | finally 111 | { 112 | if (Logger.IsDebugEnabled) 113 | Logger.Debug($"Session: {clientSession.Details.ConnectionId} Stopped Network Read/Write."); 114 | 115 | try 116 | { 117 | //Fire off to anyone interested in managed session ending. We should do this before we fully dispose it and remove it from the session collection. 118 | OnManagedSessionEnded?.Invoke(this, new ManagedSessionContextualEventArgs(clientSession)); 119 | } 120 | catch(Exception e) 121 | { 122 | if (Logger.IsErrorEnabled) 123 | Logger.Error($"Failed Session: {clientSession.Details.ConnectionId} ended event. Reason: {e}"); 124 | } 125 | finally 126 | { 127 | Sessions.TryRemove(clientSession.Details.ConnectionId, out _); 128 | 129 | try 130 | { 131 | clientSession.Dispose(); 132 | } 133 | catch(Exception e) 134 | { 135 | if(Logger.IsErrorEnabled) 136 | Logger.Error($"Encountered error in Client: {clientSession.Details.ConnectionId} session disposal. Error: {e}"); 137 | throw; 138 | } 139 | } 140 | } 141 | }, token); 142 | } 143 | 144 | private async Task TryGracefulDisconnectionAsync(TManagedSessionType clientSession) 145 | { 146 | try 147 | { 148 | await clientSession.ConnectionService.DisconnectAsync(); 149 | } 150 | catch (Exception e) 151 | { 152 | if (Logger.IsErrorEnabled) 153 | Logger.Error($"Session: {clientSession.Details.ConnectionId} was open but failed to disconnect. Reason: {e}"); 154 | } 155 | finally 156 | { 157 | try 158 | { 159 | clientSession.Dispose(); 160 | } 161 | catch (Exception e) 162 | { 163 | if (Logger.IsErrorEnabled) 164 | Logger.Error($"Session: {clientSession.Details.ConnectionId} failed to dispose. Reason: {e}"); 165 | } 166 | } 167 | } 168 | 169 | private async Task AwaitManagedReadWriteTasksAsync(TManagedSessionType clientSession, Task readTask, Task writeTask, CancellationToken token) 170 | { 171 | try 172 | { 173 | await Task.WhenAny(readTask, writeTask); 174 | } 175 | catch (Exception e) 176 | { 177 | //Suppress this exception, we have critical deconstruction code to run. 178 | if (Logger.IsErrorEnabled) 179 | Logger.Error($"Session: {clientSession.Details.ConnectionId} encountered critical failure in awaiting network task. Error: {e}"); 180 | } 181 | finally 182 | { 183 | 184 | } 185 | } 186 | 187 | private async Task StartSessionNetworkThreadAsync(SessionDetails details, Task task, CancellationToken token, string taskName) 188 | { 189 | if(details == null) throw new ArgumentNullException(nameof(details)); 190 | if(task == null) throw new ArgumentNullException(nameof(task)); 191 | 192 | try 193 | { 194 | await task; 195 | } 196 | catch(Exception e) 197 | { 198 | if(Logger.IsErrorEnabled) 199 | Logger.Error($"Session: {details.ConnectionId} encountered error in network {taskName} thread. Error: {e}"); 200 | } 201 | finally 202 | { 203 | 204 | } 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/GladNet.API.Server/Application/IServerApplicationBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace GladNet 8 | { 9 | /// 10 | /// Contract for server application types that are listenable. 11 | /// 12 | public interface IServerApplicationListenable 13 | { 14 | /// 15 | /// Begins the listening process of a server application. 16 | /// This is a long running method that likely won't return until application shutdown. 17 | /// 18 | /// The cancellation token. 19 | /// Awaitable 20 | Task BeginListeningAsync(CancellationToken token = default); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/GladNet.API.Server/Events/ManagedSessionContextualEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// for an event involving a managed session. 9 | /// 10 | /// The managed session type. 11 | public class ManagedSessionContextualEventArgs : EventArgs 12 | where TManagedSessionType : ManagedSession 13 | { 14 | /// 15 | /// The managed session this event is associated with. 16 | /// 17 | public TManagedSessionType Session { get; } 18 | 19 | public ManagedSessionContextualEventArgs(TManagedSessionType session) 20 | { 21 | Session = session ?? throw new ArgumentNullException(nameof(session)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/GladNet.API.Server/GladNet.API.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) server API library. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | GladNet.API.Server 16 | 4.9.45 17 | 18 | 19 | 20 | true 21 | true 22 | 23 | 24 | 25 | C:\Users\Glader\Documents\Github\GladNet3\src\GladNet.API.Server\bin\GladNet.API.Server.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | <_Parameter1>GladNet.Server.DotNetTcpServer 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/GladNet.API.TCP/GladNet.API.TCP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) common API library that provides shared API for both client and server. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | 8.0 16 | GladNet.API.TCP 17 | 4.9.45 18 | 19 | 20 | 21 | true 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | <_Parameter1>GladNet.Server.DotNetTcpServer 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/GladNet.API.TCP/Network/SocketConnectionConnectionServiceAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Pipelines.Sockets.Unofficial; 7 | 8 | namespace GladNet 9 | { 10 | /// 11 | /// Implementation of based around 12 | /// 13 | public sealed class SocketConnectionConnectionServiceAdapter : INetworkConnectionService 14 | { 15 | /// 16 | /// Internal socket connection. 17 | /// 18 | private SocketConnection Connection { get; } 19 | 20 | // This is because Connection.Socket.Connected may still be true even after Connection.Socket.ConnectAsync 21 | // down below finishes awaiting. 22 | /// 23 | public bool IsConnected => Connection.Socket.Connected 24 | && Connection.ShutdownKind == PipeShutdownKind.None; 25 | 26 | public SocketConnectionConnectionServiceAdapter(SocketConnection connection) 27 | { 28 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 29 | } 30 | 31 | /// 32 | public async Task DisconnectAsync() 33 | { 34 | //This may look RIDICULOUS but this seem to be the only way for a server 35 | //to successfully disconnect a client without raising exceptions on the read/write thread 36 | TaskCompletionSource source = new TaskCompletionSource(); 37 | Connection.Socket.BeginDisconnect(false, ar => 38 | { 39 | Connection.Socket.EndDisconnect(ar); 40 | source.SetResult(null); 41 | }, null); 42 | 43 | //TODO: We don't have a mechanism to flush?? 44 | Connection.TrySetProtocolShutdown(PipeShutdownKind.ProtocolExitServer); 45 | Connection.Input.CancelPendingRead(); 46 | Connection.Output.CancelPendingFlush(); 47 | 48 | await source.Task; 49 | return; 50 | } 51 | 52 | //TODO: This is kind of stupid to even implement. This should NEVER be called on serverside! 53 | /// 54 | public async Task ConnectAsync(string ip, int port) 55 | { 56 | if (Connection.Socket.Connected) 57 | return false; 58 | 59 | await Connection.Socket.ConnectAsync(ip, port); 60 | return Connection.Socket.Connected; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/GladNet.API.TCP/Session/BaseTcpManagedSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.IO.Pipelines; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Glader.Essentials; 9 | using Nito.AsyncEx; 10 | using Pipelines.Sockets.Unofficial; 11 | 12 | namespace GladNet 13 | { 14 | /// 15 | /// Base TCP -based 16 | /// implementation. 17 | /// 18 | /// 19 | /// 20 | public abstract class BaseTcpManagedSession 21 | : ManagedSession 22 | where TPayloadWriteType : class 23 | where TPayloadReadType : class 24 | { 25 | /// 26 | /// The socket connection. 27 | /// 28 | protected SocketConnection Connection { get; } 29 | 30 | protected BaseTcpManagedSession(NetworkConnectionOptions networkOptions, SocketConnection connection, SessionDetails details, 31 | SessionMessageBuildingServiceContext messageServices) 32 | : base(new SocketConnectionConnectionServiceAdapter(connection), details, networkOptions, messageServices, 33 | BuildMessageInterfaceContext(networkOptions, connection, messageServices)) 34 | { 35 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 36 | } 37 | 38 | //This overload lets implementer specify a messageInterface. 39 | protected BaseTcpManagedSession(NetworkConnectionOptions networkOptions, SocketConnection connection, SessionDetails details, 40 | SessionMessageBuildingServiceContext messageServices, 41 | INetworkMessageInterface messageInterface) 42 | : base(new SocketConnectionConnectionServiceAdapter(connection), details, networkOptions, messageServices, 43 | BuildMessageInterfaceContext(messageInterface)) 44 | { 45 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 46 | } 47 | 48 | //This overload lets implementer specify a message interface context. 49 | protected BaseTcpManagedSession(NetworkConnectionOptions networkOptions, SocketConnection connection, SessionDetails details, 50 | SessionMessageBuildingServiceContext messageServices, 51 | SessionMessageInterfaceServiceContext messageInterfaces) 52 | : base(new SocketConnectionConnectionServiceAdapter(connection), details, networkOptions, messageServices, messageInterfaces) 53 | { 54 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 55 | } 56 | 57 | private static SessionMessageInterfaceServiceContext BuildMessageInterfaceContext(NetworkConnectionOptions networkOptions, SocketConnection connection, SessionMessageBuildingServiceContext messageServices) 58 | { 59 | INetworkMessageInterface messageInterface = new SocketConnectionNetworkMessageInterface(networkOptions, connection, messageServices); 60 | return BuildMessageInterfaceContext(messageInterface); 61 | } 62 | 63 | private static SessionMessageInterfaceServiceContext BuildMessageInterfaceContext(INetworkMessageInterface messageInterface) 64 | { 65 | return new SessionMessageInterfaceServiceContext(new AsyncExProducerConsumerQueueAsyncMessageQueue(), messageInterface); 66 | } 67 | 68 | /// 69 | public override async Task StartListeningAsync(CancellationToken token = default) 70 | { 71 | //We override this to add some SocketConnection handling on finishing. 72 | try 73 | { 74 | await base.StartListeningAsync(token); 75 | } 76 | catch (ConnectionResetException e) 77 | { 78 | //ConnectionResetException is thrown by Pipeline Socket API when it disconnects 79 | //and I don't yet know how to suppress it fully. 80 | //Consider a cancel a graceful complete 81 | await Connection.Input.CompleteAsync(e); 82 | return; 83 | } 84 | catch (TaskCanceledException e) 85 | { 86 | //Consider a cancel a graceful complete 87 | await Connection.Input.CompleteAsync(e); 88 | return; 89 | } 90 | catch (Exception e) 91 | { 92 | await Connection.Input.CompleteAsync(e); 93 | throw; 94 | } 95 | finally 96 | { 97 | await Connection.Input.CompleteAsync(); 98 | } 99 | } 100 | 101 | /// 102 | public override async Task StartWritingAsync(CancellationToken token = default) 103 | { 104 | try 105 | { 106 | while (!token.IsCancellationRequested) 107 | { 108 | //Dequeue from the outgoing message queue and send through the send service. 109 | TPayloadWriteType payload = await MessageService.OutgoingMessageQueue.DequeueAsync(token); 110 | SendResult result = await MessageService.MessageInterface.SendMessageAsync(payload, token); 111 | 112 | //TODO: Add logging! 113 | if (result != SendResult.Sent && result != SendResult.Enqueued) 114 | return; 115 | } 116 | } 117 | catch (ConnectionResetException e) 118 | { 119 | //ConnectionResetException is thrown by Pipeline Socket API when it disconnects 120 | //and I don't yet know how to suppress it fully. 121 | //Consider a cancel a graceful complete 122 | await Connection.Output.CompleteAsync(e); 123 | return; 124 | } 125 | catch(TaskCanceledException e) 126 | { 127 | //Consider a cancel a graceful complete 128 | await Connection.Output.CompleteAsync(e); 129 | return; 130 | } 131 | catch(Exception e) 132 | { 133 | await Connection.Output.CompleteAsync(e); 134 | throw; 135 | } 136 | finally 137 | { 138 | await Connection.Output.CompleteAsync(); 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/GladNet.API.TCP/Session/SessionCreationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Pipelines.Sockets.Unofficial; 5 | 6 | namespace GladNet 7 | { 8 | public sealed class SessionCreationContext 9 | { 10 | /// 11 | /// The underlying connection object for the session. 12 | /// 13 | public SocketConnection Connection { get; } 14 | 15 | /// 16 | /// The details of the session. 17 | /// 18 | public SessionDetails Details { get; } 19 | 20 | public SessionCreationContext(SocketConnection connection, SessionDetails details) 21 | { 22 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 23 | Details = details ?? throw new ArgumentNullException(nameof(details)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.DotNetWebSocket/Connection/DotNetWebSocketConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Nito.AsyncEx; 8 | 9 | namespace GladNet 10 | { 11 | // See: https://github.com/dotnet/corefx/blob/d6b11250b5113664dd3701c25bdf9addfacae9cc/src/Common/src/System/Net/WebSockets/ManagedWebSocket.cs#L22-L28 12 | // for threadsafety restrictions 13 | // - It's acceptable to call ReceiveAsync and SendAsync in parallel. One of each may run concurrently. 14 | // - It's acceptable to have a pending ReceiveAsync while CloseOutputAsync or CloseAsync is called. 15 | // - Attemping to invoke any other operations in parallel may corrupt the instance. Attempting to invoke 16 | // a send operation while another is in progress or a receive operation while another is in progress will 17 | // result in an exception. 18 | /// 19 | /// DotNet implementation of the . 20 | /// 21 | public sealed class DotNetWebSocketConnection : IWebSocketConnection 22 | { 23 | /// 24 | /// The adapted dotnet socket. 25 | /// 26 | private WebSocket Connection { get; } 27 | 28 | /// 29 | public WebSocketState State => Connection.State; 30 | 31 | /// 32 | public WebSocketCloseStatus? CloseStatus => Connection.CloseStatus; 33 | 34 | private AsyncLock SyncObj { get; } = new(); 35 | 36 | private bool IsCloseRequested = false; 37 | 38 | /// 39 | /// Creates a new that implements 40 | /// adapting the provided connection. 41 | /// 42 | /// 43 | public DotNetWebSocketConnection(WebSocket connection) 44 | { 45 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 46 | } 47 | 48 | /// 49 | public async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken = default) 50 | { 51 | // Because Close and Send aren't threadsafe at the same time we must lock 52 | using (await SyncObj.LockAsync()) 53 | { 54 | // Sometimes we hung on Aborted it seemed like. 55 | if (IsCloseRequested || Connection.State == WebSocketState.Aborted) 56 | return; 57 | 58 | IsCloseRequested = true; 59 | await Connection.CloseAsync(closeStatus, statusDescription, cancellationToken); 60 | } 61 | } 62 | 63 | /// 64 | public Task ConnectAsync(Uri uri, CancellationToken cancellationToken = default) 65 | { 66 | if (Connection is ClientWebSocket cws) 67 | { 68 | return cws.ConnectAsync(uri, CancellationToken.None); 69 | } 70 | else 71 | throw new NotSupportedException($"It is not supported to call {nameof(ConnectAsync)} on a non-client websocket."); 72 | } 73 | 74 | // WARNING: These should not be called at the same time RecieveAnyAsync is called 75 | /// 76 | public async Task ReceiveAsync(byte[] buffer, int count, CancellationToken token = default) 77 | { 78 | ArraySegment bufferSegment = new ArraySegment(buffer, 0, count); 79 | await ReceiveAsyncInternal(bufferSegment, count, token); 80 | } 81 | 82 | private async Task ReceiveAsyncInternal(ArraySegment bufferSegment, int count, CancellationToken token) 83 | { 84 | int totalBytesRead = 0; 85 | do 86 | { 87 | WebSocketReceiveResult result 88 | = await Connection.ReceiveAsync(bufferSegment, token); 89 | 90 | // No longer computable from the offset because we might BE at offset 2 starting and we read 1 byte, that would have 91 | // been THREE but that's wrong. 92 | totalBytesRead += result.Count; 93 | 94 | // Read the buffer, don't rely on it being EndOfMessage. We might have the payload as apart of the same message 95 | if (totalBytesRead 96 | == count) 97 | break; 98 | else if (totalBytesRead > count) 99 | throw new InvalidOperationException($"Read more bytes than request. Read: {totalBytesRead} Expected: {count}."); 100 | 101 | // Move the segment forward 102 | if (result.Count > bufferSegment.Count) 103 | throw new InvalidOperationException($"The WebSocket read more data ({result.Count}) than available in the buffer ({bufferSegment.Count})."); 104 | 105 | // Move the segment forward 106 | bufferSegment = new ArraySegment(bufferSegment.Array, bufferSegment.Offset + result.Count, bufferSegment.Count - result.Count); 107 | 108 | } while (!IsCloseRequested 109 | && !token.IsCancellationRequested 110 | && Connection.State == WebSocketState.Open); 111 | } 112 | 113 | /// 114 | public async Task ReceiveAsync(byte[] buffer, int offset, int count, CancellationToken token = default) 115 | { 116 | ArraySegment bufferSegment = new ArraySegment(buffer, offset, count); 117 | await ReceiveAsyncInternal(bufferSegment, count, token); 118 | } 119 | 120 | /// 121 | public async Task ReceiveAnyAsync(byte[] buffer, CancellationToken token = default) 122 | { 123 | ArraySegment bufferSegment = new ArraySegment(buffer, 0, buffer.Length); 124 | 125 | WebSocketReceiveResult result 126 | = await Connection.ReceiveAsync(bufferSegment, token); 127 | 128 | var totalBytesRead = result.Count; 129 | 130 | if(totalBytesRead == 0) 131 | return 0; 132 | 133 | if(totalBytesRead > buffer.Length) 134 | throw new InvalidOperationException($"Read more bytes than request. Read: {totalBytesRead} Expected less than or equal to: {buffer.Length}."); 135 | 136 | return totalBytesRead; 137 | } 138 | 139 | /// 140 | public async Task SendAsync(ArraySegment buffer, bool endMessage, CancellationToken token = default) 141 | { 142 | if (IsCloseRequested) 143 | return; 144 | 145 | // Because Close and Send aren't threadsafe at the same time we must lock 146 | using(await SyncObj.LockAsync()) 147 | await Connection.SendAsync(buffer, WebSocketMessageType.Binary, endMessage, token); 148 | } 149 | 150 | public void Dispose() 151 | { 152 | Connection.Dispose(); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.DotNetWebSocket/GladNet.API.WebSocket.DotNetWebSocket.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) common API library that provides shared API for both client and server. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | latest 16 | GladNet.API.WebSocket.DotNetWebSocket 17 | 4.9.45 18 | 19 | 20 | 21 | true 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Connection/IUnityWebGLWebSocket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | 6 | namespace GladNet 7 | { 8 | /// 9 | /// Interface representing a WebSocket implementation for Unity WebGL. 10 | /// Based on GaiaOnline.Towns3 WebSocket implementation. 11 | /// 12 | public interface IUnityWebGLWebSocket 13 | { 14 | /// 15 | /// Event triggered when the WebSocket connection is successfully opened. 16 | /// 17 | event WebSocketOpenEventHandler OnOpen; 18 | 19 | /// 20 | /// Event triggered when a message is received from the WebSocket. 21 | /// 22 | event WebSocketMessageEventHandler OnMessage; 23 | 24 | /// 25 | /// Event triggered when an error occurs in the WebSocket connection. 26 | /// 27 | event WebSocketErrorEventHandler OnError; 28 | 29 | /// 30 | /// Event triggered when the WebSocket connection is closed. 31 | /// 32 | event WebSocketCloseEventHandler OnClose; 33 | 34 | /// 35 | /// The current state of the WebSocket connection. 36 | /// 37 | WebSocketState State { get; } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Connection/UnityWebGLWebSocketFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.WebSockets; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using AOT; 7 | using JetBrains.Annotations; 8 | 9 | namespace GladNet 10 | { 11 | /// 12 | /// Class providing static access methods to work with JSLIB WebSocket or WebSocketSharp interface 13 | /// 14 | public static class UnityWebGLWebSocketFactory 15 | { 16 | public static Dictionary Instances { get; } = new(); 17 | 18 | /* Delegates */ 19 | public delegate void OnOpenCallback(int instanceId); 20 | 21 | public delegate void OnMessageCallback(int instanceId, System.IntPtr msgPtr, int msgSize); 22 | 23 | public delegate void OnErrorCallback(int instanceId, System.IntPtr errorPtr); 24 | 25 | public delegate void OnCloseCallback(int instanceId, int closeCode); 26 | 27 | /* WebSocket JSLIB callback setters and other functions */ 28 | [DllImport("__Internal")] 29 | public static extern int WebSocketAllocate(string url); 30 | 31 | [DllImport("__Internal")] 32 | public static extern int WebSocketAddSubProtocol(int instanceId, string subprotocol); 33 | 34 | [DllImport("__Internal")] 35 | public static extern void WebSocketFree(int instanceId); 36 | 37 | [DllImport("__Internal")] 38 | public static extern void WebSocketSetOnOpen(OnOpenCallback callback); 39 | 40 | [DllImport("__Internal")] 41 | public static extern void WebSocketSetOnMessage(OnMessageCallback callback); 42 | 43 | [DllImport("__Internal")] 44 | public static extern void WebSocketSetOnError(OnErrorCallback callback); 45 | 46 | [DllImport("__Internal")] 47 | public static extern void WebSocketSetOnClose(OnCloseCallback callback); 48 | 49 | /// 50 | /// If callbacks was initialized and set 51 | /// 52 | public static bool IsInitialized { get; private set; } = false; 53 | 54 | /// 55 | /// Initialize WebSocket callbacks to JSLIB 56 | /// 57 | public static void Initialize() 58 | { 59 | if(IsInitialized) 60 | return; 61 | 62 | WebSocketSetOnOpen(DelegateOnOpenEvent); 63 | WebSocketSetOnMessage(DelegateOnMessageEvent); 64 | WebSocketSetOnError(DelegateOnErrorEvent); 65 | WebSocketSetOnClose(DelegateOnCloseEvent); 66 | 67 | IsInitialized = true; 68 | } 69 | 70 | /// 71 | /// Called when instance is destroyed (by destructor) 72 | /// Method removes instance from map and free it in JSLIB implementation 73 | /// 74 | /// Instance identifier. 75 | public static void HandleInstanceDestroy(int instanceId) 76 | { 77 | try 78 | { 79 | Instances.Remove(instanceId); 80 | } 81 | finally 82 | { 83 | WebSocketFree(instanceId); 84 | } 85 | } 86 | 87 | [MonoPInvokeCallback(typeof(OnOpenCallback))] 88 | public static void DelegateOnOpenEvent(int instanceId) 89 | { 90 | if(Instances.TryGetValue(instanceId, out var instanceRef)) 91 | instanceRef.DelegateOnOpenEvent(); 92 | } 93 | 94 | [MonoPInvokeCallback(typeof(OnMessageCallback))] 95 | public static void DelegateOnMessageEvent(int instanceId, System.IntPtr msgPtr, int msgSize) 96 | { 97 | if(Instances.TryGetValue(instanceId, out var instanceRef)) 98 | { 99 | // TODO: We should avoid allocating here and use a shared buffer instead maybe or something 100 | byte[] msg = new byte[msgSize]; 101 | Marshal.Copy(msgPtr, msg, 0, msgSize); 102 | 103 | instanceRef.DelegateOnMessageEvent(msg); 104 | } 105 | } 106 | 107 | [MonoPInvokeCallback(typeof(OnErrorCallback))] 108 | public static void DelegateOnErrorEvent(int instanceId, System.IntPtr errorPtr) 109 | { 110 | if(Instances.TryGetValue(instanceId, out var instanceRef)) 111 | { 112 | string errorMsg = Marshal.PtrToStringAuto(errorPtr); 113 | instanceRef.DelegateOnErrorEvent(errorMsg); 114 | } 115 | } 116 | 117 | [MonoPInvokeCallback(typeof(OnCloseCallback))] 118 | public static void DelegateOnCloseEvent(int instanceId, int closeCode) 119 | { 120 | if(Instances.TryGetValue(instanceId, out var instanceRef)) 121 | instanceRef.DelegateOnCloseEvent(closeCode); 122 | } 123 | 124 | public static int CreateSocket([NotNull] WebGLWebSocketConnection connection, [NotNull] Uri uri) 125 | { 126 | if(connection == null) throw new ArgumentNullException(nameof(connection)); 127 | if(uri == null) throw new ArgumentNullException(nameof(uri)); 128 | 129 | var id = WebSocketAllocate(uri.ToString()); 130 | Instances.Add(id, connection); 131 | 132 | return id; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Connection/WebGLMessageSendService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using GladNet; 8 | using JetBrains.Annotations; 9 | 10 | namespace GladNet 11 | { 12 | /// 13 | /// Mostly copied from GladNet, it's just a direct serializer and send implementation of 14 | /// that avoids the queueing. This is needed because the send throughput is poor and sending many packets at once will cause stalls. 15 | /// 16 | /// The type of the payload to serialize and send. 17 | public sealed class WebGLMessageSendService : IMessageSendService 18 | where TPayloadWriteType : class 19 | { 20 | private IWebSocketConnection Connection { get; } 21 | 22 | private NetworkConnectionOptions NetworkOptions { get; } 23 | 24 | private SessionMessageBuildingServiceContext MessageServices { get; } 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The WebSocket connection used to send messages. 30 | /// Network configuration options. 31 | /// Message and header serialization services. 32 | public WebGLMessageSendService([NotNull] IWebSocketConnection connection, 33 | [NotNull] NetworkConnectionOptions networkOptions, 34 | [NotNull] SessionMessageBuildingServiceContext messageServices) 35 | { 36 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 37 | NetworkOptions = networkOptions ?? throw new ArgumentNullException(nameof(networkOptions)); 38 | MessageServices = messageServices ?? throw new ArgumentNullException(nameof(messageServices)); 39 | } 40 | 41 | /// 42 | public async Task SendMessageAsync(TPayloadWriteType message, CancellationToken token = default) 43 | { 44 | var buffer = NetworkOptions.PacketArrayPool.Rent(NetworkOptions.MaximumPacketSize); 45 | try 46 | { 47 | WritePacketToBuffer(message, buffer, out var headerSize, out var payloadSize); 48 | await Connection.SendAsync(new ArraySegment(buffer, 0, headerSize + payloadSize), true, token); 49 | } 50 | finally 51 | { 52 | NetworkOptions.PacketArrayPool.Return(buffer); 53 | } 54 | 55 | return SendResult.Sent; 56 | } 57 | 58 | /// 59 | /// Writes the message payload and header to the provided buffer. 60 | /// 61 | /// The message payload to serialize. 62 | /// The buffer to write to. 63 | /// The size of the serialized header. 64 | /// The size of the serialized payload. 65 | private void WritePacketToBuffer(TPayloadWriteType payload, byte[] buffer, out int headerSize, out int payloadSize) 66 | { 67 | var bufferSpan = new Span(buffer); 68 | 69 | // It seems backwards, but we don't know what header to build until the payload is serialized. 70 | payloadSize = SerializeOutgoingPacketPayload(bufferSpan.Slice(NetworkOptions.MinimumPacketHeaderSize), payload); 71 | headerSize = SerializeOutgoingHeader(payload, payloadSize, bufferSpan.Slice(0, NetworkOptions.MaximumPacketHeaderSize)); 72 | 73 | // TODO: We must eventually support VARIABLE LENGTH packet headers. This is complicated, WoW does this for large packets sent by the server. 74 | if(headerSize != NetworkOptions.MinimumPacketHeaderSize) 75 | throw new NotSupportedException($"TODO: Variable length packet header sizes are not yet supported."); 76 | } 77 | 78 | /// 79 | /// Serializes the outgoing packet payload. 80 | /// 81 | /// The buffer to write the payload to. 82 | /// The payload to serialize. 83 | /// The size of the serialized payload. 84 | private int SerializeOutgoingPacketPayload(in Span buffer, TPayloadWriteType payload) 85 | { 86 | //Serializes the payload data to the span buffer and moves the pipe forward by the ref output offset 87 | //meaning we indicate to the pipeline that we've written bytes 88 | int offset = 0; 89 | MessageServices.MessageSerializer.Serialize(payload, buffer, ref offset); 90 | return offset; 91 | } 92 | 93 | /// 94 | /// Serializes the outgoing packet header. 95 | /// 96 | /// The payload being sent. 97 | /// The size of the payload. 98 | /// The buffer to write the header to. 99 | /// The size of the serialized header. 100 | private int SerializeOutgoingHeader(TPayloadWriteType payload, int payloadSize, in Span buffer) 101 | { 102 | int headerOffset = 0; 103 | MessageServices.HeaderSerializer.Serialize(new PacketHeaderSerializationContext(payload, payloadSize), buffer, ref headerOffset); 104 | return headerOffset; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Connection/WebGLWebSocketConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net.WebSockets; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Glader.Essentials; 12 | using GladNet; 13 | 14 | namespace GladNet 15 | { 16 | /// 17 | /// WebGL compatible implementation of GladNet's . 18 | /// 19 | public sealed class WebGLWebSocketConnection : IWebSocketConnection, IUnityWebGLWebSocket 20 | { 21 | /* WebSocket JSLIB functions */ 22 | [DllImport("__Internal")] 23 | public static extern int WebSocketConnect(int instanceId); 24 | 25 | [DllImport("__Internal")] 26 | public static extern int WebSocketClose(int instanceId, int code, string reason); 27 | 28 | [DllImport("__Internal")] 29 | public static extern int WebSocketSend(int instanceId, byte[] dataPtr, int dataLength); 30 | 31 | [DllImport("__Internal")] 32 | public static extern int WebSocketSendText(int instanceId, string message); 33 | 34 | [DllImport("__Internal")] 35 | public static extern int WebSocketGetState(int instanceId); 36 | 37 | /// 38 | /// Triggered when the WebSocket connection is successfully opened. 39 | /// From designed by https://github.com/endel/NativeWebSocket/blob/master/NativeWebSocket/Assets/WebSocket/WebSocket.cs 40 | /// 41 | public event WebSocketOpenEventHandler OnOpen; 42 | 43 | /// 44 | /// Triggered when a message is received over the WebSocket connection. 45 | /// From designed by https://github.com/endel/NativeWebSocket/blob/master/NativeWebSocket/Assets/WebSocket/WebSocket.cs 46 | /// 47 | public event WebSocketMessageEventHandler OnMessage; 48 | 49 | /// 50 | /// Triggered when an error occurs in the WebSocket connection. 51 | /// From designed by https://github.com/endel/NativeWebSocket/blob/master/NativeWebSocket/Assets/WebSocket/WebSocket.cs 52 | /// 53 | public event WebSocketErrorEventHandler OnError; 54 | 55 | /// 56 | /// Triggered when the WebSocket connection is closed. 57 | /// From designed by https://github.com/endel/NativeWebSocket/blob/master/NativeWebSocket/Assets/WebSocket/WebSocket.cs 58 | /// 59 | public event WebSocketCloseEventHandler OnClose; 60 | 61 | /// 62 | public WebSocketState State => GetState(); 63 | 64 | // TODO: Can we even implement this?? 65 | /// 66 | public WebSocketCloseStatus? CloseStatus { get; } = null; 67 | 68 | /// 69 | /// More efficient approximation of if the socket is open. 70 | /// 71 | bool IsAssumedOpen { get; set; } = false; 72 | 73 | private int InstanceId { get; set; } = -1; 74 | 75 | private ArraySegment PendingReadBuffer { get; set; } 76 | 77 | private TaskCompletionSource ReadNotifyTask { get; set; } 78 | 79 | private List> ByteMessageQueue { get; } = new(); 80 | 81 | /// 82 | /// Initializes a new instance of and sets up message queue handling. 83 | /// 84 | public WebGLWebSocketConnection() 85 | { 86 | // Setup the message queue stuff 87 | OnMessage += OnByteMessage; 88 | } 89 | 90 | /// 91 | /// Handles the event when a message is received, adding the message data to the byte message queue. 92 | /// 93 | /// The message data received. 94 | private void OnByteMessage(byte[] data) 95 | { 96 | ByteMessageQueue.Add(data); 97 | 98 | //UnityEngine.Debug.LogError($"MessageQueueSize: {ByteMessageQueue.Count}"); 99 | 100 | ProcessMessageQueue(); 101 | } 102 | 103 | /// 104 | /// Processes the byte message queue and notifies the reader if all data has been processed. 105 | /// 106 | private void ProcessMessageQueue() 107 | { 108 | // WARNING: AI once chnaged this watch out! 109 | var pendingReadBuffer = PendingReadBuffer; 110 | if (PendingReadBuffer == null 111 | || ReadNotifyTask == null) 112 | return; 113 | 114 | try 115 | { 116 | ProcessPendingBytes(ref pendingReadBuffer); 117 | PendingReadBuffer = pendingReadBuffer; 118 | 119 | // Notify the reader we have bytes fully now 120 | if (PendingReadBuffer.Count == 0) 121 | { 122 | PendingReadBuffer = null; 123 | var task = ReadNotifyTask; 124 | ReadNotifyTask = null; 125 | task?.SetResult(null); 126 | } 127 | } 128 | catch (Exception e) 129 | { 130 | UnityEngine.Debug.LogError($"Processing Bytes Failed: {e}"); 131 | } 132 | } 133 | 134 | /// 135 | /// Processes the pending bytes in the byte message queue and fills the provided buffer. 136 | /// 137 | private void ProcessPendingBytes(ref ArraySegment buffer) 138 | { 139 | try 140 | { 141 | for(int i = 0; i < ByteMessageQueue.Count; i++) 142 | { 143 | if(buffer.Count == 0) 144 | return; 145 | 146 | // We have some data to read, not more than exists. 147 | if(ByteMessageQueue[i].Count <= buffer.Count) 148 | { 149 | // Copy, we got enough thankfully 150 | Buffer.BlockCopy(ByteMessageQueue[i].Array, ByteMessageQueue[i].Offset, buffer.Array, buffer.Offset, ByteMessageQueue[i].Count); 151 | 152 | // Update segment, it's empty now. We read everything. 153 | buffer = new ArraySegment(buffer.Array, buffer.Offset + ByteMessageQueue[i].Count, buffer.Count - ByteMessageQueue[i].Count); 154 | ByteMessageQueue[i] = new ArraySegment(Array.Empty()); 155 | } 156 | else 157 | { 158 | // The buffer in this index is BIGGER than what we need 159 | Buffer.BlockCopy(ByteMessageQueue[i].Array, ByteMessageQueue[i].Offset, buffer.Array, buffer.Offset, buffer.Count); 160 | 161 | ByteMessageQueue[i] = new ArraySegment(ByteMessageQueue[i].Array, ByteMessageQueue[i].Offset + buffer.Count, ByteMessageQueue[i].Count - buffer.Count); 162 | buffer = new ArraySegment(Array.Empty()); 163 | } 164 | } 165 | } 166 | finally 167 | { 168 | ByteMessageQueue.RemoveAll(bytes => bytes.Count == 0); 169 | } 170 | } 171 | 172 | /// 173 | public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken = default) 174 | { 175 | int resultCode = WebSocketClose(InstanceId, (int)closeStatus, statusDescription); 176 | ResetConnection(); 177 | 178 | if(resultCode < 0) 179 | throw UnityWebGLWebSocketHelpers.GetErrorMessageFromCode(resultCode, null); 180 | 181 | return Task.CompletedTask; 182 | } 183 | 184 | /// 185 | /// Called when the client closes. 186 | /// Resets the WebSocket connection and releases any pending read operations. 187 | /// 188 | private void ResetConnection() 189 | { 190 | PendingReadBuffer = null; 191 | IsAssumedOpen = false; 192 | ReadNotifyTask?.SetException(new OperationCanceledException($"WebGL socket connect reset/disposed.")); 193 | ReadNotifyTask = null; 194 | } 195 | 196 | /// 197 | public Task ConnectAsync(Uri uri, CancellationToken cancellationToken = default) 198 | { 199 | UnityWebGLWebSocketFactory.Initialize(); 200 | InstanceId = UnityWebGLWebSocketFactory.CreateSocket(this, uri); 201 | 202 | int resultCode = WebSocketConnect(InstanceId); 203 | 204 | if(resultCode < 0) 205 | throw UnityWebGLWebSocketHelpers.GetErrorMessageFromCode(resultCode, null); 206 | 207 | IsAssumedOpen = true; 208 | return Task.CompletedTask; 209 | } 210 | 211 | /// 212 | public async Task ReceiveAsync(byte[] buffer, int count, CancellationToken token = default) 213 | { 214 | await ReceiveAsync(buffer, 0, count, token); 215 | } 216 | 217 | /// 218 | public async Task ReceiveAsync(byte[] buffer, int offset, int count, CancellationToken token = default) 219 | { 220 | if(!IsAssumedOpen) 221 | throw new InvalidOperationException($"WebGL socket was no longer open during read."); 222 | 223 | //UnityEngine.Debug.LogError($"About to process bytes in ReceiveAsync"); 224 | 225 | if(TryProcessReceiveNonAsync(buffer, offset, count)) 226 | { 227 | //UnityEngine.Debug.LogError($"Enough bytes read, returning to gladnet internal."); 228 | return; 229 | } 230 | 231 | //UnityEngine.Debug.LogError($"Not enough bytes available, awaiting notify task"); 232 | 233 | // This will complete once the data is ready, it wasn't read above in the non-async call. 234 | await ReadNotifyTask.Task; 235 | PendingReadBuffer = null; 236 | ReadNotifyTask = null; 237 | } 238 | 239 | /// 240 | public async Task ReceiveAnyAsync(byte[] buffer, CancellationToken token = default) 241 | { 242 | throw new NotSupportedException($"{nameof(ReceiveAnyAsync)} is not supported in WebGL"); 243 | } 244 | 245 | /// 246 | /// Attempts to process the received data without awaiting an asynchronous task. 247 | /// 248 | private bool TryProcessReceiveNonAsync(byte[] buffer, int offset, int count) 249 | { 250 | // TODO: We might want to process existing stuff in the future, since this awaits we'll only read afew packets per frame 251 | PendingReadBuffer = new ArraySegment(buffer, offset, count); 252 | 253 | var pendingReadBuffer = PendingReadBuffer; 254 | ProcessPendingBytes(ref pendingReadBuffer); 255 | PendingReadBuffer = pendingReadBuffer; 256 | 257 | // If the buffer was filled then we're good to go! 258 | if(pendingReadBuffer.Count == 0) 259 | { 260 | PendingReadBuffer = null; 261 | ReadNotifyTask = null; 262 | return true; 263 | } 264 | 265 | // TODO: Support cancel token. 266 | ReadNotifyTask = new TaskCompletionSource(); 267 | return false; 268 | } 269 | 270 | /// 271 | public Task SendAsync(ArraySegment buffer, bool endMessage, CancellationToken token = default) 272 | { 273 | // TODO: Wtf, no support for "EndMessage"??? 274 | if(buffer.Offset != 0) 275 | throw new NotSupportedException($"Cannot support sending WebGL socket bytes with segment isn't starting at zero-index."); 276 | 277 | int result = WebSocketSend(InstanceId, buffer.Array, buffer.Count); 278 | 279 | if(result < 0) 280 | throw UnityWebGLWebSocketHelpers.GetErrorMessageFromCode(result, null); 281 | 282 | return Task.CompletedTask; 283 | } 284 | 285 | /// 286 | /// Gets the from the JS implementation. 287 | /// Retrieves the current WebSocket connection state. 288 | /// 289 | /// 290 | private WebSocketState GetState() 291 | { 292 | // Some things are checking state right away so we better not call into JS 293 | if(InstanceId < 0 || !IsAssumedOpen) 294 | return WebSocketState.None; 295 | 296 | // See: https://github.com/endel/NativeWebSocket/blob/master/NativeWebSocket/Assets/WebSocket/WebSocket.cs#L320 297 | int state = WebSocketGetState(InstanceId); 298 | 299 | if(state < 0) 300 | throw UnityWebGLWebSocketHelpers.GetErrorMessageFromCode(state, null); 301 | 302 | switch(state) 303 | { 304 | case 0: 305 | return WebSocketState.Connecting; 306 | 307 | case 1: 308 | return WebSocketState.Open; 309 | 310 | case 2: 311 | return WebSocketState.CloseSent; 312 | 313 | case 3: 314 | return WebSocketState.Closed; 315 | 316 | default: 317 | return WebSocketState.Closed; 318 | } 319 | } 320 | 321 | /// 322 | public void Dispose() 323 | { 324 | UnityWebGLWebSocketFactory.HandleInstanceDestroy(InstanceId); 325 | ResetConnection(); 326 | } 327 | 328 | public void DelegateOnOpenEvent() 329 | { 330 | this.OnOpen?.Invoke(); 331 | } 332 | 333 | public void DelegateOnMessageEvent(byte[] data) 334 | { 335 | this.OnMessage?.Invoke(data); 336 | } 337 | 338 | public void DelegateOnErrorEvent(string errorMsg) 339 | { 340 | this.OnError?.Invoke(errorMsg); 341 | } 342 | 343 | public void DelegateOnCloseEvent(int closeCode) 344 | { 345 | this.OnClose?.Invoke(UnityWebGLWebSocketHelpers.ParseCloseCodeEnum(closeCode)); 346 | } 347 | } 348 | } -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Events/WebSocketCloseEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | 6 | namespace GladNet 7 | { 8 | /// 9 | /// Event delegate for the closure of a Unity WebSocket connection. 10 | /// 11 | /// The close code. 12 | public delegate void WebSocketCloseEventHandler(WebSocketCloseStatus closeCode); 13 | } 14 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Events/WebSocketErrorEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace GladNet 2 | { 3 | /// 4 | /// Delegate to handle WebSocket error events. 5 | /// This delegate will be triggered when an error occurs on the WebSocket connection. 6 | /// 7 | /// The error message describing the issue encountered. 8 | public delegate void WebSocketErrorEventHandler(string errorMsg); 9 | } -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Events/WebSocketMessageEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace GladNet 2 | { 3 | /// 4 | /// Delegate to handle WebSocket message events. 5 | /// This delegate will be triggered when a message is received via the WebSocket connection. 6 | /// 7 | /// The message data received, represented as a byte array. 8 | public delegate void WebSocketMessageEventHandler(byte[] data); 9 | } -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Events/WebSocketOpenEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace GladNet 2 | { 3 | /// 4 | /// Delegate to handle WebSocket open events. 5 | /// This delegate will be triggered when the WebSocket connection is successfully opened. 6 | /// 7 | public delegate void WebSocketOpenEventHandler(); 8 | } -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/Extensions/UnityWebGLWebSocketHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | 6 | namespace GladNet 7 | { 8 | /// 9 | /// A helper class providing utilities for handling WebSocket exceptions and close codes in Unity WebGL. 10 | /// See: https://github.com/endel/NativeWebSocket/blob/master/NativeWebSocket/Assets/WebSocket/WebSocket.cs#L126 11 | /// 12 | public static class UnityWebGLWebSocketHelpers 13 | { 14 | /// 15 | /// Returns an appropriate exception based on the given WebSocket error code. 16 | /// 17 | /// The error code received from the WebSocket. 18 | /// An optional inner exception to provide more context for the error. 19 | /// A custom exception corresponding to the error code. 20 | public static Exception GetErrorMessageFromCode(int errorCode, Exception inner) 21 | { 22 | switch(errorCode) 23 | { 24 | case -1: 25 | return new WebSocketUnexpectedException("WebSocket instance not found.", inner); 26 | case -2: 27 | return new WebSocketInvalidStateException("WebSocket is already connected or in connecting state.", inner); 28 | case -3: 29 | return new WebSocketInvalidStateException("WebSocket is not connected.", inner); 30 | case -4: 31 | return new WebSocketInvalidStateException("WebSocket is already closing.", inner); 32 | case -5: 33 | return new WebSocketInvalidStateException("WebSocket is already closed.", inner); 34 | case -6: 35 | return new WebSocketInvalidStateException("WebSocket is not in open state.", inner); 36 | case -7: 37 | return new WebSocketInvalidArgumentException("Cannot close WebSocket. An invalid code was specified or reason is too long.", inner); 38 | default: 39 | return new WebSocketUnexpectedException("Unknown error.", inner); 40 | } 41 | } 42 | 43 | /// 44 | /// Custom base exception class for WebSocket-related exceptions. 45 | /// 46 | public class CustomWebSocketException : Exception 47 | { 48 | public CustomWebSocketException() { } 49 | public CustomWebSocketException(string message) : base(message) { } 50 | public CustomWebSocketException(string message, Exception inner) : base(message, inner) { } 51 | } 52 | 53 | /// 54 | /// Exception thrown for unexpected WebSocket errors. 55 | /// 56 | public class WebSocketUnexpectedException : CustomWebSocketException 57 | { 58 | public WebSocketUnexpectedException() { } 59 | public WebSocketUnexpectedException(string message) : base(message) { } 60 | public WebSocketUnexpectedException(string message, Exception inner) : base(message, inner) { } 61 | } 62 | 63 | /// 64 | /// Exception thrown when invalid arguments are passed to WebSocket methods. 65 | /// 66 | public class WebSocketInvalidArgumentException : CustomWebSocketException 67 | { 68 | public WebSocketInvalidArgumentException() { } 69 | public WebSocketInvalidArgumentException(string message) : base(message) { } 70 | public WebSocketInvalidArgumentException(string message, Exception inner) : base(message, inner) { } 71 | } 72 | 73 | /// 74 | /// Exception thrown when a WebSocket operation is performed in an invalid state. 75 | /// 76 | public class WebSocketInvalidStateException : CustomWebSocketException 77 | { 78 | public WebSocketInvalidStateException() { } 79 | public WebSocketInvalidStateException(string message) : base(message) { } 80 | public WebSocketInvalidStateException(string message, Exception inner) : base(message, inner) { } 81 | } 82 | 83 | /// 84 | /// Parses the close code received from the WebSocket into a WebSocketCloseStatus enumeration. 85 | /// 86 | /// The integer close code from the WebSocket. 87 | /// The corresponding WebSocketCloseStatus enumeration value. 88 | public static WebSocketCloseStatus ParseCloseCodeEnum(int closeCode) 89 | { 90 | return (WebSocketCloseStatus)closeCode; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket.UnityWebGL/GladNet.API.WebSocket.UnityWebGL.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) common API library that provides shared API for both client and server. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | latest 16 | GladNet.API.WebSocket.UnityWebGL 17 | 4.9.45 18 | 19 | 20 | 21 | true 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ..\..\lib\Unity2017\UnityEngine.dll 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket/Connection/IWebSocketConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace GladNet 9 | { 10 | public interface IWebSocketConnection : IDisposable 11 | { 12 | /// 13 | /// Closes the WebSocket connection as an asynchronous operation using the close handshake defined in the WebSocket protocol specification section 7. 14 | /// 15 | /// Indicates the reason for closing the WebSocket connection. 16 | /// Specifies a human readable explanation as to why the connection is closed. 17 | /// The token that can be used to propagate notification that operations should be canceled. 18 | /// The task object representing the asynchronous operation. 19 | Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken = default); 20 | 21 | /// 22 | /// Returns the current state of the WebSocket connection. 23 | /// 24 | WebSocketState State { get; } 25 | 26 | /// 27 | /// Indicates the reason why the remote endpoint initiated the close handshake. 28 | /// If the close handshake has not been initiated yet, WebSocketCloseStatus.None is returned. 29 | /// 30 | WebSocketCloseStatus? CloseStatus { get; } 31 | 32 | /// 33 | /// Connects to a WebSocket server as an asynchronous operation. 34 | /// WARNING: This only works for clients. 35 | /// 36 | /// The URI of the WebSocket server to connect to. 37 | /// A cancellation token used to propagate notification that the operation should be canceled. 38 | /// The task object representing the asynchronous operation. 39 | /// Thrown if the socket isn't for clients. 40 | Task ConnectAsync(Uri uri, CancellationToken cancellationToken = default); 41 | 42 | /// 43 | /// Reads the many bytes into the provider starting at index 0. 44 | /// Doesn't match the .NET API, is abit simplier. 45 | /// 46 | /// The buffer. 47 | /// The amount of bytes to read. 48 | /// Cancel token. 49 | /// Awaitable 50 | Task ReceiveAsync(byte[] buffer, int count, CancellationToken token = default); 51 | 52 | /// 53 | /// Reads the many bytes into the provided starting at index . 54 | /// Doesn't match the .NET API, is abit simplier. 55 | /// 56 | /// The buffer. 57 | /// The buffer offset to start writing into. 58 | /// The amount of bytes to read. 59 | /// Cancel token. 60 | /// Awaitable 61 | Task ReceiveAsync(byte[] buffer, int offset, int count, CancellationToken token = default); 62 | 63 | // TODO: maybe all APIs should have this? Just read any available amount? 64 | /// 65 | /// Reads as many bytes as possible into the provider starting at index 0. 66 | /// Doesn't match the .NET API, is abit simplier. 67 | /// 68 | /// The buffer. 69 | /// Cancel token. 70 | /// Awaitable amount of bytes read. 71 | Task ReceiveAnyAsync(byte[] buffer, CancellationToken token = default); 72 | 73 | /// 74 | /// Sends data on ClientWebSocket as an asynchronous operation. 75 | /// 76 | /// The buffer containing the message to be sent. 77 | /// true to indicate this is the final asynchronous send; otherwise, false. 78 | /// A cancellation token used to propagate notification that this operation should be canceled. 79 | /// The task object representing the asynchronous operation. 80 | Task SendAsync(ArraySegment buffer, bool endMessage, CancellationToken token = default); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket/GladNet.API.WebSocket.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) common API library that provides shared API for both client and server. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | 8.0 16 | GladNet.API.WebSocket 17 | 4.9.45 18 | 19 | 20 | 21 | true 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | <_Parameter1>GladNet.Server.DotNetTcpServer 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket/Message/WebSocketConnectionNetworkMessageInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Net.WebSockets; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Nito.AsyncEx; 11 | 12 | namespace GladNet 13 | { 14 | /// 15 | /// WebSocket Pipelines-based implementation of 16 | /// 17 | /// 18 | /// 19 | public class WebSocketConnectionNetworkMessageInterface : INetworkMessageInterface 20 | where TPayloadWriteType : class 21 | where TPayloadReadType : class 22 | { 23 | /// 24 | /// The pipelines socket connection. 25 | /// 26 | private IWebSocketConnection Connection { get; } 27 | 28 | /// 29 | /// The messages service container. 30 | /// 31 | protected SessionMessageBuildingServiceContext MessageServices { get; } 32 | 33 | /// 34 | /// The details of the session. 35 | /// 36 | protected NetworkConnectionOptions NetworkOptions { get; } 37 | 38 | private AsyncLock PayloadWriteLock { get; } = new AsyncLock(); 39 | 40 | public WebSocketConnectionNetworkMessageInterface(NetworkConnectionOptions networkOptions, 41 | IWebSocketConnection connection, 42 | SessionMessageBuildingServiceContext messageServices) 43 | { 44 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 45 | MessageServices = messageServices ?? throw new ArgumentNullException(nameof(messageServices)); 46 | NetworkOptions = networkOptions ?? throw new ArgumentNullException(nameof(networkOptions)); 47 | } 48 | 49 | /// 50 | public async Task> ReadMessageAsync(CancellationToken token = default) 51 | { 52 | while (!token.IsCancellationRequested 53 | && Connection.State == WebSocketState.Open) 54 | { 55 | // Buffer is MAX HEADER SIZE, we read MINIMUM size into it 56 | // and then see if we need to read more. 57 | var headerBuffer = ArrayPool.Shared.Rent(NetworkOptions.MaximumPacketHeaderSize); 58 | 59 | IPacketHeader header; 60 | int headerBytesRead; 61 | try 62 | { 63 | await ReadUntilBufferFullAsync(headerBuffer, NetworkOptions.MinimumPacketHeaderSize, token); 64 | 65 | // This code below was added to support variable length headers. 66 | // At this point we either have the entire packet header OR we maybed need to read another byte 67 | var totalPacketHeaderBytes = new ReadOnlySequence(headerBuffer, 0, NetworkOptions.MinimumPacketHeaderSize); 68 | 69 | if (!MessageServices.PacketHeaderFactory.IsHeaderReadable(totalPacketHeaderBytes)) 70 | { 71 | // Read the rest because we should now be able to compute the size 72 | int fullSize = MessageServices.PacketHeaderFactory.ComputeHeaderSize(totalPacketHeaderBytes); 73 | 74 | if (fullSize > NetworkOptions.MaximumPacketHeaderSize) 75 | throw new InvalidOperationException($"Calculated packet header size exceeds max Size: {NetworkOptions.MaximumPacketHeaderSize}"); 76 | 77 | int missingByteSize = fullSize - NetworkOptions.MinimumPacketHeaderSize; 78 | 79 | if (missingByteSize <= 0) 80 | throw new InvalidOperationException($"Packet header was not readable but also had no missing size."); 81 | 82 | // Read the missing bytes into the buffer 83 | await ReadAsync(headerBuffer, NetworkOptions.MinimumPacketHeaderSize, missingByteSize, token); 84 | totalPacketHeaderBytes = new ReadOnlySequence(headerBuffer, 0, fullSize); 85 | } 86 | 87 | header = ReadIncomingPacketHeader(totalPacketHeaderBytes, out headerBytesRead); 88 | } 89 | finally 90 | { 91 | ArrayPool.Shared.Return(headerBuffer); 92 | } 93 | 94 | //TODO: This is the best way to check for 0 length payload?? Seems hacky. 95 | //There is a special case when a packet is equal to the head size 96 | //meaning for example in the case of a 4 byte header then the packet is 4 bytes. 97 | //in this case we SHOULD not read anything. All the data exists already for the packet. 98 | if (header.PacketSize == headerBytesRead) 99 | { 100 | //The header is the entire packet, so empty buffer! 101 | TPayloadReadType payload = ReadIncomingPacketPayload(ReadOnlySequence.Empty, header); 102 | 103 | return new NetworkIncomingMessage(header, payload); 104 | } 105 | 106 | // Sanity check 107 | if (header.PayloadSize >= NetworkOptions.MaximumPayloadSize) 108 | throw new InvalidOperationException($"Encountered Payload with Size: {header.PayloadSize} greater than Max: {NetworkOptions.MaximumPayloadSize}"); 109 | 110 | var payloadBuffer = NetworkOptions.PacketArrayPool.Rent(header.PayloadSize); 111 | try 112 | { 113 | await ReadUntilBufferFullAsync(payloadBuffer, header.PayloadSize, token); 114 | 115 | //TODO: Valid incoming packet lengths to avoid a stack overflow. 116 | //This point we have a VALID read result that is NOT less than header.PayloadSize 117 | //therefore it should be safe now to read the incoming packet. 118 | TPayloadReadType payload = ReadIncomingPacketPayload(new ReadOnlySequence(payloadBuffer, 0, header.PayloadSize), header); 119 | 120 | return new NetworkIncomingMessage(header, payload); 121 | } 122 | finally 123 | { 124 | NetworkOptions.PacketArrayPool.Return(payloadBuffer); 125 | } 126 | } 127 | 128 | return null; 129 | } 130 | 131 | private async Task ReadUntilBufferFullAsync(byte[] buffer, int bufferSize, CancellationToken token) 132 | { 133 | await Connection.ReceiveAsync(buffer, bufferSize, token); 134 | } 135 | 136 | private async Task ReadAsync(byte[] buffer, int offset, int length, CancellationToken token) 137 | { 138 | await Connection.ReceiveAsync(buffer, offset, length, token); 139 | } 140 | 141 | /// 142 | /// Reads an incoming packet payload the result. 143 | /// Default just reads it from the buffer but special handling for some users may be required. 144 | /// Therefore it is virtual, and the reading buffer logic can be overriden. 145 | /// 146 | /// The incoming read buffer. 147 | /// The header that matches the payload type. 148 | /// 149 | protected virtual TPayloadReadType ReadIncomingPacketPayload(in ReadOnlySequence result, IPacketHeader header) 150 | { 151 | //Special case for zero-sized payload buffer 152 | if (result.IsEmpty) 153 | { 154 | int offset = 0; 155 | return MessageServices.MessageDeserializer.Deserialize(Span.Empty, ref offset); 156 | } 157 | 158 | //I opted to do this instead of stack alloc because of HUGE dangers in stack alloc and this is pretty efficient 159 | //buffer usage anyway. 160 | byte[] rentedBuffer = NetworkOptions.PacketArrayPool.Rent(header.PayloadSize); 161 | Span buffer = new Span(rentedBuffer, 0, header.PayloadSize); 162 | 163 | try 164 | { 165 | //This copy is BAD but it really avoids a lot of API headaches 166 | result.Slice(0, header.PayloadSize).CopyTo(buffer); 167 | 168 | int offset = 0; 169 | return MessageServices.MessageDeserializer.Deserialize(buffer, ref offset); 170 | } 171 | finally 172 | { 173 | NetworkOptions.PacketArrayPool.Return(rentedBuffer); 174 | } 175 | } 176 | 177 | private IPacketHeader ReadIncomingPacketHeader(ReadOnlySequence buffer, out int bytesRead) 178 | { 179 | //The implementation MUST be that this can be trusted to be the EXACT size of binary data that will be read. 180 | bytesRead = MessageServices.PacketHeaderFactory.ComputeHeaderSize(buffer); 181 | return DeserializePacketHeader(buffer, bytesRead); 182 | } 183 | 184 | /// 185 | /// Method should deserialize a object based on the input buffer. 186 | /// 187 | /// The data buffer containing the header. 188 | /// 189 | /// 190 | protected virtual IPacketHeader DeserializePacketHeader(ReadOnlySequence buffer, int exactHeaderByteCount) 191 | { 192 | IPacketHeader header; 193 | using (var context = new PacketHeaderCreationContext(buffer, exactHeaderByteCount)) 194 | header = MessageServices.PacketHeaderFactory.Create(context); 195 | 196 | return header; 197 | } 198 | 199 | /// 200 | public async Task SendMessageAsync(TPayloadWriteType message, CancellationToken token = default) 201 | { 202 | if (Connection.State != WebSocketState.Open) 203 | return SendResult.Disconnected; 204 | 205 | //THIS IS CRITICAL, IT'S NOT SAFE TO SEND MULTIPLE THREADS AT ONCE!! 206 | using (await PayloadWriteLock.LockAsync(token)) 207 | { 208 | try 209 | { 210 | await WriteOutgoingMessageAsync(message, token); 211 | } 212 | catch (Exception e) 213 | { 214 | //TODO: Logging! 215 | return SendResult.Error; 216 | } 217 | 218 | return SendResult.Sent; 219 | } 220 | } 221 | 222 | private async Task WriteOutgoingMessageAsync(TPayloadWriteType payload, CancellationToken token = default) 223 | { 224 | if(payload == null) throw new ArgumentNullException(nameof(payload)); 225 | 226 | //TODO: We should find a way to predict the size of a payload type. 227 | var buffer = NetworkOptions.PacketArrayPool.Rent(NetworkOptions.MaximumPacketSize); 228 | try 229 | { 230 | WritePacketToBuffer(payload, buffer, out var headerSize, out var payloadSize); 231 | await Connection.SendAsync(new ArraySegment(buffer, 0, headerSize + payloadSize), true, token); 232 | } 233 | finally 234 | { 235 | NetworkOptions.PacketArrayPool.Return(buffer); 236 | } 237 | } 238 | 239 | void WritePacketToBuffer(TPayloadWriteType payload, byte[] buffer, out int headerSize, out int payloadSize) 240 | { 241 | var bufferSpan = new Span(buffer); 242 | 243 | //It seems backwards, but we don't know what header to build until the payload is serialized. 244 | payloadSize = SerializeOutgoingPacketPayload(bufferSpan.Slice(NetworkOptions.MinimumPacketHeaderSize), payload); 245 | headerSize = SerializeOutgoingHeader(payload, payloadSize, bufferSpan.Slice(0, NetworkOptions.MaximumPacketHeaderSize)); 246 | 247 | //TODO: We must eventually support VARIABLE LENGTH packet headers. This is complicated, WoW does this for large packets sent by the server. 248 | if (headerSize != NetworkOptions.MinimumPacketHeaderSize) 249 | throw new NotSupportedException($"TODO: Variable length packet header sizes are not yet supported."); 250 | } 251 | 252 | private int SerializeOutgoingHeader(TPayloadWriteType payload, int payloadSize, in Span buffer) 253 | { 254 | int headerOffset = 0; 255 | MessageServices.HeaderSerializer.Serialize(new PacketHeaderSerializationContext(payload, payloadSize), buffer, ref headerOffset); 256 | return headerOffset; 257 | } 258 | 259 | /// 260 | /// Writes the outgoing packet payload. 261 | /// Returns the number of bytes the payload was sent as. 262 | /// 263 | /// The buffer to write the packet payload to. 264 | /// The payload instance. 265 | /// The number of bytes the payload was sent as. 266 | private int SerializeOutgoingPacketPayload(in Span buffer, TPayloadWriteType payload) 267 | { 268 | //Serializes the payload data to the span buffer and moves the pipe forward by the ref output offset 269 | //meaning we indicate to the pipeline that we've written bytes 270 | int offset = 0; 271 | MessageServices.MessageSerializer.Serialize(payload, buffer, ref offset); 272 | return offset; 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket/Network/WebSocketConnectionConnectionServiceAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Sockets; 4 | using System.Net.WebSockets; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace GladNet 10 | { 11 | /// 12 | /// Implementation of based around 13 | /// 14 | public sealed class WebSocketConnectionConnectionServiceAdapter : INetworkConnectionService 15 | { 16 | /// 17 | /// Internal socket connection. 18 | /// 19 | private IWebSocketConnection Connection { get; } 20 | 21 | /// 22 | public bool IsConnected => (Connection.State == WebSocketState.Open || Connection.State == WebSocketState.Connecting) 23 | && !Connection.CloseStatus.HasValue; 24 | 25 | public WebSocketConnectionConnectionServiceAdapter(IWebSocketConnection connection) 26 | { 27 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 28 | } 29 | 30 | /// 31 | public async Task DisconnectAsync() 32 | { 33 | await Connection.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, CancellationToken.None); 34 | } 35 | 36 | //TODO: This is kind of stupid to even implement. This should NEVER be called on serverside! 37 | /// 38 | public async Task ConnectAsync(string ip, int port) 39 | { 40 | if (IsConnected) 41 | return false; 42 | 43 | // Use UriBuilder to modify the URI 44 | UriBuilder uriBuilder = new UriBuilder(ip); 45 | 46 | // Check if the port is already present in the URI 47 | // -1 means no port was specified 48 | if (uriBuilder.Port <= 0 49 | || port > 0 && uriBuilder.Port != port) 50 | uriBuilder.Port = port; 51 | 52 | Uri finalUri = uriBuilder.Uri; 53 | 54 | await Connection.ConnectAsync(finalUri, CancellationToken.None); 55 | return Connection.State == WebSocketState.Open; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket/Session/BaseClientWebSocketManagedSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Net.WebSockets; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Glader.Essentials; 9 | using Nito.AsyncEx; 10 | 11 | namespace GladNet 12 | { 13 | /// 14 | /// Base WebSocket -based 15 | /// implementation. 16 | /// 17 | /// 18 | /// 19 | public abstract class BaseWebSocketManagedSession 20 | : ManagedSession 21 | where TPayloadWriteType : class 22 | where TPayloadReadType : class 23 | { 24 | /// 25 | /// The socket connection. 26 | /// 27 | protected IWebSocketConnection Connection { get; } 28 | 29 | protected BaseWebSocketManagedSession(NetworkConnectionOptions networkOptions, IWebSocketConnection connection, SessionDetails details, 30 | SessionMessageBuildingServiceContext messageServices) 31 | : base(new WebSocketConnectionConnectionServiceAdapter(connection), details, networkOptions, messageServices, 32 | BuildMessageInterfaceContext(networkOptions, connection, messageServices)) 33 | { 34 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 35 | } 36 | 37 | //This overload lets implementer specify a messageInterface. 38 | protected BaseWebSocketManagedSession(NetworkConnectionOptions networkOptions, IWebSocketConnection connection, SessionDetails details, 39 | SessionMessageBuildingServiceContext messageServices, 40 | INetworkMessageInterface messageInterface) 41 | : base(new WebSocketConnectionConnectionServiceAdapter(connection), details, networkOptions, messageServices, 42 | BuildMessageInterfaceContext(messageInterface)) 43 | { 44 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 45 | } 46 | 47 | //This overload lets implementer specify a message interface context. 48 | protected BaseWebSocketManagedSession(NetworkConnectionOptions networkOptions, IWebSocketConnection connection, SessionDetails details, 49 | SessionMessageBuildingServiceContext messageServices, 50 | SessionMessageInterfaceServiceContext messageInterfaces) 51 | : base(new WebSocketConnectionConnectionServiceAdapter(connection), details, networkOptions, messageServices, messageInterfaces) 52 | { 53 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 54 | } 55 | 56 | private static SessionMessageInterfaceServiceContext BuildMessageInterfaceContext(NetworkConnectionOptions networkOptions, IWebSocketConnection connection, SessionMessageBuildingServiceContext messageServices) 57 | { 58 | INetworkMessageInterface messageInterface = new WebSocketConnectionNetworkMessageInterface(networkOptions, connection, messageServices); 59 | return BuildMessageInterfaceContext(messageInterface); 60 | } 61 | 62 | private static SessionMessageInterfaceServiceContext BuildMessageInterfaceContext(INetworkMessageInterface messageInterface) 63 | { 64 | return new SessionMessageInterfaceServiceContext(new AsyncExProducerConsumerQueueAsyncMessageQueue(), messageInterface); 65 | } 66 | 67 | /// 68 | public override async Task StartListeningAsync(CancellationToken token = default) 69 | { 70 | await base.StartListeningAsync(token); 71 | } 72 | 73 | /// 74 | public override async Task StartWritingAsync(CancellationToken token = default) 75 | { 76 | while (!token.IsCancellationRequested) 77 | { 78 | //Dequeue from the outgoing message queue and send through the send service. 79 | TPayloadWriteType payload = await MessageService.OutgoingMessageQueue.DequeueAsync(token); 80 | SendResult result = await MessageService.MessageInterface.SendMessageAsync(payload, token); 81 | 82 | //TODO: Add logging! 83 | if (result != SendResult.Sent && result != SendResult.Enqueued) 84 | return; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/GladNet.API.WebSocket/Session/SessionCreationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | 6 | namespace GladNet 7 | { 8 | public sealed class SessionCreationContext 9 | { 10 | /// 11 | /// The underlying connection object for the session. 12 | /// 13 | public IWebSocketConnection Connection { get; } 14 | 15 | /// 16 | /// The details of the session. 17 | /// 18 | public SessionDetails Details { get; } 19 | 20 | public SessionCreationContext(IWebSocketConnection connection, SessionDetails details) 21 | { 22 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 23 | Details = details ?? throw new ArgumentNullException(nameof(details)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/GladNet.API/Collections/AsyncExProducerConsumerQueueAsyncMessageQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Nito.AsyncEx; 7 | 8 | namespace GladNet 9 | { 10 | /// 11 | /// implementation of the GladNet 12 | /// 13 | /// 14 | public sealed class AsyncExProducerConsumerQueueAsyncMessageQueue 15 | : IAsyncMessageQueue, IDisposable 16 | { 17 | private AsyncProducerConsumerQueue InternalQueue { get; } = new AsyncProducerConsumerQueue(); 18 | 19 | /// 20 | public async Task DequeueAsync(CancellationToken token = default) 21 | { 22 | return await InternalQueue.DequeueAsync(token); 23 | } 24 | 25 | /// 26 | public async Task EnqueueAsync(TMessageType message, CancellationToken token = default) 27 | { 28 | await InternalQueue.EnqueueAsync(message, token); 29 | return true; 30 | } 31 | 32 | public void Dispose() 33 | { 34 | InternalQueue.CompleteAdding(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/GladNet.API/Collections/IAsyncMessageQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace GladNet 8 | { 9 | /// 10 | /// Common interface for an async message queue for GladNet. 11 | /// Implementers should provide a THREADSAFE queueing and dequeue functionality 12 | /// for the provided generic message type. 13 | /// 14 | /// The generic message type the queue supports. 15 | public interface IAsyncMessageQueue 16 | { 17 | /// 18 | /// Attempts to dequeue a message from the async message queue. 19 | /// Will not return until cancelled or a message is available. 20 | /// 21 | /// Cancel token indicating if the operation should be cancelled. 22 | /// 23 | Task DequeueAsync(CancellationToken token = default); 24 | 25 | /// 26 | /// Queues a message into the async message queue. 27 | /// 28 | /// The message instance to queue. 29 | /// Cancel token indicating if the operation should be cancelled. 30 | /// True if the enqueue operation was successful. If false, then was NOT queued. 31 | Task EnqueueAsync(TMessageType message, CancellationToken token = default); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/GladNet.API/Constants/NetworkConnectionOptionsConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | public static class NetworkConnectionOptionsConstants 8 | { 9 | /// 10 | /// The default maximum handlable packet payload size. 11 | /// 12 | public const int DEFAULT_MAXIMUM_PACKET_PAYLOAD_SIZE = 36000; 13 | 14 | public const int DEFAULT_MINIMUM_PACKET_HEADER_SIZE = 2; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/GladNet.API/Extensions/IDisposableAttachableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Text; 5 | using Glader.Essentials; 6 | 7 | namespace GladNet 8 | { 9 | /// 10 | /// Extensions for type. 11 | /// 12 | public static class IDisposableAttachableExtensions 13 | { 14 | /// 15 | /// Attaches the disposable instance to the object. 16 | /// 17 | /// The target to attach the disposable to. 18 | /// The disposable to attach. 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | [Obsolete("This method has been deprecated. Use IDisposableAttachable.AttachDisposable.")] 21 | public static void AttachDisposableResource(this IDisposableAttachable attachTarget, IDisposable disposable) 22 | { 23 | if (attachTarget == null) throw new ArgumentNullException(nameof(attachTarget)); 24 | attachTarget.AttachDisposable(disposable); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/GladNet.API/Extensions/IMessageSendServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace GladNet 8 | { 9 | /// 10 | /// extension methods. 11 | /// 12 | public static class IMessageSendServiceExtensions 13 | { 14 | /// 15 | /// Sends a asyncronously. 16 | /// The task will complete when the network message is sent or when the token is cancelled. 17 | /// This special overload requires the message type to be newable (contain a public parameterless ctor). 18 | /// 19 | /// The send service. 20 | /// The cancel token for the send operation. 21 | /// Returns an awaitable when the send operation is completed. 22 | public static Task SendMessageAsync(this IMessageSendService service, CancellationToken token = default) 23 | where TMessageType : class, new() 24 | { 25 | //TODO: We could do some polling with TMessageType at some point probably. 26 | return service.SendMessageAsync(new TMessageType(), token); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/GladNet.API/GladNet.API.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) common API library that provides shared API for both client and server. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | 8.0 16 | GladNet.API 17 | 4.9.45 18 | 19 | 20 | 21 | true 22 | true 23 | 24 | 25 | 26 | C:\Users\Glader\Documents\Github\GladNet3\src\GladNet.API\bin\GladNet.API.xml 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | <_Parameter1>GladNet.Server.DotNetTcpServer 39 | 40 | 41 | 42 | <_Parameter1>GladNet.Server.DotNetWebSocketServer 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Dispatching/INetworkMessageDispatchingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace GladNet 8 | { 9 | //You may wonder why this exists. It exists because you may want to async route messages to other services/servers. 10 | //The responses may be awaited asyncronouosly and then sent back to the client. This could be for microserivces or for 11 | //load balancing reasons. This only exists on the server for that reason. Handler API is for clients too though, but this 12 | //exists before the handler API for the reasoning above. 13 | /// 14 | /// Contract for strategies for dispatching network messages. 15 | /// 16 | public interface INetworkMessageDispatchingStrategy 17 | where TPayloadWriteType : class 18 | where TPayloadReadType : class 19 | { 20 | /// 21 | /// Message handling method. 22 | /// The task should complete not when the message is done being handled but when the 23 | /// session should continue reading messages. This means that if desired you should be able to read more 24 | /// messages even while other session's messages are being handled. 25 | /// 26 | /// The context for the incoming message. 27 | /// The incoming message. 28 | /// The cancel token for the operation. 29 | /// A task that completes when the session should begin reading more messages. 30 | Task DispatchNetworkMessageAsync(SessionMessageContext context, NetworkIncomingMessage message, CancellationToken token = default); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Dispatching/InPlaceNetworkMessageDispatchingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Glader.Essentials; 7 | 8 | namespace GladNet 9 | { 10 | /// 11 | /// The default in place dispatching strategy for network messages which 12 | /// handles them immediately without any enqueueing and will yield back when 13 | /// the message handling has been completed. 14 | /// 15 | /// The type of payload that is writeable by the application.. 16 | /// The type of payload that is handled. 17 | public sealed class InPlaceNetworkMessageDispatchingStrategy : INetworkMessageDispatchingStrategy 18 | where TPayloadWriteType : class 19 | where TPayloadReadType : class 20 | { 21 | /// 22 | /// Handler service that can handle the incoming message. 23 | /// 24 | private IMessageHandlerService> HandlerService { get; } 25 | 26 | public InPlaceNetworkMessageDispatchingStrategy(IMessageHandlerService> handlerService) 27 | { 28 | HandlerService = handlerService ?? throw new ArgumentNullException(nameof(handlerService)); 29 | } 30 | 31 | /// 32 | public async Task DispatchNetworkMessageAsync(SessionMessageContext context, NetworkIncomingMessage message, CancellationToken token = default) 33 | { 34 | await HandlerService.HandleMessageAsync(context, message.Payload, token); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/GladNet.API/Message/Header/HeaderlessPacketHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Header for a packet that doesn't contain an actual header. 9 | /// 10 | public sealed class HeaderlessPacketHeader : IPacketHeader 11 | { 12 | /// 13 | public int PacketSize { get; } 14 | 15 | //Packet size is the same as payload size in a headerless message. 16 | /// 17 | public int PayloadSize => PacketSize; 18 | 19 | /// 20 | public HeaderlessPacketHeader(int packetSize) 21 | { 22 | if(packetSize < 0) throw new ArgumentOutOfRangeException(nameof(packetSize)); 23 | 24 | PacketSize = packetSize; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Header/IPacketHeader.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 GladNet 8 | { 9 | /// 10 | /// Contract for packet headers. 11 | /// 12 | public interface IPacketHeader 13 | { 14 | /// 15 | /// The size of the packet. 16 | /// 17 | int PacketSize { get; } 18 | 19 | //The PacketSize contains the whole size of the packet 20 | //So the payload is just the size minus the size of the packetsize field. 21 | /// 22 | /// Indicates the size of the payload. 23 | /// 24 | int PayloadSize { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Header/IPacketHeaderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Glader.Essentials; 6 | 7 | namespace GladNet 8 | { 9 | /// 10 | /// Contract for factories that can build packet headers from 11 | /// 12 | public interface IPacketHeaderFactory : IFactoryCreatable 13 | { 14 | /// 15 | /// Indicates if a header is readable from the provided buffer. 16 | /// (Ex. not enough bytes, header is not readable.) 17 | /// 18 | /// The buffer to inspect. 19 | /// True if a header is readable. 20 | bool IsHeaderReadable(in Span buffer); 21 | 22 | /// 23 | /// Indicates if a header is readable from the provided buffer. 24 | /// (Ex. not enough bytes, header is not readable.) 25 | /// 26 | /// The buffer to inspect. 27 | /// True if a header is readable. 28 | bool IsHeaderReadable(in ReadOnlySequence buffer); 29 | 30 | /// 31 | /// Computes the binary size of a header given the buffer. 32 | /// This exists so that does not need to indicate 33 | /// how many bytes were read via an out parameter. 34 | /// 35 | /// The buffer to inspect. 36 | /// The binary size of the header. 37 | int ComputeHeaderSize(in ReadOnlySequence buffer); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Header/PacketHeaderCreationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace GladNet 7 | { 8 | //Due to Span constraints we actually cannly use a ref struct as the context due to generics issue. 9 | //See: https://stackoverflow.com/questions/50871135/why-ref-structs-cannot-be-used-as-type-arguments 10 | //Below is ideal if C# 10 ever supports it. 11 | /*public ref struct PacketHeaderCreationContext 12 | { 13 | /// 14 | /// The packet header buffer. 15 | /// 16 | public Span Buffer { get; } 17 | 18 | /// 19 | /// Creates a new context for header creation. 20 | /// 21 | /// The binary buffer the packet is contained within. 22 | public PacketHeaderCreationContext(Span buffer) 23 | { 24 | Buffer = buffer; 25 | } 26 | }*/ 27 | 28 | /// 29 | /// The creation context for a packet header. 30 | /// 31 | public sealed class PacketHeaderCreationContext : IDisposable 32 | { 33 | /// 34 | /// Represents the internal buffer wrapper for the header data. 35 | /// 36 | private ReadOnlySequence InternalBuffer { get; } 37 | 38 | /// 39 | /// Internal sync/lock object. 40 | /// 41 | private readonly object SyncObj = new object(); 42 | 43 | /// 44 | /// Represents the internal temporarily allocated buffer for the header bytes. 45 | /// 46 | private byte[] InternalHeaderByteBuffer { get; set; } 47 | 48 | /// 49 | /// We create an internal header pool for allocating and avoiding contention on a general pool. 50 | /// 51 | private static ArrayPool HeaderCreationPool { get; } = ArrayPool.Create(); 52 | 53 | /// 54 | /// Indicates if the header creation context's buffer/memory is disposed. 55 | /// 56 | public bool isDisposed { get; private set; } = false; 57 | 58 | /// 59 | /// Indicates the exact binary size of the header. 60 | /// Must be smaller or equal to length. 61 | /// 62 | public int HeaderBinarySize { get; } 63 | 64 | public PacketHeaderCreationContext(ReadOnlySequence internalBuffer, int headerBinarySize) 65 | { 66 | InternalBuffer = internalBuffer; 67 | HeaderBinarySize = headerBinarySize; 68 | 69 | if (internalBuffer.Length < headerBinarySize) 70 | throw new InvalidOperationException($"Specified header length: {headerBinarySize} exceed buffer length: {internalBuffer.Length}"); 71 | } 72 | 73 | /// 74 | /// Gets the that represents the header. 75 | /// 76 | /// 77 | /// 78 | public Span GetSpan() 79 | { 80 | lock (SyncObj) 81 | { 82 | if (isDisposed) 83 | throw new ObjectDisposedException(nameof(PacketHeaderCreationContext)); 84 | 85 | if (InternalHeaderByteBuffer != null) 86 | { 87 | //Slice, don't assume array pool gives exact size. IT DOES NOT! 88 | if(InternalHeaderByteBuffer.Length == HeaderBinarySize) 89 | return new Span(InternalHeaderByteBuffer); 90 | else 91 | return new Span(InternalHeaderByteBuffer).Slice(0, HeaderBinarySize); 92 | } 93 | 94 | //Idea here is to temporarily rent a buffer. 95 | InternalHeaderByteBuffer = HeaderCreationPool.Rent(HeaderBinarySize); 96 | 97 | //TODO: This may needlessly copy a bunch of data into a buffer that we don't need, above we 98 | //allocate a buffer of InternalBuffer.Length size. This could be thousands of read bytes 99 | //but we may only want 100 | Span buffer = new Span(InternalHeaderByteBuffer, 0, HeaderBinarySize); 101 | 102 | //This may seem unsafe at first but the implementation will not try to copy more bytes than 103 | //are available in the Span. Therefore Span CAN be much smaller than the available internal buffer 104 | //without worrying. 105 | //See: https://github.com/dotnet/corefxlab/blob/master/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs#L29 106 | InternalBuffer.Slice(0, HeaderBinarySize).CopyTo(buffer); 107 | 108 | return buffer; 109 | } 110 | } 111 | 112 | /// 113 | /// Disposes the internal buffers related to the packet header. 114 | /// SHOULD ONLY BE CALLED INTERNALLY BY THE LIBRARY! 115 | /// 116 | public void Dispose() 117 | { 118 | lock (SyncObj) 119 | { 120 | if (isDisposed) 121 | return; 122 | 123 | if(InternalHeaderByteBuffer != null) 124 | HeaderCreationPool.Return(InternalHeaderByteBuffer); 125 | 126 | InternalHeaderByteBuffer = null; 127 | isDisposed = true; 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Header/PacketHeaderSerializationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Context for header serialization/creation from a . 9 | /// For creation from binary see 10 | /// 11 | /// 12 | public sealed class PacketHeaderSerializationContext 13 | where TPayloadType : class 14 | { 15 | /// 16 | /// The packet payload instance/object. 17 | /// 18 | public TPayloadType Payload { get; } 19 | 20 | /// 21 | /// The binary size of the . 22 | /// 23 | public int PayloadSize { get; } 24 | 25 | public PacketHeaderSerializationContext(TPayloadType payload, int payloadSize) 26 | { 27 | if (payloadSize < 0) throw new ArgumentOutOfRangeException(nameof(payloadSize)); 28 | Payload = payload ?? throw new ArgumentNullException(nameof(payload)); 29 | PayloadSize = payloadSize; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/INetworkOutgoingMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using GladNet; 8 | 9 | namespace GladNet 10 | { 11 | /// 12 | /// Contract for an outgoing network message. 13 | /// 14 | public interface INetworkOutgoingMessage 15 | where THeaderType : IPacketHeader 16 | { 17 | /// 18 | /// The header for the outgoing message. 19 | /// 20 | THeaderType Header { get; } 21 | 22 | /// 23 | /// The byte representation of the payload. 24 | /// 25 | Span Payload { get; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/IPeerMessageContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using GladNet; 7 | 8 | namespace GladNet 9 | { 10 | /// 11 | /// Contract for the context of a common GladNet message. 12 | /// 13 | public interface IPeerMessageContext 14 | where TPayloadWriteType : class 15 | { 16 | //Below you'll see a bunch of interfaces that the client 17 | //actually implements. However we don't have to generic to hell 18 | //all of the code. It becomes too cumbersome to understand and consume in that 19 | //case and leads to generic type parameter carrying, a design fault. 20 | 21 | /// 22 | /// The connection service that provides ways to disconnect and connect 23 | /// the client associated with this message connect. 24 | /// 25 | INetworkConnectionService ConnectionService { get; } 26 | 27 | /// 28 | /// The sending service that allows clients to send messages. 29 | /// 30 | IMessageSendService MessageService { get; } 31 | } 32 | } -------------------------------------------------------------------------------- /src/GladNet.API/Message/IPeerSessionMessageContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Contract for session/server peer connections message contexts. 9 | /// 10 | /// Outgoing payload type. 11 | public interface IPeerSessionMessageContext : IPeerMessageContext 12 | where TPayloadWriteType : class 13 | { 14 | /// 15 | /// The deatils of the session. 16 | /// 17 | SessionDetails Details { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/NetworkIncomingMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using GladNet; 8 | 9 | namespace GladNet 10 | { 11 | /// 12 | /// Full message for an incoming packet. 13 | /// 14 | public sealed class NetworkIncomingMessage 15 | where TPayloadType : class 16 | { 17 | /// 18 | /// The header for the incoming message. 19 | /// 20 | public IPacketHeader Header { get; } 21 | 22 | /// 23 | /// The deserialized payload for the incoming header. 24 | /// 25 | public TPayloadType Payload { get; } 26 | 27 | /// 28 | /// Creates a new incoming message envelope. Represents a complete 29 | /// logical packet including the header and the deserialized payload. 30 | /// 31 | /// 32 | /// The payload. 33 | public NetworkIncomingMessage(IPacketHeader header, TPayloadType payload) 34 | { 35 | if(header == null) throw new ArgumentNullException(nameof(header)); 36 | if(payload == null) throw new ArgumentNullException(nameof(payload)); 37 | 38 | Header = header; 39 | Payload = payload; 40 | } 41 | 42 | //Serializer ctor 43 | private NetworkIncomingMessage() 44 | { 45 | 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/SendResult.cs: -------------------------------------------------------------------------------- 1 | namespace GladNet 2 | { 3 | /// 4 | /// Enumeration of results of trying to send 5 | /// a message. 6 | /// 7 | public enum SendResult 8 | { 9 | //TODO: Implement 10 | /// 11 | /// Indicates that the message has been enqueued for sending. 12 | /// The result can't be known, and would be only determined in the future. 13 | /// 14 | Enqueued = 0, 15 | 16 | /// 17 | /// Indicates that the message has been sent. 18 | /// 19 | Sent = 1, 20 | 21 | /// 22 | /// Indicates that the network is disconnected 23 | /// and the message cannot be sent. 24 | /// 25 | Disconnected = 2, 26 | 27 | /// 28 | /// Indicates an error was encountered. 29 | /// 30 | Error = 3 31 | } 32 | } -------------------------------------------------------------------------------- /src/GladNet.API/Message/Serialization/IMessageDeserializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Contract for types that can deserialize an instance of 9 | /// from the provided binary buffers. 10 | /// 11 | /// 12 | public interface IMessageDeserializer 13 | where TMessageType : class 14 | { 15 | /// 16 | /// Deserializes an instance of from the provided buffer. 17 | /// Offset should indicate how far offset the buffer now is. 18 | /// 19 | /// The buffer. 20 | /// The offset into the buffer to start with (and should represent the ending position too). 21 | /// 22 | TMessageType Deserialize(Span buffer, ref int offset); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Serialization/IMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Contract for types that can serializes an instance of 9 | /// into the provided binary buffers. 10 | /// 11 | /// The serializable message type supported. 12 | public interface IMessageSerializer 13 | where TMessageType : class 14 | { 15 | /// 16 | /// Serializes an instance of into the provided buffer. 17 | /// Offset should indicate how far offset the buffer now is. 18 | /// 19 | /// The value to serialize. 20 | /// The buffer. 21 | /// The offset into the buffer to start with (and should represent the ending position too). 22 | /// 23 | void Serialize(TMessageType value, Span buffer, ref int offset); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Services/IMessageSendService.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 GladNet; 8 | 9 | namespace GladNet 10 | { 11 | /// 12 | /// Contract for services that provide the ability to send messages. 13 | /// 14 | /// The message's base type that is sent. 15 | public interface IMessageSendService 16 | where TMessageBaseType : class 17 | { 18 | //Implementer of this method should assume multi callers may call this method at one time 19 | //Therefore threadsafety must be implemented an assured by the implementer of this method. 20 | /// 21 | /// Sends a asyncronously. 22 | /// The task will complete when the network message is sent or when the token is cancelled. 23 | /// 24 | /// The message to send. 25 | /// The cancel token for the send operation. 26 | /// Returns an awaitable when the send operation is completed. 27 | Task SendMessageAsync(TMessageBaseType message, CancellationToken token = default); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Services/INetworkMessageInterface.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 GladNet; 8 | 9 | namespace GladNet 10 | { 11 | /// 12 | /// Contract for a type that provided read/write interface for network messages 13 | /// and payloads. 14 | /// 15 | /// The incoming/read payload type. 16 | /// The outgoing/written payload type. 17 | public interface INetworkMessageInterface : IMessageSendService 18 | where TPayloadReadType : class 19 | where TPayloadWriteType : class 20 | { 21 | /// 22 | /// Produces a asyncronously. 23 | /// The task will complete when a network message is available or when the token is cancelled. 24 | /// 25 | /// Returns a future that will complete when a message is available. 26 | Task> ReadMessageAsync(CancellationToken token = default(CancellationToken)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/Services/INetworkMessageReceivable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace GladNet 8 | { 9 | /// 10 | /// Contract for types that handle a received network message. 11 | /// 12 | /// 13 | public interface INetworkMessageReceivable 14 | where TPayloadType : class 15 | { 16 | /// 17 | /// Called when a network message is received. 18 | /// Can also be called to simulate the receiving a network message. 19 | /// 20 | /// The network message recieved. 21 | /// Cancel token that can be used to indicate if a session is cancelled. 22 | /// 23 | Task OnNetworkMessageReceivedAsync(NetworkIncomingMessage message, CancellationToken token = default(CancellationToken)); 24 | } 25 | } -------------------------------------------------------------------------------- /src/GladNet.API/Message/Services/QueueBasedMessageSendService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace GladNet 8 | { 9 | /// 10 | /// Queue-based implementation of . 11 | /// Using a queuing mechanism returning immediately after enqueueing. 12 | /// This strategy should be preferred compared to direct/inplace message send/handling to avoid 13 | /// delaying/awaiting operations on critical threads. 14 | /// 15 | /// 16 | public class QueueBasedMessageSendService : IMessageSendService 17 | where TMessageBaseType : class 18 | { 19 | /// 20 | /// Internal message queue for the send service to enqueue into. 21 | /// 22 | private IAsyncMessageQueue MessageQueue { get; } 23 | 24 | public QueueBasedMessageSendService(IAsyncMessageQueue messageQueue) 25 | { 26 | MessageQueue = messageQueue ?? throw new ArgumentNullException(nameof(messageQueue)); 27 | } 28 | 29 | /// 30 | public async Task SendMessageAsync(TMessageBaseType message, CancellationToken token = default) 31 | { 32 | bool enqueued = await MessageQueue.EnqueueAsync(message, token); 33 | 34 | return enqueued ? SendResult.Enqueued : SendResult.Error; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/GladNet.API/Message/SessionMessageContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | //TODO: We need better naming. Kinda conflicts with the existing message context for handlers 8 | /// 9 | /// The context for a network message. 10 | /// 11 | /// 12 | public class SessionMessageContext : IPeerSessionMessageContext 13 | where TPayloadWriteType : class 14 | { 15 | /// 16 | /// The session associated with the message. 17 | /// 18 | public SessionDetails Details { get; } 19 | 20 | /// 21 | public INetworkConnectionService ConnectionService { get; } 22 | 23 | /// 24 | public IMessageSendService MessageService { get; } 25 | 26 | /// 27 | public SessionMessageContext(SessionDetails details, 28 | IMessageSendService messageService, 29 | INetworkConnectionService connectionService) 30 | { 31 | MessageService = messageService ?? throw new ArgumentNullException(nameof(messageService)); 32 | ConnectionService = connectionService ?? throw new ArgumentNullException(nameof(connectionService)); 33 | Details = details ?? throw new ArgumentNullException(nameof(details)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/GladNet.API/Network/INetworkConnectable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace GladNet 9 | { 10 | /// 11 | /// Contract for a network connectable type. 12 | /// 13 | public interface INetworkConnectable 14 | { 15 | /// 16 | /// Connects to the provided with on the given . 17 | /// 18 | /// The ip. 19 | /// The port. 20 | /// True if connection was successful (WARNING: May not return until connect disconnects). 21 | Task ConnectAsync(string ip, int port); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/GladNet.API/Network/INetworkDisconnectable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace GladNet 9 | { 10 | /// 11 | /// Contract for a disconnectable type. 12 | /// 13 | public interface INetworkDisconnectable 14 | { 15 | /// 16 | /// Disconnects asyncronously to send or recieve remaining data. 17 | /// 18 | /// A wait. 19 | Task DisconnectAsync(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/GladNet.API/Network/NetworkAddressInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Text; 5 | 6 | namespace GladNet 7 | { 8 | /// 9 | /// Encapsulated object for a network address. 10 | /// 11 | public sealed class NetworkAddressInfo 12 | { 13 | /// 14 | /// The IPAddress of the network. 15 | /// 16 | public IPAddress AddressEndpoint { get; } 17 | 18 | /// 19 | /// The port of the network. 20 | /// 21 | public int Port { get; } 22 | 23 | /// 24 | public NetworkAddressInfo(IPAddress addressEndpoint, int port) 25 | { 26 | if(addressEndpoint == null) throw new ArgumentNullException(nameof(addressEndpoint)); 27 | if(port <= 0) throw new ArgumentOutOfRangeException(nameof(port)); 28 | 29 | AddressEndpoint = addressEndpoint; 30 | Port = port; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/GladNet.API/Network/NetworkConnectionOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Glader.Essentials; 6 | 7 | namespace GladNet 8 | { 9 | public class NetworkConnectionOptions 10 | { 11 | /// 12 | /// Indicates the configured maximum packet size. 13 | /// 14 | public int MaximumPacketSize => MaximumPacketHeaderSize + MaximumPayloadSize; 15 | 16 | /// 17 | /// Indicates the configured minimum required size of a packet header. 18 | /// Some protocols have variable length headers such as World of Warcraft. 19 | /// 20 | public int MinimumPacketHeaderSize { get; } 21 | 22 | /// 23 | /// Indicates the configured maximum required size of a packet header. 24 | /// Some protocols have variable length headers such as World of Warcraft. 25 | /// 26 | public int MaximumPacketHeaderSize { get; } 27 | 28 | /// 29 | /// Indicates the configured maximum packet payload size. 30 | /// 31 | public int MaximumPayloadSize { get; } 32 | 33 | /// 34 | /// Retrieves an array pool specific to the options. 35 | /// Specifically 36 | /// 37 | public ArrayPool PacketArrayPool => GetPacketArrayPool(); 38 | 39 | public NetworkConnectionOptions() 40 | { 41 | MaximumPayloadSize = NetworkConnectionOptionsConstants.DEFAULT_MAXIMUM_PACKET_PAYLOAD_SIZE; 42 | MinimumPacketHeaderSize = NetworkConnectionOptionsConstants.DEFAULT_MINIMUM_PACKET_HEADER_SIZE; 43 | 44 | //TODO: Don't use same header size 45 | MaximumPacketHeaderSize = MinimumPacketHeaderSize; 46 | } 47 | 48 | public NetworkConnectionOptions(int minimumPacketHeaderSize, int maximumPacketHeaderSize, int maximumPayloadSize) 49 | { 50 | MinimumPacketHeaderSize = minimumPacketHeaderSize; 51 | MaximumPacketHeaderSize = maximumPacketHeaderSize; 52 | MaximumPayloadSize = maximumPayloadSize; 53 | } 54 | 55 | private const int DefaultMaxArrayPoolArrayLength = 1024 * 1024; 56 | 57 | /// 58 | /// Determines which array pool to use for the network options. 59 | /// 60 | /// An array pool to use for the packet buffers. 61 | private ArrayPool GetPacketArrayPool() 62 | { 63 | // See: https://github.com/dotnet/runtime/blob/6221ddb3051463309801c9008f332b34361da798/src/libraries/System.Private.CoreLib/src/System/Buffers/ConfigurableArrayPool.cs#L12 64 | if (MaximumPacketSize >= DefaultMaxArrayPoolArrayLength) 65 | { 66 | return LargeArrayPool.Shared; 67 | } 68 | else 69 | { 70 | return ArrayPool.Shared; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/GladNet.API/Service/INetworkConnectionService.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 GladNet 8 | { 9 | /// 10 | /// Unified contract for and 11 | /// making it easier to consume. 12 | /// 13 | public interface INetworkConnectionService : INetworkDisconnectable, INetworkConnectable 14 | { 15 | /// 16 | /// Indicates if a connection is established. 17 | /// 18 | bool IsConnected { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/GladNet.API/Session/IManagedSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Contract for managed client session 9 | /// 10 | public interface IManagedSession 11 | { 12 | //TODO: We should create an event that can be subscribed to for disconnecting 13 | //TODO: Is this the best way to do this? 14 | /// 15 | /// Service that can be used for disconnecting the session. 16 | /// 17 | INetworkConnectionService ConnectionService { get; } 18 | 19 | /// 20 | /// The details of the session. 21 | /// 22 | SessionDetails Details { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/GladNet.API/Session/ManagedSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Glader.Essentials; 7 | using GladNet; 8 | 9 | namespace GladNet 10 | { 11 | /// 12 | /// Base type for managed sessions. 13 | /// 14 | public abstract class ManagedSession : IManagedSession, ISingleDisposable, IDisposableAttachable 15 | { 16 | /// 17 | /// Internal lock object. 18 | /// 19 | protected readonly object SyncObj = new object(); 20 | 21 | /// 22 | /// Represents all the disposable dependencies of a . 23 | /// This will be disposed when the session is disposed. 24 | /// 25 | private List InternalDisposables { get; } = new List(); 26 | 27 | /// 28 | public INetworkConnectionService ConnectionService { get; } 29 | 30 | /// 31 | public SessionDetails Details { get; } 32 | 33 | /// 34 | /// Represents the network configuration options for the session. 35 | /// 36 | protected NetworkConnectionOptions NetworkOptions { get; } 37 | 38 | /// 39 | public bool isDisposed { get; private set; } 40 | 41 | internal ManagedSession(INetworkConnectionService connectionService, SessionDetails details, NetworkConnectionOptions networkOptions) 42 | { 43 | ConnectionService = connectionService ?? throw new ArgumentNullException(nameof(connectionService)); 44 | Details = details ?? throw new ArgumentNullException(nameof(details)); 45 | NetworkOptions = networkOptions ?? throw new ArgumentNullException(nameof(networkOptions)); 46 | } 47 | 48 | /// 49 | /// Implementers should start the internal network listening async algorithm. 50 | /// 51 | /// Awaitable that completes when listening is finished. 52 | public abstract Task StartListeningAsync(CancellationToken token = default); 53 | 54 | /// 55 | /// Implementers should start the internal network writing async algorithm. 56 | /// 57 | /// Awaitable that completes when writing is finished. 58 | public abstract Task StartWritingAsync(CancellationToken token = default); 59 | 60 | //See: https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose 61 | /// 62 | public void Dispose() 63 | { 64 | lock (SyncObj) 65 | { 66 | if (isDisposed) 67 | return; 68 | 69 | try 70 | { 71 | //Foreach but make sure to guard against exceptions 72 | //caused by disposal because we need to dispose of ALL resources first or else we 73 | //may leak. 74 | Exception optionalException = null; 75 | foreach(var disposable in InternalDisposables) 76 | try 77 | { 78 | disposable.Dispose(); 79 | } 80 | catch(Exception e) 81 | { 82 | optionalException = e; 83 | } 84 | 85 | //We throw so we don't silently supress the error. 86 | //We could have multiple exceptions from this operation!! We only get the last though. 87 | if(optionalException != null) 88 | throw new InvalidOperationException($"Failed to dispose of all resources gracefully. Error: {optionalException}", optionalException); 89 | } 90 | finally 91 | { 92 | isDisposed = true; 93 | 94 | // Suppress finalization. 95 | GC.SuppressFinalize(this); 96 | 97 | // Dispose of unmanaged resources. 98 | Dispose(true); 99 | } 100 | } 101 | } 102 | 103 | //See: https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose 104 | /// 105 | /// Implements can additionally dispose of resources. 106 | /// This is called via or the runtime finalizer. 107 | /// Implementers should always call the base. 108 | /// 109 | /// indicates whether the method call comes from a Dispose method (its value is true) or from a finalizer (its value is false). 110 | protected virtual void Dispose(bool disposing) 111 | { 112 | 113 | } 114 | 115 | /// 116 | /// Attaches a disposable dependency/resource to the . 117 | /// This will be disposed of alongside when 's is called. 118 | /// 119 | /// 120 | public void AttachDisposable(IDisposable disposable) 121 | { 122 | if (disposable == null) throw new ArgumentNullException(nameof(disposable)); 123 | 124 | lock (SyncObj) 125 | { 126 | if (isDisposed) 127 | throw new ObjectDisposedException($"Cannot attach {disposable.GetType().Name} as an attached disposable if the session is already disposed."); 128 | 129 | InternalDisposables.Add(disposable); 130 | } 131 | } 132 | 133 | /// 134 | /// Called internally by GladNet when the session is considered 135 | /// initialized. It's safer to do things here than in the constructor. 136 | /// (Ex. Sending a network message to the client). 137 | /// WARNING: If this method throws then the session may be closed. 138 | /// 139 | protected internal virtual void OnSessionInitialized() 140 | { 141 | 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/GladNet.API/Session/ManagedSessionGeneric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace GladNet 8 | { 9 | /// 10 | /// Generic payload-based implementation of 11 | /// that implements basic functionality for reading/writing payload-based messages. 12 | /// 13 | /// The type of payload being read. 14 | /// The type of payload being written. 15 | public abstract class ManagedSession 16 | : ManagedSession, INetworkMessageReceivable 17 | where TPayloadReadType : class 18 | where TPayloadWriteType : class 19 | { 20 | /// 21 | /// The message sending interface. 22 | /// 23 | protected SessionMessageInterfaceServiceContext MessageService { get; } 24 | 25 | //These aren't currently used directly in the session themselves but we expose them incase implementer 26 | //wants them. 27 | /// 28 | /// Message serialization/building services. 29 | /// 30 | protected SessionMessageBuildingServiceContext MessageBuilders { get; } 31 | 32 | /// 33 | protected ManagedSession(INetworkConnectionService connectionService, SessionDetails details, NetworkConnectionOptions networkOptions, 34 | SessionMessageBuildingServiceContext messageBuilders, 35 | SessionMessageInterfaceServiceContext messageService) 36 | : base(connectionService, details, networkOptions) 37 | { 38 | MessageBuilders = messageBuilders ?? throw new ArgumentNullException(nameof(messageBuilders)); 39 | MessageService = messageService ?? throw new ArgumentNullException(nameof(messageService)); 40 | } 41 | 42 | /// 43 | public override async Task StartListeningAsync(CancellationToken token = default) 44 | { 45 | while(!token.IsCancellationRequested) 46 | { 47 | //The producer service knows HOW to generate a message, all we need to do is await it and dispatch it to the session. 48 | NetworkIncomingMessage message = await MessageService.MessageInterface.ReadMessageAsync(token); 49 | 50 | //TODO: Returning null to indicate failure is kinda DUMB. 51 | if(message == null) 52 | return; 53 | 54 | //A throw will stop the session. 55 | await OnNetworkMessageReceivedAsync(message, token); 56 | } 57 | } 58 | 59 | //Warning to implementer, if you THROW from this you WILL stop the network connection completely. 60 | //GladNet does not sustain exceptions in unexpected cases, choosing to shutdown the session instead. 61 | /// 62 | public abstract Task OnNetworkMessageReceivedAsync(NetworkIncomingMessage message, CancellationToken token = default(CancellationToken)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/GladNet.API/Session/ServiceContainers/SessionMessageBuildingServiceContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Encapsulates Requires session message building services. 9 | /// Mostly for serialization/building purposes. 10 | /// 11 | /// 12 | /// 13 | public sealed class SessionMessageBuildingServiceContext 14 | where TPayloadReadType : class 15 | where TPayloadWriteType : class 16 | { 17 | /// 18 | /// The factory for building packet headers. 19 | /// 20 | public IPacketHeaderFactory PacketHeaderFactory { get; } 21 | 22 | /// 23 | /// The incoming message deserializer. 24 | /// 25 | public IMessageDeserializer MessageDeserializer { get; } 26 | 27 | /// 28 | /// The incoming message deserializer. 29 | /// 30 | public IMessageSerializer MessageSerializer { get; } 31 | 32 | /// 33 | /// The outgoing message header serializer. 34 | /// 35 | public IMessageSerializer> HeaderSerializer { get; } 36 | 37 | public SessionMessageBuildingServiceContext(IPacketHeaderFactory packetHeaderFactory, 38 | IMessageDeserializer messageDeserializer, 39 | IMessageSerializer messageSerializer, 40 | IMessageSerializer> headerSerializer) 41 | { 42 | PacketHeaderFactory = packetHeaderFactory ?? throw new ArgumentNullException(nameof(packetHeaderFactory)); 43 | MessageDeserializer = messageDeserializer ?? throw new ArgumentNullException(nameof(messageDeserializer)); 44 | MessageSerializer = messageSerializer ?? throw new ArgumentNullException(nameof(messageSerializer)); 45 | HeaderSerializer = headerSerializer ?? throw new ArgumentNullException(nameof(headerSerializer)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/GladNet.API/Session/ServiceContainers/SessionMessageInterfaceServiceContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Service container for a session's network message interfacing services. 9 | /// Not to be confused with which provides 10 | /// a network interface for messages. 11 | /// This container contains services such as that for interacting/interfacing with the network. 12 | /// 13 | /// The outgoing write type for payloads. 14 | /// The incoming read type for payloads. 15 | public sealed class SessionMessageInterfaceServiceContext 16 | where TPayloadReadType : class 17 | where TPayloadWriteType : class 18 | { 19 | /// 20 | /// The outgoing network payload queue. 21 | /// This queue should be added into if the session desires a message be outgoing to the peer. 22 | /// 23 | public IAsyncMessageQueue OutgoingMessageQueue { get; } 24 | 25 | /// 26 | /// The service that provides an interface for communication for reading and writing payloads. 27 | /// 28 | public INetworkMessageInterface MessageInterface { get; } 29 | 30 | public SessionMessageInterfaceServiceContext(IAsyncMessageQueue outgoingMessageQueue, INetworkMessageInterface networkMessageInterface) 31 | { 32 | OutgoingMessageQueue = outgoingMessageQueue ?? throw new ArgumentNullException(nameof(outgoingMessageQueue)); 33 | MessageInterface = networkMessageInterface ?? throw new ArgumentNullException(nameof(networkMessageInterface)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/GladNet.API/Session/SessionDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | /// 8 | /// Contains information about the session. 9 | /// 10 | public sealed class SessionDetails 11 | { 12 | /// 13 | /// Address for the session. 14 | /// 15 | public NetworkAddressInfo Address { get; } 16 | 17 | /// 18 | /// Application specific identifier for the connection instance. 19 | /// 20 | public int ConnectionId { get; } 21 | 22 | public SessionDetails(NetworkAddressInfo address, int connectionId) 23 | { 24 | if(connectionId < 0) throw new ArgumentOutOfRangeException(nameof(connectionId)); 25 | 26 | Address = address ?? throw new ArgumentNullException(nameof(address)); 27 | ConnectionId = connectionId; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/GladNet.Client.DotNetTcpClient/GladNet.Client.DotNetTcpClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) common API library that provides shared API for both client and server. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | latest 16 | GladNet.Client.DotNetTcpClient 17 | 4.9.45 18 | 19 | 20 | 21 | true 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/GladNet.Client.DotNetTcpClient/Memory/GladNetPipeMemoryPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | namespace GladNet 8 | { 9 | // Based on: https://github.com/dotnet/runtime/blob/77a714d26dc434012c8234447b9e1183ce7be4a6/src/libraries/System.Memory/src/System/Buffers/ArrayMemoryPool.cs 10 | internal class GladNetPipeMemoryPool : MemoryPool 11 | { 12 | public sealed override int MaxBufferSize => int.MaxValue; 13 | 14 | // See: https://github.com/dotnet/corert/blob/c6af4cfc8b625851b91823d9be746c4f7abdc667/src/System.Private.CoreLib/shared/System/Buffers/ConfigurableArrayPool.cs#L13 15 | /// The default maximum number of arrays per bucket that are available for rent. 16 | private const int DefaultMaxNumberOfArraysPerBucket = 50; 17 | 18 | private ArrayPool SharedPipePool { get; } = ArrayPool.Create(int.MaxValue, DefaultMaxNumberOfArraysPerBucket); 19 | 20 | public sealed override IMemoryOwner Rent(int minimumBufferSize = -1) 21 | { 22 | if(minimumBufferSize == -1) 23 | minimumBufferSize = 1 + (4095 / Unsafe.SizeOf()); 24 | else if(((uint)minimumBufferSize) > int.MaxValue) 25 | throw new InvalidOperationException($"Provided buffer size: {minimumBufferSize} is too high."); 26 | 27 | return new GladNetPipeMemoryPoolBuffer(minimumBufferSize, SharedPipePool); 28 | } 29 | 30 | protected sealed override void Dispose(bool disposing) { } // ArrayMemoryPool is a shared pool so Dispose() would be a nop even if there were native resources to dispose. 31 | 32 | // Based on: https://github.com/dotnet/runtime/blob/9768606ea1f0aa1be6098143ded330dadac8cf91/src/libraries/System.Memory/src/System/Buffers/ArrayMemoryPool.ArrayMemoryPoolBuffer.cs 33 | private sealed class GladNetPipeMemoryPoolBuffer : IMemoryOwner, IDisposable 34 | { 35 | private ArrayPool Pool { get; } 36 | 37 | private byte[] _array; 38 | 39 | public GladNetPipeMemoryPoolBuffer(int size, ArrayPool pool) 40 | { 41 | Pool = pool ?? throw new ArgumentNullException(nameof(pool)); 42 | _array = Pool.Rent(size); 43 | } 44 | 45 | public Memory Memory 46 | { 47 | get 48 | { 49 | byte[] array = _array; 50 | 51 | if (array is null) 52 | throw new ObjectDisposedException("array"); 53 | 54 | return new Memory(array); 55 | } 56 | } 57 | 58 | public void Dispose() 59 | { 60 | byte[] array = _array; 61 | if(array != null) 62 | { 63 | _array = null; 64 | Pool.Return(array); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/GladNet.Client.DotNetTcpClient/TCPSocketConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.IO.Pipelines; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using Glader.Essentials; 9 | using Pipelines.Sockets.Unofficial; 10 | 11 | namespace GladNet 12 | { 13 | /// 14 | /// Factory that creates . 15 | /// Warning: You are responsible for calling Dispose. 16 | /// 17 | public sealed class TCPSocketConnectionFactory : IFactoryCreatable 18 | { 19 | /// 20 | public SocketConnection Create(EmptyFactoryContext context) 21 | { 22 | if (context == null) throw new ArgumentNullException(nameof(context)); 23 | 24 | Socket client = new Socket(SocketType.Stream, ProtocolType.Tcp); 25 | 26 | // We cannot allow default memory pool usage, see: https://github.com/dotnet/runtime/blob/main/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs#L596 27 | // It can be seen that this will opt to use default array pool which will be TlsOverPerCoreLockedStacksArrayPool which can have significant contention 28 | //Let the caller connect if they want to. 29 | //await client.ConnectAsync(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6969)); 30 | PipeOptions sendOptions = new PipeOptions(new GladNetPipeMemoryPool()); 31 | 32 | return SocketConnection.Create(client, sendOptions, PipeOptions.Default, SocketConnectionOptions.ZeroLengthReads); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/GladNet.Server.DotNetTcpServer/GladNet.Server.DotNetTcpServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) server implementation of the .NET TCP Server (TcpListener) for server emulation. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | GladNet.Server.DotNetTcpServer 16 | 4.9.45 17 | 18 | 19 | 20 | true 21 | true 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | 5 30 | C:\Users\Glader\Documents\Github\GladNet3\src\GladNet.Server.DotNetTcpServer\bin\GladNet.Server.DotNetTcpServer.xml 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/GladNet.Server.DotNetTcpServer/TcpGladNetServerApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO.Pipelines; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Common.Logging; 11 | using Common.Logging.Simple; 12 | using Glader.Essentials; 13 | using GladNet; 14 | using Pipelines.Sockets.Unofficial; 15 | 16 | namespace GladNet 17 | { 18 | /// 19 | /// Base application base for a high performance async TCP server application. 20 | /// Builds, manages and maintains s internally 21 | /// when clients connect. 22 | /// 23 | public abstract class TcpGladNetServerApplication : GladNetServerApplication 24 | where TManagedSessionType : ManagedSession 25 | { 26 | private int _lifetimeConnectionCount = 0; 27 | 28 | /// 29 | /// The number of connections that have been serviced 30 | /// lifetime by this application. 31 | /// 32 | public int LifetimeConnectionCount 33 | { 34 | get => _lifetimeConnectionCount; 35 | private set => _lifetimeConnectionCount = value; 36 | } 37 | 38 | /// 39 | protected TcpGladNetServerApplication(NetworkAddressInfo serverAddress, ILog logger) 40 | : base(serverAddress, logger) 41 | { 42 | 43 | } 44 | 45 | /// 46 | /// Indicates if the provided is acceptable. 47 | /// Return true if the client should be handled. This will likely lead to 48 | /// a call to . Returning false 49 | /// will mean this connection should be disconnected and rejected and no 50 | /// client representation will be created for it. 51 | /// 52 | /// The to check the acceptance for. 53 | /// 54 | protected abstract bool IsClientAcceptable(Socket connection); 55 | 56 | /// 57 | public override async Task BeginListeningAsync(CancellationToken token = default) 58 | { 59 | SocketConnection.AssertDependencies(); 60 | 61 | if (Logger.IsInfoEnabled) 62 | Logger.Info($"Server begin listening."); 63 | 64 | using (Socket listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp)) 65 | { 66 | listenSocket.Bind(new IPEndPoint(ServerAddress.AddressEndpoint, ServerAddress.Port)); 67 | 68 | if (!listenSocket.IsBound) 69 | { 70 | if(Logger.IsErrorEnabled) 71 | Logger.Error($"Socket failed to bind to Port: {ServerAddress.Port} on Address: {ServerAddress.AddressEndpoint.ToString()}"); 72 | 73 | return; 74 | } 75 | 76 | //TODO: This is the maximum unacceptaed in-queue connection attempts. I don't know if this should be configurable 77 | //See: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.listen?view=netframework-4.7.2 78 | listenSocket.Listen(1000); 79 | 80 | if(token.IsCancellationRequested) 81 | return; 82 | 83 | if(Logger.IsInfoEnabled) 84 | Logger.Info($"Server bound to Port: {ServerAddress.Port} on Address: {ServerAddress.AddressEndpoint.ToString()}"); 85 | 86 | try 87 | { 88 | await SocketAcceptLoopAsync(token, listenSocket); 89 | } 90 | catch (Exception e) 91 | { 92 | if (Logger.IsErrorEnabled) 93 | Logger.Error($"Server encountered unhandled exception in socket accept loop. Error: {e}"); 94 | 95 | throw; 96 | } 97 | } 98 | } 99 | 100 | private async Task SocketAcceptLoopAsync(CancellationToken token, Socket listenSocket) 101 | { 102 | if (listenSocket == null) throw new ArgumentNullException(nameof(listenSocket)); 103 | 104 | while (!token.IsCancellationRequested) 105 | { 106 | //For some reason the REMOTE connection/socket closing before this 107 | //completes can cause a throw 108 | //Example: System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host. 109 | //Therefore we must wrap this in a try 110 | Socket socket = null; 111 | try 112 | { 113 | socket = await listenSocket.AcceptAsync(); 114 | } 115 | catch(SocketException e) 116 | { 117 | socket?.Dispose(); 118 | 119 | SocketError error = (SocketError) e.ErrorCode; 120 | switch (error) 121 | { 122 | case SocketError.ConnectionReset: // 10054 123 | case SocketError.ConnectionAborted: 124 | if(Logger.IsInfoEnabled) 125 | Logger.Info($"Socket disconnected before accept. This is expected and can occur if the remote client disconnects before fully accepted."); 126 | continue; //continue the loop on this expected exception. 127 | default: 128 | throw; 129 | } 130 | } 131 | 132 | //Try required because we're calling into user code. 133 | try 134 | { 135 | if (!IsClientAcceptable(socket)) 136 | { 137 | socket.Shutdown(SocketShutdown.Both); 138 | socket.Dispose(); 139 | continue; 140 | } 141 | } 142 | catch (Exception e) 143 | { 144 | if (Logger.IsInfoEnabled) 145 | Logger.Error($"Socket failed to creation. Exception in accept check. Reason: {e}"); 146 | 147 | socket.Shutdown(SocketShutdown.Both); 148 | socket.Dispose(); 149 | continue; 150 | } 151 | 152 | try 153 | { 154 | TManagedSessionType session = await AcceptNewSessionsAsync(socket, token); 155 | 156 | //Once everything is setup for the session we should let the session know it's initialized. 157 | session.OnSessionInitialized(); 158 | } 159 | catch (Exception e) 160 | { 161 | if (Logger.IsErrorEnabled) 162 | Logger.Error($"Socket failed to create session. Reason: {e}"); 163 | 164 | //We wrap this in a trp because socket maybe fails to shutdown, but we MUST dispose. 165 | try 166 | { 167 | if(socket.Connected) 168 | socket.Shutdown(SocketShutdown.Both); 169 | } 170 | finally 171 | { 172 | socket.Dispose(); 173 | } 174 | } 175 | } 176 | } 177 | 178 | /// 179 | /// Preforms the socket and session managed and creation logic for creating a new managed network session. 180 | /// 181 | /// Socket to make a session for. 182 | /// Cancel token. 183 | /// Awaitable that indicates when the session has connected. 184 | private async Task AcceptNewSessionsAsync(Socket socket, CancellationToken token) 185 | { 186 | SocketConnection connection = default; 187 | TManagedSessionType clientSession = default; 188 | int connectionId = 0; 189 | try 190 | { 191 | IPEndPoint clientAddress = ((IPEndPoint)socket.RemoteEndPoint); 192 | connectionId = Interlocked.Increment(ref _lifetimeConnectionCount); 193 | connection = SocketConnection.Create(socket, PipeOptions.Default, PipeOptions.Default, SocketConnectionOptions.ZeroLengthReads); 194 | 195 | if(Logger.IsInfoEnabled) 196 | Logger.Info($"Attempting to create Session for Address: {clientAddress.Address} Id: {connectionId}"); 197 | 198 | clientSession = Create(new SessionCreationContext(connection, new SessionDetails(new NetworkAddressInfo(clientAddress.Address, ServerAddress.Port), connectionId))); 199 | 200 | clientSession.AttachDisposable(connection); 201 | clientSession.AttachDisposable(socket); 202 | 203 | StartNetworkSessionTasks(token, clientSession); 204 | 205 | if(!Sessions.TryAdd(connectionId, clientSession)) 206 | throw new InvalidOperationException($"Failed to add Session: {clientSession} to {Sessions} container with Id: {connectionId}"); 207 | 208 | return clientSession; 209 | } 210 | catch (Exception e) 211 | { 212 | if (Logger.IsErrorEnabled) 213 | Logger.Error($"Failed to creation Session: {connectionId}. Reason: {e}"); 214 | 215 | //Encounter a critical issue and could not create the session 216 | //but we also don't want to leak. 217 | clientSession?.Dispose(); 218 | connection?.Dispose(); 219 | throw; 220 | } 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/GladNet.Server.DotNetWebSocketServer/GladNet.Server.DotNetWebSocketServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Andrew Blakely, HelloKitty 6 | 7 | GladNet 8 | GladNet (Glader's Library for Advanced Development of Network Emulation Technologies) server implementation of the .NET TCP Server (TcpListener) for server emulation. 9 | Andrew Blakely 10 | https://github.com/HelloKitty/GladNet3/blob/master/LICENSE 11 | https://github.com/HelloKitty/GladNet3 12 | https://github.com/HelloKitty/GladNet3 13 | git 14 | GladNet Emulation Emulator WoW PSO Network Unity3D Network Networking Netlib GladNet4 15 | GladNet.Server.DotNetWebSocketServer 16 | 4.9.45 17 | 18 | 19 | 20 | true 21 | true 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | 5 30 | C:\Users\Glader\Documents\Github\GladNet3\src\GladNet.Server.DotNetTcpServer\bin\GladNet.Server.DotNetTcpServer.xml 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/GladNet.Server.DotNetWebSocketServer/WebSocketGladNetServerApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO.Pipes; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Net.WebSockets; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Common.Logging; 12 | using Common.Logging.Simple; 13 | using Glader.Essentials; 14 | using GladNet; 15 | 16 | namespace GladNet 17 | { 18 | /// 19 | /// Base application base for a high performance async WebSocket server application. 20 | /// Builds, manages and maintains s internally 21 | /// when clients connect. 22 | /// 23 | public abstract class WebSocketGladNetServerApplication : GladNetServerApplication 24 | where TManagedSessionType : ManagedSession 25 | { 26 | private int _lifetimeConnectionCount = 0; 27 | 28 | /// 29 | /// The number of connections that have been serviced 30 | /// lifetime by this application. 31 | /// 32 | public int LifetimeConnectionCount 33 | { 34 | get => _lifetimeConnectionCount; 35 | private set => _lifetimeConnectionCount = value; 36 | } 37 | 38 | /// 39 | protected WebSocketGladNetServerApplication(NetworkAddressInfo serverAddress, ILog logger) 40 | : base(serverAddress, logger) 41 | { 42 | 43 | } 44 | 45 | /// 46 | /// Indicates if the provided is acceptable. 47 | /// Return true if the client should be handled. This will likely lead to 48 | /// a call to . Returning false 49 | /// will mean this connection should be disconnected and rejected and no 50 | /// client representation will be created for it. 51 | /// 52 | /// 53 | /// The to check the acceptance for. 54 | /// 55 | protected abstract bool IsClientAcceptable(HttpListenerContext context, IWebSocketConnection connection); 56 | 57 | /// 58 | public override async Task BeginListeningAsync(CancellationToken token = default) 59 | { 60 | if (Logger.IsInfoEnabled) 61 | Logger.Info($"Server begin listening."); 62 | 63 | using (HttpListener listenSocket = new HttpListener()) 64 | { 65 | RegisterListenerEndpoints(ServerAddress, listenSocket.Prefixes); 66 | listenSocket.Start(); 67 | 68 | if (!listenSocket.IsListening) 69 | { 70 | if (Logger.IsErrorEnabled) 71 | Logger.Error($"Socket failed to bind to Port: {ServerAddress.Port} on Address: {ServerAddress.AddressEndpoint.ToString()}"); 72 | 73 | return; 74 | } 75 | 76 | if (Logger.IsInfoEnabled) 77 | Logger.Info($"Server bound to Port: {ServerAddress.Port} on Address: {ServerAddress.AddressEndpoint.ToString()}"); 78 | 79 | try 80 | { 81 | await SocketAcceptLoopAsync(listenSocket, token); 82 | } 83 | catch(Exception e) 84 | { 85 | if(Logger.IsErrorEnabled) 86 | Logger.Error($"Server encountered unhandled exception in socket accept loop. Error: {e}"); 87 | 88 | throw; 89 | } 90 | } 91 | } 92 | 93 | /// 94 | /// Implementers can derive this to override the default handling for registering endpoints. 95 | /// 96 | /// Endpoints. 97 | /// The network address info. 98 | protected virtual void RegisterListenerEndpoints(NetworkAddressInfo info, HttpListenerPrefixCollection endpointCollection) 99 | { 100 | endpointCollection.Add($"https://{info.AddressEndpoint.MapToIPv4().ToString()}:{info.Port}/"); 101 | } 102 | 103 | private async Task SocketAcceptLoopAsync(HttpListener listenSocket, CancellationToken token) 104 | { 105 | if (listenSocket == null) throw new ArgumentNullException(nameof(listenSocket)); 106 | 107 | while (!token.IsCancellationRequested) 108 | { 109 | IWebSocketConnection socket = null; 110 | HttpListenerContext context = null; 111 | try 112 | { 113 | context = await listenSocket.GetContextAsync(); 114 | 115 | // Only handle websocket request, no idea what else can be went. Guess normal HTTP lol? 116 | if (!context.Request.IsWebSocketRequest) 117 | continue; 118 | 119 | HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null); 120 | socket = new DotNetWebSocketConnection(webSocketContext.WebSocket); 121 | } 122 | catch (Exception e) 123 | { 124 | if (Logger.IsErrorEnabled) 125 | Logger.Error($"Encountered error in WebSocket accept. Error: {e}"); 126 | 127 | continue; 128 | } 129 | 130 | //Try required because we're calling into user code. 131 | try 132 | { 133 | if (!IsClientAcceptable(context, socket)) 134 | { 135 | await socket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, $"Client unacceptable.", token); 136 | socket.Dispose(); 137 | continue; 138 | } 139 | } 140 | catch (Exception e) 141 | { 142 | if (Logger.IsInfoEnabled) 143 | Logger.Error($"Socket failed to creation. Exception in accept check. Reason: {e}"); 144 | 145 | await socket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, $"Socket accept Error: {e.GetType().Name}", token); 146 | socket.Dispose(); 147 | continue; 148 | } 149 | 150 | try 151 | { 152 | TManagedSessionType session = await AcceptNewSessionsAsync(context, socket, token); 153 | 154 | //Once everything is setup for the session we should let the session know it's initialized. 155 | session.OnSessionInitialized(); 156 | } 157 | catch (Exception e) 158 | { 159 | if (Logger.IsErrorEnabled) 160 | Logger.Error($"Socket failed to create session. Reason: {e}"); 161 | 162 | //We wrap this in a trp because socket maybe fails to shutdown, but we MUST dispose. 163 | try 164 | { 165 | if (socket.State == WebSocketState.Open) 166 | await socket.CloseAsync(WebSocketCloseStatus.InternalServerError, $"Session Creation Error: {e.GetType().Name}", token); 167 | } 168 | finally 169 | { 170 | socket.Dispose(); 171 | } 172 | } 173 | } 174 | } 175 | 176 | /// 177 | /// Preforms the socket and session managed and creation logic for creating a new managed network session. 178 | /// 179 | /// Socket to make a session for. 180 | /// Cancel token. 181 | /// Awaitable that indicates when the session has connected. 182 | private Task AcceptNewSessionsAsync(HttpListenerContext context, IWebSocketConnection socket, CancellationToken token) 183 | { 184 | TManagedSessionType clientSession = default; 185 | int connectionId = 0; 186 | try 187 | { 188 | IPEndPoint clientAddress = context.Request.RemoteEndPoint; 189 | connectionId = Interlocked.Increment(ref _lifetimeConnectionCount); 190 | 191 | if (Logger.IsInfoEnabled) 192 | Logger.Info($"Attempting to create Session for Address: {clientAddress?.Address} Id: {connectionId}"); 193 | 194 | clientSession = Create(new SessionCreationContext(socket, new SessionDetails(new NetworkAddressInfo(clientAddress?.Address, ServerAddress.Port), connectionId))); 195 | 196 | clientSession.AttachDisposable(socket); 197 | 198 | StartNetworkSessionTasks(token, clientSession); 199 | 200 | if (!Sessions.TryAdd(connectionId, clientSession)) 201 | throw new InvalidOperationException($"Failed to add Session: {clientSession} to {Sessions} container with Id: {connectionId}"); 202 | 203 | return Task.FromResult(clientSession); 204 | } 205 | catch (Exception e) 206 | { 207 | if (Logger.IsErrorEnabled) 208 | Logger.Error($"Failed to creation Session: {connectionId}. Reason: {e}"); 209 | 210 | //Encounter a critical issue and could not create the session 211 | //but we also don't want to leak. 212 | clientSession?.Dispose(); 213 | socket?.Dispose(); 214 | throw; 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpClient.EchoTest/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Common.Logging; 5 | using Common.Logging.Simple; 6 | 7 | namespace GladNet 8 | { 9 | public sealed class ConsoleLogger : AbstractSimpleLogger 10 | { 11 | public ConsoleLogger(LogLevel logLevel, bool showlevel) 12 | : base(nameof(ConsoleLogger), logLevel, showlevel, false, false, String.Empty) 13 | { 14 | 15 | } 16 | 17 | protected override void WriteInternal(LogLevel level, object message, Exception exception) 18 | { 19 | if (ShowLevel) 20 | { 21 | if(exception != null) 22 | Console.WriteLine($"{level}: {message.ToString()}. Error: {exception}"); 23 | else 24 | Console.WriteLine($"{level}: {message.ToString()}."); 25 | } 26 | else 27 | { 28 | if(exception != null) 29 | Console.WriteLine($"{message.ToString()}. Error: {exception}"); 30 | else 31 | Console.WriteLine($"{message.ToString()}."); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpClient.EchoTest/GladNet.DotNetTcpClient.EchoTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpClient.EchoTest/Network/Handlers/DefaultStringMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Glader.Essentials; 7 | 8 | namespace GladNet 9 | { 10 | public sealed class DefaultStringMessageHandler : BaseDefaultMessageHandler> 11 | { 12 | public override async Task HandleMessageAsync(SessionMessageContext context, string message, CancellationToken token = default) 13 | { 14 | Console.WriteLine($"Echoed Content: {message}"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpClient.EchoTest/Network/Session/StringMessagePacketHeaderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | namespace GladNet 8 | { 9 | public sealed class StringMessagePacketHeaderFactory : IPacketHeaderFactory 10 | { 11 | public IPacketHeader Create(PacketHeaderCreationContext context) 12 | { 13 | ushort size = Unsafe.As(ref context.GetSpan()[0]); 14 | 15 | return new HeaderlessPacketHeader(size); 16 | } 17 | 18 | public bool IsHeaderReadable(in Span buffer) 19 | { 20 | return buffer.Length >= 2; 21 | } 22 | 23 | public bool IsHeaderReadable(in ReadOnlySequence buffer) 24 | { 25 | return buffer.Length >= 2; 26 | } 27 | 28 | public int ComputeHeaderSize(in ReadOnlySequence buffer) 29 | { 30 | return 2; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpClient.EchoTest/Network/Session/StringMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | public sealed class StringMessageSerializer : IMessageSerializer, IMessageDeserializer 8 | { 9 | public void Serialize(string value, Span buffer, ref int offset) 10 | { 11 | offset += Encoding.ASCII.GetBytes(value, buffer); 12 | } 13 | 14 | public string Deserialize(Span buffer, ref int offset) 15 | { 16 | if (offset != 0) 17 | { 18 | if(offset > buffer.Length) 19 | throw new InvalidOperationException($"Offset: {offset} outside of buffer Size: {buffer.Length}"); 20 | 21 | buffer = buffer.Slice(offset); 22 | } 23 | 24 | string value = Encoding.ASCII.GetString(buffer); 25 | offset += buffer.Length * 1; 26 | return value; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpClient.EchoTest/Network/Session/StringPacketHeaderSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Reinterpret.Net; 5 | 6 | namespace GladNet 7 | { 8 | public sealed class StringPacketHeaderSerializer : IMessageSerializer> 9 | { 10 | public void Serialize(PacketHeaderSerializationContext value, Span buffer, ref int offset) 11 | { 12 | //2 byte size 13 | short size = (short)value.PayloadSize; 14 | size.Reinterpret(buffer, offset); 15 | offset += 2; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpClient.EchoTest/Network/TCPEchoClientSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Pipelines.Sockets.Unofficial; 7 | 8 | namespace GladNet 9 | { 10 | public sealed class TCPEchoClientSession : BaseTcpManagedSession 11 | { 12 | private INetworkMessageDispatchingStrategy MessageDispatcher { get; } 13 | 14 | private SessionMessageContext CachedSessionContext { get; } 15 | 16 | public TCPEchoClientSession(NetworkConnectionOptions networkOptions, SocketConnection connection, SessionDetails details, SessionMessageBuildingServiceContext messageServices, 17 | INetworkMessageDispatchingStrategy messageDispatcher) 18 | : base(networkOptions, connection, details, messageServices) 19 | { 20 | MessageDispatcher = messageDispatcher ?? throw new ArgumentNullException(nameof(messageDispatcher)); 21 | 22 | //We build the session context 1 time because it should not change 23 | //Rather than inject as a dependency, we can build it in here. Really optional to inject 24 | //or build it in CTOR 25 | //Either way we build a send service and session context that captures it to provide to handling. 26 | IMessageSendService sendService = new QueueBasedMessageSendService(this.MessageService.OutgoingMessageQueue); 27 | CachedSessionContext = new SessionMessageContext(details, sendService, ConnectionService); 28 | } 29 | 30 | public TCPEchoClientSession(NetworkConnectionOptions networkOptions, SocketConnection connection, SessionDetails details, SessionMessageBuildingServiceContext messageServices, 31 | INetworkMessageDispatchingStrategy messageDispatcher, INetworkMessageInterface messageInterface) 32 | : base(networkOptions, connection, details, messageServices, messageInterface) 33 | { 34 | MessageDispatcher = messageDispatcher ?? throw new ArgumentNullException(nameof(messageDispatcher)); 35 | 36 | //We build the session context 1 time because it should not change 37 | //Rather than inject as a dependency, we can build it in here. Really optional to inject 38 | //or build it in CTOR 39 | //Either way we build a send service and session context that captures it to provide to handling. 40 | IMessageSendService sendService = new QueueBasedMessageSendService(this.MessageService.OutgoingMessageQueue); 41 | CachedSessionContext = new SessionMessageContext(details, sendService, ConnectionService); 42 | } 43 | 44 | /// 45 | public override async Task OnNetworkMessageReceivedAsync(NetworkIncomingMessage message, CancellationToken token = default) 46 | { 47 | await MessageDispatcher.DispatchNetworkMessageAsync(CachedSessionContext, message, token); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpClient.EchoTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Pipelines; 3 | using System.Net; 4 | using System.Net.Http.Headers; 5 | using System.Net.Sockets; 6 | using System.Runtime.CompilerServices; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Common.Logging; 10 | using Glader.Essentials; 11 | using Pipelines.Sockets.Unofficial; 12 | 13 | namespace GladNet.DotNetTcpClient.EchoTest 14 | { 15 | class Program 16 | { 17 | static async Task Main(string[] args) 18 | { 19 | SocketConnection socket = new TCPSocketConnectionFactory() 20 | .Create(); 21 | 22 | SocketConnectionConnectionServiceAdapter connectionService = new SocketConnectionConnectionServiceAdapter(socket); 23 | 24 | //await socket.Socket.ConnectAsync(IPAddress.Parse("127.0.0.1"), 6969); 25 | //await Task.Delay(1); 26 | //await socket.Socket.ConnectAsync("127.0.0.1", 6969); 27 | if (!await connectionService.ConnectAsync("127.0.0.1", 6969)) 28 | throw new InvalidOperationException($"Failed to connect."); 29 | 30 | /*await Task.Factory.FromAsync( 31 | socket.Socket.BeginConnect, 32 | socket.Socket.EndConnect, 33 | "127.0.0.1", 34 | 6969, 35 | null) 36 | .ConfigureAwait(true);*/ 37 | 38 | //if(!await connectionService.ConnectAsync("127.0.0.1", 6969)) 39 | // throw new InvalidOperationException($"Failed to connect."); 40 | 41 | //await Connect(socket); 42 | //if(!socket.Socket.Connected) 43 | // throw new InvalidOperationException($"Failed to connect."); 44 | 45 | NetworkConnectionOptions options = new NetworkConnectionOptions(2, 2, 1024); 46 | var serializer = new StringMessageSerializer(); 47 | 48 | SessionMessageBuildingServiceContext messageServices = 49 | new SessionMessageBuildingServiceContext(new StringMessagePacketHeaderFactory(), serializer, serializer, new StringPacketHeaderSerializer()); 50 | 51 | //Build the message disaptching strategy, for how and where and in what way messages will be handled 52 | var handlerService = new DefaultMessageHandlerService>(); 53 | var dispatcher = new InPlaceNetworkMessageDispatchingStrategy(handlerService); 54 | 55 | //Bind one of the default handlers 56 | handlerService.Bind(new DefaultStringMessageHandler()); 57 | 58 | //We build the session context 1 time because it should not change 59 | //Rather than inject as a dependency, we can build it in here. Really optional to inject 60 | //or build it in CTOR 61 | //Either way we build a send service and session context that captures it to provide to handling. 62 | SocketConnectionNetworkMessageInterface messageInterface = new SocketConnectionNetworkMessageInterface(options, socket, messageServices); 63 | var session = new TCPEchoClientSession(options, socket, new SessionDetails(new NetworkAddressInfo(IPAddress.Parse("127.0.0.1"), 6969), 1), messageServices, dispatcher, messageInterface); 64 | 65 | session.AttachDisposable(socket); 66 | session.AttachDisposable(socket.Socket); 67 | 68 | SessionStarter starter = new SessionStarter(new ConsoleLogger(LogLevel.All, true)); 69 | 70 | Task.Run(async () => 71 | { 72 | while (session.ConnectionService.IsConnected) 73 | { 74 | Console.Write($"Enter: "); 75 | string input = Console.ReadLine(); 76 | await messageInterface.SendMessageAsync(input); 77 | } 78 | }); 79 | 80 | await starter.StartAsync(session, CancellationToken.None); 81 | } 82 | 83 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 84 | private static async Task Connect(SocketConnection socket) 85 | { 86 | await socket.Socket.ConnectAsync(IPAddress.Parse("127.0.0.1"), 6969); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Common.Logging; 5 | using Common.Logging.Simple; 6 | 7 | namespace GladNet 8 | { 9 | public sealed class ConsoleLogger : AbstractSimpleLogger 10 | { 11 | public ConsoleLogger(LogLevel logLevel, bool showlevel) 12 | : base(nameof(ConsoleLogger), logLevel, showlevel, false, false, String.Empty) 13 | { 14 | 15 | } 16 | 17 | protected override void WriteInternal(LogLevel level, object message, Exception exception) 18 | { 19 | if (ShowLevel) 20 | { 21 | if(exception != null) 22 | Console.WriteLine($"{level}: {message.ToString()}. Error: {exception}"); 23 | else 24 | Console.WriteLine($"{level}: {message.ToString()}."); 25 | } 26 | else 27 | { 28 | if(exception != null) 29 | Console.WriteLine($"{message.ToString()}. Error: {exception}"); 30 | else 31 | Console.WriteLine($"{message.ToString()}."); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/GladNet.DotNetTcpServer.EchoTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/Network/Handlers/DefaultStringMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Glader.Essentials; 7 | 8 | namespace GladNet 9 | { 10 | public sealed class DefaultStringMessageHandler : BaseDefaultMessageHandler> 11 | { 12 | public override async Task HandleMessageAsync(SessionMessageContext context, string message, CancellationToken token = default) 13 | { 14 | Console.WriteLine($"Message Content: {message}"); 15 | 16 | //echos back the message to the client. 17 | await context.MessageService.SendMessageAsync(message, token); 18 | 19 | if (message.ToLower() == "quit") 20 | await context.ConnectionService.DisconnectAsync(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/Network/Session/StringMessagePacketHeaderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | namespace GladNet 8 | { 9 | public sealed class StringMessagePacketHeaderFactory : IPacketHeaderFactory 10 | { 11 | public IPacketHeader Create(PacketHeaderCreationContext context) 12 | { 13 | ushort size = Unsafe.As(ref context.GetSpan()[0]); 14 | 15 | return new HeaderlessPacketHeader(size); 16 | } 17 | 18 | public bool IsHeaderReadable(in Span buffer) 19 | { 20 | return buffer.Length >= 2; 21 | } 22 | 23 | public bool IsHeaderReadable(in ReadOnlySequence buffer) 24 | { 25 | return buffer.Length >= 2; 26 | } 27 | 28 | public int ComputeHeaderSize(in ReadOnlySequence buffer) 29 | { 30 | return 2; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/Network/Session/StringMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace GladNet 6 | { 7 | public sealed class StringMessageSerializer : IMessageSerializer, IMessageDeserializer 8 | { 9 | public void Serialize(string value, Span buffer, ref int offset) 10 | { 11 | offset += Encoding.ASCII.GetBytes(value, buffer); 12 | } 13 | 14 | public string Deserialize(Span buffer, ref int offset) 15 | { 16 | if (offset != 0) 17 | { 18 | if(offset > buffer.Length) 19 | throw new InvalidOperationException($"Offset: {offset} outside of buffer Size: {buffer.Length}"); 20 | 21 | buffer = buffer.Slice(offset); 22 | } 23 | 24 | string value = Encoding.ASCII.GetString(buffer); 25 | offset += buffer.Length * 1; 26 | return value; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/Network/Session/StringPacketHeaderSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Reinterpret.Net; 5 | 6 | namespace GladNet 7 | { 8 | public sealed class StringPacketHeaderSerializer : IMessageSerializer> 9 | { 10 | public void Serialize(PacketHeaderSerializationContext value, Span buffer, ref int offset) 11 | { 12 | //2 byte size 13 | short size = (short)value.PayloadSize; 14 | 15 | size.Reinterpret() 16 | .CopyTo(buffer.Slice(offset)); 17 | 18 | offset += 2; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/Network/TCPEchoGladNetServerApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Sockets; 4 | using System.Text; 5 | using Common.Logging; 6 | using Glader.Essentials; 7 | 8 | namespace GladNet 9 | { 10 | public sealed class TCPEchoGladNetServerApplication : TcpGladNetServerApplication 11 | { 12 | public TCPEchoGladNetServerApplication(NetworkAddressInfo serverAddress, ILog logger) 13 | : base(serverAddress, logger) 14 | { 15 | 16 | } 17 | 18 | protected override bool IsClientAcceptable(Socket connection) 19 | { 20 | return true; 21 | } 22 | 23 | public override TCPEchoManagedSession Create(SessionCreationContext context) 24 | { 25 | if (context == null) throw new ArgumentNullException(nameof(context)); 26 | 27 | NetworkConnectionOptions options = new NetworkConnectionOptions(2, 2, 1024); 28 | var serializer = new StringMessageSerializer(); 29 | 30 | SessionMessageBuildingServiceContext messageServices = 31 | new SessionMessageBuildingServiceContext(new StringMessagePacketHeaderFactory(), serializer, serializer, new StringPacketHeaderSerializer()); 32 | 33 | //Build the message disaptching strategy, for how and where and in what way messages will be handled 34 | var handlerService = new DefaultMessageHandlerService>(); 35 | var dispatcher = new InPlaceNetworkMessageDispatchingStrategy(handlerService); 36 | 37 | //Bind one of the default handlers 38 | handlerService.Bind(new DefaultStringMessageHandler()); 39 | 40 | return new TCPEchoManagedSession(options, context.Connection, context.Details, messageServices, dispatcher); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/Network/TCPEchoManagedSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Pipelines.Sockets.Unofficial; 7 | 8 | namespace GladNet 9 | { 10 | public sealed class TCPEchoManagedSession : BaseTcpManagedSession 11 | { 12 | private INetworkMessageDispatchingStrategy MessageDispatcher { get; } 13 | 14 | private SessionMessageContext CachedSessionContext { get; } 15 | 16 | public TCPEchoManagedSession(NetworkConnectionOptions networkOptions, SocketConnection connection, SessionDetails details, SessionMessageBuildingServiceContext messageServices, 17 | INetworkMessageDispatchingStrategy messageDispatcher) 18 | : base(networkOptions, connection, details, messageServices) 19 | { 20 | MessageDispatcher = messageDispatcher ?? throw new ArgumentNullException(nameof(messageDispatcher)); 21 | 22 | //We build the session context 1 time because it should not change 23 | //Rather than inject as a dependency, we can build it in here. Really optional to inject 24 | //or build it in CTOR 25 | //Either way we build a send service and session context that captures it to provide to handling. 26 | IMessageSendService sendService = new QueueBasedMessageSendService(this.MessageService.OutgoingMessageQueue); 27 | CachedSessionContext = new SessionMessageContext(details, sendService, ConnectionService); 28 | } 29 | 30 | /// 31 | public override async Task OnNetworkMessageReceivedAsync(NetworkIncomingMessage message, CancellationToken token = default) 32 | { 33 | await MessageDispatcher.DispatchNetworkMessageAsync(CachedSessionContext, message, token); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/GladNet.DotNetTcpServer.EchoTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using Common.Logging; 6 | using Common.Logging.Simple; 7 | 8 | namespace GladNet 9 | { 10 | class Program 11 | { 12 | public static IPAddress Address { get; } = IPAddress.Parse("127.0.0.1"); 13 | 14 | static async Task Main(string[] args) 15 | { 16 | ILog logger = new ConsoleLogger(LogLevel.All, true); 17 | logger.Info($"Starting server."); 18 | 19 | await new TCPEchoGladNetServerApplication(new NetworkAddressInfo(Address, 6969), logger) 20 | .BeginListeningAsync(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/GladNet.WebSocket.EchoTest/GladNet.WebSocket.EchoTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/GladNet.WebSocket.EchoTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.WebSockets; 4 | using System.Threading.Tasks; 5 | 6 | namespace GladNet.WebSocket.EchoTest 7 | { 8 | class Program 9 | { 10 | static async Task Main(string[] args) 11 | { 12 | HttpListener httpListener = new HttpListener(); 13 | httpListener.Prefixes.Add("http://localhost:4999/"); 14 | httpListener.Start(); 15 | 16 | HttpListenerContext context = await httpListener.GetContextAsync(); 17 | if(context.Request.IsWebSocketRequest) 18 | { 19 | HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null); 20 | System.Net.WebSockets.WebSocket webSocket = webSocketContext.WebSocket; 21 | while(webSocket.State == WebSocketState.Open) 22 | { 23 | await Task.Delay(1); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | --------------------------------------------------------------------------------