作为c#程序员,没有精力和激情去学习java了,又遇到一些项目需要开发手机端,于是我们的networkcomms2.3.1网络通讯框架又要出场了,是的,这是一款来自英国的网络通讯框架,由c#语言编写,其在编写时根据不用的应用环境,写了不同的代码,支持安卓,平果,winphone等平台开发。
找了一个类大家看看他的书写方法 ,使用预编译语句,编写针对不同系统的代码
1 public static class NetworkComms
2 {
3 /// <summary>
4 /// Static constructor which sets comm default values
5 /// </summary>
6 static NetworkComms()
7 {
8 //Generally comms defaults are defined here
9 NetworkIdentifier = ShortGuid.NewGuid();
10 NetworkLoadUpdateWindowMS = 2000;
11
12 InterfaceLinkSpeed = 95000000;
13
14 DefaultListenPort = 10000;
15 ListenOnAllAllowedInterfaces = true;
16
17 CheckSumMismatchSentPacketCacheMaxByteLimit = 75000;
18 MinimumSentPacketCacheTimeMinutes = 1;
19
20 ConnectionEstablishTimeoutMS = 10000;
21 PacketConfirmationTimeoutMS = 5000;
22 ConnectionAliveTestTimeoutMS = 1000;
23
24 #if SILVERLIGHT || WINDOWS_PHONE
25 CurrentRuntimeEnvironment = RuntimeEnvironment.WindowsPhone_Silverlight;
26 SendBufferSizeBytes = ReceiveBufferSizeBytes = 8000;
27 #elif iOS
28 CurrentRuntimeEnvironment = RuntimeEnvironment.Xamarin_iOS;
29 SendBufferSizeBytes = ReceiveBufferSizeBytes = 8000;
30 #elif ANDROID
31 CurrentRuntimeEnvironment = RuntimeEnvironment.Xamarin_Android;
32 SendBufferSizeBytes = ReceiveBufferSizeBytes = 8000;
33 #elif NET2
34 if (Type.GetType("Mono.Runtime") != null)
35 {
36 CurrentRuntimeEnvironment = RuntimeEnvironment.Mono_Net2;
37 //Mono send buffer smaller as different large object heap limit
38 SendBufferSizeBytes = ReceiveBufferSizeBytes = 8000;
39 }
40 else
41 {
42 CurrentRuntimeEnvironment = RuntimeEnvironment.Native_Net2;
43 SendBufferSizeBytes = ReceiveBufferSizeBytes = 80000;
44 }
45 #elif NET35
46 if (Type.GetType("Mono.Runtime") != null)
47 {
48 CurrentRuntimeEnvironment = RuntimeEnvironment.Mono_Net35;
49 //Mono send buffer smaller as different large object heap limit
50 SendBufferSizeBytes = ReceiveBufferSizeBytes = 8000;
51 }
52 else
53 {
54 CurrentRuntimeEnvironment = RuntimeEnvironment.Native_Net35;
55 SendBufferSizeBytes = ReceiveBufferSizeBytes = 80000;
56 }
57 #else
58 if (Type.GetType("Mono.Runtime") != null)
59 {
60 CurrentRuntimeEnvironment = RuntimeEnvironment.Mono_Net4;
61 //Mono send buffer smaller as different large object heap limit
62 SendBufferSizeBytes = ReceiveBufferSizeBytes = 8000;
63 }
64 else
65 {
66 CurrentRuntimeEnvironment = RuntimeEnvironment.Native_Net4;
67 SendBufferSizeBytes = ReceiveBufferSizeBytes = 80000;
68 }
69 #endif
70
71 //We want to instantiate our own thread pool here
72 CommsThreadPool = new CommsThreadPool(1, Environment.ProcessorCount*2, Environment.ProcessorCount * 20, new TimeSpan(0, 0, 10));
73
74 //Initialise the core extensions
75 DPSManager.AddDataSerializer<ProtobufSerializer>();
76
77 DPSManager.AddDataSerializer<NullSerializer>();
78 DPSManager.AddDataProcessor<SevenZipLZMACompressor.LZMACompressor>();
79
80 #if !FREETRIAL
81 //Only the full version includes the encrypter
82 DPSManager.AddDataProcessor<RijndaelPSKEncrypter>();
83 #endif
84
85 #if !WINDOWS_PHONE
86 DPSManager.AddDataSerializer<BinaryFormaterSerializer>();
87 #endif
88
89 InternalFixedSendReceiveOptions = new SendReceiveOptions(DPSManager.GetDataSerializer<ProtobufSerializer>(),
90 new List<DataProcessor>(),
91 new Dictionary<string, string>());
92
93 DefaultSendReceiveOptions = new SendReceiveOptions(DPSManager.GetDataSerializer<ProtobufSerializer>(),
94 new List<DataProcessor>() { DPSManager.GetDataProcessor<SevenZipLZMACompressor.LZMACompressor>() },
95 new Dictionary<string, string>());
96 }
97
98 #region Local Host Information
99 /// <summary>
100 /// Returns the current machine hostname
101 /// </summary>
102 public static string HostName
103 {
104 get
105 {
106 #if WINDOWS_PHONE
107 return Windows.Networking.Connectivity.NetworkInformation.GetInternetConnectionProfile().ToString();
108 #else
109 return Dns.GetHostName();
110 #endif
111 }
112 }
113
114 /// <summary>
115 /// If set NetworkCommsDotNet will only operate on matching IP Addresses. Also see <see cref="AllowedAdaptorNames"/>.
116 /// Correct format is string[] { "192.168", "213.111.10" }. If multiple prefixes are provided the earlier prefix, if found, takes priority.
117 /// </summary>
118 public static string[] AllowedIPPrefixes { get; set; }
119
120 /// <summary>
121 /// If set NetworkCommsDotNet will only operate on specified adaptors. Correct format is string[] { "eth0", "en0", "wlan0" }.
122 /// </summary>
123 public static string[] AllowedAdaptorNames { get; set; }
124
125 /// <summary>
126 /// Returns all allowed local IP addresses.
127 /// If <see cref="AllowedAdaptorNames"/> has been set only returns IP addresses corresponding with specified adaptors.
128 /// If <see cref="AllowedIPPrefixes"/> has been set only returns matching addresses ordered in descending preference. i.e. Most preffered at [0].
129 /// </summary>
130 /// <returns></returns>
131 public static List<IPAddress> AllAllowedIPs()
132 {
133
134 #if WINDOWS_PHONE
135 //On windows phone we simply ignore ip addresses from the autoassigned range as well as those without a valid prefix
136 List<IPAddress> allowedIPs = new List<IPAddress>();
137
138 foreach (var hName in Windows.Networking.Connectivity.NetworkInformation.GetHostNames())
139 {
140 if (!hName.DisplayName.StartsWith("169.254"))
141 {
142 if (AllowedIPPrefixes != null)
143 {
144 bool valid = false;
145
146 for (int i = 0; i < AllowedIPPrefixes.Length; i++)
147 valid |= hName.DisplayName.StartsWith(AllowedIPPrefixes[i]);
148
149 if(valid)
150 allowedIPs.Add(IPAddress.Parse(hName.DisplayName));
151 }
152 else
153 allowedIPs.Add(IPAddress.Parse(hName.DisplayName));
154 }
155 }
156
157 return allowedIPs;
158 #else
159
160 //We want to ignore IP's that have been autoassigned
161 //169.254.0.0
162 IPAddress autoAssignSubnetv4 = new IPAddress(new byte[] { 169, 254, 0, 0 });
163 //255.255.0.0
164 IPAddress autoAssignSubnetMaskv4 = new IPAddress(new byte[] { 255, 255, 0, 0 });
165
166 List<IPAddress> validIPAddresses = new List<IPAddress>();
167 IPComparer comparer = new IPComparer();
168
169 #if ANDROID
170
171 var iFaces = Java.Net.NetworkInterface.NetworkInterfaces;
172 while (iFaces.HasMoreElements)
173 {
174 bool interfaceValid = false;
175 var iFace = iFaces.NextElement() as Java.Net.NetworkInterface;
176 var javaAddresses = iFace.InetAddresses;
177
178 while (javaAddresses.HasMoreElements)
179 {
180 var javaAddress = javaAddresses.NextElement() as Java.Net.InetAddress;
181 IPAddress address = default(IPAddress);
182 if (IPAddress.TryParse(javaAddress.HostAddress, out address))
183 {
184 if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
185 {
186 if (AllowedAdaptorNames != null)
187 {
188 foreach (var id in AllowedAdaptorNames)
189 if (id == iFace.Name)
190 {
191 interfaceValid = true;
192 break;
193 }
194 }
195 else
196 interfaceValid = true;
197
198 if (interfaceValid)
199 break;
200 }
201 }
202 }
203
204 if (!interfaceValid)
205 continue;
206
207 javaAddresses = iFace.InetAddresses;
208
209 while (javaAddresses.HasMoreElements)
210 {
211 var javaAddress = javaAddresses.NextElement() as Java.Net.InetAddress;
212 IPAddress address = default(IPAddress);
213
214 if (IPAddress.TryParse(javaAddress.HostAddress, out address))
215 {
216 if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
217 {
218 if (!IsAddressInSubnet(address, autoAssignSubnetv4, autoAssignSubnetMaskv4))
219 {
220 bool allowed = false;
221
222 if (AllowedAdaptorNames != null)
223 {
224 foreach (var id in AllowedAdaptorNames)
225 {
226 if (id == iFace.Name)
227 {
228 allowed = true;
229 break;
230 }
231 }
232 }
233 else
234 allowed = true;
235
236 if (!allowed)
237 continue;
238
239 allowed = false;
240
241 if (AllowedIPPrefixes != null)
242 {
243 foreach (var ip in AllowedIPPrefixes)
244 {
245 if (comparer.Equals(address.ToString(), ip))
246 {
247 allowed = true;
248 break;
249 }
250 }
251 }
252 else
253 allowed = true;
254
255 if (!allowed)
256 continue;
257
258 if (address != IPAddress.None)
259 validIPAddresses.Add(address);
260 }
261 }
262 }
263 }
264 }
265
266 #else
267
268
269 foreach (var iFace in NetworkInterface.GetAllNetworkInterfaces())
270 {
271 bool interfaceValid = false;
272 var unicastAddresses = iFace.GetIPProperties().UnicastAddresses;
273
274 foreach (var address in unicastAddresses)
275 {
276 if (address.Address.AddressFamily == AddressFamily.InterNetwork || address.Address.AddressFamily == AddressFamily.InterNetworkV6)
277 {
278 if (AllowedAdaptorNames != null)
279 {
280 foreach (var id in AllowedAdaptorNames)
281 if (iFace.Id == id)
282 {
283 interfaceValid = true;
284 break;
285 }
286 }
287 else
288 interfaceValid = true;
289
290 if (interfaceValid)
291 break;
292 }
293 }
294
295 if (!interfaceValid)
296 continue;
297
298 foreach (var address in unicastAddresses)
299 {
300 var addressInformation = address.Address;
301 if (addressInformation.AddressFamily == AddressFamily.InterNetwork || addressInformation.AddressFamily == AddressFamily.InterNetworkV6)
302 {
303 if (!IsAddressInSubnet(addressInformation, autoAssignSubnetv4, autoAssignSubnetMaskv4))
304 {
305 bool allowed = false;
306
307 if (AllowedAdaptorNames != null)
308 {
309 foreach (var id in AllowedAdaptorNames)
310 {
311 if(id == iFace.Id)
312 {
313 allowed = true;
314 break;
315 }
316 }
317 }
318 else
319 allowed = true;
320
321 if (!allowed)
322 continue;
323
324 allowed = false;
325
326 if (AllowedIPPrefixes != null)
327 {
328 foreach (var ip in AllowedIPPrefixes)
329 {
330 if (comparer.Equals(addressInformation.ToString(), ip))
331 {
332 allowed = true;
333 break;
334 }
335 }
336 }
337 else
338 allowed = true;
339
340 if (!allowed)
341 continue;
342
343 if (addressInformation != IPAddress.None)
344 validIPAddresses.Add(addressInformation);
345 }
346 }
347 }
348 }
349 #endif
350
351 if (AllowedIPPrefixes != null)
352 {
353 validIPAddresses.Sort((a, b) =>
354 {
355 for (int i = 0; i < AllowedIPPrefixes.Length; i++)
356 {
357 if (a.ToString().StartsWith(AllowedIPPrefixes[i]))
358 {
359 if (b.ToString().StartsWith(AllowedIPPrefixes[i]))
360 return 0;
361 else
362 return -1;
363 }
364 else if (b.ToString().StartsWith(AllowedIPPrefixes[i]))
365 return 1;
366 }
367
368 return 0;
369 });
370 }
371
372 return validIPAddresses;
373 #endif
374 }
375
376 /// <summary>
377 /// Custom comparer for IP addresses. Used by <see cref="AllAllowedIPs"/>
378 /// </summary>
379 class IPComparer : IEqualityComparer<string>
380 {
381 // Products are equal if their names and product numbers are equal.
382 public bool Equals(string x, string y)
383 {
384 //Check whether the compared objects reference the same data.
385 if (Object.ReferenceEquals(x, y)) return true;
386
387 //Check whether any of the compared objects is null.
388 if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
389 return false;
390
391 return (y.StartsWith(x) || x.StartsWith(y));
392 }
393
394 // If Equals() returns true for a pair of objects
395 // then GetHashCode() must return the same value for these objects.
396 public int GetHashCode(string ipAddress)
397 {
398 return ipAddress.GetHashCode();
399 }
400 }
401
402 /// <summary>
403 /// Returns true if the provided address exists within the provided subnet.
404 /// </summary>
405 /// <param name="address">The address to check, i.e. 192.168.0.10</param>
406 /// <param name="subnet">The subnet, i.e. 192.168.0.0</param>
407 /// <param name="mask">The subnet mask, i.e. 255.255.255.0</param>
408 /// <returns>True if address is in the provided subnet</returns>
409 public static bool IsAddressInSubnet(IPAddress address, IPAddress subnet, IPAddress mask)
410 {
411 if (address == null) throw new ArgumentNullException("address", "Provided IPAddress cannot be null.");
412 if (subnet == null) throw new ArgumentNullException("subnet", "Provided IPAddress cannot be null.");
413 if (mask == null) throw new ArgumentNullException("mask", "Provided IPAddress cannot be null.");
414
415 //Catch for IPv6
416 if (subnet.AddressFamily == AddressFamily.InterNetworkV6 ||
417 mask.AddressFamily == AddressFamily.InterNetworkV6)
418 throw new NotImplementedException("This method does not yet support IPv6. Please contact NetworkComms.Net support if you would like this functionality.");
419 //If we have provided IPV4 subnets and masks and we have an ipv6 address then return false
420 else if (address.AddressFamily == AddressFamily.InterNetworkV6)
421 return false;
422
423 byte[] addrBytes = address.GetAddressBytes();
424 byte[] maskBytes = mask.GetAddressBytes();
425 byte[] maskedAddressBytes = new byte[addrBytes.Length];
426
427 //Catch for IPv6
428 if (maskBytes.Length < maskedAddressBytes.Length)
429 return false;
430
431 for (int i = 0; i < maskedAddressBytes.Length; ++i)
432 maskedAddressBytes[i] = (byte)(addrBytes[i] & maskBytes[i]);
433
434 IPAddress maskedAddress = new IPAddress(maskedAddressBytes);
435 bool equal = subnet.Equals(maskedAddress);
436
437 return equal;
438 }
439
440 /// <summary>
441 /// The default port NetworkCommsDotNet will operate on
442 /// </summary>
443 public static int DefaultListenPort { get; set; }
444
445 /// <summary>
446 /// The local identifier for this instance of NetworkCommsDotNet. This is an application unique identifier.
447 /// </summary>
448 public static ShortGuid NetworkIdentifier { get; private set; }
449
450 /// <summary>
451 /// The current runtime environment. Detected automatically on startup. Performance may be adversly affected if this is changed.
452 /// </summary>
453 public static RuntimeEnvironment CurrentRuntimeEnvironment { get; set; }
454
455 /// <summary>
456 /// An internal random object
457 /// </summary>
458 internal static Random randomGen = new Random();
459
460 /// <summary>
461 /// A single boolean used to control a NetworkCommsDotNet shutdown
462 /// </summary>
463 internal static volatile bool commsShutdown;
464
465 /// <summary>
466 /// A running total of the number of packets sent on all connections. Used to initialise packet sequence counters to ensure duplicates can not occur.
467 /// </summary>
468 internal static long totalPacketSendCount;
469
470 /// <summary>
471 /// The number of millisconds over which to take an instance load (CurrentNetworkLoad) to be used in averaged values (AverageNetworkLoad).
472 /// Default is 2000ms. Shorter values can be used but less than 200ms may cause significant errors in the value of returned value, especially in mono environments.
473 /// </summary>
474 public static int NetworkLoadUpdateWindowMS { get; set; }
475
476 private static double currentNetworkLoadIncoming;
477 private static double currentNetworkLoadOutgoing;
478 #if !WINDOWS_PHONE && !ANDROID
479 private static Thread NetworkLoadThread = null;
480 private static CommsMath currentNetworkLoadValuesIncoming;
481 private static CommsMath currentNetworkLoadValuesOutgoing;
482 private static ManualResetEvent NetworkLoadThreadWait;
483 #endif
484
485 /// <summary>
486 /// The interface link speed in bits/sec used for network load calculations. Default is 100Mb/sec
487 /// </summary>
488 public static long InterfaceLinkSpeed { get; set; }
489
490 /// <summary>
491 /// Returns the current instance network usage, as a value between 0 and 1. Returns the largest value for any available network adaptor. Triggers load analysis upon first call.
492 /// </summary>
493 public static double CurrentNetworkLoadIncoming
494 {
495 get
496 {
497 #if !WINDOWS_PHONE && !ANDROID
498 //We start the load thread when we first access the network load
499 //this helps cut down on uncessary threads if unrequired
500 if (!commsShutdown && NetworkLoadThread == null)
501 {
502 lock (globalDictAndDelegateLocker)
503 {
504 if (!commsShutdown && NetworkLoadThread == null)
505 {
506 currentNetworkLoadValuesIncoming = new CommsMath();
507 currentNetworkLoadValuesOutgoing = new CommsMath();
508
509 NetworkLoadThread = new Thread(NetworkLoadWorker);
510 NetworkLoadThread.Name = "NetworkLoadThread";
511 NetworkLoadThread.Start();
512 }
513 }
514 }
515 #endif
516 return currentNetworkLoadIncoming;
517 }
518 private set { currentNetworkLoadIncoming = value; }
519 }
520
521 /// <summary>
522 /// Returns the current instance network usage, as a value between 0 and 1. Returns the largest value for any available network adaptor. Triggers load analysis upon first call.
523 /// </summary>
524 public static double CurrentNetworkLoadOutgoing
525 {
526 get
527 {
528 #if !WINDOWS_PHONE && !ANDROID
529 //We start the load thread when we first access the network load
530 //this helps cut down on uncessary threads if unrequired
531 if (!commsShutdown && NetworkLoadThread == null)
532 {
533 lock (globalDictAndDelegateLocker)
534 {
535 if (!commsShutdown && NetworkLoadThread == null)
536 {
537 currentNetworkLoadValuesIncoming = new CommsMath();
538 currentNetworkLoadValuesOutgoing = new CommsMath();
539
540 NetworkLoadThread = new Thread(NetworkLoadWorker);
541 NetworkLoadThread.Name = "NetworkLoadThread";
542 NetworkLoadThread.Start();
543 }
544 }
545 }
546 #endif
547 return currentNetworkLoadOutgoing;
548 }
549 private set { currentNetworkLoadOutgoing = value; }
550 }
551
552 /// <summary>
553 /// Returns the averaged value of CurrentNetworkLoadIncoming, as a value between 0 and 1, for a time window of upto 254 seconds. Triggers load analysis upon first call.
554 /// </summary>
555 /// <param name="secondsToAverage">Number of seconds over which historial data should be used to arrive at an average</param>
556 /// <returns>Average network load as a double between 0 and 1</returns>
557 public static double AverageNetworkLoadIncoming(byte secondsToAverage)
558 {
559 #if !WINDOWS_PHONE && !ANDROID
560
561 if (!commsShutdown && NetworkLoadThread == null)
562 {
563 lock (globalDictAndDelegateLocker)
564 {
565 if (!commsShutdown && NetworkLoadThread == null)
566 {
567 currentNetworkLoadValuesIncoming = new CommsMath();
568 currentNetworkLoadValuesOutgoing = new CommsMath();
569
570 NetworkLoadThread = new Thread(NetworkLoadWorker);
571 NetworkLoadThread.Name = "NetworkLoadThread";
572 NetworkLoadThread.Start();
573 }
574 }
575 }
576
577 return currentNetworkLoadValuesIncoming.CalculateMean((int)((secondsToAverage * 1000.0) / NetworkLoadUpdateWindowMS));
578 #else
579 return 0;
580 #endif
581 }
582
583 /// <summary>
584 /// Returns the averaged value of CurrentNetworkLoadIncoming, as a value between 0 and 1, for a time window of upto 254 seconds. Triggers load analysis upon first call.
585 /// </summary>
586 /// <param name="secondsToAverage">Number of seconds over which historial data should be used to arrive at an average</param>
587 /// <returns>Average network load as a double between 0 and 1</returns>
588 public static double AverageNetworkLoadOutgoing(byte secondsToAverage)
589 {
590 #if !WINDOWS_PHONE && !ANDROID
591 if (!commsShutdown && NetworkLoadThread == null)
592 {
593 lock (globalDictAndDelegateLocker)
594 {
595 if (!commsShutdown && NetworkLoadThread == null)
596 {
597 currentNetworkLoadValuesIncoming = new CommsMath();
598 currentNetworkLoadValuesOutgoing = new CommsMath();
599
600 NetworkLoadThread = new Thread(NetworkLoadWorker);
601 NetworkLoadThread.Name = "NetworkLoadThread";
602 NetworkLoadThread.Start();
603 }
604 }
605 }
606
607 return currentNetworkLoadValuesOutgoing.CalculateMean((int)((secondsToAverage * 1000.0) / NetworkLoadUpdateWindowMS));
608 #else
609 return 0;
610 #endif
611 }
612
613 /// <summary>
614 /// Determines the most appropriate local end point to contact the provided remote end point.
615 /// Testing shows this method takes on average 1.6ms to return.
616 /// </summary>
617 /// <param name="remoteIPEndPoint">The remote end point</param>
618 /// <returns>The selected local end point</returns>
619 public static IPEndPoint BestLocalEndPoint(IPEndPoint remoteIPEndPoint)
620 {
621 if (remoteIPEndPoint == null) throw new ArgumentNullException("remoteIPEndPoint", "Provided IPEndPoint cannot be null.");
622
623 #if WINDOWS_PHONE
624 var t = Windows.Networking.Sockets.DatagramSocket.GetEndpointPairsAsync(new Windows.Networking.HostName(remoteIPEndPoint.Address.ToString()), remoteIPEndPoint.Port.ToString()).AsTask();
625 if (t.Wait(20) && t.Result.Count > 0)
626 {
627 var enumerator = t.Result.GetEnumerator();
628 enumerator.MoveNext();
629
630 var endpointPair = enumerator.Current;
631 return new IPEndPoint(IPAddress.Parse(endpointPair.LocalHostName.DisplayName.ToString()), int.Parse(endpointPair.LocalServiceName));
632 }
633 else
634 throw new ConnectionSetupException("Unable to determine correct local end point.");
635 #else
636 //We use UDP as its connectionless hence faster
637 IPEndPoint result;
638 using (Socket testSocket = new Socket(remoteIPEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp))
639 {
640 testSocket.Connect(remoteIPEndPoint);
641 result = (IPEndPoint)testSocket.LocalEndPoint;
642 }
643
644 return result;
645 #endif
646 }
647
648 #if !WINDOWS_PHONE && !ANDROID
649 /// <summary>
650 /// Takes a network load snapshot (CurrentNetworkLoad) every NetworkLoadUpdateWindowMS
651 /// </summary>
652 private static void NetworkLoadWorker()
653 {
654 NetworkLoadThreadWait = new ManualResetEvent(false);
655
656 //Get all interfaces
657 NetworkInterface[] interfacesToUse = NetworkInterface.GetAllNetworkInterfaces();
658
659 long[] startSent, startReceived, endSent, endReceived;
660
661 while (!commsShutdown)
662 {
663 try
664 {
665 //we need to look at the load across all adaptors, by default we will probably choose the adaptor with the highest usage
666 DateTime startTime = DateTime.Now;
667
668 IPv4InterfaceStatistics[] stats = new IPv4InterfaceStatistics[interfacesToUse.Length];
669 startSent = new long[interfacesToUse.Length];
670 startReceived = new long[interfacesToUse.Length];
671
672 for (int i = 0; i < interfacesToUse.Length; ++i)
673 {
674 stats[i] = interfacesToUse[i].GetIPv4Statistics();
675 startSent[i] = stats[i].BytesSent;
676 startReceived[i] = stats[i].BytesReceived;
677 }
678
679 if (commsShutdown) return;
680
681 //Thread.Sleep(NetworkLoadUpdateWindowMS);
682 NetworkLoadThreadWait.WaitOne(NetworkLoadUpdateWindowMS);
683
684 if (commsShutdown) return;
685
686 stats = new IPv4InterfaceStatistics[interfacesToUse.Length];
687 endSent = new long[interfacesToUse.Length];
688 endReceived = new long[interfacesToUse.Length];
689
690 for (int i = 0; i < interfacesToUse.Length; ++i)
691 {
692 stats[i] = interfacesToUse[i].GetIPv4Statistics();
693 endSent[i] = stats[i].BytesSent;
694 endReceived[i] = stats[i].BytesReceived;
695 }
696
697 DateTime endTime = DateTime.Now;
698
699 List<double> outUsage = new List<double>();
700 List<double> inUsage = new List<double>();
701 for(int i=0; i<startSent.Length; i++)
702 {
703 outUsage.Add((double)(endSent[i] - startSent[i]) / ((double)(InterfaceLinkSpeed * (endTime - startTime).TotalMilliseconds) / 8000));
704 inUsage.Add((double)(endReceived[i] - startReceived[i]) / ((double)(InterfaceLinkSpeed * (endTime - startTime).TotalMilliseconds) / 8000));
705 }
706
707 //double loadValue = Math.Max(outUsage.Max(), inUsage.Max());
708 double inMax = double.MinValue, outMax = double.MinValue;
709 for (int i = 0; i < startSent.Length; ++i)
710 {
711 if (inUsage[i] > inMax) inMax = inUsage[i];
712 if (outUsage[i] > outMax) outMax = outUsage[i];
713 }
714
715 //If either of the usage levels have gone above 2 it suggests we are most likely on a faster connection that we think
716 //As such we will bump the interfacelinkspeed upto 1Gbps so that future load calcualtions more acurately reflect the
717 //actual load.
718 if (inMax > 2 || outMax > 2) InterfaceLinkSpeed = 950000000;
719
720 //Limit to one
721 CurrentNetworkLoadIncoming = (inMax > 1 ? 1 : inMax);
722 CurrentNetworkLoadOutgoing = (outMax > 1 ? 1 : outMax);
723
724 currentNetworkLoadValuesIncoming.AddValue(CurrentNetworkLoadIncoming);
725 currentNetworkLoadValuesOutgoing.AddValue(CurrentNetworkLoadOutgoing);
726
727 //We can only have upto 255 seconds worth of data in the average list
728 int maxListSize = (int)(255000.0 / NetworkLoadUpdateWindowMS);
729 currentNetworkLoadValuesIncoming.TrimList(maxListSize);
730 currentNetworkLoadValuesOutgoing.TrimList(maxListSize);
731 }
732 catch (Exception ex)
733 {
734 LogError(ex, "NetworkLoadWorker");
735
736 //It may be the interfaces available to the OS have changed so we will reset them here
737 interfacesToUse = NetworkInterface.GetAllNetworkInterfaces();
738 //If an error has happened we dont want to thrash the problem, we wait for 5 seconds and hope whatever was wrong goes away
739 Thread.Sleep(5000);
740 }
741 }
742 }
743 #endif
744 #endregion
745
746 #region Established Connections
747 /// <summary>
748 /// Locker for connection dictionaries
749 /// </summary>
750 internal static object globalDictAndDelegateLocker = new object();
751
752 /// <summary>
753 /// Primary connection dictionary stored by network indentifier
754 /// </summary>
755 internal static Dictionary<ShortGuid, Dictionary<ConnectionType, List<Connection>>> allConnectionsById = new Dictionary<ShortGuid, Dictionary<ConnectionType, List<Connection>>>();
756
757 /// <summary>
758 /// Secondary connection dictionary stored by ip end point. Allows for quick cross referencing.
759 /// </summary>
760 internal static Dictionary<IPEndPoint, Dictionary<ConnectionType, Connection>> allConnectionsByEndPoint = new Dictionary<IPEndPoint, Dictionary<ConnectionType, Connection>>();
761
762 /// <summary>
763 /// Old connection cache so that requests for connectionInfo can be returned even after a connection has been closed.
764 /// </summary>
765 internal static Dictionary<ShortGuid, Dictionary<ConnectionType, List<ConnectionInfo>>> oldNetworkIdentifierToConnectionInfo = new Dictionary<ShortGuid, Dictionary<ConnectionType, List<ConnectionInfo>>>();
766 #endregion
767
768 #region Incoming Data and Connection Config
769 /// <summary>
770 /// Used for switching between async and sync connectionListen modes. Default is false. No noticable performance difference between the two modes.
771 /// </summary>
772 public static bool ConnectionListenModeUseSync { get; set; }
773
774 /// <summary>
775 /// Used for switching between listening on a single interface or multiple interfaces. Default is true. See <see cref="AllowedIPPrefixes"/> and <see cref="AllowedAdaptorNames"/>
776 /// </summary>
777 public static bool ListenOnAllAllowedInterfaces { get; set; }
778
779 /// <summary>
780 /// Receive data buffer size. Default is 80KB. CAUTION: Changing the default value can lead to performance degredation.
781 /// </summary>
782 public static int ReceiveBufferSizeBytes { get; set; }
783
784 /// <summary>
785 /// Send data buffer size. Default is 80KB. CAUTION: Changing the default value can lead to performance degredation.
786 /// </summary>
787 public static int SendBufferSizeBytes { get; set; }
788
789 /// <summary>
790 /// The threadpool used by networkComms.Net to execute incoming packet handlers.
791 /// </summary>
792 public static CommsThreadPool CommsThreadPool { get; set; }
793
794 /// <summary>
795 /// Once we have received all incoming data we handle it further. This is performed at the global level to help support different priorities.
796 /// </summary>
797 /// <param name="itemAsObj">Possible PriorityQueueItem. If null is provided an item will be removed from the global item queue</param>
798 internal static void CompleteIncomingItemTask(object itemAsObj)
799 {
800 if (itemAsObj == null)
801 throw new ArgumentNullException("itemAsObj", "Provided parameter itemAsObj cannot be null.");
802
803 PriorityQueueItem item = null;
804 try
805 {
806 //If the packetBytes are null we need to ask the incoming packet queue for what we should be running
807 item = itemAsObj as PriorityQueueItem;
808
809 if (item == null)
810 throw new InvalidCastException("Cast from object to PriorityQueueItem resulted in null reference, unable to continue.");
811
812 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Handling a " + item.PacketHeader.PacketType + " packet from " + item.Connection.ConnectionInfo + " with a priority of " + item.Priority.ToString() + ".");
813
814 #if !WINDOWS_PHONE
815 if (Thread.CurrentThread.Priority != (ThreadPriority)item.Priority) Thread.CurrentThread.Priority = (ThreadPriority)item.Priority;
816 #endif
817
818 //Check for a shutdown connection
819 if (item.Connection.ConnectionInfo.ConnectionState == ConnectionState.Shutdown) return;
820
821 //We only look at the check sum if we want to and if it has been set by the remote end
822 if (NetworkComms.EnablePacketCheckSumValidation && item.PacketHeader.ContainsOption(PacketHeaderStringItems.CheckSumHash))
823 {
824 var packetHeaderHash = item.PacketHeader.GetOption(PacketHeaderStringItems.CheckSumHash);
825
826 //Validate the checkSumhash of the data
827 string packetDataSectionMD5 = NetworkComms.MD5Bytes(item.DataStream);
828 if (packetHeaderHash != packetDataSectionMD5)
829 {
830 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Warn(" ... corrupted packet detected, expected " + packetHeaderHash + " but received " + packetDataSectionMD5 + ".");
831
832 //We have corruption on a resend request, something is very wrong so we throw an exception.
833 if (item.PacketHeader.PacketType == Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.CheckSumFailResend)) throw new CheckSumException("Corrupted md5CheckFailResend packet received.");
834
835 if (item.PacketHeader.PayloadPacketSize < NetworkComms.CheckSumMismatchSentPacketCacheMaxByteLimit)
836 {
837 //Instead of throwing an exception we can request the packet to be resent
838 Packet returnPacket = new Packet(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.CheckSumFailResend), packetHeaderHash, NetworkComms.InternalFixedSendReceiveOptions);
839 item.Connection.SendPacket(returnPacket);
840 //We need to wait for the packet to be resent before going further
841 return;
842 }
843 else
844 throw new CheckSumException("Corrupted packet detected from " + item.Connection.ConnectionInfo + ", expected " + packetHeaderHash + " but received " + packetDataSectionMD5 + ".");
845 }
846 }
847
848 //Remote end may have requested packet receive confirmation so we send that now
849 if (item.PacketHeader.ContainsOption(PacketHeaderStringItems.ReceiveConfirmationRequired))
850 {
851 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace(" ... sending requested receive confirmation packet.");
852
853 var hash = item.PacketHeader.ContainsOption(PacketHeaderStringItems.CheckSumHash) ? item.PacketHeader.GetOption(PacketHeaderStringItems.CheckSumHash) : "";
854
855 Packet returnPacket = new Packet(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Confirmation), hash, NetworkComms.InternalFixedSendReceiveOptions);
856 item.Connection.SendPacket(returnPacket);
857 }
858
859 //We can now pass the data onto the correct delegate
860 //First we have to check for our reserved packet types
861 //The following large sections have been factored out to make reading and debugging a little easier
862 if (item.PacketHeader.PacketType == Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.CheckSumFailResend))
863 item.Connection.CheckSumFailResendHandler(item.DataStream);
864 else if (item.PacketHeader.PacketType == Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.ConnectionSetup))
865 item.Connection.ConnectionSetupHandler(item.DataStream);
866 else if (item.PacketHeader.PacketType == Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.AliveTestPacket) &&
867 (NetworkComms.InternalFixedSendReceiveOptions.DataSerializer.DeserialiseDataObject<byte[]>(item.DataStream,
868 NetworkComms.InternalFixedSendReceiveOptions.DataProcessors,
869 NetworkComms.InternalFixedSendReceiveOptions.Options))[0] == 0)
870 {
871 //If we have received a ping packet from the originating source we reply with true
872 Packet returnPacket = new Packet(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.AliveTestPacket), new byte[1] { 1 }, NetworkComms.InternalFixedSendReceiveOptions);
873 item.Connection.SendPacket(returnPacket);
874 }
875
876 //We allow users to add their own custom handlers for reserved packet types here
877 //else
878 if (true)
879 {
880 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Triggering handlers for packet of type '" + item.PacketHeader.PacketType + "' from " + item.Connection.ConnectionInfo);
881
882 //We trigger connection specific handlers first
883 bool connectionSpecificHandlersTriggered = item.Connection.TriggerSpecificPacketHandlers(item.PacketHeader, item.DataStream, item.SendReceiveOptions);
884
885 //We trigger global handlers second
886 NetworkComms.TriggerGlobalPacketHandlers(item.PacketHeader, item.Connection, item.DataStream, item.SendReceiveOptions, connectionSpecificHandlersTriggered);
887
888 //This is a really bad place to put a garbage collection, comment left in so that it doesn't get added again at some later date
889 //We don't want the CPU to JUST be trying to garbage collect the WHOLE TIME
890 //GC.Collect();
891 }
892 }
893 catch (CommunicationException)
894 {
895 if (item != null)
896 {
897 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Fatal("A communcation exception occured in CompleteIncomingPacketWorker(), connection with " + item.Connection.ConnectionInfo + " be closed.");
898 item.Connection.CloseConnection(true, 2);
899 }
900 }
901 catch (DuplicateConnectionException ex)
902 {
903 if (item != null)
904 {
905 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Warn(ex.Message != null ? ex.Message : "A possible duplicate connection was detected with " + item.Connection + ". Closing connection.");
906 item.Connection.CloseConnection(true, 42);
907 }
908 }
909 catch (Exception ex)
910 {
911 NetworkComms.LogError(ex, "CompleteIncomingItemTaskError");
912
913 if (item != null)
914 {
915 //If anything goes wrong here all we can really do is log the exception
916 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Fatal("An unhandled exception occured in CompleteIncomingPacketWorker(), connection with " + item.Connection.ConnectionInfo + " be closed. See log file for more information.");
917 item.Connection.CloseConnection(true, 3);
918 }
919 }
920 finally
921 {
922 //We need to dispose the data stream correctly
923 if (item!=null) item.DataStream.Close();
924
925 #if !WINDOWS_PHONE
926 //Ensure the thread returns to the pool with a normal priority
927 if (Thread.CurrentThread.Priority != ThreadPriority.Normal) Thread.CurrentThread.Priority = ThreadPriority.Normal;
928 #endif
929 }
930 }
931 #endregion
932
933 #if !WINDOWS_PHONE
934 #region High CPU Usage Tuning
935 /// <summary>
936 /// In times of high CPU usage we need to ensure that certain time critical functions, like connection handshaking do not timeout.
937 /// This sets the thread priority for those processes.
938 /// </summary>
939 internal static ThreadPriority timeCriticalThreadPriority = ThreadPriority.AboveNormal;
940 #endregion
941 #endif
942
943 #region Checksum Config
944 /// <summary>
945 /// When enabled uses an MD5 checksum to validate all received packets. Default is false, relying on any possible connection checksum alone.
946 /// Also when enabled any packets sent less than CheckSumMismatchSentPacketCacheMaxByteLimit will be cached for a duration to ensure successful delivery.
947 /// Default false.
948 /// </summary>
949 public static bool EnablePacketCheckSumValidation { get; set; }
950
951 /// <summary>
952 /// When checksum validation is enabled sets the limit below which sent packets are cached to ensure successful delivery. Default 75KB.
953 /// </summary>
954 public static int CheckSumMismatchSentPacketCacheMaxByteLimit { get; set; }
955
956 /// <summary>
957 /// When a sent packet has been cached for a possible resend this is the minimum length of time it will be retained. Default is 1.0 minutes.
958 /// </summary>
959 public static double MinimumSentPacketCacheTimeMinutes { get; set; }
960
961 /// <summary>
962 /// Records the last sent packet cache cleanup time. Prevents the sent packet cache from being checked too frequently.
963 /// </summary>
964 internal static DateTime LastSentPacketCacheCleanup { get; set; }
965 #endregion
966
967 #region PacketType Config and Global Handlers
968 /// <summary>
969 /// An internal reference copy of all reservedPacketTypeNames.
970 /// </summary>
971 internal static string[] reservedPacketTypeNames = Enum.GetNames(typeof(ReservedPacketType));
972
973 /// <summary>
974 /// Dictionary of all custom packetHandlers. Key is packetType.
975 /// </summary>
976 static Dictionary<string, List<IPacketTypeHandlerDelegateWrapper>> globalIncomingPacketHandlers = new Dictionary<string, List<IPacketTypeHandlerDelegateWrapper>>();
977
978 /// <summary>
979 /// Dictionary of any non default custom packet unwrappers. Key is packetType.
980 /// </summary>
981 static Dictionary<string, PacketTypeUnwrapper> globalIncomingPacketUnwrappers = new Dictionary<string, PacketTypeUnwrapper>();
982
983 /// <summary>
984 /// Delegate for handling incoming packets. See AppendGlobalIncomingPacketHandler members.
985 /// </summary>
986 /// <typeparam name="T">The type of object which is expected for this handler</typeparam>
987 /// <param name="packetHeader">The <see cref="PacketHeader"/> of the incoming packet</param>
988 /// <param name="connection">The connection with which this packet was received</param>
989 /// <param name="incomingObject">The incoming object of specified type T</param>
990 public delegate void PacketHandlerCallBackDelegate<T>(PacketHeader packetHeader, Connection connection, T incomingObject);
991
992 /// <summary>
993 /// If true any unknown incoming packet types are ignored. Default is false and will result in an error file being created if an unknown packet type is received.
994 /// </summary>
995 public static bool IgnoreUnknownPacketTypes { get; set; }
996
997 /// <summary>
998 /// Add an incoming packet handler using default SendReceiveOptions. Multiple handlers for the same packet type will be executed in the order they are added.
999 /// </summary>
1000 /// <typeparam name="T">The type of incoming object</typeparam>
1001 /// <param name="packetTypeStr">The packet type for which this handler will be executed</param>
1002 /// <param name="packetHandlerDelgatePointer">The delegate to be executed when a packet of packetTypeStr is received</param>
1003 public static void AppendGlobalIncomingPacketHandler<T>(string packetTypeStr, PacketHandlerCallBackDelegate<T> packetHandlerDelgatePointer)
1004 {
1005 AppendGlobalIncomingPacketHandler<T>(packetTypeStr, packetHandlerDelgatePointer, DefaultSendReceiveOptions);
1006 }
1007
1008 /// <summary>
1009 /// Add an incoming packet handler using the provided SendReceiveOptions. Multiple handlers for the same packet type will be executed in the order they are added.
1010 /// </summary>
1011 /// <typeparam name="T">The type of incoming object</typeparam>
1012 /// <param name="packetTypeStr">The packet type for which this handler will be executed</param>
1013 /// <param name="packetHandlerDelgatePointer">The delegate to be executed when a packet of packetTypeStr is received</param>
1014 /// <param name="sendReceiveOptions">The SendReceiveOptions to be used for the provided packet type</param>
1015 public static void AppendGlobalIncomingPacketHandler<T>(string packetTypeStr, PacketHandlerCallBackDelegate<T> packetHandlerDelgatePointer, SendReceiveOptions sendReceiveOptions)
1016 {
1017 if (packetTypeStr == null) throw new ArgumentNullException("packetTypeStr", "Provided packetType string cannot be null.");
1018 if (packetHandlerDelgatePointer == null) throw new ArgumentNullException("packetHandlerDelgatePointer", "Provided PacketHandlerCallBackDelegate<T> cannot be null.");
1019 if (sendReceiveOptions == null) throw new ArgumentNullException("sendReceiveOptions", "Provided SendReceiveOptions cannot be null.");
1020
1021 lock (globalDictAndDelegateLocker)
1022 {
1023
1024 if (globalIncomingPacketUnwrappers.ContainsKey(packetTypeStr))
1025 {
1026 //Make sure if we already have an existing entry that it matches with the provided
1027 if (!globalIncomingPacketUnwrappers[packetTypeStr].Options.OptionsCompatible(sendReceiveOptions))
1028 throw new PacketHandlerException("The proivded SendReceiveOptions are not compatible with existing SendReceiveOptions already specified for this packetTypeStr.");
1029 }
1030 else
1031 globalIncomingPacketUnwrappers.Add(packetTypeStr, new PacketTypeUnwrapper(packetTypeStr, sendReceiveOptions));
1032
1033
1034 //Ad the handler to the list
1035 if (globalIncomingPacketHandlers.ContainsKey(packetTypeStr))
1036 {
1037 //Make sure we avoid duplicates
1038 PacketTypeHandlerDelegateWrapper<T> toCompareDelegate = new PacketTypeHandlerDelegateWrapper<T>(packetHandlerDelgatePointer);
1039
1040 bool delegateAlreadyExists = false;
1041 foreach (var handler in globalIncomingPacketHandlers[packetTypeStr])
1042 {
1043 if (handler == toCompareDelegate)
1044 {
1045 delegateAlreadyExists = true;
1046 break;
1047 }
1048 }
1049
1050 if (delegateAlreadyExists)
1051 throw new PacketHandlerException("This specific packet handler delegate already exists for the provided packetTypeStr.");
1052
1053 globalIncomingPacketHandlers[packetTypeStr].Add(new PacketTypeHandlerDelegateWrapper<T>(packetHandlerDelgatePointer));
1054 }
1055 else
1056 globalIncomingPacketHandlers.Add(packetTypeStr, new List<IPacketTypeHandlerDelegateWrapper>() { new PacketTypeHandlerDelegateWrapper<T>(packetHandlerDelgatePointer) });
1057
1058 if (LoggingEnabled) logger.Info("Added incoming packetHandler for '" + packetTypeStr + "' packetType.");
1059 }
1060 }
1061
1062 /// <summary>
1063 /// Removes the provided delegate for the specified packet type. If the provided delegate does not exist for this packet type just returns.
1064 /// </summary>
1065 /// <param name="packetTypeStr">The packet type for which the delegate will be removed</param>
1066 /// <param name="packetHandlerDelgatePointer">The delegate to be removed</param>
1067 public static void RemoveGlobalIncomingPacketHandler(string packetTypeStr, Delegate packetHandlerDelgatePointer)
1068 {
1069 lock (globalDictAndDelegateLocker)
1070 {
1071 if (globalIncomingPacketHandlers.ContainsKey(packetTypeStr))
1072 {
1073 //Remove any instances of this handler from the delegates
1074 //The bonus here is if the delegate has not been added we continue quite happily
1075 IPacketTypeHandlerDelegateWrapper toRemove = null;
1076
1077 foreach (var handler in globalIncomingPacketHandlers[packetTypeStr])
1078 {
1079 if (handler.EqualsDelegate(packetHandlerDelgatePointer))
1080 {
1081 toRemove = handler;
1082 break;
1083 }
1084 }
1085
1086 if (toRemove != null)
1087 globalIncomingPacketHandlers[packetTypeStr].Remove(toRemove);
1088
1089 if (globalIncomingPacketHandlers[packetTypeStr] == null || globalIncomingPacketHandlers[packetTypeStr].Count == 0)
1090 {
1091 globalIncomingPacketHandlers.Remove(packetTypeStr);
1092 globalIncomingPacketUnwrappers.Remove(packetTypeStr);
1093
1094 if (LoggingEnabled) logger.Info("Removed a packetHandler for '" + packetTypeStr + "' packetType. No handlers remain.");
1095 }
1096 else
1097 if (LoggingEnabled) logger.Info("Removed a packetHandler for '" + packetTypeStr + "' packetType. Handlers remain.");
1098 }
1099 }
1100 }
1101
1102 /// <summary>
1103 /// Removes all delegates for the provided packet type.
1104 /// </summary>
1105 /// <param name="packetTypeStr">Packet type for which all delegates should be removed</param>
1106 public static void RemoveGlobalIncomingPacketHandler(string packetTypeStr)
1107 {
1108 lock (globalDictAndDelegateLocker)
1109 {
1110 //We don't need to check for potentially removing a critical reserved packet handler here because those cannot be removed.
1111 if (globalIncomingPacketHandlers.ContainsKey(packetTypeStr))
1112 {
1113 globalIncomingPacketHandlers.Remove(packetTypeStr);
1114 globalIncomingPacketUnwrappers.Remove(packetTypeStr);
1115
1116 if (LoggingEnabled) logger.Info("Removed all incoming packetHandlers for '" + packetTypeStr + "' packetType.");
1117 }
1118 }
1119 }
1120
1121 /// <summary>
1122 /// Removes all delegates for all packet types
1123 /// </summary>
1124 public static void RemoveGlobalIncomingPacketHandler()
1125 {
1126 lock (globalDictAndDelegateLocker)
1127 {
1128 globalIncomingPacketHandlers = new Dictionary<string, List<IPacketTypeHandlerDelegateWrapper>>();
1129 globalIncomingPacketUnwrappers = new Dictionary<string, PacketTypeUnwrapper>();
1130
1131 if (LoggingEnabled) logger.Info("Removed all incoming packetHandlers for all packetTypes");
1132 }
1133 }
1134
1135 /// <summary>
1136 /// Trigger incoming packet delegates for the provided parameters.
1137 /// </summary>
1138 /// <param name="packetHeader">The packet header</param>
1139 /// <param name="connection">The incoming connection</param>
1140 /// <param name="incomingDataStream">The bytes corresponding to the incoming object</param>
1141 /// <param name="options">The SendReceiveOptions to be used to convert incomingObjectBytes back to the desired object</param>
1142 public static void TriggerGlobalPacketHandlers(PacketHeader packetHeader, Connection connection, MemoryStream incomingDataStream, SendReceiveOptions options)
1143 {
1144 TriggerGlobalPacketHandlers(packetHeader, connection, incomingDataStream, options, IgnoreUnknownPacketTypes);
1145 }
1146
1147 /// <summary>
1148 /// Trigger incoming packet delegates for the provided parameters.
1149 /// </summary>
1150 /// <param name="packetHeader">The packet header</param>
1151 /// <param name="connection">The incoming connection</param>
1152 /// <param name="incomingDataStream">The bytes corresponding to the incoming object</param>
1153 /// <param name="options">The SendReceiveOptions to be used to convert incomingObjectBytes back to the desired object</param>
1154 /// <param name="ignoreUnknownPacketTypeOverride">Used to potentially override NetworkComms.IgnoreUnknownPacketTypes property</param>
1155 internal static void TriggerGlobalPacketHandlers(PacketHeader packetHeader, Connection connection, MemoryStream incomingDataStream, SendReceiveOptions options, bool ignoreUnknownPacketTypeOverride = false)
1156 {
1157 try
1158 {
1159 if (options == null) throw new PacketHandlerException("Provided sendReceiveOptions should not be null for packetType " + packetHeader.PacketType);
1160
1161 //We take a copy of the handlers list incase it is modified outside of the lock
1162 List<IPacketTypeHandlerDelegateWrapper> handlersCopy = null;
1163 lock (globalDictAndDelegateLocker)
1164 if (globalIncomingPacketHandlers.ContainsKey(packetHeader.PacketType))
1165 handlersCopy = new List<IPacketTypeHandlerDelegateWrapper>(globalIncomingPacketHandlers[packetHeader.PacketType]);
1166
1167 if (handlersCopy == null && !IgnoreUnknownPacketTypes && !ignoreUnknownPacketTypeOverride)
1168 {
1169 //We may get here if we have not added any custom delegates for reserved packet types
1170 bool isReservedType = false;
1171
1172 for (int i = 0; i < reservedPacketTypeNames.Length; i++)
1173 {
1174 if (reservedPacketTypeNames[i] == packetHeader.PacketType)
1175 {
1176 isReservedType = true;
1177 break;
1178 }
1179 }
1180
1181 if (!isReservedType)
1182 {
1183 //Change this to just a log because generally a packet of the wrong type is nothing to really worry about
1184 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Warn("The received packet type '" + packetHeader.PacketType + "' has no configured handler and network comms is not set to ignore unknown packet types. Set NetworkComms.IgnoreUnknownPacketTypes=true to prevent this error.");
1185 LogError(new UnexpectedPacketTypeException("The received packet type '" + packetHeader.PacketType + "' has no configured handler and network comms is not set to ignore unknown packet types. Set NetworkComms.IgnoreUnknownPacketTypes=true to prevent this error."), "PacketHandlerErrorGlobal_" + packetHeader.PacketType);
1186 }
1187
1188 return;
1189 }
1190 else if (handlersCopy == null && (IgnoreUnknownPacketTypes || ignoreUnknownPacketTypeOverride))
1191 //If we have received and unknown packet type and we are choosing to ignore them we just finish here
1192 return;
1193 else
1194 {
1195 //Idiot check
1196 if (handlersCopy.Count == 0)
1197 throw new PacketHandlerException("An entry exists in the packetHandlers list but it contains no elements. This should not be possible.");
1198
1199 //Deserialise the object only once
1200 object returnObject = handlersCopy[0].DeSerialize(incomingDataStream, options);
1201
1202 //Pass the data onto the handler and move on.
1203 if (LoggingEnabled) logger.Trace(" ... passing completed data packet of type '" + packetHeader.PacketType + "' to " + handlersCopy.Count.ToString() + " selected global handlers.");
1204
1205 //Pass the object to all necessary delgates
1206 //We need to use a copy because we may modify the original delegate list during processing
1207 foreach (IPacketTypeHandlerDelegateWrapper wrapper in handlersCopy)
1208 {
1209 try
1210 {
1211 wrapper.Process(packetHeader, connection, returnObject);
1212 }
1213 catch (Exception ex)
1214 {
1215 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Fatal("An unhandled exception was caught while processing a packet handler for a packet type '" + packetHeader.PacketType + "'. Make sure to catch errors in packet handlers. See error log file for more information.");
1216 NetworkComms.LogError(ex, "PacketHandlerErrorGlobal_" + packetHeader.PacketType);
1217 }
1218 }
1219
1220 if (LoggingEnabled) logger.Trace(" ... all handlers for packet of type '" + packetHeader.PacketType + "' completed.");
1221 }
1222 }
1223 catch (Exception ex)
1224 {
1225 //If anything goes wrong here all we can really do is log the exception
1226 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Fatal("An exception occured in TriggerPacketHandler() for a packet type '" + packetHeader.PacketType + "'. See error log file for more information.");
1227 NetworkComms.LogError(ex, "PacketHandlerErrorGlobal_" + packetHeader.PacketType);
1228 }
1229 }
1230
1231 /// <summary>
1232 /// Returns the unwrapper <see cref="SendReceiveOptions"/> for the provided packet type. If no specific options are registered returns null.
1233 /// </summary>
1234 /// <param name="packetTypeStr">The packet type for which the <see cref="SendReceiveOptions"/> are required</param>
1235 /// <returns>The requested <see cref="SendReceiveOptions"/> otherwise null</returns>
1236 public static SendReceiveOptions GlobalPacketTypeUnwrapperOptions(string packetTypeStr)
1237 {
1238 SendReceiveOptions options = null;
1239
1240 //If we find a global packet unwrapper for this packetType we used those options
1241 lock (globalDictAndDelegateLocker)
1242 {
1243 if (globalIncomingPacketUnwrappers.ContainsKey(packetTypeStr))
1244 options = globalIncomingPacketUnwrappers[packetTypeStr].Options;
1245 }
1246
1247 return options;
1248 }
1249
1250 /// <summary>
1251 /// Returns true if a global packet handler exists for the provided packet type.
1252 /// </summary>
1253 /// <param name="packetTypeStr">The packet type for which to check incoming packet handlers</param>
1254 /// <returns>True if a global packet handler exists</returns>
1255 public static bool GlobalIncomingPacketHandlerExists(string packetTypeStr)
1256 {
1257 lock (globalDictAndDelegateLocker)
1258 return globalIncomingPacketHandlers.ContainsKey(packetTypeStr);
1259 }
1260
1261 /// <summary>
1262 /// Returns true if the provided global packet handler has been added for the provided packet type.
1263 /// </summary>
1264 /// <param name="packetTypeStr">The packet type within which to check packet handlers</param>
1265 /// <param name="packetHandlerDelgatePointer">The packet handler to look for</param>
1266 /// <returns>True if a global packet handler exists for the provided packetType</returns>
1267 public static bool GlobalIncomingPacketHandlerExists(string packetTypeStr, Delegate packetHandlerDelgatePointer)
1268 {
1269 lock (globalDictAndDelegateLocker)
1270 {
1271 if (globalIncomingPacketHandlers.ContainsKey(packetTypeStr))
1272 {
1273 foreach (var handler in globalIncomingPacketHandlers[packetTypeStr])
1274 {
1275 if (handler.EqualsDelegate(packetHandlerDelgatePointer))
1276 return true;
1277 }
1278 }
1279 }
1280
1281 return false;
1282 }
1283 #endregion
1284
1285 #region Connection Establish and Shutdown
1286 /// <summary>
1287 /// Delegate which is executed when a connection is established or shutdown. See <see cref="AppendGlobalConnectionEstablishHandler"/> and <see cref="AppendGlobalConnectionCloseHandler"/>.
1288 /// </summary>
1289 /// <param name="connection">The connection which has been established or shutdown.</param>
1290 public delegate void ConnectionEstablishShutdownDelegate(Connection connection);
1291
1292 /// <summary>
1293 /// Multicast delegate pointer for connection shutdowns.
1294 /// </summary>
1295 internal static ConnectionEstablishShutdownDelegate globalConnectionShutdownDelegates;
1296
1297 /// <summary>
1298 /// Delegate counter for debugging.
1299 /// </summary>
1300 internal static int globalConnectionShutdownDelegateCount = 0;
1301
1302 /// <summary>
1303 /// Multicast delegate pointer for connection establishments, run asynchronously.
1304 /// </summary>
1305 internal static ConnectionEstablishShutdownDelegate globalConnectionEstablishDelegatesAsync;
1306
1307 /// <summary>
1308 /// Multicast delegate pointer for connection establishments, run synchronously.
1309 /// </summary>
1310 internal static ConnectionEstablishShutdownDelegate globalConnectionEstablishDelegatesSync;
1311
1312 /// <summary>
1313 /// Delegate counter for debugging.
1314 /// </summary>
1315 internal static int globalConnectionEstablishDelegateCount = 0;
1316
1317 /// <summary>
1318 /// Comms shutdown event. This will be triggered when calling NetworkComms.Shutdown
1319 /// </summary>
1320 public static event EventHandler<EventArgs> OnCommsShutdown;
1321
1322 /// <summary>
1323 /// Add a new connection shutdown delegate which will be called for every connection as it is closes.
1324 /// </summary>
1325 /// <param name="connectionShutdownDelegate">The delegate to call on all connection shutdowns</param>
1326 public static void AppendGlobalConnectionCloseHandler(ConnectionEstablishShutdownDelegate connectionShutdownDelegate)
1327 {
1328 lock (globalDictAndDelegateLocker)
1329 {
1330 if (globalConnectionShutdownDelegates == null)
1331 globalConnectionShutdownDelegates = connectionShutdownDelegate;
1332 else
1333 globalConnectionShutdownDelegates += connectionShutdownDelegate;
1334
1335 globalConnectionShutdownDelegateCount++;
1336
1337 if (LoggingEnabled) logger.Info("Added globalConnectionShutdownDelegates. " + globalConnectionShutdownDelegateCount.ToString());
1338 }
1339 }
1340
1341 /// <summary>
1342 /// Remove a connection shutdown delegate.
1343 /// </summary>
1344 /// <param name="connectionShutdownDelegate">The delegate to remove from connection shutdown events</param>
1345 public static void RemoveGlobalConnectionCloseHandler(ConnectionEstablishShutdownDelegate connectionShutdownDelegate)
1346 {
1347 lock (globalDictAndDelegateLocker)
1348 {
1349 globalConnectionShutdownDelegates -= connectionShutdownDelegate;
1350 globalConnectionShutdownDelegateCount--;
1351
1352 if (LoggingEnabled) logger.Info("Removed globalConnectionShutdownDelegates. " + globalConnectionShutdownDelegateCount.ToString());
1353 }
1354 }
1355
1356 /// <summary>
1357 /// Add a new connection establish delegate which will be called for every connection once it has been succesfully established.
1358 /// </summary>
1359 /// <param name="connectionEstablishDelegate">The delegate to call after all connection establishments.</param>
1360 /// <param name="runSynchronously">If true this ConnectionEstablishShutdownDelegate will be called synchronously during the connection establish. The connection will not be considered established until the ConnectionEstablishShutdownDelegate has completed.</param>
1361 public static void AppendGlobalConnectionEstablishHandler(ConnectionEstablishShutdownDelegate connectionEstablishDelegate, bool runSynchronously = false)
1362 {
1363 lock (globalDictAndDelegateLocker)
1364 {
1365 if (runSynchronously)
1366 {
1367 if (globalConnectionEstablishDelegatesSync == null)
1368 globalConnectionEstablishDelegatesSync = connectionEstablishDelegate;
1369 else
1370 globalConnectionEstablishDelegatesSync += connectionEstablishDelegate;
1371 }
1372 else
1373 {
1374 if (globalConnectionEstablishDelegatesAsync == null)
1375 globalConnectionEstablishDelegatesAsync = connectionEstablishDelegate;
1376 else
1377 globalConnectionEstablishDelegatesAsync += connectionEstablishDelegate;
1378 }
1379
1380 globalConnectionEstablishDelegateCount++;
1381
1382 if (LoggingEnabled) logger.Info("Added globalConnectionEstablishDelegates. " + globalConnectionEstablishDelegateCount.ToString());
1383 }
1384 }
1385
1386 /// <summary>
1387 /// Remove a connection establish delegate.
1388 /// </summary>
1389 /// <param name="connectionEstablishDelegate">The delegate to remove from connection establish events</param>
1390 public static void RemoveGlobalConnectionEstablishHandler(ConnectionEstablishShutdownDelegate connectionEstablishDelegate)
1391 {
1392 lock (globalDictAndDelegateLocker)
1393 {
1394 //Remove from either async or sync delegates
1395 globalConnectionEstablishDelegatesAsync -= connectionEstablishDelegate;
1396 globalConnectionEstablishDelegatesSync -= connectionEstablishDelegate;
1397
1398 globalConnectionEstablishDelegateCount--;
1399
1400 if (LoggingEnabled) logger.Info("Removed globalConnectionEstablishDelegates. " + globalConnectionEstablishDelegateCount.ToString());
1401 }
1402 }
1403
1404 /// <summary>
1405 /// Shutdown all connections, comms threads and execute OnCommsShutdown event. Any packet handlers are left unchanged. If any comms activity has taken place this should be called on application close.
1406 /// </summary>
1407 /// <param name="threadShutdownTimeoutMS">The time to wait for worker threads to close before attempting a thread abort.</param>
1408 public static void Shutdown(int threadShutdownTimeoutMS = 1000)
1409 {
1410 if (LoggingEnabled) logger.Trace("NetworkCommsDotNet shutdown initiated.");
1411 commsShutdown = true;
1412
1413 CommsThreadPool.BeginShutdown();
1414 Connection.ShutdownBase(threadShutdownTimeoutMS);
1415 TCPConnection.Shutdown(threadShutdownTimeoutMS);
1416 UDPConnection.Shutdown();
1417
1418 try
1419 {
1420 CloseAllConnections();
1421 }
1422 catch (CommsException)
1423 {
1424
1425 }
1426 catch (Exception ex)
1427 {
1428 LogError(ex, "CommsShutdownError");
1429 }
1430
1431 #if !WINDOWS_PHONE && !ANDROID
1432 try
1433 {
1434 if (NetworkLoadThread != null)
1435 {
1436 NetworkLoadThreadWait.Set();
1437 if (!NetworkLoadThread.Join(threadShutdownTimeoutMS))
1438 {
1439 NetworkLoadThread.Abort();
1440 throw new CommsSetupShutdownException("Timeout waiting for NetworkLoadThread thread to shutdown after " + threadShutdownTimeoutMS.ToString() + " ms. ");
1441 }
1442 }
1443 }
1444 catch (Exception ex)
1445 {
1446 LogError(ex, "CommsShutdownError");
1447 }
1448 #endif
1449
1450 try
1451 {
1452 if (OnCommsShutdown != null) OnCommsShutdown(null, new EventArgs());
1453 }
1454 catch (Exception ex)
1455 {
1456 LogError(ex, "CommsShutdownError");
1457 }
1458
1459 CommsThreadPool.EndShutdown(threadShutdownTimeoutMS);
1460
1461 commsShutdown = false;
1462 if (LoggingEnabled) logger.Info("NetworkCommsDotNet has shutdown");
1463
1464 #if !WINDOWS_PHONE && !NO_LOGGING
1465 //Mono bug fix
1466 //Sometimes NLog ends up in a deadlock on close, workaround provided on NLog website
1467 if (Logger != null)
1468 {
1469 LogManager.Flush();
1470 Logger.Factory.Flush();
1471
1472 if (NetworkComms.CurrentRuntimeEnvironment == RuntimeEnvironment.Mono_Net2 ||
1473 NetworkComms.CurrentRuntimeEnvironment == RuntimeEnvironment.Mono_Net35 ||
1474 NetworkComms.CurrentRuntimeEnvironment == RuntimeEnvironment.Mono_Net4)
1475 LogManager.Configuration = null;
1476 }
1477 #endif
1478 }
1479 #endregion
1480
1481 #region Timeouts
1482 /// <summary>
1483 /// Time to wait in milliseconds before throwing an exception when waiting for a connection to be established. Default is 30000.
1484 /// </summary>
1485 public static int ConnectionEstablishTimeoutMS { get; set; }
1486
1487 /// <summary>
1488 /// Time to wait in milliseconds before throwing an exception when waiting for confirmation of packet receipt. Default is 5000.
1489 /// </summary>
1490 public static int PacketConfirmationTimeoutMS { get; set; }
1491
1492 /// <summary>
1493 /// Time to wait in milliseconds before assuming a remote connection is dead when doing a connection test. Default is 1000.
1494 /// </summary>
1495 public static int ConnectionAliveTestTimeoutMS { get; set; }
1496
1497 /// <summary>
1498 /// By default NetworkComms.Net closes connections for which sends take a long time. The timeout is calculated based on previous connection send performances. Set this to true to disable this feature.
1499 /// </summary>
1500 public static bool DisableConnectionSendTimeouts { get; set; }
1501 #endregion
1502
1503 #region Logging
1504 /// <summary>
1505 /// Returns true if comms logging has been enabled.
1506 /// </summary>
1507 public static bool LoggingEnabled { get; private set; }
1508
1509 private static Logger logger = null;
1510
1511 /// <summary>
1512 /// Access the NetworkCommsDotNet logger externally.
1513 /// </summary>
1514 public static Logger Logger
1515 {
1516 get { return logger; }
1517 }
1518
1519 #if NO_LOGGING
1520 /// <summary>
1521 /// Enable basic logging using the provided logFileLocation
1522 /// </summary>
1523 /// <param name="loggingConfiguration"></param>
1524 public static void EnableLogging(string logFileLocation)
1525 {
1526 lock (globalDictAndDelegateLocker)
1527 {
1528 LoggingEnabled = true;
1529 logger = new Logger();
1530 logger.LogFileLocation = logFileLocation;
1531 }
1532 }
1533
1534 /// <summary>
1535 /// Disable all logging in NetworkCommsDotNet
1536 /// </summary>
1537 public static void DisableLogging()
1538 {
1539 lock (globalDictAndDelegateLocker)
1540 {
1541 LoggingEnabled = false;
1542 logger = null;
1543 }
1544 }
1545 #else
1546 /// <summary>
1547 /// Enable logging using a default config. All log output is written directly to the local console.
1548 /// </summary>
1549 public static void EnableLogging()
1550 {
1551 LoggingConfiguration logConfig = new LoggingConfiguration();
1552 NLog.Targets.ConsoleTarget consoleTarget = new NLog.Targets.ConsoleTarget();
1553 consoleTarget.Layout = "${date:format=HH\\:MM\\:ss} [${level}] - ${message}";
1554 logConfig.AddTarget("console", consoleTarget);
1555 logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget));
1556 EnableLogging(logConfig);
1557 }
1558
1559 /// <summary>
1560 /// Enable logging using the provided config. See examples for usage.
1561 /// </summary>
1562 /// <param name="loggingConfiguration"></param>
1563 public static void EnableLogging(LoggingConfiguration loggingConfiguration)
1564 {
1565 lock (globalDictAndDelegateLocker)
1566 {
1567 LoggingEnabled = true;
1568 LogManager.Configuration = loggingConfiguration;
1569 logger = LogManager.GetCurrentClassLogger();
1570 LogManager.EnableLogging();
1571 }
1572 }
1573
1574 /// <summary>
1575 /// Disable all logging in NetworkCommsDotNet
1576 /// </summary>
1577 public static void DisableLogging()
1578 {
1579 lock (globalDictAndDelegateLocker)
1580 {
1581 LoggingEnabled = false;
1582 LogManager.DisableLogging();
1583 }
1584 }
1585 #endif
1586
1587 /// <summary>
1588 /// Locker for LogError() which ensures thread safe saves.
1589 /// </summary>
1590 static object errorLocker = new object();
1591
1592 /// <summary>
1593 /// Appends the provided logString to end of fileName.txt. If the file does not exist it will be created.
1594 /// </summary>
1595 /// <param name="fileName">The filename to use. The extension .txt will be appended automatically</param>
1596 /// <param name="logString">The string to append.</param>
1597 public static void AppendStringToLogFile(string fileName, string logString)
1598 {
1599 try
1600 {
1601 lock (errorLocker)
1602 {
1603 using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fileName + ".txt", true))
1604 sw.WriteLine(logString);
1605 }
1606 }
1607 catch (Exception)
1608 {
1609 //If an error happens here, such as if the file is locked then we lucked out.
1610 }
1611 }
1612
1613 /// <summary>
1614 /// Logs the provided exception to a file to assist troubleshooting.
1615 /// </summary>
1616 /// <param name="ex">The exception to be logged</param>
1617 /// <param name="fileName">The filename to use. A timestamp and extension .txt will be appended automatically</param>
1618 /// <param name="optionalCommentStr">An optional string which will appear at the top of the error file</param>
1619 /// <returns>The entire fileName used.</returns>
1620 public static string LogError(Exception ex, string fileName, string optionalCommentStr = "")
1621 {
1622 string entireFileName;
1623
1624 lock (errorLocker)
1625 {
1626
1627 #if iOS
1628 //We need to ensure we add the correct document path for iOS
1629 entireFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), fileName + " " + DateTime.Now.Hour.ToString() + "." + DateTime.Now.Minute.ToString() + "." + DateTime.Now.Second.ToString() + "." + DateTime.Now.Millisecond.ToString() + " " + DateTime.Now.ToString("dd-MM-yyyy" + " [" + Thread.CurrentThread.ManagedThreadId.ToString() + "]"));
1630 #elif ANDROID
1631 entireFileName = Path.Combine(global::Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, fileName + " " + DateTime.Now.Hour.ToString() + "." + DateTime.Now.Minute.ToString() + "." + DateTime.Now.Second.ToString() + "." + DateTime.Now.Millisecond.ToString() + " " + DateTime.Now.ToString("dd-MM-yyyy" + " [" + Thread.CurrentThread.ManagedThreadId.ToString() + "]"));
1632 #elif WINDOWS_PHONE
1633 entireFileName = fileName + " " + DateTime.Now.Hour.ToString() + "." + DateTime.Now.Minute.ToString() + "." + DateTime.Now.Second.ToString() + "." + DateTime.Now.Millisecond.ToString() + " " + DateTime.Now.ToString("dd-MM-yyyy" + " [" + Thread.CurrentThread.ManagedThreadId.ToString() + "]");
1634 #else
1635 using (Process currentProcess = System.Diagnostics.Process.GetCurrentProcess())
1636 entireFileName = fileName + " " + DateTime.Now.Hour.ToString() + "." + DateTime.Now.Minute.ToString() + "." + DateTime.Now.Second.ToString() + "." + DateTime.Now.Millisecond.ToString() + " " + DateTime.Now.ToString("dd-MM-yyyy" + " [" + currentProcess.Id.ToString() + "-" + Thread.CurrentContext.ContextID.ToString() + "]");
1637 #endif
1638
1639 if (LoggingEnabled) logger.Fatal(entireFileName, ex);
1640
1641 try
1642 {
1643 using (System.IO.StreamWriter sw = new System.IO.StreamWriter(entireFileName + ".txt", false))
1644 {
1645 if (optionalCommentStr != "")
1646 {
1647 sw.WriteLine("Comment: " + optionalCommentStr);
1648 sw.WriteLine("");
1649 }
1650
1651 if (ex.GetBaseException() != null)
1652 sw.WriteLine("Base Exception Type: " + ex.GetBaseException().ToString());
1653
1654 if (ex.InnerException != null)
1655 sw.WriteLine("Inner Exception Type: " + ex.InnerException.ToString());
1656
1657 if (ex.StackTrace != null)
1658 {
1659 sw.WriteLine("");
1660 sw.WriteLine("Stack Trace: " + ex.StackTrace.ToString());
1661 }
1662 }
1663 }
1664 catch (Exception)
1665 {
1666 //This should never really happen, but just incase.
1667 }
1668 }
1669
1670 return entireFileName;
1671 }
1672 #endregion
1673
1674 #region Serializers and Compressors
1675
1676 /// <summary>
1677 /// The following are used for internal comms objects, packet headers, connection establishment etc.
1678 /// We generally seem to increase the size of our data if compressing small objects (~50 bytes)
1679 /// Given the typical header size is 40 bytes we might as well not compress these objects.
1680 /// </summary>
1681 internal static SendReceiveOptions InternalFixedSendReceiveOptions { get; set; }
1682
1683 /// <summary>
1684 /// Default options for sending and receiving in the absence of specific values
1685 /// </summary>
1686 public static SendReceiveOptions DefaultSendReceiveOptions { get; set; }
1687 #endregion
1688
1689 #region Connection Access
1690 /// <summary>
1691 /// Send the provided object to the specified destination using TCP. Uses default sendReceiveOptions. For more control over options see connection specific methods.
1692 /// </summary>
1693 /// <param name="packetTypeStr">Packet type to use for send</param>
1694 /// <param name="destinationIPAddress">The destination ip address</param>
1695 /// <param name="destinationPort">The destination listen port</param>
1696 /// <param name="sendObject">The obect to send</param>
1697 public static void SendObject(string packetTypeStr, string destinationIPAddress, int destinationPort, object sendObject)
1698 {
1699 TCPConnection conn = TCPConnection.GetConnection(new ConnectionInfo(destinationIPAddress, destinationPort));
1700 conn.SendObject(packetTypeStr, sendObject);
1701 }
1702
1703 /// <summary>
1704 /// Send the provided object to the specified destination and wait for a return object using TCP. Uses default sendReceiveOptions. For more control over options see connection specific methods.
1705 /// </summary>
1706 /// <typeparam name="returnObjectType">The expected return object type, i.e. string, int[], etc</typeparam>
1707 /// <param name="sendingPacketTypeStr">Packet type to use during send</param>
1708 /// <param name="destinationIPAddress">The destination ip address</param>
1709 /// <param name="destinationPort">The destination listen port</param>
1710 /// <param name="expectedReturnPacketTypeStr">Expected packet type used for return object</param>
1711 /// <param name="returnPacketTimeOutMilliSeconds">Time to wait in milliseconds for return object</param>
1712 /// <param name="sendObject">Object to send</param>
1713 /// <returns>The expected return object</returns>
1714 public static returnObjectType SendReceiveObject<returnObjectType>(string sendingPacketTypeStr, string destinationIPAddress, int destinationPort, string expectedReturnPacketTypeStr, int returnPacketTimeOutMilliSeconds, object sendObject)
1715 {
1716 TCPConnection conn = TCPConnection.GetConnection(new ConnectionInfo(destinationIPAddress, destinationPort));
1717 return conn.SendReceiveObject<returnObjectType>(sendingPacketTypeStr, expectedReturnPacketTypeStr, returnPacketTimeOutMilliSeconds, sendObject);
1718 }
1719
1720 /// <summary>
1721 /// Return the MD5 hash of the provided memory stream as a string. Stream position will be equal to the length of stream on return, this ensures the MD5 is consistent.
1722 /// </summary>
1723 /// <param name="streamToMD5">The bytes which will be checksummed</param>
1724 /// <returns>The MD5 checksum as a string</returns>
1725 public static string MD5Bytes(Stream streamToMD5)
1726 {
1727 if (streamToMD5 == null) throw new ArgumentNullException("streamToMD5", "Provided Stream cannot be null.");
1728
1729 string resultStr;
1730
1731 using (System.Security.Cryptography.HashAlgorithm md5 =
1732 #if WINDOWS_PHONE
1733 new DPSBase.MD5Managed())
1734 #else
1735 System.Security.Cryptography.MD5.Create())
1736 #endif
1737 {
1738 //If we don't ensure the position is consistent the MD5 changes
1739 streamToMD5.Seek(0, SeekOrigin.Begin);
1740 resultStr = BitConverter.ToString(md5.ComputeHash(streamToMD5)).Replace("-", "");
1741 }
1742
1743 return resultStr;
1744 }
1745
1746 /// <summary>
1747 /// Return the MD5 hash of the provided memory stream as a string. Stream position will be equal to the length of stream on return, this ensures the MD5 is consistent.
1748 /// </summary>
1749 /// <param name="streamToMD5">The bytes which will be checksummed</param>
1750 /// <param name="start">The start position in the stream</param>
1751 /// <param name="length">The length in the stream to MD5</param>
1752 /// <returns>The MD5 checksum as a string</returns>
1753 public static string MD5Bytes(Stream streamToMD5, long start, int length)
1754 {
1755 if (streamToMD5 == null) throw new ArgumentNullException("streamToMD5", "Provided Stream cannot be null.");
1756
1757 using (MemoryStream stream = new MemoryStream(length))
1758 {
1759 StreamWriteWithTimeout.Write(streamToMD5, start, length, stream, 8000, 100, 2000);
1760 return MD5Bytes(stream);
1761 }
1762 }
1763
1764 /// <summary>
1765 /// Return the MD5 hash of the provided byte array as a string
1766 /// </summary>
1767 /// <param name="bytesToMd5">The bytes which will be checksummed</param>
1768 /// <returns>The MD5 checksum as a string</returns>
1769 public static string MD5Bytes(byte[] bytesToMd5)
1770 {
1771 if (bytesToMd5 == null) throw new ArgumentNullException("bytesToMd5", "Provided byte[] cannot be null.");
1772
1773 using(MemoryStream stream = new MemoryStream(bytesToMd5, 0, bytesToMd5.Length, false, true))
1774 return MD5Bytes(stream);
1775 }
1776
1777 /// <summary>
1778 /// Returns a ConnectionInfo array containing information for all connections
1779 /// </summary>
1780 /// <param name="includeClosedConnections">If true information for closed connections will also be included</param>
1781 /// <returns>List of ConnectionInfo containing information for all requested connections</returns>
1782 public static List<ConnectionInfo> AllConnectionInfo(bool includeClosedConnections = false)
1783 {
1784 List<ConnectionInfo> returnList = new List<ConnectionInfo>();
1785
1786 lock (globalDictAndDelegateLocker)
1787 {
1788 foreach (var connectionsByEndPoint in allConnectionsByEndPoint)
1789 {
1790 foreach (var connection in connectionsByEndPoint.Value.Values)
1791 {
1792 if (connection.ConnectionInfo != null)
1793 returnList.Add(connection.ConnectionInfo);
1794 }
1795 }
1796
1797 if (includeClosedConnections)
1798 {
1799 foreach (var pair in oldNetworkIdentifierToConnectionInfo)
1800 {
1801 foreach (var infoList in pair.Value.Values)
1802 {
1803 returnList.AddRange(infoList);
1804 }
1805 }
1806 }
1807 }
1808
1809 List<ConnectionInfo> distinctList = new List<ConnectionInfo>();
1810 foreach (var info in returnList)
1811 if (!distinctList.Contains(info))
1812 distinctList.Add(info);
1813
1814 return distinctList;
1815 }
1816
1817 /// <summary>
1818 /// Returns a ConnectionInfo array containing information for all connections which have the provided networkIdentifier. It is also possible to include information for closed connections.
1819 /// </summary>
1820 /// <param name="networkIdentifier">The networkIdentifier corresponding to the desired connectionInfo information</param>
1821 /// <param name="includeClosedConnections">If true will include information for connections which are closed. Otherwise only active connections will be included.</param>
1822 /// <returns>List of ConnectionInfo containing information for matching connections</returns>
1823 public static List<ConnectionInfo> AllConnectionInfo(ShortGuid networkIdentifier, bool includeClosedConnections = false)
1824 {
1825 List<ConnectionInfo> returnList = new List<ConnectionInfo>();
1826
1827 lock (globalDictAndDelegateLocker)
1828 {
1829 foreach (var pair in allConnectionsByEndPoint)
1830 {
1831 foreach (var connection in pair.Value.Values)
1832 {
1833 if (connection.ConnectionInfo != null && connection.ConnectionInfo.NetworkIdentifier == networkIdentifier)
1834 returnList.Add(connection.ConnectionInfo);
1835 }
1836 }
1837
1838 if (includeClosedConnections)
1839 {
1840 foreach (var pair in oldNetworkIdentifierToConnectionInfo)
1841 {
1842 if (pair.Key == networkIdentifier)
1843 {
1844 foreach (var infoList in pair.Value.Values)
1845 foreach (var info in infoList)
1846 returnList.Add(info);
1847
1848 break;
1849 }
1850 }
1851 }
1852 }
1853
1854 List<ConnectionInfo> distinctList = new List<ConnectionInfo>();
1855 foreach (var info in returnList)
1856 if (!distinctList.Contains(info))
1857 distinctList.Add(info);
1858
1859 return distinctList;
1860 }
1861
1862 /// <summary>
1863 /// Returns the total number of connections
1864 /// </summary>
1865 /// <returns>Total number of connections</returns>
1866 public static int TotalNumConnections()
1867 {
1868 lock (globalDictAndDelegateLocker)
1869 {
1870 int sum = 0;
1871
1872 foreach (var current in allConnectionsByEndPoint)
1873 sum += current.Value.Count;
1874
1875 return sum;
1876 }
1877 }
1878
1879 /// <summary>
1880 /// Returns the total number of connections where the <see cref="ConnectionInfo.RemoteEndPoint"/> matches the provided <see cref="IPAddress"/>
1881 /// </summary>
1882 /// <param name="matchIP">The <see cref="IPAddress"/> to match</param>
1883 /// <returns>Total number of connections where the <see cref="ConnectionInfo.RemoteEndPoint "/> matches the provided <see cref="IPAddress"/></returns>
1884 public static int TotalNumConnections(IPAddress matchIP)
1885 {
1886 lock (globalDictAndDelegateLocker)
1887 {
1888 int sum = 0;
1889
1890 foreach (var current in allConnectionsByEndPoint)
1891 foreach (var connection in current.Value)
1892 if (connection.Value.ConnectionInfo.RemoteEndPoint.Address.Equals(matchIP))
1893 sum++;
1894
1895 return sum;
1896 }
1897 }
1898
1899 /// <summary>
1900 /// Close all connections
1901 /// </summary>
1902 public static void CloseAllConnections()
1903 {
1904 CloseAllConnections(ConnectionType.Undefined, new IPEndPoint[0]);
1905 }
1906
1907 /// <summary>
1908 /// Close all connections of the provided <see cref="ConnectionType"/>
1909 /// </summary>
1910 /// <param name="connectionType">The type of connections to be closed</param>
1911 public static void CloseAllConnections(ConnectionType connectionType)
1912 {
1913 CloseAllConnections(connectionType, new IPEndPoint[0]);
1914 }
1915
1916 /// <summary>
1917 /// Close all connections of the provided <see cref="ConnectionType"/> except to provided <see cref="IPEndPoint"/> array.
1918 /// </summary>
1919 /// <param name="connectionTypeToClose">The type of connections to be closed. ConnectionType.<see cref="ConnectionType.Undefined"/> matches all types.</param>
1920 /// <param name="closeAllExceptTheseEndPoints">Close all except those with provided <see cref="IPEndPoint"/> array</param>
1921 public static void CloseAllConnections(ConnectionType connectionTypeToClose, IPEndPoint[] closeAllExceptTheseEndPoints)
1922 {
1923 List<Connection> connectionsToClose = new List<Connection>();
1924
1925 lock (globalDictAndDelegateLocker)
1926 {
1927 foreach (var pair in allConnectionsByEndPoint)
1928 {
1929 foreach (var innerPair in pair.Value)
1930 {
1931 if (innerPair.Value != null && (connectionTypeToClose == ConnectionType.Undefined || innerPair.Key == connectionTypeToClose))
1932 {
1933 bool dontClose = false;
1934
1935 foreach (var endPointToNotClose in closeAllExceptTheseEndPoints)
1936 {
1937 if (endPointToNotClose == innerPair.Value.ConnectionInfo.RemoteEndPoint)
1938 {
1939 dontClose = true;
1940 break;
1941 }
1942 }
1943
1944 if (!dontClose )
1945 connectionsToClose.Add(innerPair.Value);
1946 }
1947 }
1948 }
1949 }
1950
1951 if (LoggingEnabled) logger.Trace("Closing " + connectionsToClose.Count.ToString() + " connections.");
1952
1953 foreach (Connection connection in connectionsToClose)
1954 connection.CloseConnection(false, -6);
1955 }
1956
1957 /// <summary>
1958 /// Returns a list of all connections
1959 /// </summary>
1960 /// <returns>A list of requested connections. If no matching connections exist returns empty list.</returns>
1961 public static List<Connection> GetExistingConnection()
1962 {
1963 return GetExistingConnection(ConnectionType.Undefined);
1964 }
1965
1966 /// <summary>
1967 /// Returns a list of all connections matching the provided <see cref="ConnectionType"/>
1968 /// </summary>
1969 /// <param name="connectionType">The type of connections to return. ConnectionType.<see cref="ConnectionType.Undefined"/> matches all types.</param>
1970 /// <returns>A list of requested connections. If no matching connections exist returns empty list.</returns>
1971 public static List<Connection> GetExistingConnection(ConnectionType connectionType)
1972 {
1973 List<Connection> result = new List<Connection>();
1974 lock (globalDictAndDelegateLocker)
1975 {
1976 foreach (var current in allConnectionsByEndPoint)
1977 {
1978 foreach (var inner in current.Value)
1979 {
1980 if (connectionType == ConnectionType.Undefined || inner.Key == connectionType)
1981 result.Add(inner.Value);
1982 }
1983 }
1984 }
1985
1986 if (LoggingEnabled) logger.Trace("RetrieveConnection by connectionType='" + connectionType.ToString() + "'. Returning list of " + result.Count.ToString() + " connections.");
1987
1988 return result;
1989 }
1990
1991 /// <summary>
1992 /// Retrieve a list of connections with the provided <see cref="ShortGuid"/> networkIdentifier of the provided <see cref="ConnectionType"/>.
1993 /// </summary>
1994 /// <param name="networkIdentifier">The <see cref="ShortGuid"/> corresponding with the desired peer networkIdentifier</param>
1995 /// <param name="connectionType">The <see cref="ConnectionType"/> desired</param>
1996 /// <returns>A list of connections to the desired peer. If no matching connections exist returns empty list.</returns>
1997 public static List<Connection> GetExistingConnection(ShortGuid networkIdentifier, ConnectionType connectionType)
1998 {
1999 List<Connection> resultList = new List<Connection>();
2000 lock (globalDictAndDelegateLocker)
2001 {
2002 foreach (var pair in allConnectionsById)
2003 {
2004 if (pair.Key == networkIdentifier && pair.Value.ContainsKey(connectionType))
2005 {
2006 resultList.AddRange(pair.Value[connectionType]);
2007 break;
2008 }
2009 }
2010 }
2011
2012 if (LoggingEnabled) logger.Trace("RetrieveConnection by networkIdentifier='" + networkIdentifier + "' and connectionType='" + connectionType.ToString() + "'. Returning list of " + resultList.Count.ToString() + " connections.");
2013
2014 return resultList;
2015 }
2016
2017 /// <summary>
2018 /// Retrieve an existing connection with the provided ConnectionInfo.
2019 /// </summary>
2020 /// <param name="connectionInfo">ConnectionInfo corresponding with the desired connection</param>
2021 /// <returns>The desired connection. If no matching connection exists returns null.</returns>
2022 public static Connection GetExistingConnection(ConnectionInfo connectionInfo)
2023 {
2024 if (connectionInfo == null) throw new ArgumentNullException("connectionInfo", "Provided ConnectionInfo cannot be null.");
2025
2026 Connection result = null;
2027 lock (globalDictAndDelegateLocker)
2028 {
2029 foreach (var pair in allConnectionsByEndPoint)
2030 {
2031 if(pair.Key.Equals(connectionInfo.RemoteEndPoint) && pair.Value.ContainsKey(connectionInfo.ConnectionType))
2032 {
2033 result = pair.Value[connectionInfo.ConnectionType];
2034 break;
2035 }
2036 }
2037 }
2038
2039 if (LoggingEnabled)
2040 {
2041 if (result == null)
2042 logger.Trace("RetrieveConnection by connectionInfo='"+connectionInfo+"'. No matching connection was found.");
2043 else
2044 logger.Trace("RetrieveConnection by connectionInfo='"+connectionInfo+"'. Matching connection was found.");
2045 }
2046
2047 return result;
2048 }
2049
2050 /// <summary>
2051 /// Retrieve an existing connection with the provided <see cref="IPEndPoint"/> of the provided <see cref="ConnectionType"/>.
2052 /// </summary>
2053 /// <param name="remoteEndPoint">IPEndPoint corresponding with the desired connection</param>
2054 /// <param name="connectionType">The <see cref="ConnectionType"/> desired</param>
2055 /// <returns>The desired connection. If no matching connection exists returns null.</returns>
2056 public static Connection GetExistingConnection(IPEndPoint remoteEndPoint, ConnectionType connectionType)
2057 {
2058 Connection result = null;
2059 lock (globalDictAndDelegateLocker)
2060 {
2061 //return (from current in NetworkComms.allConnectionsByEndPoint where current.Key == IPEndPoint && current.Value.ContainsKey(connectionType) select current.Value[connectionType]).FirstOrDefault();
2062 //return (from current in NetworkComms.allConnectionsByEndPoint where current.Key == IPEndPoint select current.Value[connectionType]).FirstOrDefault();
2063 if (allConnectionsByEndPoint.ContainsKey(remoteEndPoint))
2064 {
2065 if (allConnectionsByEndPoint[remoteEndPoint].ContainsKey(connectionType))
2066 result = allConnectionsByEndPoint[remoteEndPoint][connectionType];
2067 }
2068 }
2069
2070 if (LoggingEnabled)
2071 {
2072 string connectionTypeStr = connectionType.ToString();
2073 if (result == null)
2074 logger.Trace("RetrieveConnection by remoteEndPoint='" + remoteEndPoint.Address + ":" + remoteEndPoint.Port.ToString() + "' and connectionType='" + connectionTypeStr + "'. No matching connection was found.");
2075 else
2076 logger.Trace("RetrieveConnection by remoteEndPoint='" + remoteEndPoint.Address + ":" + remoteEndPoint.Port.ToString() + "' and connectionType='" + connectionTypeStr + "'. Matching connection was found.");
2077 }
2078
2079 return result;
2080 }
2081
2082 /// <summary>
2083 /// Check if a connection exists with the provided IPEndPoint and ConnectionType
2084 /// </summary>
2085 /// <param name="connectionInfo">ConnectionInfo corresponding with the desired connection</param>
2086 /// <returns>True if a matching connection exists, otherwise false</returns>
2087 public static bool ConnectionExists(ConnectionInfo connectionInfo)
2088 {
2089 if (connectionInfo == null) throw new ArgumentNullException("connectionInfo", "Provided ConnectionInfo cannot be null.");
2090
2091 bool result = false;
2092 lock (globalDictAndDelegateLocker)
2093 {
2094 if (allConnectionsByEndPoint.ContainsKey(connectionInfo.RemoteEndPoint))
2095 result = allConnectionsByEndPoint[connectionInfo.RemoteEndPoint].ContainsKey(connectionInfo.ConnectionType);
2096 }
2097
2098 if (LoggingEnabled) logger.Trace("Checking for existing connection by connectionInfo='" + connectionInfo +"'");
2099 return result;
2100 }
2101
2102 /// <summary>
2103 /// Check if a connection exists with the provided networkIdentifier and ConnectionType
2104 /// </summary>
2105 /// <param name="networkIdentifier">The <see cref="ShortGuid"/> corresponding with the desired peer networkIdentifier</param>
2106 /// <param name="connectionType">The <see cref="ConnectionType"/> desired</param>
2107 /// <returns>True if a matching connection exists, otherwise false</returns>
2108 public static bool ConnectionExists(ShortGuid networkIdentifier, ConnectionType connectionType)
2109 {
2110 bool result = false;
2111 lock (globalDictAndDelegateLocker)
2112 {
2113 if (allConnectionsById.ContainsKey(networkIdentifier))
2114 {
2115 if (allConnectionsById[networkIdentifier].ContainsKey(connectionType))
2116 result = allConnectionsById[networkIdentifier][connectionType].Count > 0;
2117 }
2118 }
2119
2120 if (LoggingEnabled)
2121 {
2122 string connectionTypeStr = connectionType.ToString();
2123 logger.Trace("Checking for existing connection by identifier='" + networkIdentifier + "' and connectionType='" + connectionTypeStr + "'");
2124 }
2125 return result;
2126 }
2127
2128 /// <summary>
2129 /// Check if a connection exists with the provided IPEndPoint and ConnectionType
2130 /// </summary>
2131 /// <param name="remoteEndPoint">IPEndPoint corresponding with the desired connection</param>
2132 /// <param name="connectionType">The <see cref="ConnectionType"/> desired</param>
2133 /// <returns>True if a matching connection exists, otherwise false</returns>
2134 public static bool ConnectionExists(IPEndPoint remoteEndPoint, ConnectionType connectionType)
2135 {
2136 bool result = false;
2137 lock (globalDictAndDelegateLocker)
2138 {
2139 if (allConnectionsByEndPoint.ContainsKey(remoteEndPoint))
2140 result = allConnectionsByEndPoint[remoteEndPoint].ContainsKey(connectionType);
2141 }
2142
2143 if (LoggingEnabled)
2144 {
2145 string connectionTypeStr = connectionType.ToString();
2146 logger.Trace("Checking for existing connection by endPoint='" + remoteEndPoint.Address + ":" + remoteEndPoint.Port.ToString() + "' and connectionType='" + connectionTypeStr + "'");
2147 }
2148 return result;
2149 }
2150
2151 /// <summary>
2152 /// Removes the reference to the provided connection from within networkComms. DOES NOT CLOSE THE CONNECTION. Returns true if the provided connection reference existed and was removed, false otherwise.
2153 /// </summary>
2154 /// <param name="connection"></param>
2155 /// <param name="maintainConnectionInfoHistory"></param>
2156 /// <returns></returns>
2157 internal static bool RemoveConnectionReference(Connection connection, bool maintainConnectionInfoHistory = true)
2158 {
2159 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Entering RemoveConnectionReference for " + connection.ConnectionInfo);
2160
2161 //We don't have the connection identifier until the connection has been established.
2162 //if (!connection.ConnectionInfo.ConnectionEstablished && !connection.ConnectionInfo.ConnectionShutdown)
2163 // return false;
2164
2165 if (connection.ConnectionInfo.ConnectionState == ConnectionState.Established && !(connection.ConnectionInfo.ConnectionState == ConnectionState.Shutdown))
2166 throw new ConnectionShutdownException("A connection can only be removed once correctly shutdown.");
2167
2168 bool returnValue = false;
2169
2170 //Ensure connection references are removed from networkComms
2171 //Once we think we have closed the connection it's time to get rid of our other references
2172 lock (globalDictAndDelegateLocker)
2173 {
2174 #region Update NetworkComms Connection Dictionaries
2175 ShortGuid currentNetworkIdentifier = connection.ConnectionInfo.NetworkIdentifier;
2176
2177 //We establish whether we have already done this step
2178 if ((allConnectionsById.ContainsKey(currentNetworkIdentifier) &&
2179 allConnectionsById[currentNetworkIdentifier].ContainsKey(connection.ConnectionInfo.ConnectionType) &&
2180 allConnectionsById[currentNetworkIdentifier][connection.ConnectionInfo.ConnectionType].Contains(connection))
2181 ||
2182 (allConnectionsByEndPoint.ContainsKey(connection.ConnectionInfo.RemoteEndPoint) &&
2183 allConnectionsByEndPoint[connection.ConnectionInfo.RemoteEndPoint].ContainsKey(connection.ConnectionInfo.ConnectionType)))
2184 {
2185 //Maintain a reference if this is our first connection close
2186 returnValue = true;
2187 }
2188
2189 //Keep a reference of the connection for possible debugging later
2190 if (maintainConnectionInfoHistory)
2191 {
2192 if (oldNetworkIdentifierToConnectionInfo.ContainsKey(currentNetworkIdentifier))
2193 {
2194 if (oldNetworkIdentifierToConnectionInfo[currentNetworkIdentifier].ContainsKey(connection.ConnectionInfo.ConnectionType))
2195 oldNetworkIdentifierToConnectionInfo[currentNetworkIdentifier][connection.ConnectionInfo.ConnectionType].Add(connection.ConnectionInfo);
2196 else
2197 oldNetworkIdentifierToConnectionInfo[currentNetworkIdentifier].Add(connection.ConnectionInfo.ConnectionType, new List<ConnectionInfo>() { connection.ConnectionInfo });
2198 }
2199 else
2200 oldNetworkIdentifierToConnectionInfo.Add(currentNetworkIdentifier, new Dictionary<ConnectionType, List<ConnectionInfo>>() { { connection.ConnectionInfo.ConnectionType, new List<ConnectionInfo>() { connection.ConnectionInfo } } });
2201 }
2202
2203 if (allConnectionsById.ContainsKey(currentNetworkIdentifier) &&
2204 allConnectionsById[currentNetworkIdentifier].ContainsKey(connection.ConnectionInfo.ConnectionType))
2205 {
2206 //if (!allConnectionsById[currentNetworkIdentifier][connection.ConnectionInfo.ConnectionType].Contains(connection))
2207 // throw new ConnectionShutdownException("A reference to the connection being closed was not found in the allConnectionsById dictionary.");
2208 //else
2209 if (allConnectionsById[currentNetworkIdentifier][connection.ConnectionInfo.ConnectionType].Contains(connection))
2210 allConnectionsById[currentNetworkIdentifier][connection.ConnectionInfo.ConnectionType].Remove(connection);
2211
2212 //Remove the connection type reference if it is empty
2213 if (allConnectionsById[currentNetworkIdentifier][connection.ConnectionInfo.ConnectionType].Count == 0)
2214 allConnectionsById[currentNetworkIdentifier].Remove(connection.ConnectionInfo.ConnectionType);
2215
2216 //Remove the identifier reference
2217 if (allConnectionsById[currentNetworkIdentifier].Count == 0)
2218 allConnectionsById.Remove(currentNetworkIdentifier);
2219
2220 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Removed connection reference by ID for " + connection.ConnectionInfo);
2221 }
2222
2223 //We can now remove this connection by end point as well
2224 if (allConnectionsByEndPoint.ContainsKey(connection.ConnectionInfo.RemoteEndPoint))
2225 {
2226 if (allConnectionsByEndPoint[connection.ConnectionInfo.RemoteEndPoint].ContainsKey(connection.ConnectionInfo.ConnectionType))
2227 allConnectionsByEndPoint[connection.ConnectionInfo.RemoteEndPoint].Remove(connection.ConnectionInfo.ConnectionType);
2228
2229 //If this was the last connection type for this endpoint we can remove the endpoint reference as well
2230 if (allConnectionsByEndPoint[connection.ConnectionInfo.RemoteEndPoint].Count == 0)
2231 allConnectionsByEndPoint.Remove(connection.ConnectionInfo.RemoteEndPoint);
2232
2233 if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Removed connection reference by endPoint for " + connection.ConnectionInfo);
2234 }
2235 #endregion
2236 }
2237
2238 return returnValue;
2239 }
2240
2241 /// <summary>
2242 /// Adds a reference by IPEndPoint to the provided connection within networkComms.
2243 /// </summary>
2244 /// <param name="connection"></param>
2245 /// <param name="endPointToUse">An optional override which forces a specific IPEndPoint</param>
2246 internal static void AddConnectionByReferenceEndPoint(Connection connection, IPEndPoint endPointToUse = null)
2247 {
2248 if (NetworkComms.LoggingEnabled)
2249 NetworkComms.Logger.Trace("Adding connection reference by endPoint. Connection='"+connection.ConnectionInfo+"'." +
2250 (endPointToUse != null ? " Provided override endPoint of " + endPointToUse.Address + ":" + endPointToUse.Port.ToString() : ""));
2251
2252 //If the remoteEndPoint is IPAddress.Any we don't record it by endPoint
2253 if (connection.ConnectionInfo.RemoteEndPoint.Address.Equals(IPAddress.Any) || (endPointToUse != null && endPointToUse.Address.Equals(IPAddress.Any)))
2254 return;
2255
2256 if (connection.ConnectionInfo.ConnectionState == ConnectionState.Established || connection.ConnectionInfo.ConnectionState == ConnectionState.Shutdown)
2257 throw new ConnectionSetupException("Connection reference by endPoint should only be added before a connection is established. This is to prevent duplicate connections.");
2258
2259 if (endPointToUse == null) endPointToUse = connection.ConnectionInfo.RemoteEndPoint;
2260
2261 //We can double check for an existing connection here first so that it occurs outside the lock
2262 Connection existingConnection = GetExistingConnection(endPointToUse, connection.ConnectionInfo.ConnectionType);
2263 if (existingConnection != null && existingConnection.ConnectionInfo.ConnectionState == ConnectionState.Established && connection!=existingConnection)
2264 existingConnection.ConnectionAlive();
2265
2266 //How do we prevent multiple threads from trying to create a duplicate connection??
2267 lock (globalDictAndDelegateLocker)
2268 {
2269 //We now check for an existing connection again from within the lock
2270 if (ConnectionExists(endPointToUse, connection.ConnectionInfo.ConnectionType))
2271 {
2272 //If a connection still exist we don't assume it is the same as above
2273 existingConnection = GetExistingConnection(endPointToUse, connection.ConnectionInfo.ConnectionType);
2274 if (existingConnection != connection)
2275 {
2276 throw new DuplicateConnectionException("A different connection already exists with the desired endPoint (" + endPointToUse.Address + ":" + endPointToUse.Port.ToString() + "). This can occasionaly occur if two peers try to connect to each other simultaneously. New connection is " + (existingConnection.ConnectionInfo.ServerSide ? "server side" : "client side") + " - " + connection.ConnectionInfo +
2277 ". Existing connection is " + (existingConnection.ConnectionInfo.ServerSide ? "server side" : "client side") + ", " + existingConnection.ConnectionInfo.ConnectionState.ToString() + " - " + (existingConnection.ConnectionInfo.ConnectionState == ConnectionState.Establishing ? "creationTime:" + existingConnection.ConnectionInfo.ConnectionCreationTime.ToString() : "establishedTime:" + existingConnection.ConnectionInfo.ConnectionEstablishedTime.ToString()) + " - " + " details - " + existingConnection.ConnectionInfo);
2278 }
2279 else
2280 {
2281 //We have just tried to add the same reference twice, no need to do anything this time around
2282 }
2283 }
2284 else
2285 {
2286 #if FREETRIAL
2287 //If this is a free trial we only allow a single connection. We will throw an exception if any connections already exist
2288 if (TotalNumConnections() != 0)
2289 throw new NotSupportedException("Unable to create connection as this version of NetworkComms.Net is limited to only one connection. Please purchase a commerical license from www.networkcomms.net which supports an unlimited number of connections.");
2290 #endif
2291
2292 //Add reference to the endPoint dictionary
2293 if (allConnectionsByEndPoint.ContainsKey(endPointToUse))
2294 {
2295 if (allConnectionsByEndPoint[endPointToUse].ContainsKey(connection.ConnectionInfo.ConnectionType))
2296 throw new Exception("Idiot check fail. The method ConnectionExists should have prevented execution getting here!!");
2297 else
2298 allConnectionsByEndPoint[endPointToUse].Add(connection.ConnectionInfo.ConnectionType, connection);
2299 }
2300 else
2301 allConnectionsByEndPoint.Add(endPointToUse, new Dictionary<ConnectionType, Connection>() { { connection.ConnectionInfo.ConnectionType, connection } });
2302 }
2303 }
2304 }
2305
2306 /// <summary>
2307 /// Update the endPoint reference for the provided connection with the newEndPoint. If there is no change just returns
2308 /// </summary>
2309 /// <param name="connection"></param>
2310 /// <param name="newRemoteEndPoint"></param>
2311 internal static void UpdateConnectionReferenceByEndPoint(Connection connection, IPEndPoint newRemoteEndPoint)
2312 {
2313 if (NetworkComms.LoggingEnabled)
2314 NetworkComms.Logger.Trace("Updating connection reference by endPoint. Connection='" + connection.ConnectionInfo + "'." + (newRemoteEndPoint != null ? " Provided new endPoint of " + newRemoteEndPoint.Address + ":" + newRemoteEndPoint.Port.ToString() : ""));
2315
2316 if (!connection.ConnectionInfo.RemoteEndPoint.Equals(newRemoteEndPoint))
2317 {
2318 lock (globalDictAndDelegateLocker)
2319 {
2320 RemoveConnectionReference(connection, false);
2321 AddConnectionByReferenceEndPoint(connection, newRemoteEndPoint);
2322 }
2323 }
2324 }
2325
2326 /// <summary>
2327 /// Add a reference by networkIdentifier to the provided connection within NetworkComms. Requires a reference by IPEndPoint to already exist.
2328 /// </summary>
2329 /// <param name="connection"></param>
2330 internal static void AddConnectionReferenceByIdentifier(Connection connection)
2331 {
2332 if (!(connection.ConnectionInfo.ConnectionState == ConnectionState.Established) || connection.ConnectionInfo.ConnectionState == ConnectionState.Shutdown)
2333 throw new ConnectionSetupException("Connection reference by identifier should only be added once a connection is established. This is to prevent duplicate connections.");
2334
2335 if (connection.ConnectionInfo.NetworkIdentifier == ShortGuid.Empty)
2336 throw new ConnectionSetupException("Should not be calling AddConnectionByIdentifierReference unless the connection remote identifier has been set.");
2337
2338 if (NetworkComms.LoggingEnabled)
2339 NetworkComms.Logger.Trace("Adding connection reference by identifier. Connection=" + connection.ConnectionInfo + ".");
2340
2341 lock (globalDictAndDelegateLocker)
2342 {
2343 //There should already be a reference to this connection in the endPoint dictionary
2344 if (!ConnectionExists(connection.ConnectionInfo.RemoteEndPoint, connection.ConnectionInfo.ConnectionType))
2345 throw new ConnectionSetupException("A reference by identifier should only be added if a reference by endPoint already exists.");
2346
2347 //Check for an existing reference first, if there is one and it matches this connection then no worries
2348 if (allConnectionsById.ContainsKey(connection.ConnectionInfo.NetworkIdentifier))
2349 {
2350 if (allConnectionsById[connection.ConnectionInfo.NetworkIdentifier].ContainsKey(connection.ConnectionInfo.ConnectionType))
2351 {
2352 if (!allConnectionsById[connection.ConnectionInfo.NetworkIdentifier][connection.ConnectionInfo.ConnectionType].Contains(connection))
2353 {
2354 foreach (var current in allConnectionsById[connection.ConnectionInfo.NetworkIdentifier][connection.ConnectionInfo.ConnectionType])
2355 {
2356 if (current.ConnectionInfo.RemoteEndPoint.Equals(connection.ConnectionInfo.RemoteEndPoint))
2357 throw new ConnectionSetupException("A different connection to the same remoteEndPoint already exists. Duplicate connections should be prevented elsewhere. Existing connection " + current.ConnectionInfo + ", new connection " + connection.ConnectionInfo);
2358 }
2359 }
2360 else
2361 {
2362 //We are trying to add the same connection twice, so just do nothing here.
2363 }
2364 }
2365 else
2366 allConnectionsById[connection.ConnectionInfo.NetworkIdentifier].Add(connection.ConnectionInfo.ConnectionType, new List<Connection>() { connection });
2367 }
2368 else
2369 allConnectionsById.Add(connection.ConnectionInfo.NetworkIdentifier, new Dictionary<ConnectionType, List<Connection>>() { { connection.ConnectionInfo.ConnectionType, new List<Connection>() {connection}} });
2370 }
2371 }
2372 #endregion
2373 }
基于此系统编写了几个都不大的小项目
服务器端采用 win server 2003 .net框架2.0 数据库mssql2005
客户端 红米手机 或者其他安卓手机
能够顺利的实现在手机上提交数据到服务器,或者从服务器上获取数据。
这几天有时间会整理一个小demo出来,给大家参考,敬请期待。
www.cnblogs.com/networkcomms
www.networkcomms.cn(建设中)