Текст книги "C# 4.0: полное руководство"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 82 (всего у книги 83 страниц)
Программа из предыдущего примера составлена верно, но она совсем не защищена от простейших сетевых ошибок, которые способны преждевременно прервать ее выполнение. Конечно, для программы, служащей в качестве примера, это не так важно, как для реальных приложений. Для полноценной обработки сетевых исключений, которые могут быть сгенерированы программой, необходимо организовать контроль вызовов методов 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
ConnectionClosed
KeepAliveFailure
MessageLengthLimitExceeded
NameResolutionFailure
Pending
PipelineFailure
ProtocolError
ProxyNameResolutionFailure
ReceiveFailure
RequestCanceled
RequestProhibitedByCachePolicy
RequestProhibitedByProxy
SecureChannelFailure
SendFailure
ServerProtocolViolation
Success
Timeout
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 символов. После каждой такой порции следует нажать клавишу
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 +
"Код состояния: " + 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, что и подтверждает приведенный выше результат.
Ради краткости и ясности в программах большинства примеров из этой главы отсутствует полноценная обработка исключений. Но в реальных приложениях она просто необходима.
Как следует из табл. 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. Особый интерес представляют приведенные ниже свойства.
Свойство – Описание
public string Host { get; } – Получает имя сервера
public string LocalPath { get; } – Получает локальный путь к файлу
public string PathAndQuery { get; } – Получает абсолютный путь и строку запроса
public int Port { get; } – Получает номер порта для указанного протокола. Так, для протокола HTTP номер порта равен 80
public string Query { get; } – Получает строку запроса
public string Scheme { get; } – Получает протокол
Перечисленные выше свойства полезны для разделения URI на составные части. Применение этих свойств демонстрируется в приведенном ниже примере программы.
// Пример применения свойств из класса Uri.
using System;
using System.Net;
class UriDemo {
static void Main() {
Uri sample = new Uri(«http://HerbSchildt.com/somefile.txt?SomeQuery»);
Console.WriteLine("Хост: " + sample.Host);
Console.WriteLine("Порт: " + sample.Port);
Console.WriteLine("Протокол: " + sample.Scheme);
Console.WriteLine(«Локальный путь: 11 + sample. LocalPath») ;
Console.WriteLine("Запрос: " + sample.Query);
Console.WriteLine("Путь и запрос: " + sample.PathAndQuery);
}
}
Эта программа дает следующий результат.
Хост: herbschildt.com
Порт: 80
Протокол: http
Локальный путь: 11 + sample. LocalPath
Запрос: ?SomeQuery
Путь и запрос: /somefile.txt?SomeQuery
С помощью сетевых средств, имеющихся в классе HttpWebResponse
, можно получить доступ к другой информации, помимо содержимого указываемого ресурса. К этой информации, в частности, относится время последней модификации ресурса, а также имя сервера. Она оказывается доступной с помощью различных свойств, связанных с получаемым ответом. Все эти свойства, включая и те что, определены в классе WebResponse
, сведены в табл. 26.5. В приведенных далее примерах программ демонстрируется применение этих свойств на практике.
Таблица 26.5. Свойства, определенные в классе HttpWebResponse
Свойство – Описание
public string CharacterSet { get; } – Получает название используемого набора символов
public string ContentEncoding { get; } – Получает название схемы кодирования
public long ContentLength { get; } – Получает длину принимаемого содержимого. Если она недоступна, свойство имеет значение -1
public string ContentType { get; } – Получает описание содержимого
public CookieCollection Cookies { get; set; } – Получает или устанавливает список cookie-наборов, присоединяемых к ответу
public WebHeaderCollection Headers! get; } – Получает коллекцию заголовков, присоединяемых к ответу
public bool IsFromCache { get; } – Принимает логическое значение true, если запрос получен из кеша. А если запрос доставлен по сети, то принимает логическое значение false
public bool IsMutuallyAuthenticated { get; } – Принимает логическое значение true, если клиент и сервер опознают друг друга, а иначе – принимает логическое значение false
public DateTime LastModified { get; } – Получает время последней модификации ресурса
public string Method { get; } – Получает строку, которая задает способ ответа
public Version ProtocolVersion { get; } – Получает объект типа Version, описывающий версию протокола HTTP, используемую в транзакции
public Uri ReponseUri { get; } – Получает URI, по которому был сформирован ответ. Этот идентификатор может отличаться от запрашиваемого, если ответ был переадресован по другому URI
public string Server { get; } – Получает строку, обозначающую имя сервера
public HttpStatusCode StatusCode { get; } – Получает объект типа HttpStatusCode, описывающий состояние транзакции
public string StatusDescription { get; } – Получает строку, обозначающую состояние транзакции в удобочитаемой форме
Доступ к заголовку
Для доступа к заголовку с информацией, получаемой в ответ по протоколу HTTP, служит свойство Headers
, определенное в классе HttpWebResponse
.
public WebHeaderCollection Headers{ get; }
Заголовок протокола HTTP состоит из пар «имя-значение», представленных строками. Каждая пара «имя-значение» хранится в коллекции класса WebHeaderCollection
. Эта коллекция специально предназначена для хранения пар «имя-значение» и применяется аналогично любой другой коллекции (подробнее об этом см. в главе 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}», «Имя», «Значение»);
foreach (string n in names) {
Console.Write(«{0,-20}», n);
foreach (string v in resp.Headers.GetValues(n))
Console.WriteLine(v);
}
// Закрыть ответный поток, resp.Close();
}
}
Ниже приведен полученный результат. Не следует забывать, что информация в заголовке периодически меняется, поэтому у вас результат может оказаться несколько иным.
Имя Значение
X-Server-Name va-c1-r4-u27-b1
X-Cache MISS from proxy.ukr.zal
X-Cache-Lookup MISS from proxy.ukr.zal:3129
Connection keep-alive
Content-Length 4442
Content-Type text/html;charset=utf-8
Date Wed, 27 Jul 2016 09:01:26 GMT
Last-Modified Mon, 25 Jul 2016 11:57:20 GMT
Server Apache
(В оригинале приведено:)
Имя Значение
Transfer-encoding chunked
Content-Type text/html
Date Sun, 06 Dec 2009 20:32:06 GMT
Server Sun-ONE-Web-Server/6.1
Доступ к cookie-наборам
Для доступа к cookie-наборам, получаемым в ответ по протоколу HTTP, служит свойство Cookies
, определенное в классе HttpWebResponse
. В cookie-Ha6opax содержится информация, сохраняемая браузером. Они состоят из пар «имя-значение» и упрощают некоторые виды доступа к веб-сайтам. Ниже показано, каким образом определяется свойство Cookies
.
public CookieCollection Cookies { get; set; }
В классе CookieCollection
реализуются интерфейсы ICollection
и IEnumerable
, и поэтому его можно использовать аналогично классу любой другой коллекции (подробнее об этом см. в главе 25). У этого класса имеется также индексатор, позволяющий получать cookie-Ha6op по указанному индексу или имени.
В коллекции типа CookieCollection
хранятся объекты класса Cookie
. В классе Cookie
определяется несколько свойств, предоставляющих доступ к различным фрагментам информации, связанной с cookie-набором. Ниже приведены два свойства, Name
и Value
, используемые в примерах программ из этой главы.
public string Name { get; set; }
public string Value { get; set; }
Имя cookie-Ha6opa содержится в свойстве Name
, а его значение – в свойстве Value
.
Для того чтобы получить список cookie-наборов из принятого ответа, необходимо предоставить сооkiе-контейнер с запросом. И для этой цели в классе HttpWebRequest
определяется свойство CookieContainer
, приведенное ниже.
public CookieContainer CookieContainer { get; set; }
В классе CookieContainer
предоставляются различные поля, свойства и методы, позволяющие хранить сооkiе-наборы. По умолчанию свойство 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 введите команду
LastModif iedDemo http: //HerbSchildt. com
*/
using System;
using System.Net;
class LastModifiedDemo {
static void Main(string[] args) {
if (args.Length != 1) {
Console.WriteLine(«Применение: LastModifiedDemo
}
HttpWebRequest req = (HttpWebRequest)
WebRequest.Create(args[0]);
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
Console.WriteLine("Последняя модификация: " + resp.LastModified);
resp.Close();
}
}
Для того чтобы показать, насколько просто программировать для Интернета средствами классов 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]; // содержит текущий URI
HttpWebResponse 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.OrdinalIgnoreCase)) {
break;
}
else if (string.Equals(answer, "И", StringComparison.OrdinalIgnoreCase)) {
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 +
"Код состояния: " + 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 и текущее состояние поиска в строке URL С этой целью можно, в частности, воспользоваться коллекцией класса Stack
. В качестве более сложной, но интересной задачи попробуйте организовать вывод ссылок в виде дерева.