作为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(建设中)