├── .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 |
--------------------------------------------------------------------------------