1 using System; 2 using System.Diagnostics; 3 using System.Globalization; 4 using System.Net; 5 using System.IO; 6 using System.IO.Compression; 7 using System.Threading; 8 using System.Xml; 9 using Utility; 10 using WorldWind; 11 12 namespace WorldWind.Net 13 { 14 public delegate void DownloadProgressHandler(int bytesRead, int totalBytes); 15 public delegate void DownloadCompleteHandler(WebDownload wd); 16 17 public enum DownloadType 18 { 19 Unspecified, 20 Wms 21 } 22 /// <summary> 23 /// 网络下载对象,负责下载数据 24 /// </summary> 25 public class WebDownload : IDisposable 26 { 27 #region Static proxy properties 28 29 static public bool Log404Errors = false; 30 static public bool useWindowsDefaultProxy = true; 31 static public string proxyUrl = ""; 32 static public bool useDynamicProxy; 33 static public string proxyUserName = ""; 34 static public string proxyPassword = ""; 35 36 #endregion 37 public static string UserAgent = String.Format( 38 CultureInfo.InvariantCulture, 39 "World Wind v{0} ({1}, {2})", 40 System.Windows.Forms.Application.ProductVersion, 41 Environment.OSVersion.ToString(), 42 CultureInfo.CurrentCulture.Name); 43 44 //下载连接字符串 45 public string Url; 46 47 /// <summary> 48 /// Memory downloads fills this stream 49 /// </summary> 50 public Stream ContentStream; 51 52 public string SavedFilePath; 53 public bool IsComplete; 54 55 /// <summary> 56 /// Called when data is being received. 57 /// Note that totalBytes will be zero if the server does not respond with content-length. 58 /// 开始接收数据时回调 59 /// 总的字节数为0,如果服务没有返回目录长度 60 /// </summary> 61 public DownloadProgressHandler ProgressCallback; 62 63 /// <summary> 64 /// Called to update debug window. 65 /// 更新debug窗体,回调函数 66 /// </summary> 67 public static DownloadCompleteHandler DebugCallback; 68 69 /// <summary> 70 /// Called when a download has ended with success or failure 71 /// 当下载成功或者失败时回调 72 /// </summary> 73 public static DownloadCompleteHandler DownloadEnded; 74 75 /// <summary> 76 /// Called when download is completed. Call Verify from event handler to throw any exception. 77 /// 下载完成时回调,调用验证事件是否抛出异常。 78 /// </summary> 79 public DownloadCompleteHandler CompleteCallback; 80 81 public DownloadType DownloadType = DownloadType.Unspecified; 82 public string ContentType; 83 public int BytesProcessed; 84 public int ContentLength; 85 86 // variables to allow placefinder to use gzipped requests 87 // *default to uncompressed requests to avoid breaking other things 88 public bool Compressed = false; 89 public string ContentEncoding; 90 91 /// <summary> 92 /// The download start time (or MinValue if not yet started) 93 /// </summary> 94 public DateTime DownloadStartTime = DateTime.MinValue; 95 96 internal HttpWebRequest request; 97 internal HttpWebResponse response; 98 99 protected Exception downloadException; 100 101 protected bool isMemoryDownload; 102 /// <summary> 103 /// used to signal thread abortion; if true, the download thread was aborted 104 /// </summary> 105 private bool stopFlag = false; 106 //下载线程 107 protected Thread dlThread; 108 109 /// <summary> 110 /// Initializes a new instance of the <see cref="WebDownload"/> class. 111 /// 构造函数,初始化下载对象 112 /// </summary> 113 /// <param name="url">The URL to download from.</param> 114 public WebDownload(string url) 115 { 116 this.Url = url; 117 } 118 119 /// <summary> 120 /// Initializes a new instance of the <see cref="T:WorldWind.Net.WebDownload"/> class. 121 /// </summary> 122 public WebDownload() 123 { 124 } 125 126 /// <summary> 127 /// Whether the download is currently being processed (active). 128 /// 当前下载是否仍在进行 129 /// </summary> 130 public bool IsDownloadInProgress 131 { 132 get 133 { 134 return dlThread != null && dlThread.IsAlive; 135 } 136 } 137 138 /// <summary> 139 /// Contains the exception that occurred during download, or null if successful. 140 /// </summary> 141 public Exception Exception 142 { 143 get 144 { 145 return downloadException; 146 } 147 } 148 149 /// <summary> 150 /// Asynchronous download of HTTP data to file. 151 /// 异步下载Http数据,通过开启新线程实现。 152 /// </summary> 153 public void BackgroundDownloadFile() 154 { 155 if (CompleteCallback == null) 156 throw new ArgumentException("No download complete callback specified."); 157 //实例化下载线程 158 dlThread = new Thread(new ThreadStart(Download)); 159 dlThread.Name = "WebDownload.dlThread"; 160 dlThread.IsBackground = true; 161 dlThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture; 162 //开启后台下载线程 163 dlThread.Start(); 164 } 165 166 /// <summary> 167 /// Asynchronous download of HTTP data to file. 168 /// 异步下载Http数据,绑定下载完成后的回调函数。 169 /// </summary> 170 public void BackgroundDownloadFile(DownloadCompleteHandler completeCallback) 171 { 172 CompleteCallback += completeCallback; 173 BackgroundDownloadFile(); 174 } 175 176 /// <summary> 177 /// Download image of specified type. (handles server errors for wms type) 178 /// </summary> 179 public void BackgroundDownloadFile(DownloadType dlType) 180 { 181 DownloadType = dlType; 182 BackgroundDownloadFile(); 183 } 184 185 /// <summary> 186 /// Asynchronous download of HTTP data to in-memory buffer. 187 /// 异步下载Http数据到内存缓冲区 188 /// </summary> 189 public void BackgroundDownloadMemory() 190 { 191 if (CompleteCallback == null) 192 throw new ArgumentException("No download complete callback specified."); 193 194 isMemoryDownload = true; 195 dlThread = new Thread(new ThreadStart(Download)); 196 dlThread.Name = "WebDownload.dlThread(2)"; 197 dlThread.IsBackground = true; 198 dlThread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture; 199 dlThread.Start(); 200 } 201 202 /// <summary> 203 /// Asynchronous download of HTTP data to in-memory buffer. 204 /// </summary> 205 public void BackgroundDownloadMemory(DownloadCompleteHandler completeCallback) 206 { 207 CompleteCallback += completeCallback; 208 BackgroundDownloadMemory(); 209 } 210 211 /// <summary> 212 /// Download image of specified type. (handles server errors for WMS type) 213 /// </summary> 214 /// <param name="dlType">Type of download.</param> 215 public void BackgroundDownloadMemory(DownloadType dlType) 216 { 217 DownloadType = dlType; 218 BackgroundDownloadMemory(); 219 } 220 221 /// <summary> 222 /// Synchronous download of HTTP data to in-memory buffer. 223 /// </summary> 224 public void DownloadMemory() 225 { 226 isMemoryDownload = true; 227 Download(); 228 } 229 230 /// <summary> 231 /// Download image of specified type. (handles server errors for WMS type) 232 /// </summary> 233 public void DownloadMemory(DownloadType dlType) 234 { 235 DownloadType = dlType; 236 DownloadMemory(); 237 } 238 239 /// <summary> 240 /// HTTP downloads to memory. 241 /// </summary> 242 /// <param name="progressCallback">The progress callback.</param> 243 public void DownloadMemory(DownloadProgressHandler progressCallback) 244 { 245 ProgressCallback += progressCallback; 246 DownloadMemory(); 247 } 248 249 /// <summary> 250 /// Synchronous download of HTTP data to in-memory buffer. 251 /// </summary> 252 public void DownloadFile(string destinationFile) 253 { 254 SavedFilePath = destinationFile; 255 256 Download(); 257 } 258 259 /// <summary> 260 /// Download image of specified type to a file. (handles server errors for WMS type) 261 /// </summary> 262 public void DownloadFile(string destinationFile, DownloadType dlType) 263 { 264 DownloadType = dlType; 265 DownloadFile(destinationFile); 266 } 267 268 /// <summary> 269 /// Saves a http in-memory download to file. 270 /// </summary> 271 /// <param name="destinationFilePath">File to save the downloaded data to.</param> 272 public void SaveMemoryDownloadToFile(string destinationFilePath) 273 { 274 if (ContentStream == null) 275 throw new InvalidOperationException("No data available."); 276 277 // Cache the capabilities on file system 278 ContentStream.Seek(0, SeekOrigin.Begin); 279 using (Stream fileStream = File.Create(destinationFilePath)) 280 { 281 if (ContentStream is MemoryStream) 282 { 283 // Write the MemoryStream buffer directly (2GB limit) 284 MemoryStream ms = (MemoryStream)ContentStream; 285 fileStream.Write(ms.GetBuffer(), 0, (int)ms.Length); 286 } 287 else 288 { 289 // Block copy 290 byte[] buffer = new byte[4096]; 291 while (true) 292 { 293 int numRead = ContentStream.Read(buffer, 0, buffer.Length); 294 if (numRead <= 0) 295 break; 296 fileStream.Write(buffer, 0, numRead); 297 } 298 } 299 } 300 ContentStream.Seek(0, SeekOrigin.Begin); 301 } 302 303 /// <summary> 304 /// Aborts the current download. 305 /// 终止当前下载 306 /// </summary> 307 public void Cancel() 308 { 309 CompleteCallback = null; 310 ProgressCallback = null; 311 if (dlThread != null && dlThread != Thread.CurrentThread) 312 { 313 if (dlThread.IsAlive) 314 { 315 Log.Write(Log.Levels.Verbose, "WebDownload.Cancel() : stopping download thread..."); 316 stopFlag = true; 317 if (!dlThread.Join(500)) 318 { 319 Log.Write(Log.Levels.Warning, "WebDownload.Cancel() : download thread refuses to die, forcing Abort()"); 320 dlThread.Abort(); 321 } 322 } 323 dlThread = null; 324 } 325 } 326 327 /// <summary> 328 /// Notify event subscribers of download progress. 329 /// </summary> 330 /// <param name="bytesRead">Number of bytes read.</param> 331 /// <param name="totalBytes">Total number of bytes for request or 0 if unknown.</param> 332 private void OnProgressCallback(int bytesRead, int totalBytes) 333 { 334 if (ProgressCallback != null) 335 { 336 ProgressCallback(bytesRead, totalBytes); 337 } 338 } 339 340 /// <summary> 341 /// Called with detailed information about the download. 342 /// </summary> 343 /// <param name="wd">The WebDownload.</param> 344 private static void OnDebugCallback(WebDownload wd) 345 { 346 if (DebugCallback != null) 347 { 348 DebugCallback(wd); 349 } 350 } 351 352 /// <summary> 353 /// Called when downloading has ended. 354 /// </summary> 355 /// <param name="wd">The download.</param> 356 private static void OnDownloadEnded(WebDownload wd) 357 { 358 if (DownloadEnded != null) 359 { 360 DownloadEnded(wd); 361 } 362 } 363 364 /// <summary> 365 /// Synchronous HTTP download 366 /// 线程异步调用的方法Download中,采用请求响应同步下载数据 367 /// </summary> 368 protected void Download() 369 { 370 Log.Write(Log.Levels.Debug, "Starting download thread..."); 371 372 Debug.Assert(Url.StartsWith("http://")); 373 DownloadStartTime = DateTime.Now; 374 try 375 { 376 try 377 { 378 // If a registered progress-callback, inform it of our download progress so far. 379 OnProgressCallback(0, 1); 380 OnDebugCallback(this); 381 382 // check to see if thread was aborted (multiple such checks within the thread function) 383 if (stopFlag) 384 { 385 IsComplete = true; 386 return; 387 } 388 389 390 // create content stream from memory or file 391 if (isMemoryDownload && ContentStream == null) 392 { 393 ContentStream = new MemoryStream(); 394 } 395 else 396 { 397 // Download to file 398 string targetDirectory = Path.GetDirectoryName(SavedFilePath); 399 if (targetDirectory.Length > 0) 400 Directory.CreateDirectory(targetDirectory); 401 ContentStream = new FileStream(SavedFilePath, FileMode.Create); 402 } 403 404 // Create the request object. 405 request = (HttpWebRequest)WebRequest.Create(Url); 406 request.UserAgent = UserAgent; 407 408 409 if (this.Compressed) 410 { 411 request.Headers.Add("Accept-Encoding", "gzip,deflate"); 412 } 413 if (stopFlag) 414 { 415 IsComplete = true; 416 return; 417 } 418 request.Proxy = ProxyHelper.DetermineProxyForUrl( 419 Url, 420 useWindowsDefaultProxy, 421 useDynamicProxy, 422 proxyUrl, 423 proxyUserName, 424 proxyPassword); 425 426 // TODO: probably better done via BeginGetResponse() / EndGetResponse() because this may block for a while 427 // causing warnings in thread abortion. 428 using (response = request.GetResponse() as HttpWebResponse) 429 { 430 // only if server responds 200 OK 431 if (response.StatusCode == HttpStatusCode.OK) 432 { 433 ContentType = response.ContentType; 434 ContentEncoding = response.ContentEncoding; 435 436 // Find the data size from the headers. 437 string strContentLength = response.Headers["Content-Length"]; 438 if (strContentLength != null) 439 { 440 ContentLength = int.Parse(strContentLength, CultureInfo.InvariantCulture); 441 } 442 //缓存字节数组,大小1500byte 443 byte[] readBuffer = new byte[1500]; 444 using (Stream responseStream = response.GetResponseStream()) 445 { 446 while (true) 447 { 448 if (stopFlag) 449 { 450 IsComplete = true; 451 return; 452 } 453 454 // Pass do.readBuffer to BeginRead. 455 int bytesRead = responseStream.Read(readBuffer, 0, readBuffer.Length); 456 if (bytesRead <= 0) 457 break; 458 459 //TODO: uncompress responseStream if necessary so that ContentStream is always uncompressed 460 // - at the moment, ContentStream is compressed if the requesting code sets 461 // download.Compressed == true, so ContentStream must be decompressed 462 // by whatever code is requesting the gzipped download 463 // - this hack only works for requests made using the methods that download to memory, 464 // requests downloading to file will result in a gzipped file 465 // - requests that do not explicity set download.Compressed = true should be unaffected 466 467 ContentStream.Write(readBuffer, 0, bytesRead); 468 469 BytesProcessed += bytesRead; 470 471 // If a registered progress-callback, inform it of our download progress so far. 472 OnProgressCallback(BytesProcessed, ContentLength); 473 OnDebugCallback(this); 474 } 475 } 476 477 } 478 } 479 480 HandleErrors(); 481 } 482 catch (ThreadAbortException) 483 { 484 // re-throw to avoid it being caught by the catch-all below 485 Log.Write(Log.Levels.Verbose, "Re-throwing ThreadAbortException."); 486 throw; 487 } 488 catch (System.Configuration.ConfigurationException) 489 { 490 // is thrown by WebRequest.Create if App.config is not in the correct format 491 // TODO: don't know what to do with it 492 throw; 493 } 494 catch (Exception caught) 495 { 496 try 497 { 498 // Remove broken file download 499 if (ContentStream != null) 500 { 501 ContentStream.Close(); 502 ContentStream = null; 503 } 504 if (SavedFilePath != null && SavedFilePath.Length > 0) 505 { 506 File.Delete(SavedFilePath); 507 } 508 } 509 catch (Exception) 510 { 511 } 512 SaveException(caught); 513 } 514 515 if (stopFlag) 516 { 517 IsComplete = true; 518 return; 519 } 520 521 if (ContentLength == 0) 522 { 523 ContentLength = BytesProcessed; 524 // If a registered progress-callback, inform it of our completion 525 OnProgressCallback(BytesProcessed, ContentLength); 526 } 527 528 if (ContentStream is MemoryStream) 529 { 530 ContentStream.Seek(0, SeekOrigin.Begin); 531 } 532 else if (ContentStream != null) 533 { 534 ContentStream.Close(); 535 ContentStream = null; 536 } 537 538 OnDebugCallback(this); 539 540 if (CompleteCallback == null) 541 { 542 Verify(); 543 } 544 else 545 { 546 CompleteCallback(this); 547 } 548 } 549 catch (ThreadAbortException) 550 { 551 Log.Write(Log.Levels.Verbose, "Download aborted."); 552 } 553 finally 554 { 555 IsComplete = true; 556 } 557 558 OnDownloadEnded(this); 559 } 560 561 /// <summary> 562 /// Handle server errors that don't get trapped by the web request itself. 563 /// </summary> 564 private void HandleErrors() 565 { 566 // HACK: Workaround for TerraServer failing to return 404 on not found 567 if (ContentStream.Length == 15) 568 { 569 // a true 404 error is a System.Net.WebException, so use the same text here 570 Exception ex = new FileNotFoundException("The remote server returned an error: (404) Not Found.", SavedFilePath); 571 SaveException(ex); 572 } 573 574 // TODO: WMS 1.1 content-type != xml 575 // TODO: Move WMS logic to WmsDownload 576 if (DownloadType == DownloadType.Wms && ( 577 ContentType.StartsWith("text/xml") || 578 ContentType.StartsWith("application/vnd.ogc.se"))) 579 { 580 // WMS request failure 581 SetMapServerError(); 582 } 583 } 584 585 /// <summary> 586 /// If exceptions occurred they will be thrown by calling this function. 587 /// </summary> 588 public void Verify() 589 { 590 if (Exception != null) 591 throw Exception; 592 } 593 594 /// <summary> 595 /// Log download error to log file 596 /// </summary> 597 /// <param name="exception"></param> 598 private void SaveException(Exception exception) 599 { 600 // Save the exception 601 downloadException = exception; 602 603 if (Exception is ThreadAbortException) 604 // Don't log canceled downloads 605 return; 606 607 if (Log404Errors) 608 { 609 Log.Write(Log.Levels.Error, "HTTP", "Error: " + Url); 610 Log.Write(Log.Levels.Error + 1, "HTTP", " : " + exception.Message); 611 } 612 } 613 614 /// <summary> 615 /// Reads the xml response from the server and throws an error with the message. 616 /// </summary> 617 private void SetMapServerError() 618 { 619 try 620 { 621 XmlDocument errorDoc = new XmlDocument(); 622 ContentStream.Seek(0, SeekOrigin.Begin); 623 errorDoc.Load(ContentStream); 624 string msg = ""; 625 foreach (XmlNode node in errorDoc.GetElementsByTagName("ServiceException")) 626 msg += node.InnerText.Trim() + Environment.NewLine; 627 SaveException(new WebException(msg.Trim())); 628 } 629 catch (XmlException) 630 { 631 SaveException(new WebException("An error occurred while trying to download " + request.RequestUri.ToString() + ".")); 632 } 633 } 634 635 #region IDisposable Members 636 637 /// <summary> 638 /// Performs application-defined tasks associated with freeing, releasing, or 639 /// resetting unmanaged resources. 640 /// </summary> 641 public void Dispose() 642 { 643 if (dlThread != null && dlThread != Thread.CurrentThread) 644 { 645 if (dlThread.IsAlive) 646 { 647 Log.Write(Log.Levels.Verbose, "WebDownload.Dispose() : stopping download thread..."); 648 stopFlag = true; 649 if (!dlThread.Join(500)) 650 { 651 Log.Write(Log.Levels.Warning, "WebDownload.Dispose() : download thread refuses to die, forcing Abort()"); 652 dlThread.Abort(); 653 } 654 } 655 dlThread = null; 656 } 657 658 if (request != null) 659 { 660 request.Abort(); 661 request = null; 662 } 663 664 if (ContentStream != null) 665 { 666 ContentStream.Close(); 667 ContentStream = null; 668 } 669 670 if (DownloadStartTime != DateTime.MinValue) 671 OnDebugCallback(this); 672 673 GC.SuppressFinalize(this); 674 } 675 #endregion 676 } 677 }
该类基于Http协议,应用层。
Socket有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。传输层
流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;
数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。