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 ///23 /// 网络下载对象,负责下载数据 24 /// 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 ///48 /// Memory downloads fills this stream 49 /// 50 public Stream ContentStream; 51 52 public string SavedFilePath; 53 public bool IsComplete; 54 55 ///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 /// 61 public DownloadProgressHandler ProgressCallback; 62 63 ///64 /// Called to update debug window. 65 /// 更新debug窗体,回调函数 66 /// 67 public static DownloadCompleteHandler DebugCallback; 68 69 ///70 /// Called when a download has ended with success or failure 71 /// 当下载成功或者失败时回调 72 /// 73 public static DownloadCompleteHandler DownloadEnded; 74 75 ///76 /// Called when download is completed. Call Verify from event handler to throw any exception. 77 /// 下载完成时回调,调用验证事件是否抛出异常。 78 /// 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 ///92 /// The download start time (or MinValue if not yet started) 93 /// 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 ///103 /// used to signal thread abortion; if true, the download thread was aborted104 /// 105 private bool stopFlag = false;106 //下载线程107 protected Thread dlThread;108 109 ///110 /// Initializes a new instance of the 113 /// The URL to download from.114 public WebDownload(string url)115 {116 this.Url = url;117 }118 119 ///class.111 /// 构造函数,初始化下载对象112 /// 120 /// Initializes a new instance of the 122 public WebDownload()123 {124 }125 126 ///class.121 /// 127 /// Whether the download is currently being processed (active).128 /// 当前下载是否仍在进行129 /// 130 public bool IsDownloadInProgress131 {132 get133 {134 return dlThread != null && dlThread.IsAlive;135 }136 }137 138 ///139 /// Contains the exception that occurred during download, or null if successful.140 /// 141 public Exception Exception142 {143 get144 {145 return downloadException;146 }147 }148 149 ///150 /// Asynchronous download of HTTP data to file. 151 /// 异步下载Http数据,通过开启新线程实现。152 /// 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 ///167 /// Asynchronous download of HTTP data to file.168 /// 异步下载Http数据,绑定下载完成后的回调函数。169 /// 170 public void BackgroundDownloadFile(DownloadCompleteHandler completeCallback)171 {172 CompleteCallback += completeCallback;173 BackgroundDownloadFile();174 }175 176 ///177 /// Download image of specified type. (handles server errors for wms type)178 /// 179 public void BackgroundDownloadFile(DownloadType dlType)180 {181 DownloadType = dlType;182 BackgroundDownloadFile();183 }184 185 ///186 /// Asynchronous download of HTTP data to in-memory buffer.187 /// 异步下载Http数据到内存缓冲区188 /// 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 ///203 /// Asynchronous download of HTTP data to in-memory buffer. 204 /// 205 public void BackgroundDownloadMemory(DownloadCompleteHandler completeCallback)206 {207 CompleteCallback += completeCallback;208 BackgroundDownloadMemory();209 }210 211 ///212 /// Download image of specified type. (handles server errors for WMS type)213 /// 214 /// Type of download.215 public void BackgroundDownloadMemory(DownloadType dlType)216 {217 DownloadType = dlType;218 BackgroundDownloadMemory();219 }220 221 ///222 /// Synchronous download of HTTP data to in-memory buffer. 223 /// 224 public void DownloadMemory()225 {226 isMemoryDownload = true;227 Download();228 }229 230 ///231 /// Download image of specified type. (handles server errors for WMS type)232 /// 233 public void DownloadMemory(DownloadType dlType)234 {235 DownloadType = dlType;236 DownloadMemory();237 }238 239 ///240 /// HTTP downloads to memory.241 /// 242 /// The progress callback.243 public void DownloadMemory(DownloadProgressHandler progressCallback)244 {245 ProgressCallback += progressCallback;246 DownloadMemory();247 }248 249 ///250 /// Synchronous download of HTTP data to in-memory buffer. 251 /// 252 public void DownloadFile(string destinationFile)253 {254 SavedFilePath = destinationFile;255 256 Download();257 }258 259 ///260 /// Download image of specified type to a file. (handles server errors for WMS type)261 /// 262 public void DownloadFile(string destinationFile, DownloadType dlType)263 {264 DownloadType = dlType;265 DownloadFile(destinationFile);266 }267 268 ///269 /// Saves a http in-memory download to file.270 /// 271 /// File to save the downloaded data to.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 system278 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 else288 {289 // Block copy290 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 ///304 /// Aborts the current download. 305 /// 终止当前下载306 /// 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 ///328 /// Notify event subscribers of download progress.329 /// 330 /// Number of bytes read.331 /// Total number of bytes for request or 0 if unknown.332 private void OnProgressCallback(int bytesRead, int totalBytes)333 {334 if (ProgressCallback != null)335 {336 ProgressCallback(bytesRead, totalBytes);337 }338 }339 340 ///341 /// Called with detailed information about the download.342 /// 343 /// The WebDownload.344 private static void OnDebugCallback(WebDownload wd)345 {346 if (DebugCallback != null)347 {348 DebugCallback(wd);349 }350 }351 352 ///353 /// Called when downloading has ended.354 /// 355 /// The download.356 private static void OnDownloadEnded(WebDownload wd)357 {358 if (DownloadEnded != null)359 {360 DownloadEnded(wd);361 }362 }363 364 ///365 /// Synchronous HTTP download366 /// 线程异步调用的方法Download中,采用请求响应同步下载数据367 /// 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 try375 {376 try377 {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 file391 if (isMemoryDownload && ContentStream == null)392 {393 ContentStream = new MemoryStream();394 }395 else396 {397 // Download to file398 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 while427 // causing warnings in thread abortion.428 using (response = request.GetResponse() as HttpWebResponse)429 {430 // only if server responds 200 OK431 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 //缓存字节数组,大小1500byte443 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 uncompressed460 // - at the moment, ContentStream is compressed if the requesting code sets461 // download.Compressed == true, so ContentStream must be decompressed 462 // by whatever code is requesting the gzipped download463 // - this hack only works for requests made using the methods that download to memory,464 // requests downloading to file will result in a gzipped file465 // - requests that do not explicity set download.Compressed = true should be unaffected466 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 below485 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 format491 // TODO: don't know what to do with it492 throw;493 }494 catch (Exception caught)495 {496 try497 {498 // Remove broken file download499 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 completion525 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 else545 {546 CompleteCallback(this);547 }548 }549 catch (ThreadAbortException)550 {551 Log.Write(Log.Levels.Verbose, "Download aborted.");552 }553 finally554 {555 IsComplete = true;556 }557 558 OnDownloadEnded(this);559 }560 561 ///562 /// Handle server errors that don't get trapped by the web request itself.563 /// 564 private void HandleErrors()565 {566 // HACK: Workaround for TerraServer failing to return 404 on not found567 if (ContentStream.Length == 15)568 {569 // a true 404 error is a System.Net.WebException, so use the same text here570 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 != xml575 // TODO: Move WMS logic to WmsDownload576 if (DownloadType == DownloadType.Wms && (577 ContentType.StartsWith("text/xml") ||578 ContentType.StartsWith("application/vnd.ogc.se")))579 {580 // WMS request failure581 SetMapServerError();582 }583 }584 585 ///586 /// If exceptions occurred they will be thrown by calling this function.587 /// 588 public void Verify()589 {590 if (Exception != null)591 throw Exception;592 }593 594 ///595 /// Log download error to log file596 /// 597 /// 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 downloads605 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 ///615 /// Reads the xml response from the server and throws an error with the message.616 /// 617 private void SetMapServerError()618 {619 try620 {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 Members636 637 ///638 /// Performs application-defined tasks associated with freeing, releasing, or639 /// resetting unmanaged resources.640 /// 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 #endregion676 }677 }
该类基于Http协议,应用层。
Socket有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。传输层
流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;
数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。