Полное руководство. С# 4.0 - Шилдт Герберт
.Итак, выше приведена часть гипертекстового содержимого, полученного из вебсайта издательства McGraw-Hill по адресу www.McGraw-Hill.com. В рассматриваемом здесь примере программы это содержимое просто выводится в исходном виде наэкран посимвольно и не форматируется в удобочитаемом виде, как это обычно делается в окне браузера.Проанализируем данную программу построчно. Прежде всего обратите вниманиена использование в ней пространства имен System.Net. Как пояснялось ранее, в этомпространстве имен находятся классы сетевого подключения к Интернету. Обратитетакже внимание на то, что в данную программу включено пространство имен System.10, которое требуется для того, чтобы прочитать полученную на веб-сайте информацию, используя объект типа Stream.В начале программы создается объект типа WebRequest, содержащий требуемыйURI. Как видите, для этой цели используется метод Create(), а не конструктор. Этостатический член класса WebRequest. Несмотря на то что класс WebRequest являетсяабстрактным, это обстоятельство не мешает вызывать статический метод данного класса. Метод Create() возвращает объект типа HttpWebRequest. Разумеется, его значение требуется привести к типу HttpWebRequest, прежде чем присвоить его переменной req ссылки на объект типа HttpWebRequest. На этом формирование запросазавершается, но его еще нужно отправить по указанному URLДля того чтобы отправить запрос, в рассматриваемой здесь программе вызывается метод GetResponse() для объекта типа WebRequest. Отправив запрос, методGetResponse() переходит в состояние ожидания ответа. Как только ответ будет получен, метод GetResponse() возвратит объект типа WebResponse, в котором инкапсулирован ответ. Этот объект присваивается переменной resp. Но в данном случае ответ принимается по протоколу HTTP, и поэтому полученный результат приводится ктипу HttpWebResponse. Среди прочего в ответе содержится поток, предназначаемыйдля чтения данных из источника по указанному URLДалее поток ввода получается в результате вызова метода GetResponseStream()для объекта resp. Это стандартный объект класса Stream со всеми атрибутами и средствами, необходимыми для организации потока ввода. Ссылка на этот поток присваивается переменной istrm, с помощью которой данные могут быть прочитаны из источника по указанному URI, как из обычного файла.После этого в программе выполняется чтение данных из веб-сайта издательстваMcGraw-Hill по адресу www.McGraw-Hill.com и последующий их вывод на экран.А поскольку этих данных много, то они выводятся на экран отдельными порциямипо 400 символов, после чего в программе ожидается нажатие клавиши , чтобыпродолжить вывод. Благодаря этому выводимые данные можно просматривать безпрокрутки экрана. Обратите внимание на то, что данные читаются посимвольно с помощью метода ReadByte(). Напомним, что этот метод возвращает очередной байт изпотока ввода в виде значения типа int, которое требуется привести к типу char. Подостижении конца потока этот метод возвращает значение -1.И наконец, ответный поток закрывается при вызове метода Close() для объектаresp. Вместе с ответным потоком автоматически закрывается и поток ввода. Ответныйпоток следует закрывать в промежутках между последовательными запросами. В противном случае сетевые ресурсы могут быть исчерпаны, препятствуя очередному подключению к Интернету.И в заключение анализа рассматриваемого здесь примера следует обратить особоевнимание на следующее: для отображения гипертекстового содержимого, получаемого от сервера, совсем не обязательно использовать объект типа HttpWebRequest илиHttpWebResponse. Ведь для решения этой задачи в данной программе оказалось достаточно стандартных методов, определенных в классах WebRequest и WebResponse,и не потребовалось прибегать к специальным средствам протокола HTTP. Следовательно, вызовы методов Create() и GetResponse() можно было бы написать следующим образом.// Сначала создать объект запроса типа WebRequest по указанному URI.WebRequest req = WebRequest.Create("http://www.McGraw-Hill.com");// Затем отправить сформированный запрос и получить на него ответ.WebResponse resp = req.GetResponse();В тех случаях, когда не требуется приведение к конкретному типу реализации протокола, лучше пользоваться классами WebRequest и WebResponse, так как это даетвозможность менять протокол, не оказывая никакого влияния на код программы. Нопоскольку во всех примерах, приведенных в этой главе, используется протокол HTTP,то в ряде примеров демонстрируются специальные средства этого протокола из классов HttpWebRequest и HttpWebResponse.Обработка сетевых ошибокПрограмма из предыдущего примера составлена верно, но она совсем не защищенаот простейших сетевых ошибок, которые способны преждевременно прервать ее выполнение. Конечно, для программы, служащей в качестве примера, это не так важно,как для реальных приложений. Для полноценной обработки сетевых исключений,которые могут быть сгенерированы программой, необходимо организовать контрольвызовов методов Create(), GetResponse() и GetResponseStream(). Следует особо подчеркнуть, что генерирование конкретных исключений зависит от используемогопротокола. И ниже речь пойдет об ошибках, которые могут возникнуть при использовании протокола HTTP, поскольку средства сетевого подключения к Интернету, доступные в С#, рассматриваются в настоящей главе на примере именно этого протокола.Исключения, генерируемые методом Create()Метод Create(), определенный в классе WebRequest, может генерировать четыреисключения. Так, если протокол, указываемый в префиксе URI, не поддерживается,то генерируется исключение NotSupportedException. Если формат URI оказывается недействительным, то генерируется исключение UriFormatException. А если упользователя нет соответствующих полномочий для доступа к запрашиваемому сетевому ресурсу, то генерируется исключение System.Security.SecurityException.Кроме того, метод Create() генерирует исключение ArgumentNullException, еслион вызывается с пустой ссылкой, хотя этот вид ошибки не имеет непосредственногоотношения к сетевому подключению.Исключения, генерируемые методом GetResponse()При вызове метода GetResponse() для получения ответа по протоколу HTTPможет произойти целый ряд ошибок. Эти ошибки представлены следующими исключениями: InvalidOperationException, ProtocolViolationException,NotSupportedException и WebException. Наибольший интерес среди них вызываетисключение WebException.У исключения WebException имеются два свойства, связанных с сетевыми ошибками: Response и Status. С помощью свойства Response можно получить ссылку наобъект типа WebResponse в обработчике исключений. Для соединения по протоколуHTTP этот объект описывает характер возникшей ошибки. Свойство Response объявляется следующим образом.public WebResponse Response { get; }Когда возникает ошибка, то с помощью свойства Status типа WebException можно выяснить, что именно произошло. Это свойство объявляется следующим образом:public WebExceptionStatus Status {get; }где WebExceptionStatus — это перечисление, которое содержит приведенные нижезначения.CacheEntryNotFound ConnectFailure ConnectionClosedKeepAliveFailure MessageLengthLimitExceeded NameResolutionFailurePending PipelineFailure ProtocolErrorProxyNameResolutionFailure ReceiveFailure RequestCanceledRequestProhibitedByCachePolicy RequestProhibitedByProxy SecureChannelFailureSendFailure ServerProtocolViolation SuccessTimeout TrustFailure UnknownErrorКак только будет выяснена причина ошибки, в программе могут быть предприняты соответствующие действия.Исключения, генерируемые методом GetResponseStream()Для соединения по протоколу HTTP метод GetResponseStream() из классаWebResponse может сгенерировать исключение ProtocolViolationException, которое в целом означает, что в работе по указанному протоколу произошла ошибка.Что же касается метода GetResponseStream(), то это означает, что ни один из действительных ответных потоков недоступен. Исключение ObjectDisposedExceptionгенерируется в том случае, если ответ уже утилизирован. А исключение IOException,конечно, генерируется при ошибке чтения из потока, в зависимости от того, как организован ввод данных.Обработка исключенийВ приведенном ниже примере программы демонстрируется обработка всевозможных сетевых исключений, которые могут возникнуть в связи с выполнением программы из предыдущего примера, в которую теперь добавлены соответствующие обработчики исключений.// Пример обработки сетевых исключений.using System;using System.Net;using System.IO;class NetExcDemo {static void Main() {int ch;try {// Сначала создать объект запроса типа WebRequest по указанному URI.HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://www.McGraw-Hill.com");// Затем отправить сформированный запрос и получить на него ответ.HttpWebResponse resp = (HttpWebResponse)req.GetResponse();// Получить из ответа поток ввода.Stream istrm = resp.GetResponseStream() ;/ А теперь прочитать и отобразить гипертекстовое содержимое,полученное по указанному URI. Это содержимое выводился на экранотдельными порциями по 400 символов. После каждой такой порцииследует нажать клавишу , чтобы вывести на экран следующуюпорцию, состоящую из 400 символов. /for (int i=1; ; i++) {ch = istrm.ReadByte();if(ch == -1) break;Console.Write((char) ch);if((i%400)==0) {Console.Write ("nНажмите клавишу .");Console.ReadLine();}}// Закрыть ответный поток. При этом закрывается// также поток ввода istrm.resp.Close();} catch(WebException exc) {Console.WriteLine("Сетевая ошибка: " + exc.Message +"nКод состояния: " + exc.Status);} catch(ProtocolViolationException exc) {Console.WriteLine("Протокольная ошибка: " + exc.Message);} catch(UriFormatException exc) {Console.WriteLine("Ошибка формата URI: " + exc.Message);} catch(NotSupportedException exc) {Console.WriteLine("Неизвестный протокол: " + exc.Message);} catch(IOException exc) {Console.WriteLine("Ошибка ввода-вывода: " + exc.Message);} catch(System.Security.SecurityException exc) {Console.WriteLine("Исключение в связи с нарушением безопасности: " +exc.Message);} catch(InvalidOperationException exc) {Console.WriteLine("Недопустимая операция: " + exc.Message);}}}Теперь перехватываются все исключения, которые могут быть сгенерированы сетевыми методами. Так, если изменить вызов метода Create() следующим образом:WebRequest.Create("http://www.McGraw-Hill.com/moonrocket");а затем перекомпилировать и еще раз выполнить программу, то в результате можетбыть выдано приведенное ниже сообщение об ошибке.Сетевая ошибка: Удаленный сервер возвратил ошибку: (404) Не найден.Код состояния: ProtocolErrorНа веб-сайте по адресу www.McGraw-Hill.com отсутствует раздел moonrocket, ипоэтому он не найден по указанному URI, что и подтверждает приведенный выше результат.Ради краткости и ясности в программах большинства примеров из этой главы отсутствует полноценная обработка исключений. Но в реальных приложениях она просто необходима.Класс UriКак следует из табл. 26.1, метод WebRequest.Create() существует в двух вариантах. В одном варианте он принимает идентификатор URI в виде строки. Именно этотвариант и был использован в предыдущих примерах программ. А во втором варианте этот метод принимает идентификатор URI в виде экземпляра объекта класса Uri,определенного в пространстве имен System. Класс Uri инкапсулирует идентификатор URL Используя класс Uri, можно сформировать URI, чтобы затем передать этотидентификатор методу Create(). Кроме того, идентификатор URI можно разделитьна части. Для выполнения многих простых операций в Интернете класс Uri малопригоден. Тем не менее он может оказаться весьма полезным в более сложных ситуацияхсетевого подключения к Интернету.В классе Uri определяется несколько конструкторов. Ниже приведены наиболеечасто используемые конструкторы этого класса.public Uri(string uriString)public Uri(Uri baseUri, string relativeUri)В первой форме конструктора объект класса Uri создается по идентификатору URI,заданному в виде строки uriString. А во второй форме конструктора он создается поотносительному URI, заданному в виде строки relativeUri относительно абсолютного URI, обозначаемого в виде объекта baseUri типа Uri. Абсолютный URI определяетполный адрес URI, а относительный URI — только путь к искомому ресурсу.В классе Uri определяются многие поля, свойства и методы, оказывающие помощьв управлении идентификаторами URI или в получении доступа к различным частямURI. Особый интерес представляют приведенные ниже свойства.Перечисленные выше свойства полезны для разделения URI на составные части. Применение этих свойств демонстрируется в приведенном ниже примере программы.// Пример применения свойств из класса Uri.using System;using System.Net;class UriDemo {static void Main() {Uri sample = newUri("http://HerbSchildt.com/somefile.txt?SomeQuery");Console.WriteLine("Хост: " + sample.Host);Console.WriteLine("Порт: " + sample.Port);Console.WriteLine("Протокол: " + sample.Scheme);Console.WriteLine("Локальный путь: " + sample.LocalPath);Console.WriteLine("Запрос: " + sample.Query);Console.WriteLine("Путь и запрос: " + sample.PathAndQuery);}}Эта программа дает следующий результат.Хост: HerbSchildt.comПорт: 80Протокол: httpЛокальный путь: /somefile.txtЗапрос: ?ScmeQueryПуть и запрос: /somefile.txt?SomeQueryДоступ к дополнительной информации,получаемой в ответ по протоколу HTTPС помощью сетевых средств, имеющихся в классе HttpWebResponse, можно получить доступ к другой информации, помимо содержимого указываемого ресурса.К этой информации, в частности, относится время последней модификации ресурса,а также имя сервера. Она оказывается доступной с помощью различных свойств, связанных с подучаемым ответом. Все эти свойства, включая и те что, определены в классеWebResponse, сведены в табл. 26.5. В приведенных далее примерах программ демонстрируется применение этих свойств на практике.Свойство Описаниеpublic string Host { get; } Получает имя сервераpublic string LocalPath { get; } Получает локальный путь к файлуpublic string. PathAndQuery { get; }public int Port { get; }Получает абсолютный путь и строку запросаПолучает номер порта для указанного протокола. Так, для протокола HTTP номер портаравен 80public string Query { get; } Получает строку запросаpublic string Scheme { get; } Получает протоколДоступ к заголовкуДля доступа к заголовку с информацией, получаемой в ответ по протоколу HTTP,служит свойство Headers, определенное в классе HttpWebResponse.public WebHeaderCollection Headers{ get; }Заголовок протокола HTTP состоит из пар "имя-значение", представленных строками. Каждая пара "имя-значение" хранится в коллекции класса WebHeaderCollection.Эта коллекция специально предназначена для хранения пар "имя-значение" и примеТаблица 26.5. Свойства, определенные в классе HttpWebResponseСвойство Описаниеpublic string CharacterSet { get; } Получает название используемого наборасимволовpublic string ContentEncoding{ get; }Получает название схемы кодированияpublic long ContentLength { get; } Получает длину принимаемого содержимого.Если она недоступна, свойство имеет значение -1public string ContentType { get; } Получает описание содержимогоpublic CookieCollection Cookies{ get; set; }Получает или устанавливает список cookie-наборов, присоединяемых к ответуpublic WebHeaderCollectionHeaders{ get; }Получает коллекцию заголовков, присоединяемых к ответуpublic bool IsFromCache { get; } Принимает логическое значение true, еслизапрос получен из кеша. А если запрос доставлен по сети, то принимает логическое значение falsepublic boolIsMutuallyAuthenticated { get; }Принимает логическое значение true, есликлиент и сервер опознают друг друга, а иначе — принимает логическое значение falsepublic DateTime LastModified { get; } Получает время последней модификации ресурсаpublic string Method { get; } Получает строку, которая задает способ ответаpublic Version ProtocolVersion{ get; }Получает объект типа Version, описывающий версию протокола HTTP, используемую втранзакцииpublic Uri ReponseUri { get; } Получает URI, по которому был сформированответ. Этот идентификатор может отличаться отзапрашиваемого, если ответ был переадресован по другому URIpublic string Server { get; } Получает строку, обозначающую имя сервераpublic HttpStatusCode StatusCode{ get; }Получает объект типа HttpStatusCode, описывающий состояние транзакцииpublic string StatusDescription{ get; }Получает строку, обозначающую состояниетранзакции в удобочитаемой форменяется аналогично любой другой коллекции (подробнее об этом см. в главе 25). Строковый массив имен может быть получен из свойства AllKeys, а отдельные значения —по соответствующему имени при вызове метода GetValues(). Этот метод возвращаетмассив строк, содержащий значения, связанные с заголовком, передаваемым в качествеаргумента. Метод GetValues() перегружается, чтобы принять числовой индекс илиимя заголовка.В приведенной ниже программе отображаются заголовки, связанные с сетевым ресурсом, доступным по адресу www.McGraw-Hill.com.// Проверить заголовки.using System;using System.Net;class HeaderDemo {static void Main() {// Создать объект запроса типа WebRequest по указанному URI.HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://www.McGraw-Hill.com");// Отправить сформированный запрос и получить на него ответ.HttpWebResponse resp = (HttpWebResponse)req.GetResponse();// Получить список имен.string[] names = resp.Headers.AllKeys;// Отобразить пары "имя-значение" из заголовка.Console.WriteLine("{0,-20}(1}n", "Имя", "Значение");foreach(string n in names) {Console.Write("{0,-20}", n);foreach(string v in resp.Headers.GetValues(n))Console.WriteLine(v);}// Закрыть ответный поток.resp.Close();}}Ниже приведен полученный результат. Не следует забывать, что информация в заголовке периодически меняется, поэтому у вас результат может оказаться несколько иным.Имя ЗначениеTransfer-encoding chunkedContent-Type text/htmlDate Sun, 06 Dec 2009 20:32:06 GMTServer Sun-ONE-Web-Server/6.1Доступ к cookie-наборамДля доступа к cookie-наборам, получаемым в ответ по протоколу HTTP, служит свойство Cookies, определенное в классе HttpWebResponse. В cookie-наборахсодержится информация, сохраняемая браузером. Они состоят из пар "имя-значение"и упрощают некоторые виды доступа к веб-сайтам. Ниже показано, каким образомопределяется свойство Cookies.public CookieCollection Cookies { get; set; }В классе CookieCollection реализуются интерфейсы ICollection иIEnumerable, и поэтому его можно использовать аналогично классу любой другойколлекции (подробнее об этом см. в главе 25). У этого класса имеется также индексатор, позволяющий получать cookie-набор по указанному индексу или имени.В коллекции типа CookieCollection хранятся объекты класса Cookie. В классеCookie определяется несколько свойств, предоставляющих доступ к различным фрагментам информации, связанной с cookie-набором. Ниже приведены два свойства, Nameи Value, используемые в примерах программ из этой главы.public string Name { get; set; }public string Value { get; set; }Имя cookie-набора содержится в свойстве Name, а его значение — в свойствеValue.Для того чтобы получить список cookie-наборов из принятого ответа, необходимопредоставить cookie-контейнер с запросом. И для этой цели в классе HttpWebRequestопределяется свойство CookieContainer, приведенное ниже.public CookieContainer CookieContainer { get; set; }В классе CookieContainer предоставляются различные поля, свойства и методы,позволяющие хранить cookie-наборы. По умолчанию свойство CookieContainerсодержит пустое значение. Для того чтобы воспользоваться cookie-наборами, необходимо установить это свойство равным экземпляру класса CookieContainer. Вомногих приложениях свойство CookieContainer не применяется непосредственно,а вместо него из принятого ответа составляется и затем используется коллекция типаCookieCollection. Свойство CookieContainer просто обеспечивает внутренниймеханизм сохранения cookie-наборов.В приведенном ниже примере программы отображаются имена и значения cookie-наборов, получаемых из источника по URI, указываемому в командной строке. Следует, однако, иметь в виду, что cookie-наборы используются не на всех веб-сайтах, поэтому нужно еще найти такой веб-сайт, который поддерживает cookie-наборы./ Пример проверки cookie-наборов.Для того чтобы проверить, какие именно cookie-наборыиспользуются на веб-сайте, укажите его имя в командной строке.Так, если назвать эту программу CookieDemo, то по командеCookieDemo http://msn.comотобразятся cookie-наборы с веб-сайта по адресу www.msn.com. /using System;using System.Net;class CookieDemo {static void Main(string[] args) {if(args.Length != 1) {Console.WriteLine("Применение: CookieDemo ");return;}// Создать объект запроса типа WebRequest по указанному URI.HttpWebRequest req = (HttpWebRequest)WebRequest.Create(args[0]);// Получить пустой контейнер.req.CookieContainer = new CookieContainer();// Отправить сформированный запрос и получить на него ответ.HttpWebResponse resp = (HttpWebResponse)req.GetResponse();// Отобразить cookie-наборы.Console.WriteLine("Количество cookie-наборов: " +resp.Cookies.Count);Console.WriteLine("{0,-20}{1}", "Имя", "Значение");for(int i=0; i < resp.Cookies.Count; i++)Console.WriteLine("{0, -20}{1}",resp.Cookies[i].Name,resp.Cookies[i].Value);// Закрыть ответный поток.resp.Close();}}Применение свойства LastModifiedИногда требуется знать, когда именно сетевой ресурс был обновлен в последнийраз. Это нетрудно сделать, пользуясь сетевыми средствами класса HttpWebResponse,среди которых определено свойство LastModified, приведенное ниже.public DateTime LastModified { get; }С помощью свойства LastModified получается время обновления содержимого сетевого ресурса в последний раз.В приведенном ниже примере программы отображаются дата и время, когда был впоследний раз обновлен ресурс, указываемый по URI в командной строке./ Использовать свойство LastModified.Для того чтобы проверить дату последнего обновления веб-сайта,введите его URI в командной строке. Так, если назвать эту программуLastModifiedDemo, то для проверки даты последней модификации веб-сайтапо адресу www.HerbSchildt.com введите командуLastModifiedDemo http://HerbSchildt.com/using System;using System.Net;class LastModifiedDemo {static void Main(string[] args) {if(args.Length != 1) {Console.WriteLine("Применение: LastModifiedDemo ");return;}HttpWebRequest req = (HttpWebRequest)WebRequest.Create (args[0]);HttpWebResponse resp = (HttpWebResponse)req.GetResponse();Console.WriteLine("Последняя модификация: " + resp.LastModified);resp.Close();}}Практический пример создания программы MiniCrawlerДля того чтобы показать, насколько просто программировать для Интернета средствами классов WebRequest и WebReponse, обратимся к разработке скелетного варианта поискового робота под названием MiniCrawler. Поисковый робот представляетсобой программу последовательного перехода от одной ссылки на сетевой ресурс кдругой. Поисковые роботы применяются в поисковых механизмах для каталогизациисодержимого. Разумеется, поисковый робот MiniCrawler не обладает такими развитыми возможностями, как те, что применяются в поисковых механизмах. Эта программаначинается с ввода пользователем конкретного адреса URI, по которому затем читаетсясодержимое и осуществляется поиск в нем ссылки. Если ссылка найдена, то программазапрашивает пользователя, желает ли он перейти по этой ссылке к обнаруженному сетевому ресурсу, найти другую ссылку на имеющейся странице или выйти из программы. Несмотря на всю простоту такого алгоритма поиска сетевых ресурсов, он служитинтересным и наглядным примером доступа к Интернету средствами С#.Программе MiniCrawler присущ ряд ограничений. Во-первых, в ней обнаруживаются только абсолютные ссылки, указываемые по гипертекстовой команде href="http.Относительные ссылки при этом не обнаруживаются. Во-вторых, возврат к предыдущей ссылке в программе не предусматривается. И в-третьих, в ней отображаются только ссылки, но не окружающее их содержимое. Несмотря на все указанные ограниченияданного скелетного варианта поискового робота, он вполне работоспособен и можетбыть без особых хлопот усовершенствован для решения других задач. На самом деледобавление новых возможностей в программу MiniCrawler — это удобный случай освоить на практике сетевые классы и узнать больше о сетевом подключении к Интернету.Ниже приведен полностью исходный код программы MiniCrawler./ MiniCrawler: скелетный вариант поискового робота.Применение: для запуска поискового робота укажите URIв командной строке. Например, для того чтобы начать поискс адреса www.McGraw-Hill.com, введите следующую команду:MiniCrawler http://McGraw-Hill.com/using System;using System.Net;using System.IO;class MiniCrawler {// Найти ссылку в строке содержимого.static string FindLink(string htmlstr,ref int startloc) {int i;int start, end;string uri = null;i = htmlstr.IndexOf("href="http", startloc,StringComparison.OrdinalIgnoreCase);if(i != -1) {start = htmlstr.IndexOf('"', i) + 1;end = htmlstr.IndexOf('"', start);uri = htmlstr.Substring(start, end-start);startloc = end;}return uri;}static void Main(string[] args) {string link = null;string str;string answer;int curloc; // содержит текущее положение в ответеif(args.Length != 1) {Console.WriteLine("Применение: MiniCrawler ");return;}string uristr = args[0]; // содержит текущий URIHttpWebResponse resp = null;try {do {Console.WriteLine("Переход по ссылке " + uristr);// Создать объект запроса типа WebRequest по указанному URI.HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uristr);uristr = null; // запретить дальнейшее использование этого URI// Отправить сформированный запрос и получить на него ответ.resp = (HttpWebResponse) req.GetResponse();// Получить поток ввода из принятого ответа.Stream istrm = resp.GetResponseStream();// Заключить поток ввода в оболочку класса StreamReader.StreamReader rdr = new StreamReader(istrm);// Прочитать всю страницу.str = rdr.ReadToEnd();curloc = 0;do {// Найти следующий URI для перехода по ссылке.link = FindLink(str, ref curloc);if(link != null) {Console.WriteLine("Найдена ссылка: " + link);Console.Write("Перейти по ссылке, Искать дальше, Выйти?");answer = Console.ReadLine();if(string.Equals(answer, "П",StringComparison.OrdinalIgnoreCase)) {uristr = string.Copy(link);break;} else if(string.Equals(answer, "B",StringComparison.OrdinallgnoreCase)) {break;} else if(string.Equals(answer, "И",StringComparison.OrdinallgnoreCase)) (Console.WriteLine("Поиск следующей ссылки.");}} else {Console.WriteLine("Больше ссылок не найдено.");break;}} while(link.Length > 0);// Закрыть ответный поток.if(resp != null) resp.Close();} while(uristr != null);} catch(WebException exc) {Console.WriteLine("Сетевая ошибка: " + exc.Message +"nКод состояния: " + exc.Status);} catch(ProtocolViolationException exc) {Console.WriteLine("Протокольная ошибка: " + exc.Message);} catch(UriFormatException exc) {Console.WriteLine("Ошибка формата URI: " + exc.Message);} catch(NotSupportedException exc) {Console.WriteLine("Неизвестный протокол: " + exc.Message);} catch(IOException exc) {Console.WriteLine("Ошибка ввода-вывода: " + exc.Message);} finally {if(resp != null) resp.Close();}Console.WriteLine("Завершение программы MiniCrawler.");}}Ниже приведен пример сеанса поиска, начиная с адреса www.McGraw-Hill.com.Следует иметь в виду, что конкретный результат поиска зависит от состояния содержимого на момент поиска.Переход по ссылке http://mcgraw-hill.comНайдена ссылка: http://sti.mcgraw-hill.com:9000/cgi-bin/query?mss=search&pg=aqПерейти по ссылке, Искать дальше, Выйти? ИПоиск следующей ссылки.Найдена ссылка: http://investor.mcgraw-hill.com/phoenix.zhtml?c=96562&p=irol-irhomeПерейти по ссылке, Искать дальше, Выйти? ППереход по ссылке http://investor.mcgraw-hill.com/phoenix.zhtml?c=96562&p=irol-irhomeНайдена ссылка: http://www.mcgraw-hill.com/index.htmlПерейти по ссылке, Искать дальше, Выйти? ППереход по ссылке http://www.mcgraw-hill.com/index.htmlНайдена ссылка: http://sti.mcgraw-hill.com:9000/cgi-bin/query?mss=search&pg=aqПерейти по ссылке, Искать дальше. Выйти? ВЗавершение программы MiniCrawler.Рассмотрим подробнее работу программы MiniCrawler. Она начинается с вводапользователем конкретного URI в командной строке. В методе Main() этот URI сохраняется в строковой переменной uristr. Затем по указанному URI формируется запрос,и переменной uristr присваивается пустое значение, указывающее на то, что данныйURI уже использован. Далее отправляется запрос и получается ответ. После этого содержимое читается из потока ввода, возвращаемого методом GetResponseStream()и заключаемого в оболочку класса StreamReader. Для этой цели вызывается методReadToEnd(), возвращающий все содержимое в виде строки из потока ввода.Далее программа осуществляет поиск ссылки в полученном содержимом. Для этоговызывается статический метод FindLink(), определяемый в программе MiniCrawler.Этот метод вызывается со строкой содержимого и исходным положением, с которого начинается поиск в полученном содержимом. Эти значения передаются методуFindLink() в виде параметров htmlstr и startloc соответственно. Обратите внимание на то, что параметр startloc относится к типу ref. Сначала в методе FindLink()создается копия строки содержимого в нижнем регистре, а затем осуществляется поиск подстроки href="http, обозначающей ссылку. Если эта подстрока найдена, тоURI копируется в строковую переменную uri, а значение параметра startloc обновляется и становится равным концу ссылки. Но поскольку параметр startloc относится к типу ref, то это приводит к обновлению соответствующего аргумента метода Main(), активизируя поиск с того места, где он был прерван. В конечном итогевозвращается значение переменной uri. Эта переменная инициализирована пустымзначением, и поэтому если ссылка не найдена, то возвращается пустая ссылка, обозначающая неудачный исход поиска.Если ссылка, возвращаемая методом FindLink(), не является пустой, то она отображается в методе Main(), и далее программа запрашивает у пользователя очередные действия. Пользователю предоставляются одна из трех следующих возможностей:перейти по найденной ссылке, нажав клавишу <П>, искать следующую ссылку в имеющемся содержимом, нажав клавишу <И>, иди же выйти из программы, нажав клавишу <В>. Если пользователь нажмет клавишу <П>, то программа осуществит переходпо найденной ссылке и получит новое содержимое по этой ссылке. После этого поискочередной ссылки будет начат уже в новом содержимом. Этот процесс продолжаетсядо тех пор, пока не будут исчерпаны все возможные ссылки.В качестве упражнения вы сами можете усовершенствовать программу MiniCrawler,дополнив ее, например, возможностью перехода по относительным ссылкам. Сделатьэто не так уж и трудно. Кроме того, вы можете полностью автоматизировать поисковый робот, чтобы он сам переходил по найденной ссылке без вмешательства со стороныпользователя, начиная со ссылки, обнаруженной на самой первой странице полученного содержимого, и продолжая переход по ссылкам на новых страницах. Как толькобудет достигнут тупик, поисковый робот должен вернуться на один уровень назад, найти следующую ссылку и продолжить переход по ссылке. Для организации именно такого алгоритма работы программы вам потребуется стек, в котором должны хранитьсяидентификаторы URI и текущее состояние поиска в строке URI. С этой целью можно,в частности, воспользоваться коллекцией класса Stack. В качестве более сложной, ноинтересной задачи попробуйте организовать вывод ссылок в виде дерева.Применение класса WebClientВ заключение этой главы уместно рассмотреть класс WebClient. Как упоминалось в самом ее начале, класс WebClient рекомендуется использовать вместо классовWebRequest и WebResponse в том случае, если в приложении требуется лишь выгружать или загружать данные из Интернета. Преимущество класса WebClient заключается в том, что он автоматически выполняет многие операции, освобождая от ихпрограммирования вручную.В классе WebClient определяется единственный конструктор.public WebClient()Кроме того, в классе WebClient определяются свойства, сведенные в табл. 26.6, атакже целый ряд методов, поддерживающих как синхронную, так и асинхронную передачу данных. Но поскольку рассмотрение асинхронной передачи данных выходит зарамки этой главы, то в табл. 26.7 приведены только те методы, которые поддерживаютсинхронную передачу данных. Все методы класса WebClient генерируют исключениеWebException, если во время передачи данных возникает ошибка.Таблица 26.6. Свойства, определенные в классе WebClientСвойство Описаниеpublic string BaseAddress{ get; set; }Получает или устанавливает базовый адрес требуемогоURI. Если это свойство установлено, то адреса, задаваемые в методах класса WebClient, должны определяться относительно этого базового адресаpublic RequestCachePolicyCachePolicy { get; set; }Получает или устанавливает правила, определяющие,когда именно используется кэшpublic ICredentialsCredentials { get; set; }Получает или устанавливает мандат, т.е. учетные данные пользователя. По умолчанию это Свойство имеетпустое значениеpublic Encoding Encoding {get; set; }Получает или устанавливает схему кодирования символов при передаче строкТаблица 26.7. Методы синхронной передачи, определенные в классе WebClientОкончание табл. 26.6Свойство Описаниеpublic WebHeaderCollectionHeaders{ get; set; }Получает или устанавливает коллекцию заголовков запросаpublic bool IsBusy{ get; } Принимает логическое значение true, если данныепо-прежнему передаются по запросу, а иначе — логическое значение falsepublic IWebProxy Proxy {get; set; }Получает или устанавливает прокси-серверpublic NameValueCollectionQueryString { get; set; }Получает или устанавливает строку запроса, состоящую из пар “имя-значение”, которые могут быть присоединены к запросу. Строка запроса отделяется от URIсимволом ?. Если же таких пар несколько, то каждая изних отделяется символом @public WebHeaderCollectionResponseHeaders{ get; }Получает коллекцию заголовков ответаpublic boolUseDefaultCredentials {get; set; }Получает или устанавливает значение, которое определяет, используется ли для аутентификации устанавливаемый по умолчанию мандат. Если принимает логическоезначение true, то используется мандат, устанавливаемый по умолчанию, т.е. учетные данные пользователя,в противном случае этот мандат не используетсяМетод Определениеpublic byte[]DownloadData(string address)Загружает информацию по адресу URI, обозначаемому параметром address. Возвращает результатв виде массива байтовpublic byte[]DownloadData(Uri address)Загружает информацию по адресу URI, обозначаемому параметром address. Возвращает результатв виде массива байтовpublic voidDownloadFile(string uri,string fileName)Загружает информацию по адресу URI, обозначаемому параметром fileName. Сохраняет результатв файле fileNamepublic void DownloadFile(Uriaddress, string fileName)Загружает информацию по адресу URI, обозначаемому параметром address. Сохраняет результатв файле fileNamepublic stringDownloadString(stringaddress)Загружает информацию по адресу URI, обозначаемому параметром address. Возвращает результатв виде символьной строки типа stringpublic stringDownloadString(Uri address)Загружает информацию по адресу URI, обозначаемому параметром address. Возвращает результатв виде символьной строки типа stringpublic StreamOpenRead(string address)Возвращает поток ввода для чтения информации поадресу URI, обозначаемому параметром address.По окончании чтения информации этот поток необходимо закрытьПродолжение табл. 26.7Метод Определениеpublic Stream OpenRead(Uriaddress)Возвращает поток ввода для чтения информации поадресу URI, обозначаемому параметром address.По окончании чтения информации этот поток необходимо закрытьpublic StreamOpenWrite(string address)Возвращает поток вывода для записи информации по адресу URI, обозначаемому параметромaddress. По окончании записи информации этотпоток необходимо закрытьpublic Stream OpenWrite(Uriaddress)Возвращает поток вывода для записи информации по адресу URI, обозначаемому параметромaddress. По окончании записи информации этотпоток необходимо закрытьpublic StreamOpenWrite(string address,string method)Возвращает поток вывода для записи информации по адресу URI, обозначаемому параметромaddress. По окончании записи информации этотпоток необходимо закрыть. В строке, передаваемойв качестве параметра method, указывается, какименно следует записывать информациюpublic Stream OpenWrite(Uriaddress, string method)Возвращает поток вывода для записи информации по адресу URI, обозначаемому параметромaddress. По окончании записи информации этотпоток необходимо закрыть. В строке, передаваемойв качестве параметра method, указывается, какименно следует записывать информациюpublic byte[]UploadData(string address,byte[] data)Записывает информацию из массива data поадресу URI, обозначаемому параметром address.В итоге возвращается ответpublic byte[] UploadData(Uriaddress, byte[] data)Записывает информацию из массива data поадресу URI, обозначаемому параметром address.В итоге возвращается ответpublic byte[]UploadData(string address,string method, byte[] data)Записывает информацию из массива data поадресу URI, обозначаемому параметром address.В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается,как именно следует записывать информациюpublic byte[] UploadData(Uriaddress, string method,byte[] data)Записывает информацию из массива data поадресу URI, обозначаемому параметром address.В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается,как именно следует записывать информациюpublic byte[]UploadFile(string address,string fileName)Записывает информацию в файл fileName поадресу URI, обозначаемому параметром address.В итоге возвращается ответpublic byte[] UploadFile(Uriaddress, string fileName)Записывает информацию в файл fileName поадресу URI, обозначаемому параметром address.В итоге возвращается ответОкончание табл. 26.7В приведенном ниже примере программы демонстрируется применение классаWebClient для загрузки данных в файл по указанному сетевому адресу.Метод Определениеpublic byte[]UploadFile(string address,string method, stringfileName)Записывает информацию в файл fileName поадресу URI, обозначаемому параметром address.В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается,как именно следует записывать информациюpublic byte[] UploadFile(Uriaddress, string method,string fileName)Записывает информацию в файл fileName поадресу URI, обозначаемому параметром address.В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается,как именно следует записывать информациюpublic stringUploadString(string address,string data)Записывает строку data по адресу URI, обозначаемому параметром address. В итоге возвращаетсяответpublic stringUploadString(Uri address,string data)Записывает строку data по адресу URI, обозначаемому параметром address. В итоге возвращаетсяответpublic stringUploadString(string addreds,string method, string data)Записывает строку data по адресу URI, обозначаемому параметром address. В итоге возвращаетсяответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информациюpublic stringUploadString(Uri address,string method, string data)Записывает строку data по адресу URI, обозначаемому параметром address. В итоге возвращаетсяответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информациюpublic byte[]UploadValues(string address,NameValueCollection data)Записывает значения из коллекции data по адресуURI, обозначаемому параметром address. В итогевозвращается ответpublic byte[]UploadValues(Uri address,NameValueCollection data)Записывает значения из коллекции data по адресуURI, обозначаемому параметром address. В итогевозвращается ответpublic byte[]UploadValues(stringaddress, string method,NameValueCollection data)Записывает значения из коллекции data по адресуURI, обозначаемому параметром address. В итогевозвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информациюpublic byte[]UploadValues(Uriaddress, string method,NameValueCollection data)Записывает значения из коллекции data по адресуURI, обозначаемому параметром address. В итогевозвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию// Использовать класс WebClient для загрузки данных// в файл по указанному сетевому адресу.using System;using System.Net;using System.IO;class WebClientDemo {static void Main() {WebClient user = new WebClient ();string uri = "http://www.McGraw-Hill.com";string fname = "data.txt";try {Console.WriteLine("Загрузка данных по адресу " +uri + " в файл " + fname);user.DownloadFile(uri, fname);} catch (WebException exc) {Console.WriteLine(exc);}Console.WriteLine("Загрузка завершена.");}}Эта программа загружает информацию по адресу www.McGrawH