├── .gitattributes ├── .gitignore ├── Hazel Networking Protocol.docx ├── Hazel.Documentation ├── Content Layout.content ├── Content │ ├── Introduction.aml │ ├── Quickstart.aml │ ├── Technical Details │ │ ├── Protocols │ │ │ ├── Protocols.aml │ │ │ ├── TCP.aml │ │ │ └── UDP-RUDP.aml │ │ └── Technical Details.aml │ └── Welcome.aml ├── Hazel.Documentation.shfbproj ├── docinclude │ ├── TcpClientExample.cs │ ├── TcpListenerExample.cs │ ├── UdpClientExample.cs │ ├── UdpListenerExample.cs │ └── common.xml └── icons │ └── Help.png ├── Hazel.UnitTests ├── Hazel.UnitTests.csproj ├── Properties │ └── AssemblyInfo.cs ├── StatisticsTests.cs ├── TcpConnectionTests.cs ├── TestHelper.cs └── UdpConnectionTests.cs ├── Hazel.sln ├── Hazel ├── Connection.cs ├── ConnectionEndPoint.cs ├── ConnectionListener.cs ├── ConnectionState.cs ├── ConnectionStatistics.cs ├── DataReceivedEventArgs.cs ├── DisconnectedEventArgs.cs ├── Hazel.csproj ├── Hazel.nuspec ├── HazelException.cs ├── IPMode.cs ├── IRecyclable.cs ├── NetworkConnection.cs ├── NetworkConnectionListener.cs ├── NetworkEndPoint.cs ├── NewConnectionEventArgs.cs ├── ObjectPool.cs ├── Properties │ └── AssemblyInfo.cs ├── SendOption.cs ├── Tcp │ ├── StateObject.cs │ ├── TcpConnection.cs │ └── TcpConnectionListener.cs └── Udp │ ├── SendOptionInternal.cs │ ├── UdpClientConnection.cs │ ├── UdpConnection.Fragmented.cs │ ├── UdpConnection.KeepAlive.cs │ ├── UdpConnection.Reliable.cs │ ├── UdpConnection.cs │ ├── UdpConnectionListener.cs │ └── UdpServerConnection.cs ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | #Sandcastle generated documentation 20 | [Hh]elp/ 21 | 22 | # Roslyn cache directories 23 | *.ide/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | #NUNIT 30 | *.VisualState.xml 31 | TestResult.xml 32 | 33 | # Build Results of an ATL Project 34 | [Dd]ebugPS/ 35 | [Rr]eleasePS/ 36 | dlldata.c 37 | 38 | *_i.c 39 | *_p.c 40 | *_i.h 41 | *.ilk 42 | *.meta 43 | *.obj 44 | *.pch 45 | *.pdb 46 | *.pgc 47 | *.pgd 48 | *.rsp 49 | *.sbr 50 | *.tlb 51 | *.tli 52 | *.tlh 53 | *.tmp 54 | *.tmp_proj 55 | *.log 56 | *.vspscc 57 | *.vssscc 58 | .builds 59 | *.pidb 60 | *.svclog 61 | *.scc 62 | 63 | # Chutzpah Test files 64 | _Chutzpah* 65 | 66 | # Visual C++ cache files 67 | ipch/ 68 | *.aps 69 | *.ncb 70 | *.opensdf 71 | *.sdf 72 | *.cachefile 73 | 74 | # Visual Studio profiler 75 | *.psess 76 | *.vsp 77 | *.vspx 78 | 79 | # TFS 2012 Local Workspace 80 | $tf/ 81 | 82 | # Guidance Automation Toolkit 83 | *.gpState 84 | 85 | # ReSharper is a .NET coding add-in 86 | _ReSharper*/ 87 | *.[Rr]e[Ss]harper 88 | *.DotSettings.user 89 | 90 | # JustCode is a .NET coding addin-in 91 | .JustCode 92 | 93 | # TeamCity is a build add-in 94 | _TeamCity* 95 | 96 | # DotCover is a Code Coverage Tool 97 | *.dotCover 98 | 99 | # NCrunch 100 | _NCrunch_* 101 | .*crunch*.local.xml 102 | 103 | # MightyMoose 104 | *.mm.* 105 | AutoTest.Net/ 106 | 107 | # Web workbench (sass) 108 | .sass-cache/ 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.[Pp]ublish.xml 128 | *.azurePubxml 129 | ## TODO: Comment the next line if you want to checkin your 130 | ## web deploy settings but do note that will include unencrypted 131 | ## passwords 132 | #*.pubxml 133 | 134 | # NuGet Packages Directory 135 | packages/* 136 | ## TODO: If the tool you use requires repositories.config 137 | ## uncomment the next line 138 | #!packages/repositories.config 139 | 140 | # Enable "build/" folder in the NuGet Packages folder since 141 | # NuGet packages use it for MSBuild targets. 142 | # This line needs to be after the ignore of the build folder 143 | # (and the packages folder if the line above has been uncommented) 144 | !packages/build/ 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | sql/ 155 | *.Cache 156 | ClientBin/ 157 | [Ss]tyle[Cc]op.* 158 | ~$* 159 | *~ 160 | *.dbmdl 161 | *.dbproj.schemaview 162 | *.pfx 163 | *.publishsettings 164 | node_modules/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # LightSwitch generated files 190 | GeneratedArtifacts/ 191 | _Pvt_Extensions/ 192 | ModelManifest.xml 193 | *.ide 194 | -------------------------------------------------------------------------------- /Hazel Networking Protocol.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkRiftNetworking/Hazel-Networking/c903b2fc4f66080009b3a3a5acac31a6bd8f12ea/Hazel Networking Protocol.docx -------------------------------------------------------------------------------- /Hazel.Documentation/Content Layout.content: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Hazel.Documentation/Content/Introduction.aml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Welcome to the Hazel documentation! Here you will find technical 8 | details, API references and tutorials for getting started with Hazel. 9 | 10 | 11 | Hazel Networking is an open source low level networking library for C# providing 12 | connection orientated, message bassed communication via TCP, UDP and 13 | RUDP. You can download it from Github 14 | 15 | here 16 | https://github.com/DarkRiftNetworking/Hazel-Networking 17 | _blank 18 | 19 | . 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Hazel.Documentation/Content/Quickstart.aml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 6 | 7 | 8 | 9 | Hazel is a low level networking library that takes away a lot of the pain of writing sockets. Hazel provides the guarantee of connection orientated, message based communication across TCP, UDP and RUDP. 10 | 11 | 12 | This guide will take you through the stages of writing a console based server and connecting to it from a console based client. 13 | 14 | 15 | 16 | 17 | 18 |
19 | Creating a Server 20 | 21 | 22 | Creating a Listener 23 | 24 | 25 | 26 | Create a new solution containing a Console project named "Server" (or at least something obvious). 27 | 28 | 29 | 30 | 31 | Add a reference to Hazel.dll in the project. 32 | 33 | 34 | 35 | 36 | Modify the default file to look like this, we'll go through each part individually. 37 | 38 | using System; 39 | using System.Collections.Generic; 40 | using System.Linq; 41 | using System.Text; 42 | using System.Threading.Tasks; 43 | using System.Net; 44 | 45 | using Hazel; 46 | using Hazel.Tcp; 47 | 48 | namespace HazelExample 49 | { 50 | class ServerExample 51 | { 52 | static ConnectionListener listener; 53 | 54 | public static void Main(string[] args) 55 | { 56 | listener = new TcpConnectionListener(IPAddress.Any, 4296); 57 | 58 | listener.NewConnection += NewConnectionHandler; 59 | 60 | Console.WriteLine("Starting server!"); 61 | 62 | listener.Start(); 63 | 64 | Console.WriteLine("Press any key to continue..."); 65 | 66 | Console.ReadKey(); 67 | 68 | listener.Close(); 69 | } 70 | } 71 | } 72 | 73 | 74 | Firstly we need to tell the compiler that we are using Hazel; and using Hazel.Tcp; so we have access to Hazel's general and TCP specific types. Secondly we create a T:Hazel.ConnectionListener, these wait on a specified port and accept new clients to the server invoking the E:Hazel.ConnectionListener.NewConnection event each time a new client connects. 75 | 76 | 77 | We then instruct the ConnectionListener to begin listening for new clients by calling M:ConnectionListener.Start and then finally we close the listener using M:ConnectionListener.Close. 78 | 79 | 80 | In this circumstance it would be much better if we enclosed the listener within a using block as it implements IDisposable, however for the majority of use cases you are more likely to use the listener in this way. If you want to see it used in a using block then look at the unit tests. 81 | 82 | 83 | If you want to use UDP instead of TCP then it is as simple as including the Hazel.Udp namespace and creating a T:UdpConnectionListener instead. 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | Handling Events 92 | 93 | 94 | 95 | In the last example we subscribed to the E:ConnectionListener.NewConnection event but we never spsecified what to do in that event. Add the following method to your code. 96 | 97 | static void NewConnectionHandler(object sender, NewConnectionEventArgs args) 98 | { 99 | Console.WriteLine("New connection from " + args.Connection.EndPoint.ToString(); 100 | 101 | args.Connection.DataReceived += DataReceivedHandler; 102 | } 103 | 104 | This method is fairly simple, it follows the standard event handler delegate and take a sender (in this case it will be the ConnectionListener that called the event) and some args. The args contain a Connection which is the main object we use for communication with clients. 105 | You can imagine this process in a similar way to TCP. You create a listener which creates sockets on the server side for each client socket that connects to it. 106 | In this method we simply print out the IP of the client that just connected and then we subscribe to any data that this client sends us. You may also want to store the connection here for later reference as Hazel doesn't maintain a list of connection for you. 107 | 108 | 109 | 110 | 111 | Once again we've got an undeclared event handler so lets fill that in now. 112 | 113 | private static void DataReceivedHandler(object sender, DataEventArgs args) 114 | { 115 | Connection connection = (Connection)sender; 116 | 117 | Console.WriteLine("Received (" + string.Join<byte>(", ", args.Bytes) + ") from " + connection.EndPoint.ToString()); 118 | 119 | connection.SendBytes(args.Bytes, args.SendOption); 120 | 121 | args.Recycle(); 122 | } 123 | 124 | Again this method follows the standard event handler delegate and this time we make use of the sender parameter to get the connection that received the data. We then go on to print out the data received and then we send it back to the client using M:ConnectionListener.SendBytes. When we send we specify that the send option should be the same as the data that was received, we'll talk more about send options later. 125 | You have also probably noticed that I didn't mention the M:DataEventArgs.Recycle call. Recycle is an optional call that tells Hazel that it is now safe to use the object again rather than creating a new one and having to wait for the GC to collect the old object. If you are sending a lot of data then you will get less GC runs if you call Recycle but it is not necerssary to call it and if you dont the GC will collect it as normal. Also note that you should only call Recycle once you are done using the object otherwise the data inside it may change! 126 | 127 | 128 | 129 | 130 | 131 | In total, you should have something like this. 132 | 133 | using System; 134 | using System.Collections.Generic; 135 | using System.Linq; 136 | using System.Text; 137 | using System.Threading.Tasks; 138 | using System.Net; 139 | 140 | using Hazel; 141 | using Hazel.Tcp; 142 | 143 | namespace HazelExample 144 | { 145 | class ServerExample 146 | { 147 | static ConnectionListener listener; 148 | 149 | public static void Main(string[] args) 150 | { 151 | listener = new TcpConnectionListener(IPAddress.Any, 4296); 152 | 153 | listener.NewConnection += NewConnectionHandler; 154 | 155 | Console.WriteLine("Starting server!"); 156 | 157 | listener.Start(); 158 | 159 | Console.WriteLine("Press any key to continue..."); 160 | 161 | Console.ReadKey(); 162 | 163 | listener.Close(); 164 | } 165 | 166 | static void NewConnectionHandler(object sender, NewConnectionEventArgs args) 167 | { 168 | Console.WriteLine("New connection from " + args.Connection.EndPoint.ToString(); 169 | 170 | args.Connection.DataReceived += DataReceivedHandler; 171 | 172 | args.Recycle(); 173 | } 174 | 175 | private static void DataReceivedHandler(object sender, DataEventArgs args) 176 | { 177 | Connection connection = (Connection)sender; 178 | 179 | Console.WriteLine("Received (" + string.Join<byte>(", ", args.Bytes) + ") from " + connection.EndPoint.ToString()); 180 | 181 | connection.SendBytes(args.Bytes, args.SendOption); 182 | 183 | args.Recycle(); 184 | } 185 | } 186 | } 187 | 188 | 189 |
190 | 191 |
192 | Creating a Client 193 | 194 | 195 | Connecting to a Server 196 | 197 | 198 | 199 | Create a new project in your solution for th client and add a reference to Hazel.dll. 200 | 201 | 202 | 203 | 204 | 205 | Modify the default file to contain the following. 206 | 207 | using System; 208 | using System.Collections.Generic; 209 | using System.Linq; 210 | using System.Text; 211 | using System.Threading.Tasks; 212 | 213 | using Hazel; 214 | using Hazel.Tcp; 215 | 216 | namespace HazelExample 217 | { 218 | class ClientExample 219 | { 220 | static Connection connection; 221 | 222 | public static void Main(string[] args) 223 | { 224 | NetworkEndPoint endPoint = new NetworkEndPoint("127.0.0.1", 4296); 225 | 226 | connection = new TcpConnection(endPoint); 227 | 228 | connection.DataReceived += DataReceived; 229 | 230 | Console.WriteLine("Connecting!"); 231 | 232 | connection.Connect(); 233 | 234 | Console.WriteLine("Press any key to continue..."); 235 | 236 | Console.ReadKey(); 237 | 238 | connection.Close(); 239 | } 240 | } 241 | } 242 | 243 | As you can see you simply create a T:NetworkEndPoint for the remote server and then pass it into a new T:Connection. Then you can setup any events needed and finally call M:Connection.Connect to begin the connection. 244 | Finally we close the connection using M:Connection.Close. Again, Connection implements IDisposable and so must be closed, if it is easier you could wrap it in a using block. 245 | 246 | If you are using UDP instead of TCP then include the Hazel.Udp namespace and create a T:UdpClientConnection instead. 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | Handling Events 255 | 256 | 257 | 258 | Add the following event handler to receive data, you'll notice that this is the same event we used in the server and so it will not be explained. 259 | 260 | private static void DataReceived(object sender, DataEventArgs args) 261 | { 262 | Console.WriteLine("Received (" + string.Join<byte>(", ", args.Bytes) + ") from " + connection.EndPoint.ToString()); 263 | 264 | args.Recycle(); 265 | } 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | Sending Messages 274 | 275 | 276 | 277 | Sending a message is the same for both the server and client, you simply call M:Connection.SendBytes on your connection. 278 | Add the following line after the call to Connect 279 | 280 | connection.SendBytes(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); 281 | 282 | 283 | 284 | 285 | 286 | 287 | You should have something like this. 288 | 289 | using System; 290 | using System.Collections.Generic; 291 | using System.Linq; 292 | using System.Text; 293 | using System.Threading.Tasks; 294 | 295 | using Hazel; 296 | using Hazel.Tcp; 297 | 298 | namespace HazelExample 299 | { 300 | class ClientExample 301 | { 302 | static Connection connection; 303 | 304 | public static void Main(string[] args) 305 | { 306 | NetworkEndPoint endPoint = new NetworkEndPoint("127.0.0.1", 4296); 307 | 308 | connection = new TcpConnection(endPoint); 309 | 310 | connection.DataReceived += DataReceived; 311 | 312 | Console.WriteLine("Connecting!"); 313 | 314 | connection.Connect(); 315 | 316 | connection.SendBytes(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); 317 | 318 | Console.WriteLine("Press any key to continue..."); 319 | 320 | Console.ReadKey(); 321 | 322 | connection.Close(); 323 | } 324 | 325 | private static void DataReceived(object sender, DataEventArgs args) 326 | { 327 | Console.WriteLine("Received (" + string.Join<byte>(", ", args.Bytes) + ") from " + connection.EndPoint.ToString()); 328 | 329 | args.Recycle(); 330 | } 331 | } 332 | } 333 | 334 | 335 |
336 |
337 |
-------------------------------------------------------------------------------- /Hazel.Documentation/Content/Technical Details/Protocols/Protocols.aml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | In order to provide the guarantees that it does, Hazel augments each 8 | transport protocol with its own meta data (or header data) that is 9 | hidden from users. 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Hazel.Documentation/Content/Technical Details/Protocols/TCP.aml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Hazel's TCP protocol is fairly simple as TCP already provides connection 8 | based communication and thus only needs a message system implementing. 9 | 10 | 11 | 12 |
13 | Header Data 14 | 15 | 16 | The TCP protocol is fairly simple interms of header. 4 bytes are 17 | added to mark the number of bytes in each message. 18 | 19 | 20 | 21 | Length (MSB) 22 | Length 23 | Length 24 | Length (LSB) 25 | Data... 26 | 27 |
28 | All header data in Hazel is encoded in big endian format. 29 |
30 |
31 | 32 |
33 |
-------------------------------------------------------------------------------- /Hazel.Documentation/Content/Technical Details/Protocols/UDP-RUDP.aml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | UDP provides message based communication however it is not connection 8 | oriented nor does it have any built in functionality for reliable or 9 | fragmented delivery of messages hence the implementation to cover this 10 | if fairly complicated. 11 | 12 | 13 | 14 |
15 | Header Data 16 | 17 | 18 | The UDP protocol has multiple layers of header data depending on the 19 | send options that are requested with the message. 20 | 21 | 22 | 23 | Unreliable 24 | Type 25 | Data... 26 |   27 |   28 | 29 | 30 | Reliable 31 | Type 32 | ID (MSB) 33 | ID (LSB) 34 | Data... 35 | 36 |
37 | All header data in Hazel is encoded in big endian format. 38 | 39 | In both of these, Type is an identifier 40 | that holds the SendOption or another value indicating a control 41 | packet of data. The most significant 4 bits of 42 | Type are reserved for future use and the 43 | least significant bits specify the send option flags for the message. 44 | 45 | 46 | 47 | 48 | 128 49 | 64 50 | 32 51 | 16 52 | 8 53 | 4 54 | 2 55 | 1 56 | 57 | 58 | 59 | Reserved 60 | Reserved 61 | Reserved 62 | Reserved 63 | Control 64 | Reserved 65 | Fragmented 66 | Reliable 67 | 68 |
69 | 70 | Reliable and 71 | Fragmented are both flags for their 72 | respective send option, Control, if set, 73 | specifies that the 3 least significant bits refer to a control code: 74 | 75 | 76 | 77 | 0 78 | Hello 79 | 80 | 81 | 1 82 | Disconnect 83 | 84 | 85 | 2 86 | Acknowledgment 87 | 88 |
89 |
90 |
91 | 92 |
93 | Reliable Delivery 94 | 95 | 96 | To implement reliable delivery 2 ID bytes are sent which identify the 97 | packet. When the receiver receives the data is replys with an 98 | acknowledgement packet as follows: 99 | 100 | 101 | 102 | Acknowledgement 103 | Type 104 | ID (MSB) 105 | ID (LSB) 106 | 107 |
108 | 109 | Where type is specifying an acknowledgement packet as laid out above. 110 | 111 | 112 | If the sending client does not receive an acknowledgement after a 113 | specific amount of time elapses from the pakcet being sent then it 114 | resends that packet and thetime before the next resend of that packet 115 | is doubled. When the sending client receives the acknowledgement is 116 | should not resend the data again. 117 | 118 | 119 | The receiving client must also ensure that it does not present the 120 | same packet to the user twice but must acknowledge any packets it 121 | receives, even if it has already received that packet, in case an 122 | acknowledgement is lost. 123 | 124 | 125 | After a specific number of resends without acknowledgement a sending 126 | client may mark assume that communication has been interrupted and 127 | thus mark the connection as disconnected. 128 | 129 |
130 |
131 | 132 |
133 | Keepalive packets 134 | 135 | 136 | Keepalive packets should be sent after a specific time has elapsed 137 | since the last packet (either keepalive, acknowledgement or user sent) 138 | was transmitted and should be sent using reliable delivery so that an 139 | acknowledgement can be received to indicate communication has not been 140 | lost. As packets that are not acknowledged should cause a 141 | disconnection no additional logic is required for keepalives. 142 | 143 | 144 |
145 |
146 |
-------------------------------------------------------------------------------- /Hazel.Documentation/Content/Technical Details/Technical Details.aml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Underneath Hazel there are a lot of technicalities, this section will 8 | help outline those details so you understand Hazel's implementations 9 | better. 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Hazel.Documentation/Content/Welcome.aml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a sample conceptual topic. You can use this as a starting point for adding more conceptual 6 | content to your help project. 7 | 8 | 9 |
10 | Getting Started 11 | 12 | To get started, add a documentation source to the project (a Visual Studio solution, project, or 13 | assembly and XML comments file). See the Getting Started topics in the Sandcastle Help 14 | File Builder's help file for more information. The following default items are included in this project: 15 | 16 | 17 | 18 | ContentLayout.content - Use the content layout file to manage the 19 | conceptual content in the project and define its layout in the table of contents. 20 | 21 | 22 | 23 | The .\media folder - Place images in this folder that you will reference 24 | from conceptual content using medialLink or mediaLinkInline 25 | elements. If you will not have any images in the file, you may remove this folder. 26 | 27 | 28 | 29 | The .\icons folder - This contains a default logo for the help file. You 30 | may replace it or remove it and the folder if not wanted. If removed or if you change the file name, update 31 | the Transform Args project properties page by removing or changing the filename in the 32 | logoFile transform argument. Note that unlike images referenced from conceptual topics, 33 | the logo file should have its BuildAction property set to Content. 34 | 35 | 36 | 37 | The .\Content folder - Use this to store your conceptual topics. You may 38 | name the files and organize them however you like. One suggestion is to lay the files out on disk as you have 39 | them in the content layout file as shown in this project but the choice is yours. Files can be added via the 40 | Solution Explorer or from within the content layout file editor. Files must appear in the content layout file 41 | in order to be compiled into the help file. 42 | 43 | 44 | 45 | See the Conceptual Content topics in the Sandcastle Help File Builder's 46 | help file for more information. See the Sandcastle MAML Guide for details on Microsoft 47 | Assistance Markup Language (MAML) which is used to create these topics. 48 | 49 |
50 | 51 | 52 | 53 | 54 |
55 |
56 | -------------------------------------------------------------------------------- /Hazel.Documentation/Hazel.Documentation.shfbproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | Debug 7 | AnyCPU 8 | 2.0 9 | b90bb577-2080-4c22-ae86-fb0ccfb40920 10 | 2015.6.5.0 11 | 12 | Hazel.Documentation 13 | Hazel.Documentation 14 | Hazel.Documentation 15 | 16 | .NET Framework 3.5 17 | .\Help\ 18 | Hazel.Documentation 19 | en-US 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 100 33 | OnlyWarningsAndErrors 34 | Website 35 | False 36 | False 37 | False 38 | False 39 | 1.0.0.0 40 | 2 41 | False 42 | Standard 43 | Blank 44 | False 45 | VS2013 46 | False 47 | MemberName 48 | Hazel Documentation 49 | BelowNamespaces 50 | Msdn 51 | Msdn 52 | False 53 | True 54 | Hazel Networking is a low-level networking library for C# providing connection orientated, message based communication via TCP, UDP and RUDP. 55 | Its aim is to provide a standardized interface for web communication so that using and switching between protocols is incredibly simple. 56 | Hazel is going to be the basis of DarkRift 2 and it is being released completely open source so that members of the community can make use of it, improve it and help find any bugs before DarkRift 2 is released. 57 | Hazel can be downloaded as a NuGet package here or you can get the latest build directly from the releases page here! 58 | 59 | 60 | Namespace for all classes shared between multiple protocol types. 61 | Namespace for all TCP communication classes. 62 | Namespace for all UDP communication classes. 63 | 64 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 114 | 115 | 116 | 117 | 118 | 119 | OnBuildSuccess 120 | 121 | -------------------------------------------------------------------------------- /Hazel.Documentation/docinclude/TcpClientExample.cs: -------------------------------------------------------------------------------- 1 | class TcpClientExample 2 | { 3 | static void Main(string[] args) 4 | { 5 | using (TcpConnection connection = new TcpConnection()) 6 | { 7 | ManualResetEvent e = new ManualResetEvent(false); 8 | 9 | //Whenever we receive data print the number of bytes and how it was sent 10 | connection.DataReceived += (object sender, DataReceivedEventArgs a) => 11 | Console.WriteLine("Received {0} bytes via {1}!", a.Bytes.Length, a.SendOption); 12 | 13 | //When the end point disconnects from us then release the main thread and exit 14 | connection.Disconnected += (object sender, DisconnectedEventArgs a) => 15 | e.Set(); 16 | 17 | //Connect to a server 18 | connection.Connect(new NetworkEndPoint("127.0.0.1", 4296)); 19 | 20 | //Wait until the end point disconnects from us 21 | e.WaitOne(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Hazel.Documentation/docinclude/TcpListenerExample.cs: -------------------------------------------------------------------------------- 1 | class TcpListenerExample 2 | { 3 | static void Main(string[] args) 4 | { 5 | //Setup listener 6 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 7 | { 8 | //Start listening for new connection events 9 | listener.NewConnection += delegate(object sender, NewConnectionEventArgs a) 10 | { 11 | //Send the client some data 12 | a.Connection.SendBytes(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, SendOption.Reliable); 13 | 14 | //Disconnect from the client 15 | a.Connection.Close(); 16 | }; 17 | 18 | listener.Start(); 19 | 20 | Console.ReadKey(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Hazel.Documentation/docinclude/UdpClientExample.cs: -------------------------------------------------------------------------------- 1 | class UdpClientExample 2 | { 3 | static void Main(string[] args) 4 | { 5 | using (UdpConnection connection = new UdpConnection()) 6 | { 7 | ManualResetEvent e = new ManualResetEvent(false); 8 | 9 | //Whenever we receive data print the number of bytes and how it was sent 10 | connection.DataReceived += (object sender, DataReceivedEventArgs a) => 11 | Console.WriteLine("Received {0} bytes via {1}!", a.Bytes.Length, a.SendOption); 12 | 13 | //When the end point disconnects from us then release the main thread and exit 14 | connection.Disconnected += (object sender, DisconnectedEventArgs a) => 15 | e.Set(); 16 | 17 | //Connect to a server 18 | connection.Connect(new NetworkEndPoint("127.0.0.1", 4296)); 19 | 20 | //Wait until the end point disconnects from us 21 | e.WaitOne(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Hazel.Documentation/docinclude/UdpListenerExample.cs: -------------------------------------------------------------------------------- 1 | class UdpListenerExample 2 | { 3 | static void Main(string[] args) 4 | { 5 | //Setup listener 6 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 7 | { 8 | //Start listening for new connection events 9 | listener.NewConnection += delegate(object sender, NewConnectionEventArgs a) 10 | { 11 | //Send the client some data 12 | a.Connection.SendBytes(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, SendOption.Reliable); 13 | 14 | //Disconnect from the client 15 | a.Connection.Close(); 16 | }; 17 | 18 | listener.Start(); 19 | 20 | Console.ReadKey(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Hazel.Documentation/docinclude/common.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | As with all Hazel events it is invoked on a thread from the .NET 7 | ThreadPool and hence any subscribers should ensure their 8 | handling code is thread safe. Implementing connections are not bound to invoking this event in the sequence 9 | messages are received, in fact implementers are only required to ensure this method is always and only invoked 10 | for a user sent message, therefore subscribers should be aware that this event may be called out of order and 11 | may be called whilst another thread is still handling an invocation of the event. 12 | 13 | 14 | 15 | 16 | This object implements IRecyclable and hence can be recycled in order to reduce the number of objects the 17 | GC has to deal with. When you are done with the object you can either leave it unreferenced as you usually 18 | would and the GC will collect it or you can call to inform Hazel that the object 19 | should be reused. Once recycle has been called the contents can be overwritten at any time and so only 20 | call it once you are completely finished with the object. 21 | 22 | 23 | 24 | 25 | This method sends a number of bytes in a message to the end point of this client using the given 26 | to describe how the data should be sent. Sending messages requires that the 27 | this connection is connected to a remote end point and SendBytes will throw an exception if that is not 28 | the case. See the property for information on whether a connection is connected or not. 29 | 30 | 31 | -------------------------------------------------------------------------------- /Hazel.Documentation/icons/Help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkRiftNetworking/Hazel-Networking/c903b2fc4f66080009b3a3a5acac31a6bd8f12ea/Hazel.Documentation/icons/Help.png -------------------------------------------------------------------------------- /Hazel.UnitTests/Hazel.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {1394E4CA-E17A-42F5-9216-8046ACA8D16B} 7 | Library 8 | Properties 9 | Hazel.UnitTests 10 | Hazel.UnitTests 11 | v3.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | False 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {02cfbd30-d77d-400f-94b2-700f60efdd7f} 65 | Hazel 66 | 67 | 68 | 69 | 70 | 71 | 72 | False 73 | 74 | 75 | False 76 | 77 | 78 | False 79 | 80 | 81 | False 82 | 83 | 84 | 85 | 86 | 87 | 88 | 95 | -------------------------------------------------------------------------------- /Hazel.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Hazel.UnitTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Hazel.UnitTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e88c2226-946e-4f00-9336-8d8d7946ac23")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Hazel.UnitTests/StatisticsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Hazel.UnitTests 5 | { 6 | [TestClass] 7 | public class StatisticsTests 8 | { 9 | [TestMethod] 10 | public void SendTests() 11 | { 12 | ConnectionStatistics statistics = new ConnectionStatistics(); 13 | 14 | statistics.LogUnreliableSend(10, 11); 15 | 16 | Assert.AreEqual(1, statistics.MessagesSent); 17 | Assert.AreEqual(1, statistics.UnreliableMessagesSent); 18 | Assert.AreEqual(0, statistics.ReliableMessagesSent); 19 | Assert.AreEqual(0, statistics.FragmentedMessagesSent); 20 | Assert.AreEqual(0, statistics.AcknowledgementMessagesSent); 21 | Assert.AreEqual(0, statistics.HelloMessagesSent); 22 | 23 | Assert.AreEqual(10, statistics.DataBytesSent); 24 | Assert.AreEqual(11, statistics.TotalBytesSent); 25 | 26 | statistics.LogReliableSend(5, 8); 27 | 28 | Assert.AreEqual(2, statistics.MessagesSent); 29 | Assert.AreEqual(1, statistics.UnreliableMessagesSent); 30 | Assert.AreEqual(1, statistics.ReliableMessagesSent); 31 | Assert.AreEqual(0, statistics.FragmentedMessagesSent); 32 | Assert.AreEqual(0, statistics.AcknowledgementMessagesSent); 33 | Assert.AreEqual(0, statistics.HelloMessagesSent); 34 | 35 | Assert.AreEqual(15, statistics.DataBytesSent); 36 | Assert.AreEqual(19, statistics.TotalBytesSent); 37 | 38 | statistics.LogFragmentedSend(6, 10); 39 | 40 | Assert.AreEqual(3, statistics.MessagesSent); 41 | Assert.AreEqual(1, statistics.UnreliableMessagesSent); 42 | Assert.AreEqual(1, statistics.ReliableMessagesSent); 43 | Assert.AreEqual(1, statistics.FragmentedMessagesSent); 44 | Assert.AreEqual(0, statistics.AcknowledgementMessagesSent); 45 | Assert.AreEqual(0, statistics.HelloMessagesSent); 46 | 47 | Assert.AreEqual(21, statistics.DataBytesSent); 48 | Assert.AreEqual(29, statistics.TotalBytesSent); 49 | 50 | statistics.LogAcknowledgementSend(4); 51 | 52 | Assert.AreEqual(4, statistics.MessagesSent); 53 | Assert.AreEqual(1, statistics.UnreliableMessagesSent); 54 | Assert.AreEqual(1, statistics.ReliableMessagesSent); 55 | Assert.AreEqual(1, statistics.FragmentedMessagesSent); 56 | Assert.AreEqual(1, statistics.AcknowledgementMessagesSent); 57 | Assert.AreEqual(0, statistics.HelloMessagesSent); 58 | 59 | Assert.AreEqual(21, statistics.DataBytesSent); 60 | Assert.AreEqual(33, statistics.TotalBytesSent); 61 | 62 | statistics.LogHelloSend(7); 63 | 64 | Assert.AreEqual(5, statistics.MessagesSent); 65 | Assert.AreEqual(1, statistics.UnreliableMessagesSent); 66 | Assert.AreEqual(1, statistics.ReliableMessagesSent); 67 | Assert.AreEqual(1, statistics.FragmentedMessagesSent); 68 | Assert.AreEqual(1, statistics.AcknowledgementMessagesSent); 69 | Assert.AreEqual(1, statistics.HelloMessagesSent); 70 | 71 | Assert.AreEqual(21, statistics.DataBytesSent); 72 | Assert.AreEqual(40, statistics.TotalBytesSent); 73 | 74 | Assert.AreEqual(0, statistics.MessagesReceived); 75 | Assert.AreEqual(0, statistics.UnreliableMessagesReceived); 76 | Assert.AreEqual(0, statistics.ReliableMessagesReceived); 77 | Assert.AreEqual(0, statistics.FragmentedMessagesReceived); 78 | Assert.AreEqual(0, statistics.AcknowledgementMessagesReceived); 79 | Assert.AreEqual(0, statistics.HelloMessagesReceived); 80 | 81 | Assert.AreEqual(0, statistics.DataBytesReceived); 82 | Assert.AreEqual(0, statistics.TotalBytesReceived); 83 | } 84 | 85 | [TestMethod] 86 | public void ReceiveTests() 87 | { 88 | ConnectionStatistics statistics = new ConnectionStatistics(); 89 | 90 | statistics.LogUnreliableReceive(10, 11); 91 | 92 | Assert.AreEqual(1, statistics.MessagesReceived); 93 | Assert.AreEqual(1, statistics.UnreliableMessagesReceived); 94 | Assert.AreEqual(0, statistics.ReliableMessagesReceived); 95 | Assert.AreEqual(0, statistics.FragmentedMessagesReceived); 96 | Assert.AreEqual(0, statistics.AcknowledgementMessagesReceived); 97 | Assert.AreEqual(0, statistics.HelloMessagesReceived); 98 | 99 | Assert.AreEqual(10, statistics.DataBytesReceived); 100 | Assert.AreEqual(11, statistics.TotalBytesReceived); 101 | 102 | statistics.LogReliableReceive(5, 8); 103 | 104 | Assert.AreEqual(2, statistics.MessagesReceived); 105 | Assert.AreEqual(1, statistics.UnreliableMessagesReceived); 106 | Assert.AreEqual(1, statistics.ReliableMessagesReceived); 107 | Assert.AreEqual(0, statistics.FragmentedMessagesReceived); 108 | Assert.AreEqual(0, statistics.AcknowledgementMessagesReceived); 109 | Assert.AreEqual(0, statistics.HelloMessagesReceived); 110 | 111 | Assert.AreEqual(15, statistics.DataBytesReceived); 112 | Assert.AreEqual(19, statistics.TotalBytesReceived); 113 | 114 | statistics.LogFragmentedReceive(6, 10); 115 | 116 | Assert.AreEqual(3, statistics.MessagesReceived); 117 | Assert.AreEqual(1, statistics.UnreliableMessagesReceived); 118 | Assert.AreEqual(1, statistics.ReliableMessagesReceived); 119 | Assert.AreEqual(1, statistics.FragmentedMessagesReceived); 120 | Assert.AreEqual(0, statistics.AcknowledgementMessagesReceived); 121 | Assert.AreEqual(0, statistics.HelloMessagesReceived); 122 | 123 | Assert.AreEqual(21, statistics.DataBytesReceived); 124 | Assert.AreEqual(29, statistics.TotalBytesReceived); 125 | 126 | statistics.LogAcknowledgementReceive(4); 127 | 128 | Assert.AreEqual(4, statistics.MessagesReceived); 129 | Assert.AreEqual(1, statistics.UnreliableMessagesReceived); 130 | Assert.AreEqual(1, statistics.ReliableMessagesReceived); 131 | Assert.AreEqual(1, statistics.FragmentedMessagesReceived); 132 | Assert.AreEqual(1, statistics.AcknowledgementMessagesReceived); 133 | Assert.AreEqual(0, statistics.HelloMessagesReceived); 134 | 135 | Assert.AreEqual(21, statistics.DataBytesReceived); 136 | Assert.AreEqual(33, statistics.TotalBytesReceived); 137 | 138 | statistics.LogHelloReceive(7); 139 | 140 | Assert.AreEqual(5, statistics.MessagesReceived); 141 | Assert.AreEqual(1, statistics.UnreliableMessagesReceived); 142 | Assert.AreEqual(1, statistics.ReliableMessagesReceived); 143 | Assert.AreEqual(1, statistics.FragmentedMessagesReceived); 144 | Assert.AreEqual(1, statistics.AcknowledgementMessagesReceived); 145 | Assert.AreEqual(1, statistics.HelloMessagesReceived); 146 | 147 | Assert.AreEqual(21, statistics.DataBytesReceived); 148 | Assert.AreEqual(40, statistics.TotalBytesReceived); 149 | 150 | Assert.AreEqual(0, statistics.MessagesSent); 151 | Assert.AreEqual(0, statistics.UnreliableMessagesSent); 152 | Assert.AreEqual(0, statistics.ReliableMessagesSent); 153 | Assert.AreEqual(0, statistics.FragmentedMessagesSent); 154 | Assert.AreEqual(0, statistics.AcknowledgementMessagesSent); 155 | Assert.AreEqual(0, statistics.HelloMessagesSent); 156 | 157 | Assert.AreEqual(0, statistics.DataBytesSent); 158 | Assert.AreEqual(0, statistics.TotalBytesSent); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Hazel.UnitTests/TcpConnectionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Net; 4 | using System.Threading; 5 | 6 | using Hazel.Tcp; 7 | using System.Linq; 8 | 9 | namespace Hazel.UnitTests 10 | { 11 | [TestClass] 12 | public class TcpConnectionTests 13 | { 14 | /// 15 | /// Tests the fields on TcpConnection. 16 | /// 17 | [TestMethod] 18 | public void TcpFieldTest() 19 | { 20 | NetworkEndPoint ep = new NetworkEndPoint(IPAddress.Loopback, 4296); 21 | 22 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 23 | using (TcpConnection connection = new TcpConnection(ep)) 24 | { 25 | listener.Start(); 26 | 27 | connection.Connect(); 28 | 29 | //Connection fields 30 | Assert.AreEqual(ep, connection.EndPoint); 31 | 32 | //TcpConnection fields 33 | Assert.AreEqual(new IPEndPoint(IPAddress.Loopback, 4296), connection.RemoteEndPoint); 34 | Assert.AreEqual(1, connection.Statistics.DataBytesSent); 35 | Assert.AreEqual(0, connection.Statistics.DataBytesReceived); 36 | } 37 | } 38 | 39 | [TestMethod] 40 | public void TcpHandshakeTest() 41 | { 42 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296, IPMode.IPv4))) 43 | using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4))) 44 | { 45 | listener.Start(); 46 | 47 | listener.NewConnection += delegate (object sender, NewConnectionEventArgs e) 48 | { 49 | Assert.IsTrue(Enumerable.SequenceEqual(e.HandshakeData, new byte[] { 1, 2, 3, 4, 5, 6 })); 50 | }; 51 | 52 | connection.Connect(new byte[] { 1, 2, 3, 4, 5, 6 }); 53 | } 54 | } 55 | 56 | /// 57 | /// Tests IPv4 connectivity. 58 | /// 59 | [TestMethod] 60 | public void TcpIPv4ConnectionTest() 61 | { 62 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296, IPMode.IPv4))) 63 | using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4))) 64 | { 65 | listener.Start(); 66 | 67 | connection.Connect(); 68 | } 69 | } 70 | 71 | /// 72 | /// Tests dual mode connectivity. 73 | /// 74 | [TestMethod] 75 | public void TcpIPv6ConnectionTest() 76 | { 77 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.IPv6Any, 4296, IPMode.IPv6))) 78 | { 79 | listener.Start(); 80 | 81 | using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.IPv6Loopback, 4296, IPMode.IPv6))) 82 | { 83 | connection.Connect(); 84 | } 85 | } 86 | } 87 | 88 | /// 89 | /// Tests sending and receiving on the TcpConnection. 90 | /// 91 | [TestMethod] 92 | public void TcpServerToClientTest() 93 | { 94 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 95 | using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 96 | { 97 | TestHelper.RunServerToClientTest(listener, connection, 10, SendOption.FragmentedReliable); 98 | } 99 | } 100 | 101 | /// 102 | /// Tests sending and receiving on the TcpConnection. 103 | /// 104 | [TestMethod] 105 | public void TcpClientToServerTest() 106 | { 107 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 108 | using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 109 | { 110 | TestHelper.RunClientToServerTest(listener, connection, 10, SendOption.FragmentedReliable); 111 | } 112 | } 113 | 114 | /// 115 | /// Tests disconnection from the client. 116 | /// 117 | [TestMethod] 118 | public void ClientDisconnectTest() 119 | { 120 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 121 | using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 122 | { 123 | TestHelper.RunClientDisconnectTest(listener, connection); 124 | } 125 | } 126 | 127 | /// 128 | /// Tests disconnection from the server. 129 | /// 130 | [TestMethod] 131 | public void ServerDisconnectTest() 132 | { 133 | using (TcpConnectionListener listener = new TcpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 134 | using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 135 | { 136 | TestHelper.RunServerDisconnectTest(listener, connection); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Hazel.UnitTests/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | using Hazel; 5 | using System.Net; 6 | using System.Threading; 7 | using System.Diagnostics; 8 | 9 | namespace Hazel.UnitTests 10 | { 11 | [TestClass] 12 | public static class TestHelper 13 | { 14 | /// 15 | /// Runs a general test on the given listener and connection. 16 | /// 17 | /// The listener to test. 18 | /// The connection to test. 19 | internal static void RunServerToClientTest(ConnectionListener listener, Connection connection, int dataSize, SendOption sendOption) 20 | { 21 | //Setup meta stuff 22 | byte[] data = BuildData(dataSize); 23 | ManualResetEvent mutex = new ManualResetEvent(false); 24 | 25 | //Setup listener 26 | listener.NewConnection += delegate(object sender, NewConnectionEventArgs args) 27 | { 28 | args.Connection.SendBytes(data, sendOption); 29 | }; 30 | 31 | listener.Start(); 32 | 33 | //Setup conneciton 34 | connection.DataReceived += delegate(object sender, DataReceivedEventArgs args) 35 | { 36 | Trace.WriteLine("Data was received correctly."); 37 | 38 | Assert.AreEqual(data.Length, args.Bytes.Length); 39 | 40 | for (int i = 0; i < data.Length; i++) 41 | { 42 | Assert.AreEqual(data[i], args.Bytes[i]); 43 | } 44 | 45 | Assert.AreEqual(sendOption, args.SendOption); 46 | 47 | mutex.Set(); 48 | }; 49 | 50 | connection.Connect(); 51 | 52 | //Wait until data is received 53 | mutex.WaitOne(); 54 | } 55 | 56 | /// 57 | /// Runs a general test on the given listener and connection. 58 | /// 59 | /// The listener to test. 60 | /// The connection to test. 61 | internal static void RunClientToServerTest(ConnectionListener listener, Connection connection, int dataSize, SendOption sendOption) 62 | { 63 | //Setup meta stuff 64 | byte[] data = BuildData(dataSize); 65 | ManualResetEvent mutex = new ManualResetEvent(false); 66 | ManualResetEvent mutex2 = new ManualResetEvent(false); 67 | 68 | //Setup listener 69 | listener.NewConnection += delegate(object sender, NewConnectionEventArgs args) 70 | { 71 | args.Connection.DataReceived += delegate(object innerSender, DataReceivedEventArgs innerArgs) 72 | { 73 | Trace.WriteLine("Data was received correctly."); 74 | 75 | Assert.AreEqual(data.Length, innerArgs.Bytes.Length); 76 | 77 | for (int i = 0; i < data.Length; i++) 78 | { 79 | Assert.AreEqual(data[i], innerArgs.Bytes[i]); 80 | } 81 | 82 | Assert.AreEqual(sendOption, innerArgs.SendOption); 83 | 84 | mutex2.Set(); 85 | }; 86 | 87 | mutex.Set(); 88 | }; 89 | 90 | listener.Start(); 91 | 92 | //Connect 93 | connection.Connect(); 94 | 95 | mutex.WaitOne(); 96 | 97 | connection.SendBytes(data, sendOption); 98 | 99 | //Wait until data is received 100 | mutex2.WaitOne(); 101 | } 102 | 103 | /// 104 | /// Runs a server disconnect test on the given listener and connection. 105 | /// 106 | /// The listener to test. 107 | /// The connection to test. 108 | internal static void RunServerDisconnectTest(ConnectionListener listener, Connection connection) 109 | { 110 | ManualResetEvent mutex = new ManualResetEvent(false); 111 | 112 | connection.Disconnected += delegate(object sender, DisconnectedEventArgs args) 113 | { 114 | mutex.Set(); 115 | }; 116 | 117 | listener.NewConnection += delegate(object sender, NewConnectionEventArgs args) 118 | { 119 | args.Connection.Close(); 120 | }; 121 | 122 | listener.Start(); 123 | 124 | connection.Connect(); 125 | 126 | mutex.WaitOne(); 127 | } 128 | 129 | /// 130 | /// Runs a client disconnect test on the given listener and connection. 131 | /// 132 | /// The listener to test. 133 | /// The connection to test. 134 | internal static void RunClientDisconnectTest(ConnectionListener listener, Connection connection) 135 | { 136 | ManualResetEvent mutex = new ManualResetEvent(false); 137 | ManualResetEvent mutex2 = new ManualResetEvent(false); 138 | 139 | listener.NewConnection += delegate(object sender, NewConnectionEventArgs args) 140 | { 141 | args.Connection.Disconnected += delegate(object sender2, DisconnectedEventArgs args2) 142 | { 143 | mutex2.Set(); 144 | }; 145 | 146 | mutex.Set(); 147 | }; 148 | 149 | listener.Start(); 150 | 151 | connection.Connect(); 152 | 153 | mutex.WaitOne(); 154 | 155 | connection.Close(); 156 | 157 | mutex2.WaitOne(); 158 | } 159 | 160 | /// 161 | /// Builds new data of increaseing value bytes. 162 | /// 163 | /// The number of bytes to generate. 164 | /// The data. 165 | static byte[] BuildData(int dataSize) 166 | { 167 | byte[] data = new byte[dataSize]; 168 | for (int i = 0; i < dataSize; i++) 169 | data[i] = (byte)i; 170 | return data; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Hazel.UnitTests/UdpConnectionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Net; 4 | using System.Threading; 5 | 6 | using Hazel.Udp; 7 | using System.Linq; 8 | 9 | namespace Hazel.UnitTests 10 | { 11 | [TestClass] 12 | public class UdpConnectionTests 13 | { 14 | /// 15 | /// Tests the fields on UdpConnection. 16 | /// 17 | [TestMethod] 18 | public void UdpFieldTest() 19 | { 20 | NetworkEndPoint ep = new NetworkEndPoint(IPAddress.Loopback, 4296); 21 | 22 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 23 | using (UdpConnection connection = new UdpClientConnection(ep)) 24 | { 25 | listener.Start(); 26 | 27 | connection.Connect(); 28 | 29 | //Connection fields 30 | Assert.AreEqual(ep, connection.EndPoint); 31 | 32 | //UdpConnection fields 33 | Assert.AreEqual(new IPEndPoint(IPAddress.Loopback, 4296), connection.RemoteEndPoint); 34 | Assert.AreEqual(1, connection.Statistics.DataBytesSent); 35 | Assert.AreEqual(0, connection.Statistics.DataBytesReceived); 36 | } 37 | } 38 | 39 | [TestMethod] 40 | public void UdpHandshakeTest() 41 | { 42 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296, IPMode.IPv4))) 43 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4))) 44 | { 45 | listener.Start(); 46 | 47 | listener.NewConnection += delegate (object sender, NewConnectionEventArgs e) 48 | { 49 | Assert.IsTrue(Enumerable.SequenceEqual(e.HandshakeData, new byte[] { 1, 2, 3, 4, 5, 6 })); 50 | }; 51 | 52 | connection.Connect(new byte[] { 1, 2, 3, 4, 5, 6 }); 53 | } 54 | } 55 | 56 | /// 57 | /// Tests IPv4 connectivity. 58 | /// 59 | [TestMethod] 60 | public void UdpIPv4ConnectionTest() 61 | { 62 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296, IPMode.IPv4))) 63 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4))) 64 | { 65 | listener.Start(); 66 | 67 | connection.Connect(); 68 | } 69 | } 70 | 71 | /// 72 | /// Tests dual mode connectivity. 73 | /// 74 | [TestMethod] 75 | public void UdpIPv6ConnectionTest() 76 | { 77 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.IPv6Any, 4296, IPMode.IPv6))) 78 | { 79 | listener.Start(); 80 | 81 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.IPv6Loopback, 4296, IPMode.IPv6))) 82 | { 83 | connection.Connect(); 84 | } 85 | } 86 | } 87 | 88 | /// 89 | /// Tests server to client unreliable communication on the UdpConnection. 90 | /// 91 | [TestMethod] 92 | public void UdpUnreliableServerToClientTest() 93 | { 94 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 95 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 96 | { 97 | TestHelper.RunServerToClientTest(listener, connection, 10, SendOption.None); 98 | } 99 | } 100 | 101 | /// 102 | /// Tests server to client reliable communication on the UdpConnection. 103 | /// 104 | [TestMethod] 105 | public void UdpReliableServerToClientTest() 106 | { 107 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 108 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 109 | { 110 | TestHelper.RunServerToClientTest(listener, connection, 10, SendOption.Reliable); 111 | } 112 | } 113 | 114 | /// 115 | /// Tests server to client reliable communication on the UdpConnection. 116 | /// 117 | [TestMethod] 118 | public void UdpFragmentedServerToClientTest() 119 | { 120 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 121 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 122 | { 123 | TestHelper.RunServerToClientTest(listener, connection, (int)(connection.FragmentSize * 9.5), SendOption.FragmentedReliable); 124 | } 125 | } 126 | 127 | /// 128 | /// Tests server to client unreliable communication on the UdpConnection. 129 | /// 130 | [TestMethod] 131 | public void UdpUnreliableClientToServerTest() 132 | { 133 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 134 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 135 | { 136 | TestHelper.RunClientToServerTest(listener, connection, 10, SendOption.None); 137 | } 138 | } 139 | 140 | /// 141 | /// Tests server to client reliable communication on the UdpConnection. 142 | /// 143 | [TestMethod] 144 | public void UdpReliableClientToServerTest() 145 | { 146 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 147 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 148 | { 149 | TestHelper.RunClientToServerTest(listener, connection, 10, SendOption.Reliable); 150 | } 151 | } 152 | 153 | /// 154 | /// Tests server to client reliable communication on the UdpConnection. 155 | /// 156 | [TestMethod] 157 | public void UdpFragmentedClientToServerTest() 158 | { 159 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 160 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 161 | { 162 | TestHelper.RunClientToServerTest(listener, connection, (int)(connection.FragmentSize * 9.5), SendOption.FragmentedReliable); 163 | } 164 | } 165 | 166 | /// 167 | /// Tests the keepalive functionality from the client, 168 | /// 169 | [TestMethod] 170 | public void KeepAliveClientTest() 171 | { 172 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 173 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 174 | { 175 | listener.Start(); 176 | 177 | connection.Connect(); 178 | connection.KeepAliveInterval = 100; 179 | 180 | System.Threading.Thread.Sleep(1050); //Enough time for ~10 keep alive packets 181 | 182 | Assert.IsTrue( 183 | connection.Statistics.TotalBytesSent >= 30 && 184 | connection.Statistics.TotalBytesSent <= 50, 185 | "Sent: " + connection.Statistics.TotalBytesSent 186 | ); 187 | } 188 | } 189 | 190 | /// 191 | /// Tests the keepalive functionality from the client, 192 | /// 193 | [TestMethod] 194 | public void KeepAliveServerTest() 195 | { 196 | ManualResetEvent mutex = new ManualResetEvent(false); 197 | 198 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 199 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 200 | { 201 | listener.NewConnection += delegate(object sender, NewConnectionEventArgs args) 202 | { 203 | ((UdpConnection)args.Connection).KeepAliveInterval = 100; 204 | 205 | Thread.Sleep(1050); //Enough time for ~10 keep alive packets 206 | 207 | Assert.IsTrue( 208 | args.Connection.Statistics.TotalBytesSent >= 30 && 209 | args.Connection.Statistics.TotalBytesSent <= 50, 210 | "Sent: " + args.Connection.Statistics.TotalBytesSent 211 | ); 212 | 213 | mutex.Set(); 214 | }; 215 | 216 | listener.Start(); 217 | 218 | connection.Connect(); 219 | 220 | mutex.WaitOne(); 221 | } 222 | } 223 | 224 | /// 225 | /// Tests disconnection from the client. 226 | /// 227 | [TestMethod] 228 | public void ClientDisconnectTest() 229 | { 230 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 231 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 232 | { 233 | TestHelper.RunClientDisconnectTest(listener, connection); 234 | } 235 | } 236 | 237 | /// 238 | /// Tests disconnection from the server. 239 | /// 240 | [TestMethod] 241 | public void ServerDisconnectTest() 242 | { 243 | using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296))) 244 | using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296))) 245 | { 246 | TestHelper.RunServerDisconnectTest(listener, connection); 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /Hazel.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hazel", "Hazel\Hazel.csproj", "{02CFBD30-D77D-400F-94B2-700F60EFDD7F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hazel.UnitTests", "Hazel.UnitTests\Hazel.UnitTests.csproj", "{1394E4CA-E17A-42F5-9216-8046ACA8D16B}" 9 | EndProject 10 | Project("{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}") = "Hazel.Documentation", "Hazel.Documentation\Hazel.Documentation.shfbproj", "{B90BB577-2080-4C22-AE86-FB0CCFB40920}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {1394E4CA-E17A-42F5-9216-8046ACA8D16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {1394E4CA-E17A-42F5-9216-8046ACA8D16B}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {1394E4CA-E17A-42F5-9216-8046ACA8D16B}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {1394E4CA-E17A-42F5-9216-8046ACA8D16B}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {B90BB577-2080-4C22-AE86-FB0CCFB40920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {B90BB577-2080-4C22-AE86-FB0CCFB40920}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {B90BB577-2080-4C22-AE86-FB0CCFB40920}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /Hazel/Connection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net.Sockets; 6 | using System.Net; 7 | using System.Threading; 8 | 9 | namespace Hazel 10 | { 11 | /// 12 | /// Base class for all connections. 13 | /// 14 | /// 15 | /// 16 | /// Connection is the base class for all connections that Hazel can make. It provides common functionality and a 17 | /// standard interface to allow connections to be swapped easily. 18 | /// 19 | /// 20 | /// Any class inheriting from Connection should provide the 3 standard guarantees that Hazel provides: 21 | /// 22 | /// 23 | /// Thread Safe 24 | /// 25 | /// 26 | /// Connection Orientated 27 | /// 28 | /// 29 | /// Packet/Message Based 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | public abstract class Connection : IDisposable 36 | { 37 | /// 38 | /// Called when a message has been received. 39 | /// 40 | /// 41 | /// 42 | /// DataReceived is invoked everytime a message is received from the end point of this connection, the message 43 | /// that was received can be found in the alongside other information from the 44 | /// event. 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | /// 51 | public event EventHandler DataReceived; 52 | 53 | /// 54 | /// Called when the end point disconnects or an error occurs. 55 | /// 56 | /// 57 | /// 58 | /// Disconnected is invoked when the connection is closed due to an exception occuring or because the remote 59 | /// end point disconnected. If it was invoked due to an exception occuring then the exception is available 60 | /// in the passed with the event. 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// 66 | /// 67 | public event EventHandler Disconnected; 68 | 69 | /// 70 | /// The remote end point of this Connection. 71 | /// 72 | /// 73 | /// This is the end point that this connection is connected to (i.e. the other device). This returns an abstract 74 | /// which can then be cast to an appropriate end point depending on the 75 | /// connection type. 76 | /// 77 | public ConnectionEndPoint EndPoint { get; protected set; } 78 | 79 | /// 80 | /// The traffic statistics about this Connection. 81 | /// 82 | /// 83 | /// Contains statistics about the number of messages and bytes sent and received by this connection. 84 | /// 85 | public ConnectionStatistics Statistics { get; protected set; } 86 | 87 | /// 88 | /// The state of this connection. 89 | /// 90 | /// 91 | /// 92 | /// Connections go round 4 states in their lifetime: they start as to 93 | /// indicate they have no endpoint, calling takes them into 94 | /// , once they have received confirmation they are connected they enter 95 | /// and finally calling sets them to 96 | /// and then the sequence repeats back to 97 | /// once disconnection is complete. 98 | /// 99 | /// 100 | /// Data can only be sent while in and all attempts to send data when 101 | /// in any other state will throw an InvalidOperationException. 102 | /// 103 | /// 104 | /// All implementers should be aware that when this is set to it will 105 | /// release all threads that are blocked on . 106 | /// 107 | /// 108 | public ConnectionState State 109 | { 110 | get 111 | { 112 | return state; 113 | } 114 | 115 | protected set 116 | { 117 | state = value; 118 | 119 | if (state == ConnectionState.Connected) 120 | connectWaitLock.Set(); 121 | else 122 | connectWaitLock.Reset(); 123 | } 124 | } 125 | volatile ConnectionState state; 126 | 127 | /// 128 | /// Reset event that is triggered when the connection is marked Connected. 129 | /// 130 | ManualResetEvent connectWaitLock = new ManualResetEvent(false); 131 | 132 | /// 133 | /// Constructor that initializes the ConnecitonStatistics object. 134 | /// 135 | /// 136 | /// This constructor initialises with empty statistics and sets to 137 | /// . 138 | /// 139 | protected Connection() 140 | { 141 | Statistics = new ConnectionStatistics(); 142 | 143 | State = ConnectionState.NotConnected; 144 | } 145 | 146 | /// 147 | /// Sends a number of bytes to the end point of the connection using the specified . 148 | /// 149 | /// The bytes of the message to send. 150 | /// The option specifying how the message should be sent. 151 | /// 152 | /// 153 | /// 154 | /// The sendOptions parameter is only a request to use those options and the actual method used to send the 155 | /// data is up to the implementation. There are circumstances where this parameter may be ignored but in 156 | /// general any implementer should aim to always follow the user's request. 157 | /// 158 | /// 159 | public abstract void SendBytes(byte[] bytes, SendOption sendOption = SendOption.None); 160 | 161 | /// 162 | /// Connects the connection to a server and begins listening. 163 | /// 164 | /// The bytes of data to send in the handshake. 165 | /// The number of milliseconds to wait before giving up on the connect attempt. 166 | /// 167 | /// Calling Connect makes the connection attempt to connect to the end point that's specified in the 168 | /// constructor. This method will block until the connection attempt completes and will throw a 169 | /// if there is a problem connecting. 170 | /// 171 | public abstract void Connect(byte[] bytes = null, int timeout = 5000); 172 | 173 | /// 174 | /// Invokes the DataReceived event. 175 | /// 176 | /// The bytes received. 177 | /// The the message was received with. 178 | /// 179 | /// Invokes the event on this connection to alert subscribers a new message has been 180 | /// received. The bytes and the send option that the message was sent with should be passed in to give to the 181 | /// subscribers. 182 | /// 183 | protected void InvokeDataReceived(byte[] bytes, SendOption sendOption) 184 | { 185 | DataReceivedEventArgs args = DataReceivedEventArgs.GetObject(); 186 | args.Set(bytes, sendOption); 187 | 188 | //Make a copy to avoid race condition between null check and invocation 189 | EventHandler handler = DataReceived; 190 | if (handler != null) 191 | handler(this, args); 192 | } 193 | 194 | /// 195 | /// Invokes the Disconnected event. 196 | /// 197 | /// The exception, if any, that occured to cause this. 198 | /// 199 | /// Invokes the event to alert subscribres this connection has been disconnected either 200 | /// by the end point or because an error occured. If an error occured the error should be passed in in order to 201 | /// pass to the subscribers, otherwise null can be passed in. 202 | /// 203 | protected void InvokeDisconnected(Exception e = null) 204 | { 205 | DisconnectedEventArgs args = DisconnectedEventArgs.GetObject(); 206 | args.Set(e); 207 | 208 | //Make a copy to avoid race condition between null check and invocation 209 | EventHandler handler = Disconnected; 210 | if (handler != null) 211 | handler(this, args); 212 | } 213 | 214 | /// 215 | /// Blocks until the Connection is connected. 216 | /// 217 | /// The number of milliseconds to wait before timing out. 218 | /// 219 | /// This is a helper method for waiting until the connection is connected. It will block until the 220 | /// property is set to allowing the main thread to 221 | /// wait until specific data is received etc. before returning to the user's code. 222 | /// 223 | protected bool WaitOnConnect(int timeout) 224 | { 225 | return connectWaitLock.WaitOne(timeout); 226 | } 227 | 228 | /// 229 | /// Closes this connection safely. 230 | /// 231 | /// 232 | /// 233 | /// Informs the end point of the connection that we are disconnecting from them and disposes of this 234 | /// connection. 235 | /// 236 | /// 237 | /// This calls and therefore sets straight to 238 | /// . Once you call Close you will not be able to send any more 239 | /// data using this connection and no more data will be received. 240 | /// 241 | /// 242 | public virtual void Close() 243 | { 244 | Dispose(); 245 | } 246 | 247 | /// 248 | /// Disposes of this NetworkConnection. 249 | /// 250 | public void Dispose() 251 | { 252 | Dispose(true); 253 | GC.SuppressFinalize(this); 254 | } 255 | 256 | /// 257 | /// Disposes of this NetworkConnection. 258 | /// 259 | /// Are we currently disposing? 260 | protected virtual void Dispose(bool disposing) 261 | { 262 | if (disposing) 263 | { 264 | } 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /Hazel/ConnectionEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel 7 | { 8 | /// 9 | /// Base class for all end points of connections. 10 | /// 11 | /// 12 | public abstract class ConnectionEndPoint 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Hazel/ConnectionListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | 8 | namespace Hazel 9 | { 10 | /// 11 | /// Base class for all connection listeners. 12 | /// 13 | /// 14 | /// 15 | /// ConnectionListeners are server side objects that listen for clients and create matching server side connections 16 | /// for each client in a similar way to TCP does. These connections should already have a 17 | /// State of and so should be ready for 18 | /// comunication immediately. 19 | /// 20 | /// 21 | /// Each time a client connects the event will be invoked to alert all subscribers to 22 | /// the new connection. A disconnected event is then present on the that is passed to the 23 | /// subscribers. 24 | /// 25 | /// 26 | /// 27 | public abstract class ConnectionListener : IDisposable 28 | { 29 | /// 30 | /// Invoked when a new client connects. 31 | /// 32 | /// 33 | /// 34 | /// NewConnection is invoked each time a client connects to the listener. The 35 | /// contains the new for communication with this 36 | /// client. 37 | /// 38 | /// 39 | /// Hazel doesn't store connections so it is your responsibility to keep track of the connections to your 40 | /// server. Note that as implements if you are not storing 41 | /// a connection then as a bare minimum you should call here in order to 42 | /// release the connection correctly. 43 | /// 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | public event EventHandler NewConnection; 50 | 51 | /// 52 | /// Makes this connection listener begin listening for connections. 53 | /// 54 | /// 55 | /// 56 | /// This instructs the listener to begin listening for new clients connecting to the server. When a new client 57 | /// connects the event will be invoked containing the connection to the new client. 58 | /// 59 | /// 60 | /// To stop listening you should call . 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// 66 | public abstract void Start(); 67 | 68 | /// 69 | /// Invokes the NewConnection event with the supplied connection. 70 | /// 71 | /// The user sent bytes that were received as part of the handshake. 72 | /// The connection to pass in the arguments. 73 | /// 74 | /// Implementers should call this to invoke the event before data is received so that 75 | /// subscribers do not miss any data that may have been sent immediately after connecting. 76 | /// 77 | protected void InvokeNewConnection(byte[] bytes, Connection connection) 78 | { 79 | //Get new args 80 | NewConnectionEventArgs args = NewConnectionEventArgs.GetObject(); 81 | args.Set(bytes, connection); 82 | 83 | //Make a copy to avoid race condition between null check and invocation 84 | EventHandler handler = NewConnection; 85 | if (handler != null) 86 | handler(this, args); 87 | } 88 | 89 | /// 90 | /// Closes the connection listener safely. 91 | /// 92 | /// 93 | /// Internally this simply calls Dispose therefore trying to reuse the ConnectionListener after calling Close will 94 | /// cause ObjectDisposedExceptions. 95 | /// 96 | public virtual void Close() 97 | { 98 | Dispose(); 99 | } 100 | 101 | /// 102 | /// Call to dispose of the connection listener. 103 | /// 104 | public void Dispose() 105 | { 106 | Dispose(true); 107 | } 108 | 109 | /// 110 | /// Called when the object is being disposed. 111 | /// 112 | /// Are we disposing? 113 | protected virtual void Dispose(bool disposing) 114 | { 115 | 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Hazel/ConnectionState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel 7 | { 8 | /// 9 | /// Represents the state a is currently in. 10 | /// 11 | public enum ConnectionState 12 | { 13 | /// 14 | /// The Connection has either not been established yet or has been disconnected. 15 | /// 16 | NotConnected, 17 | 18 | /// 19 | /// The Connection is currently connecting to an endpoint. 20 | /// 21 | Connecting, 22 | 23 | /// 24 | /// The Connection is connected and data can be transfered. 25 | /// 26 | Connected, 27 | 28 | /// 29 | /// The Connection is currently disconnecting. 30 | /// 31 | Disconnecting 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Hazel/DataReceivedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel 7 | { 8 | /// 9 | /// Event arguments for the event. 10 | /// 11 | /// 12 | /// 13 | /// This contains information about messages received by a connection and is passed to subscribers of the 14 | /// DataEvent. 15 | /// 16 | /// 17 | /// 18 | /// 19 | public class DataReceivedEventArgs : EventArgs, IRecyclable 20 | { 21 | /// 22 | /// Object pool for this event. 23 | /// 24 | static readonly ObjectPool objectPool = new ObjectPool(() => new DataReceivedEventArgs()); 25 | 26 | /// 27 | /// Returns an instance of this object from the pool. 28 | /// 29 | /// A new or recycled DataEventArgs object. 30 | internal static DataReceivedEventArgs GetObject() 31 | { 32 | return objectPool.GetObject(); 33 | } 34 | 35 | /// 36 | /// The bytes received from the client. 37 | /// 38 | public byte[] Bytes { get; private set; } 39 | 40 | /// 41 | /// The the data was sent with. 42 | /// 43 | public SendOption SendOption { get; private set; } 44 | 45 | /// 46 | /// Private constructor for object pool. 47 | /// 48 | DataReceivedEventArgs() 49 | { 50 | 51 | } 52 | 53 | /// 54 | /// Sets the members of the arguments. 55 | /// 56 | /// The bytes received. 57 | /// The send option used to send the data. 58 | internal void Set(byte[] bytes, SendOption sendOption) 59 | { 60 | this.Bytes = bytes; 61 | this.SendOption = sendOption; 62 | } 63 | 64 | /// 65 | public void Recycle() 66 | { 67 | objectPool.PutObject(this); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Hazel/DisconnectedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel 7 | { 8 | /// 9 | /// Event arguments for the event. 10 | /// 11 | /// 12 | /// 13 | /// This contains information about the cause of a disconnection and is passed to subscribers of the 14 | /// event. 15 | /// 16 | /// 17 | /// 18 | /// 19 | public class DisconnectedEventArgs : EventArgs, IRecyclable 20 | { 21 | /// 22 | /// Object pool for this event. 23 | /// 24 | static readonly ObjectPool objectPool = new ObjectPool(() => new DisconnectedEventArgs()); 25 | 26 | /// 27 | /// Returns an instance of this object from the pool. 28 | /// 29 | /// A new or recycled DisconnectedEventArgs object. 30 | internal static DisconnectedEventArgs GetObject() 31 | { 32 | return objectPool.GetObject(); 33 | } 34 | 35 | /// 36 | /// The exception, if any, that caused the disconnect. 37 | /// 38 | /// 39 | /// If the disconnection was caused because of an exception occuring (for exemple a 40 | /// on network based connections) this will contain the error 41 | /// that caused it or a with the details of the exception, if the disconnection 42 | /// wasn't caused by an error then this will contain null. 43 | /// 44 | public Exception Exception { get; private set; } 45 | 46 | /// 47 | /// Private constructor for object pool. 48 | /// 49 | DisconnectedEventArgs() 50 | { 51 | 52 | } 53 | 54 | /// 55 | /// Sets the given exception for the arguments. 56 | /// 57 | /// The exception if the cause. 58 | internal void Set(Exception e) 59 | { 60 | this.Exception = e; 61 | } 62 | 63 | /// 64 | public void Recycle() 65 | { 66 | objectPool.PutObject(this); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Hazel/Hazel.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {02CFBD30-D77D-400F-94B2-700F60EFDD7F} 8 | Library 9 | Properties 10 | Hazel 11 | Hazel 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | bin\Release\Hazel.XML 35 | 36 | 37 | true 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Code 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /Hazel/Hazel.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DarkRiftNetworking.Hazel 5 | $version$ 6 | $title$ 7 | DarkRiftNetworking 8 | DarkRiftNetworking 9 | https://github.com/DarkRiftNetworking/Hazel-Networking/blob/master/LICENSE 10 | https://github.com/DarkRiftNetworking/Hazel-Networking 11 | false 12 | Hazel Networking is a low-level networking library for C# providing connection orientated, message based communication via TCP, UDP and RUDP. 13 | First release (Beta) 14 | Copyright 2016 15 | network networking TCP UDP RUDP socket sockets Unity3d Unity 16 | 17 | -------------------------------------------------------------------------------- /Hazel/HazelException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel 7 | { 8 | /// 9 | /// Wrapper for exceptions thrown from Hazel. 10 | /// 11 | [Serializable] 12 | public class HazelException : Exception 13 | { 14 | internal HazelException(string msg) : base (msg) 15 | { 16 | 17 | } 18 | 19 | internal HazelException(string msg, Exception e) : base (msg, e) 20 | { 21 | 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Hazel/IPMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | 7 | namespace Hazel 8 | { 9 | /// 10 | /// Represents the IP version that a connection or listener will use. 11 | /// 12 | /// 13 | /// If you wand a client to connect or be able to connect using IPv6 then you should use , 14 | /// this sets the underlying sockets to use IPv6 but still allow IPv4 sockets to connect for backwards compatability 15 | /// and hence it is the default IPMode in most cases. 16 | /// 17 | public enum IPMode 18 | { 19 | /// 20 | /// Instruction to use IPv4 only, IPv6 connections will not be able to connect. 21 | /// 22 | IPv4, 23 | 24 | /// 25 | /// Instruction to use IPv6 only, IPv4 connections will not be able to connect. IPv4 addresses can be connected 26 | /// by converting to IPv6 addresses. 27 | /// 28 | IPv6 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Hazel/IRecyclable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel 7 | { 8 | /// 9 | /// Interface for all items that can be returned to an object pool. 10 | /// 11 | /// 12 | interface IRecyclable 13 | { 14 | /// 15 | /// Returns this object back to the object pool. 16 | /// 17 | /// 18 | /// 19 | /// Calling this when you are done with the object returns the object back to a pool in order to be reused. 20 | /// This can reduce the amount of work the GC has to do dramatically but it is optional to call this. 21 | /// 22 | /// 23 | /// Calling this indicates to Hazel that this can be reused and thus you should only call this when you are 24 | /// completely finished with the object as the contents can be overwritten at any point after. 25 | /// 26 | /// 27 | void Recycle(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Hazel/NetworkConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | 8 | namespace Hazel 9 | { 10 | /// 11 | /// Abstract base class for a to a remote end point via a network protocol like TCP or UDP. 12 | /// 13 | /// 14 | public abstract class NetworkConnection : Connection 15 | { 16 | /// 17 | /// The remote end point of this connection. 18 | /// 19 | /// 20 | /// This is the end point of the other device given as an rather than a generic 21 | /// as the base does. 22 | /// 23 | public EndPoint RemoteEndPoint { get; protected set; } 24 | 25 | /// 26 | /// The IPMode the client is connected using. 27 | /// 28 | public IPMode IPMode { get; protected set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Hazel/NetworkConnectionListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | 8 | namespace Hazel 9 | { 10 | /// 11 | /// Abstract base class for a for network based connections. 12 | /// 13 | /// 14 | public abstract class NetworkConnectionListener : ConnectionListener 15 | { 16 | /// 17 | /// The local end point the listener is listening for new clients on. 18 | /// 19 | public EndPoint EndPoint { get; protected set; } 20 | 21 | /// 22 | /// The IPMode the listener is listening for new clients on. 23 | /// 24 | public IPMode IPMode { get; protected set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Hazel/NetworkEndPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | 7 | using System.Net; 8 | 9 | namespace Hazel 10 | { 11 | /// 12 | /// Represents an endpoint to a remote resource on a network. 13 | /// 14 | /// 15 | /// This wraps a for connecting across a network using protocols like TCP or UDP. 16 | /// 17 | /// 18 | public sealed class NetworkEndPoint : ConnectionEndPoint 19 | { 20 | /// 21 | /// The EndPoint this points to. 22 | /// 23 | public EndPoint EndPoint { get; set; } 24 | 25 | /// 26 | /// The this will instruct connections to use. 27 | /// 28 | public IPMode IPMode { get; set; } 29 | 30 | /// 31 | /// Creates a NetworkEndPoint from a given EndPoint. 32 | /// 33 | /// The end point to wrap. 34 | /// The IP mode to use. 35 | public NetworkEndPoint(EndPoint endPoint, IPMode mode = IPMode.IPv4) 36 | { 37 | this.EndPoint = endPoint; 38 | this.IPMode = mode; 39 | } 40 | 41 | /// 42 | /// Create a NetworkEndPoint to the specified IPAddress and port. 43 | /// 44 | /// The IP address of the server. 45 | /// The port the server is listening on. 46 | /// The IP mode to use. 47 | /// 48 | /// When using this constructor will contain an . 49 | /// 50 | public NetworkEndPoint(IPAddress address, int port, IPMode mode = IPMode.IPv4) 51 | : this(new IPEndPoint(address, port), mode) 52 | { 53 | 54 | } 55 | 56 | /// 57 | /// Creates a NetworkEndPoint to the specified IP address and port. 58 | /// 59 | /// A valid IP address of the server. 60 | /// The port the server is listening on. 61 | /// The IP mode to use. 62 | /// 63 | /// When using this constructor will contain an . 64 | /// 65 | public NetworkEndPoint(string IP, int port, IPMode mode = IPMode.IPv4) 66 | : this(IPAddress.Parse(IP), port, mode) 67 | { 68 | 69 | } 70 | 71 | /// 72 | public override string ToString() 73 | { 74 | return EndPoint.ToString(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Hazel/NewConnectionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel 7 | { 8 | /// 9 | /// Event arguments for the event. 10 | /// 11 | /// 12 | /// 13 | /// This contains the new connection for the client that connection and is passed to subscribers of the 14 | /// event. 15 | /// 16 | /// 17 | /// 18 | /// 19 | public class NewConnectionEventArgs : EventArgs, IRecyclable 20 | { 21 | /// 22 | /// Object pool for this event. 23 | /// 24 | static readonly ObjectPool objectPool = new ObjectPool(() => new NewConnectionEventArgs()); 25 | 26 | /// 27 | /// Returns an instance of this object from the pool. 28 | /// 29 | /// A new or recycled NewConnectionEventArgs object. 30 | internal static NewConnectionEventArgs GetObject() 31 | { 32 | return objectPool.GetObject(); 33 | } 34 | 35 | /// 36 | /// The data received from the client in the handshake. 37 | /// 38 | public byte[] HandshakeData { get; private set; } 39 | 40 | /// 41 | /// The to the new client. 42 | /// 43 | public Connection Connection { get; private set; } 44 | 45 | /// 46 | /// Private constructor for object pool. 47 | /// 48 | NewConnectionEventArgs() 49 | { 50 | 51 | } 52 | 53 | /// 54 | /// Sets the members of the arguments. 55 | /// 56 | /// The bytes that were received in the handshake. 57 | /// The new connection 58 | internal void Set(byte[] bytes, Connection connection) 59 | { 60 | this.HandshakeData = bytes; 61 | this.Connection = connection; 62 | } 63 | 64 | /// 65 | public void Recycle() 66 | { 67 | objectPool.PutObject(this); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Hazel/ObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if NET_45 3 | using System.Collections.Concurrent; 4 | #else 5 | using System.Collections.Generic; 6 | #endif 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace Hazel 11 | { 12 | /// 13 | /// A fairly simple object pool for items that will be created a lot. 14 | /// 15 | /// The type that is pooled. 16 | /// 17 | sealed class ObjectPool where T : IRecyclable 18 | { 19 | /// 20 | /// Our pool of objects 21 | /// 22 | #if NET_45 23 | ConcurrentBag pool = new ConcurrentBag(); 24 | #else 25 | Queue pool = new Queue(); 26 | #endif 27 | 28 | /// 29 | /// The generator for creating new objects. 30 | /// 31 | /// 32 | Func objectFactory; 33 | 34 | /// 35 | /// Internal constructor for our ObjectPool. 36 | /// 37 | internal ObjectPool(Func objectFactory) 38 | { 39 | this.objectFactory = objectFactory; 40 | } 41 | 42 | /// 43 | /// Returns a pooled object of type T, if none are available another is created. 44 | /// 45 | /// An instance of T. 46 | internal T GetObject() 47 | { 48 | #if NET_45 49 | T item; 50 | if (pool.TryTake(out item)) 51 | return item; 52 | #else 53 | lock (pool) 54 | { 55 | if (pool.Count > 0) 56 | return pool.Dequeue(); 57 | } 58 | #endif 59 | return objectFactory.Invoke(); 60 | } 61 | 62 | /// 63 | /// Returns an object to the pool. 64 | /// 65 | /// The item to return. 66 | internal void PutObject(T item) 67 | { 68 | #if NET_45 69 | pool.Add(item); 70 | #else 71 | lock (pool) 72 | pool.Enqueue(item); 73 | #endif 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Hazel/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Hazel")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Hazel")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f3935f38-a904-40c7-ab9b-8d01aefe0489")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | 38 | // NuGet version information 39 | [assembly: AssemblyInformationalVersion("0.1.2-beta")] 40 | 41 | // Show internals to unit testing assembly so it can test 42 | [assembly:InternalsVisibleTo("Hazel.UnitTests")] -------------------------------------------------------------------------------- /Hazel/SendOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel 7 | { 8 | /// 9 | /// Specifies how a message should be sent between connections. 10 | /// 11 | [Flags] 12 | public enum SendOption : byte 13 | { 14 | /// 15 | /// Requests unreliable delivery with no framentation. 16 | /// 17 | /// 18 | /// Sending data using unreliable delivery means that data is not guaranteed to arrive at it's destination nor is 19 | /// it guarenteed to arrive only once. However, unreliable delivery can be faster than other methods and it 20 | /// typically requires a smaller number of protocol bytes than other methods. There is also typically less 21 | /// processing involved and less memory needed as packets are not stored once sent. 22 | /// 23 | None = 0, 24 | 25 | /// 26 | /// Requests data be sent reliably but with no fragmentation. 27 | /// 28 | /// 29 | /// Sending data reliably means that data is guarenteed to arrive and to arrive only once. Reliable delivery 30 | /// typically requires more processing, more memory (as packets need to be stored in case they need resending), 31 | /// a larger number of protocol bytes and can be slower than unreliable delivery. 32 | /// 33 | Reliable = 1, 34 | 35 | /// 36 | /// Requests data be sent so that large messages are fragmented into smaller chunks of 37 | /// data and reassembled when received. 38 | /// 39 | /// 40 | /// Fragmented messages allow large amounts of data to be transmitted in smaller chunks when using connections 41 | /// that do not support the transmission of large messages. By specifying reliable delivery messages are 42 | /// guaranteed to arrive and to arrive only once but the sending process may require more memory, processing, 43 | /// a larger number protocol bytes and may be slower than sending unreliably. 44 | /// 45 | FragmentedReliable = 2 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Hazel/Tcp/StateObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel.Tcp 7 | { 8 | /// 9 | /// Represents the state of the current receive operation for TCP connections. 10 | /// 11 | struct StateObject 12 | { 13 | /// 14 | /// The buffer we're receiving. 15 | /// 16 | internal byte[] buffer; 17 | 18 | /// 19 | /// The total number of bytes received so far. 20 | /// 21 | internal int totalBytesReceived; 22 | 23 | /// 24 | /// The callback to invoke once the buffer has been filled. 25 | /// 26 | internal Action callback; 27 | 28 | /// 29 | /// Creates a StateObject with the specified length. 30 | /// 31 | /// The number of bytes expected to be received. 32 | /// The callback to invoke once data has been received. 33 | internal StateObject(int length, Action callback) 34 | { 35 | this.buffer = new byte[length]; 36 | this.totalBytesReceived = 0; 37 | this.callback = callback; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Hazel/Tcp/TcpConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | namespace Hazel.Tcp 9 | { 10 | /// 11 | /// Represents a connection that uses the TCP protocol. 12 | /// 13 | /// 14 | public sealed class TcpConnection : NetworkConnection 15 | { 16 | /// 17 | /// The socket we're managing. 18 | /// 19 | Socket socket; 20 | 21 | /// 22 | /// Lock for the socket. 23 | /// 24 | Object socketLock = new Object(); 25 | 26 | /// 27 | /// Creates a TcpConnection from a given TCP Socket. 28 | /// 29 | /// The TCP socket to wrap. 30 | internal TcpConnection(Socket socket) 31 | { 32 | //Check it's a TCP socket 33 | if (socket.ProtocolType != System.Net.Sockets.ProtocolType.Tcp) 34 | throw new ArgumentException("A TcpConnection requires a TCP socket."); 35 | 36 | lock (this.socketLock) 37 | { 38 | this.EndPoint = new NetworkEndPoint(socket.RemoteEndPoint); 39 | this.RemoteEndPoint = socket.RemoteEndPoint; 40 | 41 | this.socket = socket; 42 | this.socket.NoDelay = true; 43 | 44 | State = ConnectionState.Connected; 45 | } 46 | } 47 | 48 | /// 49 | /// Creates a new TCP connection. 50 | /// 51 | /// A to connect to. 52 | public TcpConnection(NetworkEndPoint remoteEndPoint) 53 | { 54 | lock (socketLock) 55 | { 56 | if (State != ConnectionState.NotConnected) 57 | throw new InvalidOperationException("Cannot connect as the Connection is already connected."); 58 | 59 | this.EndPoint = remoteEndPoint; 60 | this.RemoteEndPoint = remoteEndPoint.EndPoint; 61 | this.IPMode = remoteEndPoint.IPMode; 62 | 63 | //Create a socket 64 | if (remoteEndPoint.IPMode == IPMode.IPv4) 65 | socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); 66 | else 67 | { 68 | if (!Socket.OSSupportsIPv6) 69 | throw new HazelException("IPV6 not supported!"); 70 | 71 | socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); 72 | socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); 73 | } 74 | 75 | socket.NoDelay = true; 76 | } 77 | } 78 | 79 | /// 80 | public override void Connect(byte[] bytes = null, int timeout = 5000) 81 | { 82 | lock(socketLock) 83 | { 84 | //Connect 85 | State = ConnectionState.Connecting; 86 | 87 | try 88 | { 89 | IAsyncResult result = socket.BeginConnect(RemoteEndPoint, null, null); 90 | 91 | result.AsyncWaitHandle.WaitOne(timeout); 92 | 93 | socket.EndConnect(result); 94 | } 95 | catch (Exception e) 96 | { 97 | throw new HazelException("Could not connect as an exception occured.", e); 98 | } 99 | 100 | //Start receiving data 101 | try 102 | { 103 | StartWaitingForHeader(BodyReadCallback); 104 | } 105 | catch (Exception e) 106 | { 107 | throw new HazelException("An exception occured while initiating the first receive operation.", e); 108 | } 109 | 110 | //Send handshake 111 | byte[] actualBytes; 112 | if (bytes == null) 113 | { 114 | actualBytes = new byte[1]; 115 | } 116 | else 117 | { 118 | actualBytes = new byte[bytes.Length + 1]; 119 | Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length); 120 | } 121 | 122 | //Set connected 123 | State = ConnectionState.Connected; 124 | 125 | SendBytes(actualBytes); 126 | } 127 | } 128 | 129 | /// 130 | /// 131 | /// 132 | /// 133 | /// The sendOption parameter is ignored by the TcpConnection as TCP only supports FragmentedReliable 134 | /// communication, specifying anything else will have no effect. 135 | /// 136 | /// 137 | public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.FragmentedReliable) 138 | { 139 | //Get bytes for length 140 | byte[] fullBytes = AppendLengthHeader(bytes); 141 | 142 | //Write the bytes to the socket 143 | lock (socketLock) 144 | { 145 | if (State != ConnectionState.Connected) 146 | throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); 147 | 148 | try 149 | { 150 | socket.BeginSend(fullBytes, 0, fullBytes.Length, SocketFlags.None, null, null); 151 | } 152 | catch (Exception e) 153 | { 154 | HazelException he = new HazelException("Could not send data as an occured.", e); 155 | HandleDisconnect(he); 156 | throw he; 157 | } 158 | } 159 | 160 | Statistics.LogFragmentedSend(bytes.Length, fullBytes.Length); 161 | } 162 | 163 | /// 164 | /// Called when a 4 byte header has been received. 165 | /// 166 | /// The 4 header bytes read. 167 | /// The callback to invoke when the body has been received. 168 | void HeaderReadCallback(byte[] bytes, Action callback) 169 | { 170 | //Get length 171 | int length = GetLengthFromBytes(bytes); 172 | 173 | //Begin receiving the body 174 | try 175 | { 176 | StartWaitingForBytes(length, callback); 177 | } 178 | catch (Exception e) 179 | { 180 | HandleDisconnect(new HazelException("An exception occured while initiating a body receive operation.", e)); 181 | } 182 | } 183 | 184 | /// 185 | /// Callback for when a body has been read. 186 | /// 187 | /// The data bytes received by the connection. 188 | void BodyReadCallback(byte[] bytes) 189 | { 190 | //Begin receiving from the start 191 | try 192 | { 193 | StartWaitingForHeader(BodyReadCallback); 194 | } 195 | catch (Exception e) 196 | { 197 | HandleDisconnect(new HazelException("An exception occured while initiating a header receive operation.", e)); 198 | } 199 | 200 | Statistics.LogFragmentedReceive(bytes.Length, bytes.Length + 4); 201 | 202 | //Fire DataReceived event 203 | InvokeDataReceived(bytes, SendOption.FragmentedReliable); 204 | } 205 | 206 | /// 207 | /// Starts this connection receiving data. 208 | /// 209 | internal void StartReceiving() 210 | { 211 | try 212 | { 213 | StartWaitingForHeader(BodyReadCallback); 214 | } 215 | catch (Exception e) 216 | { 217 | HandleDisconnect(new HazelException("An exception occured while initiating the first receive operation.", e)); 218 | } 219 | } 220 | 221 | /// 222 | /// Starts waiting for a first handshake packet to be received. 223 | /// 224 | /// The callback to invoke when the handshake has been received. 225 | internal void StartWaitingForHandshake(Action callback) 226 | { 227 | try 228 | { 229 | StartWaitingForHeader( 230 | delegate (byte[] bytes) 231 | { 232 | //Remove version byte 233 | byte[] dataBytes = new byte[bytes.Length - 1]; 234 | Buffer.BlockCopy(bytes, 1, dataBytes, 0, bytes.Length - 1); 235 | 236 | callback.Invoke(dataBytes); 237 | } 238 | ); 239 | } 240 | catch (Exception e) 241 | { 242 | HandleDisconnect(new HazelException("An exception occured while initiating the first receive operation.", e)); 243 | } 244 | } 245 | 246 | /// 247 | /// Starts this connections waiting for the header. 248 | /// 249 | /// The callback to invoke when the body has been read. 250 | void StartWaitingForHeader(Action callback) 251 | { 252 | StartWaitingForBytes(4, (bytes) => HeaderReadCallback(bytes, callback)); 253 | } 254 | 255 | /// 256 | /// Waits for the specified amount of bytes to be received. 257 | /// 258 | /// The number of bytes to receive. 259 | /// The callback 260 | void StartWaitingForBytes(int length, Action callback) 261 | { 262 | StateObject state = new StateObject(length, callback); 263 | 264 | StartWaitingForChunk(state); 265 | } 266 | 267 | /// 268 | /// Waits for the next chunk of data from this socket. 269 | /// 270 | /// The StateObject for the receive operation. 271 | void StartWaitingForChunk(StateObject state) 272 | { 273 | lock (socketLock) 274 | { 275 | //Double check we've not disconnected then begin receiving 276 | if (State == ConnectionState.Connected || State == ConnectionState.Connecting) 277 | socket.BeginReceive(state.buffer, state.totalBytesReceived, state.buffer.Length - state.totalBytesReceived, SocketFlags.None, ChunkReadCallback, state); 278 | } 279 | } 280 | 281 | /// 282 | /// Called when a chunk has been read. 283 | /// 284 | /// 285 | void ChunkReadCallback(IAsyncResult result) 286 | { 287 | int bytesReceived; 288 | 289 | //End the receive operation 290 | try 291 | { 292 | lock (socketLock) 293 | bytesReceived = socket.EndReceive(result); 294 | } 295 | catch (ObjectDisposedException) 296 | { 297 | //If the socket's been disposed then we can just end there. 298 | return; 299 | } 300 | catch (Exception e) 301 | { 302 | HandleDisconnect(new HazelException("An exception occured while completing a chunk read operation.", e)); 303 | return; 304 | } 305 | 306 | StateObject state = (StateObject)result.AsyncState; 307 | 308 | state.totalBytesReceived += bytesReceived; //TODO threading issues on state? 309 | 310 | //Exit if receive nothing 311 | if (bytesReceived == 0) 312 | { 313 | HandleDisconnect(); 314 | return; 315 | } 316 | 317 | //If we need to receive more then wait for more, else process it. 318 | if (state.totalBytesReceived < state.buffer.Length) 319 | { 320 | try 321 | { 322 | StartWaitingForChunk(state); 323 | } 324 | catch (Exception e) 325 | { 326 | HandleDisconnect(new HazelException("An exception occured while initiating a chunk receive operation.", e)); 327 | return; 328 | } 329 | } 330 | else 331 | state.callback.Invoke(state.buffer); 332 | } 333 | 334 | /// 335 | /// Called when the socket has been disconnected at the remote host. 336 | /// 337 | /// The exception if one was the cause. 338 | void HandleDisconnect(HazelException e = null) 339 | { 340 | bool invoke = false; 341 | 342 | lock (socketLock) 343 | { 344 | //Only invoke the disconnected event if we're not already disconnecting 345 | if (State == ConnectionState.Connected) 346 | { 347 | State = ConnectionState.Disconnecting; 348 | invoke = true; 349 | } 350 | } 351 | 352 | //Invoke event outide lock if need be 353 | if (invoke) 354 | { 355 | InvokeDisconnected(e); 356 | 357 | Dispose(); 358 | } 359 | } 360 | 361 | /// 362 | /// Appends the length header to the bytes. 363 | /// 364 | /// The source bytes. 365 | /// The new bytes. 366 | static byte[] AppendLengthHeader(byte[] bytes) 367 | { 368 | byte[] fullBytes = new byte[bytes.Length + 4]; 369 | 370 | //Append length 371 | fullBytes[0] = (byte)(((uint)bytes.Length >> 24) & 0xFF); 372 | fullBytes[1] = (byte)(((uint)bytes.Length >> 16) & 0xFF); 373 | fullBytes[2] = (byte)(((uint)bytes.Length >> 8) & 0xFF); 374 | fullBytes[3] = (byte)(uint)bytes.Length; 375 | 376 | //Add rest of bytes 377 | Buffer.BlockCopy(bytes, 0, fullBytes, 4, bytes.Length); 378 | 379 | return fullBytes; 380 | } 381 | 382 | /// 383 | /// Returns the length from a length header. 384 | /// 385 | /// The bytes received. 386 | /// The number of bytes. 387 | static int GetLengthFromBytes(byte[] bytes) 388 | { 389 | if (bytes.Length < 4) 390 | throw new IndexOutOfRangeException("Not enough bytes passed to calculate length."); 391 | 392 | return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; 393 | } 394 | 395 | /// 396 | protected override void Dispose(bool disposing) 397 | { 398 | if (disposing) 399 | { 400 | lock (socketLock) 401 | { 402 | State = ConnectionState.NotConnected; 403 | 404 | if (socket.Connected) 405 | socket.Shutdown(SocketShutdown.Send); 406 | socket.Close(); 407 | } 408 | } 409 | 410 | base.Dispose(disposing); 411 | } 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /Hazel/Tcp/TcpConnectionListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | 9 | namespace Hazel.Tcp 10 | { 11 | /// 12 | /// Listens for new TCP connections and creates TCPConnections for them. 13 | /// 14 | /// 15 | public sealed class TcpConnectionListener : NetworkConnectionListener 16 | { 17 | /// 18 | /// The socket listening for connections. 19 | /// 20 | Socket listener; 21 | 22 | /// 23 | /// Creates a new TcpConnectionListener for the given , port and . 24 | /// 25 | /// The IPAddress to listen on. 26 | /// The port to listen on. 27 | /// The to listen with. 28 | [Obsolete("Temporary constructor in beta only, use NetworkEndPoint constructor instead.")] 29 | public TcpConnectionListener(IPAddress IPAddress, int port, IPMode mode = IPMode.IPv4) 30 | : this (new NetworkEndPoint(IPAddress, port, mode)) 31 | { 32 | 33 | } 34 | 35 | /// 36 | /// Creates a new TcpConnectionListener for the given , port and . 37 | /// 38 | /// The end point to listen on. 39 | public TcpConnectionListener(NetworkEndPoint endPoint) 40 | { 41 | this.EndPoint = endPoint.EndPoint; 42 | this.IPMode = endPoint.IPMode; 43 | 44 | if (endPoint.IPMode == IPMode.IPv4) 45 | this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 46 | else 47 | { 48 | if (!Socket.OSSupportsIPv6) 49 | throw new HazelException("IPV6 not supported!"); 50 | 51 | this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); 52 | this.listener.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); 53 | } 54 | } 55 | 56 | /// 57 | public override void Start() 58 | { 59 | try 60 | { 61 | lock (listener) 62 | { 63 | listener.Bind(EndPoint); 64 | listener.Listen(1000); 65 | 66 | listener.BeginAccept(AcceptConnection, null); 67 | } 68 | } 69 | catch (SocketException e) 70 | { 71 | throw new HazelException("Could not start listening as a SocketException occured", e); 72 | } 73 | } 74 | 75 | /// 76 | /// Called when a new connection has been accepted by the listener. 77 | /// 78 | /// The asyncronous operation's result. 79 | void AcceptConnection(IAsyncResult result) 80 | { 81 | lock (listener) 82 | { 83 | //Accept Tcp socket 84 | Socket tcpSocket; 85 | try 86 | { 87 | tcpSocket = listener.EndAccept(result); 88 | } 89 | catch (ObjectDisposedException) 90 | { 91 | //If the socket's been disposed then we can just end there. 92 | return; 93 | } 94 | 95 | //Start listening for the next connection 96 | listener.BeginAccept(new AsyncCallback(AcceptConnection), null); 97 | 98 | //Sort the event out 99 | TcpConnection tcpConnection = new TcpConnection(tcpSocket); 100 | 101 | //Wait for handshake 102 | tcpConnection.StartWaitingForHandshake( 103 | delegate (byte[] bytes) 104 | { 105 | //Invoke 106 | InvokeNewConnection(bytes, tcpConnection); 107 | 108 | tcpConnection.StartReceiving(); 109 | } 110 | ); 111 | } 112 | } 113 | 114 | /// 115 | protected override void Dispose(bool disposing) 116 | { 117 | if (disposing) 118 | { 119 | lock (listener) 120 | listener.Close(); 121 | } 122 | 123 | base.Dispose(disposing); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Hazel/Udp/SendOptionInternal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | 7 | namespace Hazel.Udp 8 | { 9 | /// 10 | /// Extra internal states for SendOption enumeration when using UDP. 11 | /// 12 | enum UdpSendOption : byte 13 | { 14 | /// 15 | /// Hello message for initiating communication. 16 | /// 17 | Hello = 8, 18 | 19 | /// 20 | /// Message for discontinuing communication. 21 | /// 22 | Disconnect = 9, 23 | 24 | /// 25 | /// Message acknowledging the receipt of a message. 26 | /// 27 | Acknowledgement = 10, 28 | 29 | /// 30 | /// Message that is part of a larger, fragmented message. 31 | /// 32 | Fragment = 11 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Hazel/Udp/UdpClientConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | 10 | namespace Hazel.Udp 11 | { 12 | /// 13 | /// Represents a client's connection to a server that uses the UDP protocol. 14 | /// 15 | /// 16 | public sealed class UdpClientConnection : UdpConnection 17 | { 18 | /// 19 | /// The socket we're connected via. 20 | /// 21 | Socket socket; 22 | 23 | /// 24 | /// Object for locking the state. 25 | /// 26 | Object stateLock = new Object(); 27 | 28 | /// 29 | /// The buffer to store incomming data in. 30 | /// 31 | byte[] dataBuffer = new byte[ushort.MaxValue]; 32 | 33 | /// 34 | /// Creates a new UdpClientConnection. 35 | /// 36 | /// A to connect to. 37 | public UdpClientConnection(NetworkEndPoint remoteEndPoint) 38 | : base() 39 | { 40 | this.EndPoint = remoteEndPoint; 41 | this.RemoteEndPoint = remoteEndPoint.EndPoint; 42 | this.IPMode = remoteEndPoint.IPMode; 43 | 44 | if (remoteEndPoint.IPMode == IPMode.IPv4) 45 | socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 46 | else 47 | { 48 | if (!Socket.OSSupportsIPv6) 49 | throw new HazelException("IPV6 not supported!"); 50 | 51 | socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); 52 | socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); //TODO these lines shouldn't be needed anymore 53 | } 54 | } 55 | 56 | /// 57 | protected override void WriteBytesToConnection(byte[] bytes) 58 | { 59 | lock (stateLock) 60 | { 61 | if (State != ConnectionState.Connected && State != ConnectionState.Connecting) 62 | throw new InvalidOperationException("Could not send data as this Connection is not connected and is not connecting. Did you disconnect?"); 63 | } 64 | 65 | try 66 | { 67 | socket.BeginSendTo( 68 | bytes, 69 | 0, 70 | bytes.Length, 71 | SocketFlags.None, 72 | RemoteEndPoint, 73 | delegate (IAsyncResult result) 74 | { 75 | try 76 | { 77 | lock (socket) 78 | socket.EndSendTo(result); 79 | } 80 | catch (ObjectDisposedException e) 81 | { 82 | HandleDisconnect(new HazelException("Could not send as the socket was disposed of.", e)); 83 | } 84 | catch (SocketException e) 85 | { 86 | HandleDisconnect(new HazelException("Could not send data as a SocketException occured.", e)); 87 | } 88 | }, 89 | null 90 | ); 91 | } 92 | catch (ObjectDisposedException) 93 | { 94 | //User probably called Disconnect in between this method starting and here so report the issue 95 | throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); 96 | } 97 | catch (SocketException e) 98 | { 99 | HazelException he = new HazelException("Could not send data as a SocketException occured.", e); 100 | HandleDisconnect(he); 101 | throw he; 102 | } 103 | } 104 | 105 | /// 106 | public override void Connect(byte[] bytes = null, int timeout = 5000) 107 | { 108 | lock (stateLock) 109 | { 110 | if (State != ConnectionState.NotConnected) 111 | throw new InvalidOperationException("Cannot connect as the Connection is already connected."); 112 | 113 | State = ConnectionState.Connecting; 114 | } 115 | 116 | //Begin listening 117 | try 118 | { 119 | if (IPMode == IPMode.IPv4) 120 | socket.Bind(new IPEndPoint(IPAddress.Any, 0)); 121 | else 122 | socket.Bind(new IPEndPoint(IPAddress.IPv6Any, 0)); 123 | } 124 | catch (SocketException e) 125 | { 126 | State = ConnectionState.NotConnected; 127 | throw new HazelException("A socket exception occured while binding to the port.", e); 128 | } 129 | 130 | try 131 | { 132 | StartListeningForData(); 133 | } 134 | catch (ObjectDisposedException) 135 | { 136 | //If the socket's been disposed then we can just end there but make sure we're in NotConnected state. 137 | //If we end up here I'm really lost... 138 | lock (stateLock) 139 | State = ConnectionState.NotConnected; 140 | return; 141 | } 142 | catch (SocketException e) 143 | { 144 | Dispose(); 145 | throw new HazelException("A Socket exception occured while initiating a receive operation.", e); 146 | } 147 | 148 | //Write bytes to the server to tell it hi (and to punch a hole in our NAT, if present) 149 | //When acknowledged set the state to connected 150 | SendHello(bytes, () => { lock (stateLock) State = ConnectionState.Connected; }); 151 | 152 | //Wait till hello packet is acknowledged and the state is set to Connected 153 | bool timedOut = !WaitOnConnect(timeout); 154 | 155 | //If we timed out raise an exception 156 | if (timedOut) 157 | { 158 | Dispose(); 159 | throw new HazelException("Connection attempt timed out."); 160 | } 161 | } 162 | 163 | /// 164 | /// Instructs the listener to begin listening. 165 | /// 166 | void StartListeningForData() 167 | { 168 | socket.BeginReceive(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, ReadCallback, dataBuffer); 169 | } 170 | 171 | /// 172 | /// Called when data has been received by the socket. 173 | /// 174 | /// The asyncronous operation's result. 175 | void ReadCallback(IAsyncResult result) 176 | { 177 | int bytesReceived; 178 | 179 | //End the receive operation 180 | try 181 | { 182 | bytesReceived = socket.EndReceive(result); 183 | } 184 | catch (ObjectDisposedException) 185 | { 186 | //If the socket's been disposed then we can just end there. 187 | return; 188 | } 189 | catch (SocketException e) 190 | { 191 | HandleDisconnect(new HazelException("A socket exception occured while reading data.", e)); 192 | return; 193 | } 194 | 195 | //Exit if no bytes read, we've failed. 196 | if (bytesReceived == 0) 197 | { 198 | HandleDisconnect(); 199 | return; 200 | } 201 | 202 | //Copy data to new array 203 | byte[] bytes = new byte[bytesReceived]; 204 | Buffer.BlockCopy(dataBuffer, 0, bytes, 0, bytesReceived); 205 | 206 | //Begin receiving again 207 | try 208 | { 209 | StartListeningForData(); 210 | } 211 | catch (SocketException e) 212 | { 213 | HandleDisconnect(new HazelException("A Socket exception occured while initiating a receive operation.", e)); 214 | } 215 | catch (ObjectDisposedException) 216 | { 217 | //If the socket's been disposed then we can just end there. 218 | return; 219 | } 220 | 221 | HandleReceive(bytes); 222 | } 223 | 224 | /// 225 | protected override void HandleDisconnect(HazelException e = null) 226 | { 227 | bool invoke = false; 228 | 229 | lock (stateLock) 230 | { 231 | //Only invoke the disconnected event if we're not already disconnecting 232 | if (State == ConnectionState.Connected) 233 | { 234 | State = ConnectionState.Disconnecting; 235 | invoke = true; 236 | } 237 | } 238 | 239 | //Invoke event outide lock if need be 240 | if (invoke) 241 | { 242 | InvokeDisconnected(e); 243 | 244 | Dispose(); 245 | } 246 | } 247 | 248 | /// 249 | protected override void Dispose(bool disposing) 250 | { 251 | if (disposing) 252 | { 253 | //Send disconnect message if we're not already disconnecting 254 | bool connected; 255 | lock (stateLock) 256 | connected = State == ConnectionState.Connected; 257 | 258 | if (connected) 259 | SendDisconnect(); 260 | 261 | //Dispose of the socket 262 | lock (stateLock) 263 | State = ConnectionState.NotConnected; 264 | 265 | socket.Close(); 266 | } 267 | 268 | base.Dispose(disposing); 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /Hazel/Udp/UdpConnection.Fragmented.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Hazel.Udp 7 | { 8 | partial class UdpConnection 9 | { 10 | /// 11 | /// The amount of data that can be put into a fragment. 12 | /// 13 | public int FragmentSize { get { return fragmentSize; } } 14 | int fragmentSize = 65507 - 1 - 2 - 2 - 2; 15 | 16 | /// 17 | /// The last fragmented message ID that was written. 18 | /// 19 | volatile ushort lastFragmentIDAllocated; 20 | 21 | Dictionary fragmentedMessagesReceived = new Dictionary(); 22 | 23 | /// 24 | /// Sends a message fragmenting it as needed to pass over the network. 25 | /// 26 | /// The send option the message was sent with. 27 | /// The data of the message to send. 28 | void FragmentedSend(byte[] data) 29 | { 30 | //Get an ID not used yet. 31 | ushort id = ++lastFragmentIDAllocated; //TODO is extra code needed to manage loop around? 32 | 33 | for (ushort i = 0; i < Math.Ceiling(data.Length / (double)FragmentSize); i++) 34 | { 35 | byte[] buffer = new byte[Math.Min(data.Length - (FragmentSize * i), FragmentSize) + 7]; 36 | 37 | //Add send option 38 | buffer[0] = i == 0 ? (byte)SendOption.FragmentedReliable : (byte)UdpSendOption.Fragment; 39 | 40 | //Add fragment message ID 41 | buffer[1] = (byte)((id >> 8) & 0xFF); 42 | buffer[2] = (byte)id; 43 | 44 | //Add length or fragment id 45 | if (i == 0) 46 | { 47 | ushort fragments = (ushort)Math.Ceiling(data.Length / (double)FragmentSize); 48 | buffer[3] = (byte)((fragments >> 8) & 0xFF); 49 | buffer[4] = (byte)fragments; 50 | } 51 | else 52 | { 53 | buffer[3] = (byte)((i >> 8) & 0xFF); 54 | buffer[4] = (byte)i; 55 | } 56 | 57 | //Pass fragment to reliable send code to ensure it will arrive 58 | AttachReliableID(buffer, 5); 59 | 60 | //Copy data into fragment 61 | Buffer.BlockCopy(data, FragmentSize * i, buffer, 7, buffer.Length - 7); 62 | 63 | //Send 64 | WriteBytesToConnection(buffer); 65 | } 66 | } 67 | 68 | /// 69 | /// Gets a message from those we've begun receiving or adds a new one. 70 | /// 71 | /// The Id of the message to find. 72 | /// 73 | FragmentedMessage GetFragmentedMessage(ushort messageId) 74 | { 75 | lock (fragmentedMessagesReceived) 76 | { 77 | FragmentedMessage message; 78 | if (fragmentedMessagesReceived.ContainsKey(messageId)) 79 | { 80 | message = fragmentedMessagesReceived[messageId]; 81 | } 82 | else 83 | { 84 | message = new FragmentedMessage(); 85 | 86 | fragmentedMessagesReceived.Add(messageId, message); 87 | } 88 | 89 | return message; 90 | } 91 | } 92 | 93 | /// 94 | /// Handles a the start message of a fragmented message. 95 | /// 96 | /// The buffer received. 97 | void FragmentedStartMessageReceive(byte[] buffer) 98 | { 99 | //Send to reliable code to send the acknowledgement 100 | if (!ProcessReliableReceive(buffer, 5)) 101 | return; 102 | 103 | ushort id = (ushort)((buffer[1] << 8) + buffer[2]); 104 | 105 | ushort length = (ushort)((buffer[3] << 8) + buffer[4]); 106 | 107 | FragmentedMessage message; 108 | bool messageComplete; 109 | lock (fragmentedMessagesReceived) 110 | { 111 | message = GetFragmentedMessage(id); 112 | message.received.Add(new FragmentedMessage.Fragment(0, buffer, 7)); 113 | message.noFragments = length; 114 | 115 | messageComplete = message.noFragments == message.received.Count; 116 | } 117 | 118 | if (messageComplete) 119 | FinalizeFragmentedMessage(message); 120 | } 121 | 122 | /// 123 | /// Handles a fragment message of a fragmented message. 124 | /// 125 | /// The buffer received. 126 | void FragmentedMessageReceive(byte[] buffer) 127 | { 128 | //Send to reliable code to send the acknowledgement 129 | if (!ProcessReliableReceive(buffer, 5)) 130 | return; 131 | 132 | ushort id = (ushort)((buffer[1] << 8) + buffer[2]); 133 | 134 | ushort fragmentID = (ushort)((buffer[3] << 8) + buffer[4]); 135 | 136 | FragmentedMessage message; 137 | bool messageComplete; 138 | lock (fragmentedMessagesReceived) 139 | { 140 | message = GetFragmentedMessage(id); 141 | message.received.Add(new FragmentedMessage.Fragment(fragmentID, buffer, 7)); 142 | 143 | messageComplete = message.noFragments == message.received.Count; 144 | } 145 | 146 | if (messageComplete) 147 | FinalizeFragmentedMessage(message); 148 | } 149 | 150 | /// 151 | /// Finalizes a completed fragmented message and invokes message received events. 152 | /// 153 | /// The message received. 154 | void FinalizeFragmentedMessage(FragmentedMessage message) 155 | { 156 | IEnumerable orderedFragments = message.received.OrderBy((x) => x.fragmentID); 157 | FragmentedMessage.Fragment last = orderedFragments.Last(); 158 | 159 | byte[] completeData = new byte[(orderedFragments.Count() - 1) * FragmentSize + last.data.Length - last.offset]; 160 | int ptr = 0; 161 | foreach (FragmentedMessage.Fragment fragment in orderedFragments) 162 | { 163 | Buffer.BlockCopy(fragment.data, fragment.offset, completeData, ptr, fragment.data.Length - fragment.offset); 164 | ptr += fragment.data.Length - fragment.offset; 165 | } 166 | 167 | InvokeDataReceived(completeData, SendOption.FragmentedReliable); 168 | } 169 | 170 | /// 171 | /// Holding class for the parts of a fragmented message so far received. 172 | /// 173 | private class FragmentedMessage 174 | { 175 | /// 176 | /// The total number of fragments expected. 177 | /// 178 | public int noFragments = -1; 179 | 180 | /// 181 | /// The fragments received so far. 182 | /// 183 | public List received = new List(); 184 | 185 | public struct Fragment 186 | { 187 | public int fragmentID; 188 | public byte[] data; 189 | public int offset; 190 | 191 | public Fragment(int fragmentID, byte[] data, int offset) 192 | { 193 | this.fragmentID = fragmentID; 194 | this.data = data; 195 | this.offset = offset; 196 | } 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /Hazel/Udp/UdpConnection.KeepAlive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | 9 | namespace Hazel.Udp 10 | { 11 | partial class UdpConnection 12 | { 13 | /// 14 | /// The interval from data being received or transmitted to a keepalive packet being sent in milliseconds. 15 | /// 16 | /// 17 | /// 18 | /// Keepalive packets serve to close connections when an endpoint abruptly disconnects and to ensure than any 19 | /// NAT devices do not close their translation for our argument. By ensuring there is regular contact the 20 | /// connection can detect and prevent these issues. 21 | /// 22 | /// 23 | /// The default value is 10 seconds, set to System.Threading.Timeout.Infinite to disable keepalive packets. 24 | /// 25 | /// 26 | public int KeepAliveInterval 27 | { 28 | get 29 | { 30 | return keepAliveInterval; 31 | } 32 | 33 | set 34 | { 35 | keepAliveInterval = value; 36 | 37 | //Update timer 38 | ResetKeepAliveTimer(); 39 | } 40 | } 41 | int keepAliveInterval = 10000; 42 | 43 | /// 44 | /// The timer creating keepalive pulses. 45 | /// 46 | Timer keepAliveTimer; 47 | 48 | /// 49 | /// Lock for keep alive timer. 50 | /// 51 | Object keepAliveTimerLock = new Object(); 52 | 53 | /// 54 | /// Has the keep alive timer been disposed already? 55 | /// 56 | bool keepAliveTimerDisposed; 57 | 58 | /// 59 | /// Starts the keepalive timer. 60 | /// 61 | void InitializeKeepAliveTimer() 62 | { 63 | lock (keepAliveTimerLock) 64 | { 65 | keepAliveTimer = new Timer( 66 | (o) => 67 | { 68 | Trace.WriteLine("Keepalive packet sent."); 69 | SendHello(null, null); 70 | }, 71 | null, 72 | keepAliveInterval, 73 | keepAliveInterval 74 | ); 75 | } 76 | } 77 | 78 | /// 79 | /// Resets the keepalive timer to zero. 80 | /// 81 | void ResetKeepAliveTimer() 82 | { 83 | lock (keepAliveTimerLock) 84 | keepAliveTimer.Change(keepAliveInterval, keepAliveInterval); 85 | } 86 | 87 | /// 88 | /// Disposes of the keep alive timer. 89 | /// 90 | void DisposeKeepAliveTimer() 91 | { 92 | lock(keepAliveTimerLock) 93 | { 94 | if (!keepAliveTimerDisposed) 95 | keepAliveTimer.Dispose(); 96 | keepAliveTimerDisposed = true; 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Hazel/Udp/UdpConnection.Reliable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | 9 | namespace Hazel.Udp 10 | { 11 | partial class UdpConnection 12 | { 13 | /// 14 | /// The starting timeout, in miliseconds, at which data will be resent. 15 | /// 16 | /// 17 | /// 18 | /// For reliable delivery data is resent at specified intervals unless an acknowledgement is received from the 19 | /// receiving device. The ResendTimeout specifies the interval between the packets being resent, each time a packet 20 | /// is resent the interval is doubled for that packet until the number of resends exceeds the 21 | /// value. 22 | /// 23 | /// 24 | /// Setting this to its default of 0 will mean the timout is 4 times the value of the average ping, usually 25 | /// resulting in a more dynamic resend that responds to endpoints on slower or faster connections. 26 | /// 27 | /// 28 | public int ResendTimeout { get { return resendTimeout; } set { resendTimeout = value; } } 29 | private volatile int resendTimeout = 0; 30 | 31 | /// 32 | /// Holds the last ID allocated. 33 | /// 34 | volatile ushort lastIDAllocated; 35 | 36 | /// 37 | /// The packets of data that have been transmitted reliably and not acknowledged. 38 | /// 39 | Dictionary reliableDataPacketsSent = new Dictionary(); 40 | 41 | /// 42 | /// The last packets that were received. 43 | /// 44 | HashSet reliableDataPacketsMissing = new HashSet(); 45 | 46 | /// 47 | /// The packet id that was received last. 48 | /// 49 | volatile ushort reliableReceiveLast = 0; 50 | 51 | /// 52 | /// Has the connection received anything yet 53 | /// 54 | volatile bool hasReceivedSomething = false; 55 | 56 | /// 57 | /// The total time it has taken reliable packets to make a round trip. 58 | /// 59 | long totalRoundTime = 0; 60 | 61 | /// 62 | /// The number of reliable messages that have been sent. 63 | /// 64 | long totalReliableMessages = 0; 65 | 66 | /// 67 | /// Returns the average ping to this endpoint. 68 | /// 69 | /// 70 | /// This returns the average ping for a one-way trip as calculated from the reliable packets that have been sent 71 | /// and acknowledged by the endpoint. 72 | /// 73 | public double AveragePing 74 | { 75 | get 76 | { 77 | long t = Interlocked.Read(ref totalReliableMessages); 78 | if (t == 0) 79 | return 0; 80 | else 81 | return Interlocked.Read(ref totalRoundTime) / t / 2; 82 | } 83 | } 84 | 85 | /// 86 | /// The maximum times a message should be resent before marking the endpoint as disconnected. 87 | /// 88 | /// 89 | /// Reliable packets will be resent at an interval defined in for the number of times 90 | /// specified here. Once a packet has been retransmitted this number of times and has not been acknowledged the 91 | /// connection will be marked as disconnected and the Disconnected event 92 | /// will be invoked. 93 | /// 94 | public int ResendsBeforeDisconnect { get { return resendsBeforeDisconnect; } set { resendsBeforeDisconnect = value; } } 95 | private volatile int resendsBeforeDisconnect = 3; 96 | 97 | /// 98 | /// Class to hold packet data 99 | /// 100 | class Packet : IRecyclable, IDisposable 101 | { 102 | /// 103 | /// Object pool for this event. 104 | /// 105 | static readonly ObjectPool objectPool = new ObjectPool(() => new Packet()); 106 | 107 | /// 108 | /// Returns an instance of this object from the pool. 109 | /// 110 | /// 111 | internal static Packet GetObject() 112 | { 113 | return objectPool.GetObject(); 114 | } 115 | 116 | public byte[] Data; 117 | public Timer Timer; 118 | public volatile int LastTimeout; 119 | public Action AckCallback; 120 | public volatile bool Acknowledged; 121 | public volatile int Retransmissions; 122 | public Stopwatch Stopwatch = new Stopwatch(); 123 | 124 | Packet() 125 | { 126 | 127 | } 128 | 129 | internal void Set(byte[] data, Action resendAction, int timeout, Action ackCallback) 130 | { 131 | Data = data; 132 | 133 | Timer = new Timer( 134 | (object obj) => resendAction(this), 135 | null, 136 | timeout, 137 | Timeout.Infinite 138 | ); 139 | 140 | LastTimeout = timeout; 141 | AckCallback = ackCallback; 142 | Acknowledged = false; 143 | Retransmissions = 0; 144 | 145 | Stopwatch.Reset(); 146 | Stopwatch.Start(); 147 | } 148 | 149 | /// 150 | /// Returns this object back to the object pool from whence it came. 151 | /// 152 | public void Recycle() 153 | { 154 | lock (Timer) 155 | Timer.Dispose(); 156 | 157 | objectPool.PutObject(this); 158 | } 159 | 160 | /// 161 | /// Disposes of this object. 162 | /// 163 | public void Dispose() 164 | { 165 | Dispose(true); 166 | GC.SuppressFinalize(this); 167 | } 168 | 169 | protected void Dispose(bool disposing) 170 | { 171 | if (disposing) 172 | { 173 | lock (Timer) 174 | Timer.Dispose(); 175 | } 176 | } 177 | } 178 | 179 | /// 180 | /// Adds a 2 byte ID to the packet at offset and stores the packet reference for retransmission. 181 | /// 182 | /// The buffer to attach to. 183 | /// The offset to attach at. 184 | /// The callback to make once the packet has been acknowledged. 185 | void AttachReliableID(byte[] buffer, int offset, Action ackCallback = null) 186 | { 187 | //Find and reliable ID 188 | lock (reliableDataPacketsSent) 189 | { 190 | //Find an ID not used yet. 191 | ushort id; 192 | 193 | do 194 | id = ++lastIDAllocated; 195 | while (reliableDataPacketsSent.ContainsKey(id)); 196 | 197 | //Write ID 198 | buffer[offset] = (byte)((id >> 8) & 0xFF); 199 | buffer[offset + 1] = (byte)id; 200 | 201 | //Create packet object 202 | Packet packet = Packet.GetObject(); 203 | packet.Set( 204 | buffer, 205 | (Packet p) => 206 | { 207 | //Double packet timeout 208 | lock (p.Timer) 209 | { 210 | if (!p.Acknowledged) 211 | { 212 | p.Timer.Change(p.LastTimeout *= 2, Timeout.Infinite); 213 | if (++p.Retransmissions > ResendsBeforeDisconnect) 214 | { 215 | HandleDisconnect(); 216 | 217 | //Set acknowledged so we dont change the timer again 218 | p.Acknowledged = true; 219 | 220 | p.Recycle(); 221 | return; 222 | } 223 | } 224 | } 225 | 226 | try 227 | { 228 | WriteBytesToConnection(p.Data); 229 | } 230 | catch (InvalidOperationException e) 231 | { 232 | //No longer connected 233 | HandleDisconnect(new HazelException("Could not resend data as connection is no longer connected", e)); 234 | } 235 | 236 | Trace.WriteLine("Resend."); 237 | }, 238 | resendTimeout > 0 ? resendTimeout : (AveragePing != 0 ? (int)AveragePing * 4 : 200), 239 | ackCallback 240 | ); 241 | 242 | //Remember packet 243 | reliableDataPacketsSent.Add(id, packet); 244 | } 245 | } 246 | 247 | /// 248 | /// Sends the bytes reliably and stores the send. 249 | /// 250 | /// The byte array to write to. 251 | /// The callback to make once the packet has been acknowledged. 252 | void ReliableSend(byte sendOption, byte[] data, Action ackCallback = null) 253 | { 254 | byte[] bytes = new byte[data.Length + 3]; 255 | 256 | //Add message type 257 | bytes[0] = sendOption; 258 | 259 | //Add reliable ID 260 | AttachReliableID(bytes, 1, ackCallback); 261 | 262 | //Copy data into new array 263 | Buffer.BlockCopy(data, 0, bytes, bytes.Length - data.Length, data.Length); 264 | 265 | //Write to connection 266 | WriteBytesToConnection(bytes); 267 | 268 | Statistics.LogReliableSend(data.Length, bytes.Length); 269 | } 270 | 271 | /// 272 | /// Handles a reliable message being received and invokes the data event. 273 | /// 274 | /// The buffer received. 275 | void ReliableMessageReceive(byte[] buffer) 276 | { 277 | if (ProcessReliableReceive(buffer, 1)) 278 | InvokeDataReceived(SendOption.Reliable, buffer, 3); 279 | 280 | Statistics.LogReliableReceive(buffer.Length - 3, buffer.Length); 281 | } 282 | 283 | /// 284 | /// Handles receives from reliable packets. 285 | /// 286 | /// The buffer containing the data. 287 | /// The offset of the reliable header. 288 | /// Whether the packet was a new packet or not. 289 | bool ProcessReliableReceive(byte[] bytes, int offset) 290 | { 291 | //Get the ID form the packet 292 | ushort id = (ushort)((bytes[offset] << 8) + bytes[offset + 1]); 293 | 294 | //Send an acknowledgement 295 | SendAck(bytes[offset], bytes[offset + 1]); 296 | 297 | /* 298 | * It gets a little complicated here (note the fact I'm actually using a multiline comment for once...) 299 | * 300 | * In a simple world if our data is greater than the last reliable packet received (reliableReceiveLast) 301 | * then it is guaranteed to be a new packet, if it's not we can see if we are missing that packet (lookup 302 | * in reliableDataPacketsMissing). 303 | * 304 | * --------rrl############# (1) 305 | * 306 | * (where --- are packets received already and #### are packets that will be counted as new) 307 | * 308 | * Unfortunately if id becomes greater than 65535 it will loop back to zero so we will add a pointer that 309 | * specifies any packets with an id behind it are also new (overwritePointer). 310 | * 311 | * ####op----------rrl##### (2) 312 | * 313 | * ------rll#########op---- (3) 314 | * 315 | * Anything behind than the reliableReceiveLast pointer (but greater than the overwritePointer is either a 316 | * missing packet or something we've already received so when we change the pointers we need to make sure 317 | * we keep note of what hasn't been received yet (reliableDataPacketsMissing). 318 | * 319 | * So... 320 | */ 321 | 322 | lock (reliableDataPacketsMissing) 323 | { 324 | //Calculate overwritePointer 325 | ushort overwritePointer = (ushort)(reliableReceiveLast - 32768); 326 | 327 | //Calculate if it is a new packet by examining if it is within the range 328 | bool isNew; 329 | if (overwritePointer < reliableReceiveLast) 330 | isNew = id > reliableReceiveLast || id <= overwritePointer; //Figure (2) 331 | else 332 | isNew = id > reliableReceiveLast && id <= overwritePointer; //Figure (3) 333 | 334 | //If it's new or we've not received anything yet 335 | if (isNew || !hasReceivedSomething) 336 | { 337 | //Mark items between the most recent receive and the id received as missing 338 | for (ushort i = (ushort)(reliableReceiveLast + 1); i < id; i++) 339 | reliableDataPacketsMissing.Add(i); 340 | 341 | //Update the most recently received 342 | reliableReceiveLast = id; 343 | hasReceivedSomething = true; 344 | } 345 | 346 | //Else it could be a missing packet 347 | else 348 | { 349 | //See if we're missing it, else this packet is a duplicate as so we return false 350 | if (reliableDataPacketsMissing.Contains(id)) 351 | reliableDataPacketsMissing.Remove(id); 352 | else 353 | return false; 354 | } 355 | } 356 | 357 | return true; 358 | } 359 | 360 | /// 361 | /// Handles acknowledgement packets to us. 362 | /// 363 | /// The buffer containing the data. 364 | void AcknowledgementMessageReceive(byte[] bytes) 365 | { 366 | //Get ID 367 | ushort id = (ushort)((bytes[1] << 8) + bytes[2]); 368 | 369 | lock (reliableDataPacketsSent) 370 | { 371 | //Dispose of timer and remove from dictionary 372 | if (reliableDataPacketsSent.ContainsKey(id)) 373 | { 374 | Packet packet = reliableDataPacketsSent[id]; 375 | 376 | packet.Acknowledged = true; 377 | 378 | if (packet.AckCallback != null) 379 | packet.AckCallback.Invoke(); 380 | 381 | //Add to average ping 382 | packet.Stopwatch.Stop(); 383 | Interlocked.Add(ref totalRoundTime, packet.Stopwatch.Elapsed.Milliseconds); 384 | Interlocked.Increment(ref totalReliableMessages); 385 | 386 | packet.Recycle(); 387 | 388 | reliableDataPacketsSent.Remove(id); 389 | } 390 | } 391 | 392 | Statistics.LogReliableReceive(0, bytes.Length); 393 | } 394 | 395 | /// 396 | /// Sends an acknowledgement for a packet given its identification bytes. 397 | /// 398 | /// The first identification byte. 399 | /// The second identification byte. 400 | internal void SendAck(byte byte1, byte byte2) 401 | { 402 | //Always reply with acknowledgement in order to stop the sender repeatedly sending it 403 | WriteBytesToConnection( //TODO group acks together 404 | new byte[] 405 | { 406 | (byte)UdpSendOption.Acknowledgement, 407 | byte1, 408 | byte2 409 | } 410 | ); 411 | } 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /Hazel/Udp/UdpConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | namespace Hazel.Udp 10 | { 11 | /// 12 | /// Represents a connection that uses the UDP protocol. 13 | /// 14 | /// 15 | public abstract partial class UdpConnection : NetworkConnection 16 | { 17 | /// 18 | /// Creates a new UdpConnection and initializes the keep alive timer. 19 | /// 20 | protected UdpConnection() 21 | { 22 | InitializeKeepAliveTimer(); 23 | } 24 | 25 | /// 26 | /// Writes the given bytes to the connection. 27 | /// 28 | /// The bytes to write. 29 | protected abstract void WriteBytesToConnection(byte[] bytes); 30 | 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// Udp connections can currently send messages using and 36 | /// . Fragmented messages are not currently supported and will default to 37 | /// until implemented. 38 | /// 39 | /// 40 | public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.None) 41 | { 42 | //Early check 43 | if (State != ConnectionState.Connected) 44 | throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); 45 | 46 | //Add header information and send 47 | HandleSend(bytes, (byte)sendOption); 48 | } 49 | 50 | /// 51 | /// Handles the reliable/fragmented sending from this connection. 52 | /// 53 | /// The data being sent. 54 | /// The specified as its byte value. 55 | /// The callback to invoke when this packet is acknowledged. 56 | /// The bytes that should actually be sent. 57 | protected void HandleSend(byte[] data, byte sendOption, Action ackCallback = null) 58 | { 59 | //Inform keepalive not to send for a while 60 | ResetKeepAliveTimer(); 61 | 62 | switch (sendOption) 63 | { 64 | //Handle reliable header and hellos 65 | case (byte)SendOption.Reliable: 66 | case (byte)UdpSendOption.Hello: 67 | ReliableSend(sendOption, data, ackCallback); 68 | break; 69 | 70 | case (byte)SendOption.FragmentedReliable: 71 | FragmentedSend(data); 72 | break; 73 | 74 | //Treat all else as unreliable 75 | default: 76 | UnreliableSend(data, sendOption); 77 | break; 78 | } 79 | } 80 | 81 | /// 82 | /// Handles the receiving of data. 83 | /// 84 | /// The buffer containing the bytes received. 85 | protected internal void HandleReceive(byte[] buffer) 86 | { 87 | //Inform keepalive not to send for a while 88 | ResetKeepAliveTimer(); 89 | 90 | switch (buffer[0]) 91 | { 92 | //Handle reliable receives 93 | case (byte)SendOption.Reliable: 94 | ReliableMessageReceive(buffer); 95 | break; 96 | 97 | //Handle acknowledgments 98 | case (byte)UdpSendOption.Acknowledgement: 99 | AcknowledgementMessageReceive(buffer); 100 | break; 101 | 102 | //We need to acknowledge hello messages but dont want to invoke any events! 103 | case (byte)UdpSendOption.Hello: 104 | ProcessReliableReceive(buffer, 1); 105 | Statistics.LogHelloReceive(buffer.Length); 106 | break; 107 | 108 | case (byte)UdpSendOption.Disconnect: 109 | HandleDisconnect(); 110 | break; 111 | 112 | //Handle fragmented messages 113 | case (byte)SendOption.FragmentedReliable: 114 | FragmentedStartMessageReceive(buffer); 115 | break; 116 | 117 | case (byte)UdpSendOption.Fragment: 118 | FragmentedMessageReceive(buffer); 119 | break; 120 | 121 | //Treat everything else as unreliable 122 | default: 123 | InvokeDataReceived(SendOption.None, buffer, 1); 124 | Statistics.LogUnreliableReceive(buffer.Length - 1, buffer.Length); 125 | break; 126 | } 127 | } 128 | 129 | /// 130 | /// Sends bytes using the unreliable UDP protocol. 131 | /// 132 | /// The data. 133 | /// The SendOption to attach. 134 | void UnreliableSend(byte[] data, byte sendOption) 135 | { 136 | byte[] bytes = new byte[data.Length + 1]; 137 | 138 | //Add message type 139 | bytes[0] = sendOption; 140 | 141 | //Copy data into new array 142 | Buffer.BlockCopy(data, 0, bytes, bytes.Length - data.Length, data.Length); 143 | 144 | //Write to connection 145 | WriteBytesToConnection(bytes); 146 | 147 | Statistics.LogUnreliableSend(data.Length, bytes.Length); 148 | } 149 | 150 | /// 151 | /// Helper method to invoke the data received event. 152 | /// 153 | /// The send option the message was received with. 154 | /// The buffer received. 155 | /// The offset of data in the buffer. 156 | void InvokeDataReceived(SendOption sendOption, byte[] buffer, int dataOffset) 157 | { 158 | byte[] dataBytes = new byte[buffer.Length - dataOffset]; 159 | Buffer.BlockCopy(buffer, dataOffset, dataBytes, 0, dataBytes.Length); 160 | 161 | InvokeDataReceived(dataBytes, sendOption); 162 | } 163 | 164 | /// 165 | /// Sends a hello packet to the remote endpoint. 166 | /// 167 | /// The callback to invoke when the hello packet is acknowledged. 168 | protected void SendHello(byte[] bytes, Action acknowledgeCallback) 169 | { 170 | //First byte of handshake is version indicator so add data after 171 | byte[] actualBytes; 172 | if (bytes == null) 173 | { 174 | actualBytes = new byte[1]; 175 | } 176 | else 177 | { 178 | actualBytes = new byte[bytes.Length + 1]; 179 | Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length); 180 | } 181 | 182 | HandleSend(actualBytes, (byte)UdpSendOption.Hello, acknowledgeCallback); 183 | } 184 | 185 | /// 186 | /// Called when the socket has been disconnected at the remote host. 187 | /// 188 | /// The exception if one was the cause. 189 | protected abstract void HandleDisconnect(HazelException e = null); 190 | 191 | /// 192 | /// Sends a disconnect message to the end point. 193 | /// 194 | protected void SendDisconnect() 195 | { 196 | HandleSend(new byte[0], (byte)UdpSendOption.Disconnect); //TODO Should disconnect wait for an ack? 197 | } 198 | 199 | /// 200 | protected override void Dispose(bool disposing) 201 | { 202 | if (disposing) 203 | { 204 | DisposeKeepAliveTimer(); 205 | } 206 | 207 | base.Dispose(disposing); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Hazel/Udp/UdpConnectionListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | 9 | namespace Hazel.Udp 10 | { 11 | /// 12 | /// Listens for new UDP connections and creates UdpConnections for them. 13 | /// 14 | /// 15 | public class UdpConnectionListener : NetworkConnectionListener 16 | { 17 | /// 18 | /// The socket listening for connections. 19 | /// 20 | Socket listener; 21 | 22 | /// 23 | /// Buffer to store incoming data in. 24 | /// 25 | byte[] dataBuffer = new byte[ushort.MaxValue]; 26 | 27 | /// 28 | /// The connections we currently hold 29 | /// 30 | Dictionary connections = new Dictionary(); 31 | 32 | /// 33 | /// Creates a new UdpConnectionListener for the given , port and . 34 | /// 35 | /// The IPAddress to listen on. 36 | /// The port to listen on. 37 | /// The to listen with. 38 | [Obsolete("Temporary constructor in beta only, use NetworkEndPoint constructor instead.")] 39 | public UdpConnectionListener(IPAddress IPAddress, int port, IPMode mode = IPMode.IPv4) 40 | : this (new NetworkEndPoint(IPAddress, port, mode)) 41 | { 42 | 43 | } 44 | 45 | /// 46 | /// Creates a new UdpConnectionListener for the given , port and . 47 | /// 48 | /// The endpoint to listen on. 49 | public UdpConnectionListener(NetworkEndPoint endPoint) 50 | { 51 | this.EndPoint = endPoint.EndPoint; 52 | this.IPMode = endPoint.IPMode; 53 | 54 | if (endPoint.IPMode == IPMode.IPv4) 55 | this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 56 | else 57 | { 58 | if (!Socket.OSSupportsIPv6) 59 | throw new HazelException("IPV6 not supported!"); 60 | 61 | this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); 62 | this.listener.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); 63 | } 64 | } 65 | 66 | /// 67 | public override void Start() 68 | { 69 | try 70 | { 71 | listener.Bind(EndPoint); 72 | } 73 | catch (SocketException e) 74 | { 75 | throw new HazelException("Could not start listening as a SocketException occured", e); 76 | } 77 | 78 | StartListeningForData(); 79 | } 80 | 81 | /// 82 | /// Instructs the listener to begin listening. 83 | /// 84 | void StartListeningForData() 85 | { 86 | EndPoint remoteEP = EndPoint; 87 | 88 | try 89 | { 90 | listener.BeginReceiveFrom(dataBuffer, 0, dataBuffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, dataBuffer); 91 | } 92 | catch (ObjectDisposedException) 93 | { 94 | return; 95 | } 96 | catch (SocketException) 97 | { 98 | //Client no longer reachable, pretend it didn't happen 99 | //TODO possibly able to disconnect client, see other TODO 100 | StartListeningForData(); 101 | return; 102 | } 103 | } 104 | 105 | /// 106 | /// Called when data has been received by the listener. 107 | /// 108 | /// The asyncronous operation's result. 109 | void ReadCallback(IAsyncResult result) 110 | { 111 | int bytesReceived; 112 | EndPoint remoteEndPoint = new IPEndPoint(IPMode == IPMode.IPv4 ? IPAddress.Any : IPAddress.IPv6Any, 0); 113 | 114 | //End the receive operation 115 | try 116 | { 117 | bytesReceived = listener.EndReceiveFrom(result, ref remoteEndPoint); 118 | } 119 | catch (ObjectDisposedException) 120 | { 121 | //If the socket's been disposed then we can just end there. 122 | return; 123 | } 124 | catch (SocketException) 125 | { 126 | //Client no longer reachable, pretend it didn't happen 127 | //TODO should this not inform the connection this client is lost??? 128 | 129 | //This thread suggests the IP is not passed out from WinSoc so maybe not possible 130 | //http://stackoverflow.com/questions/2576926/python-socket-error-on-udp-data-receive-10054 131 | 132 | StartListeningForData(); 133 | return; 134 | } 135 | 136 | //Exit if no bytes read, we've closed. 137 | if (bytesReceived == 0) 138 | return; 139 | 140 | //Copy to new buffer 141 | byte[] buffer = new byte[bytesReceived]; 142 | Buffer.BlockCopy((byte[])result.AsyncState, 0, buffer, 0, bytesReceived); 143 | 144 | //Begin receiving again 145 | StartListeningForData(); 146 | 147 | bool aware; 148 | UdpServerConnection connection; 149 | lock (connections) 150 | { 151 | aware = connections.ContainsKey(remoteEndPoint); 152 | 153 | //If we're aware of this connection use the one already 154 | if (aware) 155 | connection = connections[remoteEndPoint]; 156 | 157 | //If this is a new client then connect with them! 158 | else 159 | { 160 | //Check for malformed connection attempts 161 | if (buffer[0] != (byte)UdpSendOption.Hello) 162 | return; 163 | 164 | connection = new UdpServerConnection(this, remoteEndPoint, IPMode); 165 | connections.Add(remoteEndPoint, connection); 166 | } 167 | } 168 | 169 | //Inform the connection of the buffer (new connections need to send an ack back to client) 170 | connection.HandleReceive(buffer); 171 | 172 | //If it's a new connection invoke the NewConnection event. 173 | if (!aware) 174 | { 175 | byte[] dataBuffer = new byte[buffer.Length - 3]; 176 | Buffer.BlockCopy(buffer, 3, dataBuffer, 0, buffer.Length - 3); 177 | InvokeNewConnection(dataBuffer, connection); 178 | } 179 | } 180 | 181 | /// 182 | /// Sends data from the listener socket. 183 | /// 184 | /// The bytes to send. 185 | /// The endpoint to send to. 186 | internal void SendData(byte[] bytes, EndPoint endPoint) 187 | { 188 | try 189 | { 190 | listener.BeginSendTo( 191 | bytes, 192 | 0, 193 | bytes.Length, 194 | SocketFlags.None, 195 | endPoint, 196 | delegate (IAsyncResult result) 197 | { 198 | listener.EndSendTo(result); 199 | }, 200 | null 201 | ); 202 | } 203 | catch (SocketException e) 204 | { 205 | throw new HazelException("Could not send data as a SocketException occured.", e); 206 | } 207 | catch (ObjectDisposedException) 208 | { 209 | //Keep alive timer probably ran, ignore 210 | return; 211 | } 212 | } 213 | 214 | /// 215 | /// Removes a virtual connection from the list. 216 | /// 217 | /// The endpoint of the virtual connection. 218 | internal void RemoveConnectionTo(EndPoint endPoint) 219 | { 220 | lock (connections) 221 | connections.Remove(endPoint); 222 | } 223 | 224 | /// 225 | protected override void Dispose(bool disposing) 226 | { 227 | if (disposing) 228 | listener.Close(); 229 | 230 | base.Dispose(disposing); 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Hazel/Udp/UdpServerConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | 7 | 8 | namespace Hazel.Udp 9 | { 10 | /// 11 | /// Represents a servers's connection to a client that uses the UDP protocol. 12 | /// 13 | /// 14 | sealed class UdpServerConnection : UdpConnection 15 | { 16 | /// 17 | /// The connection listener that we use the socket of. 18 | /// 19 | /// 20 | /// Udp server connections utilize the same socket in the listener for sends/receives, this is the listener that 21 | /// created this connection and is hence the listener this conenction sends and receives via. 22 | /// 23 | public UdpConnectionListener Listener { get; private set; } 24 | 25 | /// 26 | /// Lock object for the writing to the state of the connection. 27 | /// 28 | Object stateLock = new Object(); 29 | 30 | /// 31 | /// Creates a UdpConnection for the virtual connection to the endpoint. 32 | /// 33 | /// The listener that created this connection. 34 | /// The endpoint that we are connected to. 35 | /// The IPMode we are connected using. 36 | internal UdpServerConnection(UdpConnectionListener listener, EndPoint endPoint, IPMode IPMode) 37 | : base() 38 | { 39 | this.Listener = listener; 40 | this.RemoteEndPoint = endPoint; 41 | this.EndPoint = new NetworkEndPoint(endPoint); 42 | this.IPMode = IPMode; 43 | 44 | State = ConnectionState.Connected; 45 | } 46 | 47 | /// 48 | protected override void WriteBytesToConnection(byte[] bytes) 49 | { 50 | lock (stateLock) 51 | { 52 | if (State != ConnectionState.Connected) 53 | throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); 54 | } 55 | 56 | Listener.SendData(bytes, RemoteEndPoint); 57 | } 58 | 59 | /// 60 | /// 61 | /// This will always throw a HazelException. 62 | /// 63 | public override void Connect(byte[] bytes = null, int timeout = 5000) 64 | { 65 | throw new HazelException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?"); 66 | } 67 | 68 | /// 69 | protected override void HandleDisconnect(HazelException e = null) 70 | { 71 | bool invoke = false; 72 | 73 | lock (stateLock) 74 | { 75 | //Only invoke the disconnected event if we're not already disconnecting 76 | if (State == ConnectionState.Connected) 77 | { 78 | State = ConnectionState.Disconnecting; 79 | invoke = true; 80 | } 81 | } 82 | 83 | //Invoke event outide lock if need be 84 | if (invoke) 85 | { 86 | InvokeDisconnected(e); 87 | 88 | Dispose(); 89 | } 90 | } 91 | 92 | /// 93 | protected override void Dispose(bool disposing) 94 | { 95 | //Here we just need to inform the listener we no longer need data. 96 | if (disposing) 97 | { 98 | //Send disconnect message if we're not already disconnecting 99 | bool connected; 100 | 101 | lock (stateLock) 102 | connected = State == ConnectionState.Connected; 103 | 104 | if (connected) 105 | SendDisconnect(); 106 | 107 | Listener.RemoveConnectionTo(RemoteEndPoint); 108 | 109 | lock (stateLock) 110 | State = ConnectionState.NotConnected; 111 | } 112 | 113 | base.Dispose(disposing); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 DarkRift Networking 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This version of Hazel is no longer supported! For the latest Hazel please see [this fork](https://github.com/willardf/Hazel-Networking)! 2 | 3 | ----- 4 | 5 | #### Hazel Networking is a low-level networking library for C# providing connection orientated, message based communication via TCP, UDP and RUDP. 6 | 7 | Its aim is to provide a standardized interface for web communication so that using and switching between protocols is incredibly simple. 8 | 9 | Hazel can be downloaded as a NuGet package [here](https://www.nuget.org/packages/DarkRiftNetworking.Hazel/) or you can get the latest build directly from the releases page [here](/../../releases)! 10 | 11 | ----- 12 | 13 | ## Features 14 | - TCP, UDP, Reliable UDP and (at some point) Web Sockets 15 | - Completely thread safe 16 | - All protocols are connection oriented (similar to TCP) and message based (similar to UDP) 17 | - Standardised interface so that all protocols can be used interchangeably with each other 18 | - IPv4 and IPv6 support 19 | - Automatic statistics about data passing in and out of connections 20 | - Designed to be as fast and leightweight as possible 21 | 22 | ----- 23 | 24 | HTML documentation, tutorials and quickstarts are available on the DarkRift Website [here](http://www.darkriftnetworking.com/Hazel/Docs); support is available through [email](jamie@darkriftnetworking.com) or alternatively Hazel has a thread on the [Unity forum](http://forum.unity3d.com/threads/hazel-networking-open-source-rudp-tcp-library.409863/) where you'll also find updates and other news. 25 | 26 | If you want to make improvements, do so! If you find bugs, raise issues! 27 | 28 | ----- 29 | 30 | ## Building Hazel 31 | 32 | To build Hazel open [solution file](Hazel.sln) using your favourite C# IDE (I use Visual Studio 2015) and then build as you would any other project. 33 | --------------------------------------------------------------------------------