Язык С# предназначен для программирования в совре менной вычислительной среде, где Интернету, есте ственно, принадлежит весьма важная роль. Одной из главных целей разработки С# было внедрение в этот язык программирования средств, необходимых для до ступа к Интернету. Такой доступ можно было осуществить и в предыдущих версиях языков программирования, вклю чая С и C++, но поддержка операций на стороне сервера, загрузка файлов и получение сетевых ресурсов в этих язы ках не вполне отвечали потребностям большинства про граммистов. Эта ситуация коренным образом изменилась в С#. Используя стандартные средства C# и среды .NET Framework, можно довольно легко сделать приложения совместимыми с Интернетом и написать другие виды про грамм, ориентированных на подключение к Интернету.
Поддержка сетевого подключения осуществляется через несколько пространств имен, определенных в среде .NET Framework, и главным среди них является пространство имен System.Net. В нем определяется целый ряд высо коуровневых, но простых в использовании классов, поддер живающих различные виды операций, характерных для работы с Интернетом. Для этих целей доступен также ряд пространств, вложенных в пространство имен System.Net. Например, средства низкоуровневого сетевого управления через сокеты находятся в пространстве имен System.Net. Sockets, поддержка электронной почты — в простран стве имен System.Net.Mail, а поддержка защищенных сетевых потоков — в пространстве имен System.Net. Security. Дополнительные функциональные возможно сти предоставляются в ряде других вложенных пространств имен. К числу других не менее важных пространств имен, связанных с сетевым подключением к Интернету, относится пространство System. Web. Это и вложенные в него пространства имен поддерживают сетевые приложения на основе технологии ASP.NET.
В среде .NET Framework имеется достаточно гибких средств и возможностей для се тевого подключения к Интернету. Тем не менее для разработки многих приложений более предпочтительными оказываются функциональные возможности, доступные в пространстве имен System.Net. Они и удобны, и просты в использовании. Именно по этому пространству имен System.Net будет уделено основное внимание в этой главе.
Пространство имен System.Net довольно обширно и состоит из многих членов. Полное их описание и обсуждение всех аспектов программирования для Интернета выходит далеко за рамки этой главы. (На самом деле для подробного рассмотрения всех вопросов, связанных с сетевым подключением к Интернету и его поддержкой в С#, потребуется отдельная книга.) Однако целесообразно хотя бы перечислить члены пространства имен System.Net, чтобы дать какое-то представление о том, что именно доступно для использования в этом пространстве.
Ниже перечислены классы, определенные в пространстве имен System.Net.
AuthenticationManager | Authorization |
Cookie | CookieCollection |
CookieContainer | CookieException |
CredentialCache | Dns |
DnsEndPoint | DnsPermission |
DnsPermissionAttribute | DownloadDataCompletedEventArgs |
DownloadProgressChangedEventArgs | DownloadstringCompletedEventArgs |
EndPoint | EndpointPermission |
FileWebRequest | FileWebResponse |
FtpWebRequest | FtpWebResponse |
HttpListener | HttpListenerBasicIdentity |
HttpListenerContext | HttpListenerException |
HttpListenerPrefixCollection | HttpListenerRequest |
HttpListenerResponse | HttpVersion |
HttpWebRequest | HttpWebResponse |
IPAddress | IPEndPoint |
IPEndPointCollection | IPHostEntry |
IrDAEndPoint | NetworkCredential |
OpenReadCompletedEventArgs | OpenWriteCompletedEventArgs |
ProtocolViolationException | ServicePoint |
ServicePointManager | SocketAddress |
SocketPermission | SocketPermissionAttribute |
TransportContext | UploadDataCompletedEventArgs |
UploadFileCompletedEventArgs | UploadProgressChangedEventArgs |
UploadstringCompletedEventArgs | UploadValuesCompletedEventArgs |
WebClient | WebException |
WebHeaderCollection | WebPermission |
WebPermissionAttribute | WebProxy |
WebRequest | WebRequestMethods |
WebRequestMethods.File | WebRequestMethods.Ftp |
WebRequestMethods.Http | WebResponse |
WebUtility |
Кроме того, в пространстве имен System.Net определены перечисленные ниже интерфейсы.
AuthenticationModule | IcertificatePolicy | ICredentialPolicy |
ICredentials | IcredentialsByHost | IWebProxy |
IWebProxyScript | IWebRequestCreate |
В этом пространстве имен определяются также приведенные ниже перечисления.
AuthenticationSchemes | DecompressionMethods | FtpStatusCode |
HttpRequestHeader | HttpResponseHeader | HttpStatusCode |
NetworkAccess | SecurityProtocolType | TransportType |
WebExceptionStatus |
Помимо этого, в пространстве имен System.Net определен ряд делегатов.
Несмотря на то что в пространстве имен System.Net определено немало членов, лишь немногие из них на самом деле требуются при решении наиболее типичных задач программирования для Интернета. Основу сетевых программных средств составляют аб страктные классы WebRequest и WebResponse. От этих классов наследуют все классы, поддерживающие конкретные сетевые протоколы. (Протокол определяет правила пере дачи данных по сети.) Например, к производным классам, поддерживающим стандарт ный сетевой протокол HTTP, относятся классы HttpWebRequest и HttpWebResponse.
Классы HttpWebRequest и HttpWebResponse довольно просты в использовании. Тем не менее решение некоторых задач можно еще больше упростить, применяя под ход, основанный на классе WebClient. Так, если требуется только загрузить или вы грузить файл, то для этой цели лучше всего подойдет класс WebClient.
В основу программирования для Интернета положено понятие универсального иден тификатора ресурса (URI), иногда еще называемого унифицированным указателем инфор мационного ресурса (URL). Этот идентификатор описывает местоположение ресурса в сети. В корпорации Microsoft принято пользоваться сокращением URI при описании членов пространства имен System.Net, и поэтому в данной книге выбрано именно это сокращение для обозначения универсального идентификатора ресурса. Иденти фикаторы URI, без сомнения, известны каждому, кто хотя бы раз пользовался браузе ром для поиска информации в Интернете. По существу, это адрес информационного ресурса, который указывается в соответствующем поле окна браузера.
Ниже приведена общая форма идентификатора URI:
Протокол://Идентификационный_номер_сервера/Путь_к_файлу?Запрос
где Протокол — это применяемый протокол, например HTTP; Идентификацион ный_номер_сервера — конкретный сервер, например mhprofessional.com или HerbSchildt.com; Путь_к_файлу — путь к конкретному файлу. Если же Путь_к_ файлу не указан, то получается страница, доступная на указанном сервере по умолча нию. И наконец, Запрос обозначает информацию, отправляемую на сервер. Указывать Запрос необязательно. В C# идентификаторы URI инкапсулированы в класс Uri, рас сматриваемый далее в этой главе.
В классах, находящихся в пространстве имен System.Net, поддерживается модель взаимодействия с Интернетом по принципу запроса и ответа. При таком подходе пользовательская программа, являющаяся клиентом, запрашивает информацию у сервера, а затем переходит в состояние ожидания ответа. Например, в качестве запроса программа может отправить на сервер идентификатор URI некоторого веб-сайта. В от вет она получит гипертекстовую страницу, соответствующую указанному идентифи катору URI. Такой принцип запроса и ответа удобен и прост в применении, поскольку большинство деталей сетевого взаимодействия реализуются автоматически.
На вершине иерархии сетевых классов находятся классы WebRequest и WebResponse, реализующие так называемые подключаемые протоколы. Как должно быть известно большинству читателей, для передачи данных в сети применяется не сколько разнотипных протоколов. К числу наиболее распространенных в Интернете относятся протокол передачи гипертекстовых файлов (HTTP), а также протокол пере дачи файлов (FTP). При создании идентификатора URI его префикс обозначает приме няемый сетевой протокол. Например, в идентификаторе http://www.HerbSchildt. com используется префикс http, обозначающий протокол передачи гипертекстовых файлов (HTTP).
Как упоминалось выше, классы WebRequest и WebResponse являются абстракт ными, а следовательно, в них определенны в самом общем виде операции запроса и ответа, типичные для всех протоколов. От этих классов наследуют более конкретные производные классы, в которых реализуются отдельные протоколы. Эти производные классы регистрируются самостоятельно, используя для этой цели статический метод RegisterPrefix(), определенный в классе WebRequest. При создании объекта типа WebRequest автоматически используется протокол, указываемый в префиксе URI, если, конечно, он доступен. Преимущество такого принципа "подключения" протоко лов заключается в том, что большая часть кода пользовательской программы остается без изменения независимо от типа применяемого протокола.
В среде выполнения .NET Runtime протоколы HTTP, HTTPS и FTP определяются ав томатически. Так, если указать идентификатор URI с префиксом HTTP, то будет авто матически получен HTTP-совместимый класс, который поддерживает протокол HTTP. А если указать идентификатор URI с префиксом FTP, то будет автоматически получен FTP-совместимый класс, поддерживающий протокол FTP.
При сетевом подключении к Интернету чаще всего применяется протокол HTTP, поэтому именно он и рассматривается главным образом в этой главе. (Тем не менее аналогичные приемы распространяются и на все остальные поддерживаемые протоко лы.) Протокол HTTP поддерживается в классах HttpWebRequest и HttpWebResponse. Эти классы наследуют от классов WebRequest и WebResponse, а кроме того, име ют собственные дополнительные члены, применимые непосредственно к протоколу HTTP.
В пространстве имен System.Net поддерживается как синхронная, так и асинхрон ная передача данных. В Интернете предпочтение чаще всего отдается синхронным транзакциям, поскольку ими легче пользоваться. При синхронной передаче данных пользовательская программа посылает запрос и затем ожидает ответа от сервера. Но для некоторых разновидностей высокопроизводительных приложений более подхо дящей оказывается асинхронная передача данных. При таком способе передачи дан ных пользовательская программа продолжает обработку данных, ожидая ответа на переданный запрос. Но организовать асинхронную передачу данных труднее. Кроме того, не во всех программах можно извлечь выгоды из асинхронной передачи данных. Например, когда требуется получить информацию из Интернета, то зачастую ниче го другого не остается, как ожидать ее. В подобных случаях потенциал асинхронной передачи данных используется не полностью. Вследствие того что синхронный доступ к Интернету реализуется проще и намного чаще, именно он и будет рассматриваться в этой главе.
Далее речь пойдет прежде всего о классах WebRequest и WebResponse, поскольку именно они положены в основу сетевых программных средств, доступных в простран стве имен System.Net.
Класс WebRequest управляет сетевым запросом. Он является абстрактным, по скольку в нем не реализуется конкретный протокол. Тем не менее в нем определяются те методы и свойства, которые являются общими для всех сетевых запросов. В табл. 26.1 сведены методы, определенные в классе WebRequest и поддерживающие синхронную передачу данных, а в табл. 26.2 — свойства, объявляемые в классе WebRequest. Уста навливаемые по умолчанию значения свойств задаются в производных классах. Откры тые конструкторы в классе WebRequest не определены.
Для того чтобы отправить запрос по адресу URI, необходимо сначала создать объект класса, производного от класса WebRequest и реализующего требуемый про токол. С этой целью вызывается статический метод Create(), определенный в клас се WebRequest. Метод Create() возвращает объект класса, наследующего от класса WebRequest и реализующего конкретный протокол.
Таблица 26.1. Методы, определенные в классе WebRequest
Метод | Описание |
---|---|
public static WebRequest Create(string requestUriString) | Создает объект типа WebRequest для идентификатора URI, указываемого в строке requestUriString. Возвращаемый объект реализует протокол, заданный префиксом идентификатора URI. Следовательно, возвращаемый объект будет экземпляром класса, производного от класса WebRequest. Если затребованный протокол недоступен, то генерируется исключение NotSupportedException. А если недействителен указанный формат идентификатора URI, то генерируется исключение UriFormatException |
public static WebRequest Create(Uri requestUri) | Создает объект типа WebRequest для идентификатора URI, указываемого с помощью параметра requestUri. Возвращаемый объект реализует протокол, заданный префиксом идентификатора URI. Следовательно, возвращаемый объект будет экземпляром класса, производного от класса WebRequest. Если затребованный протокол недоступен, то генерируется исключение NotSupportedException |
public virtual Stream GetRequestStream() | Возвращает поток вывода, связанный с запрошенным ранее идентификатором URI |
public virtual WebResponse GetResponse() | Отправляет предварительно сформированный запрос и ожидает ответа. Получив ответ, возвращает его в виде объекта класса WebReponse. Этот объект используется затем в программе для получения информации по указанному адресу URI |
Таблица 26.2. Свойства, определенные в классе WebRequest
Свойство | Описание |
---|---|
public AuthenticationLevel AuthenticationLevel{ get; set; } | Получает или устанавливает уровень аутентификации |
public virtual RequestCachePolicy CachePolicy { get; set; } | Получает или устанавливает правила использования кеша, определяющие момент получения ответа из кеша |
public virtual string ConnectionGroupName { get; set; } | Получает или устанавливает имя группы подключения. Группы подключения представляют собой способ создания ряда запросов. Они не нужны для простых транзакций в Интернете |
public virtual long ContentLength { get; set; } | Получает или устанавливает длину передаваемого содержимого |
public virtual string ContentType { get; set; } | Получает или устанавливает описание передаваемого содержимого |
public virtual Icredentials Credentials { get; set; } | Получает или устанавливает мандат, т.е. учетные данные пользователя |
public static RequestCachePolicy DefaultCachePolicy { get; set; } | Получает или устанавливает правила использования кеша по умолчанию, определяющие момент получения ответа из кеша |
public static IWebProxy DefaultWebProxy { get; set; } | Получает или устанавливает используемый по умолчанию прокси-сервер |
public virtual WebHeaderCollection Headers{ get; set; } | Получает или устанавливает коллегию заголовков |
public TokenImpersonationLevel ImpersonationLevel { get; set; } | Получает или устанавливает уровень анонимного воплощения |
public virtual string Method { get; set; } | Получает или устанавливает протокол |
public virtual bool PreAuthenticate { get; set; } | Если принимает логическое значение true, то в отправляемый запрос включается информация для аутентификации. А если принимает логическое значение false, то информация для аутентификации предоставляется только по требованию адресата URI |
public virtual IWebProxy Proxy { get; set; } | Получает или устанавливает прокси-сервер. Применимо только в тех средах, где используется прокси-сервер |
public virtual Uri RequestUri { get; } | Получает идентификатор URI конкретного запроса |
public virtual int Timeout { get; set; } | Получает или устанавливает количество миллисекунд, в течение которых будет ожидаться ответ на запрос. Для установки бесконечного ожидания используется значение Timeout.Infinite |
public virtual bool UseDefaultCredential { get; set; } | Получает или устанавливает значение, которое определяет, используется ли для аутентификации устанавливаемый по умолчанию мандат. Если имеет логическое значение true, то используется устанавливаемый по умолчанию мандат, т.е. учетные данные пользователя, в противном случае этот мандат не используется |
В классе WebResponse инкапсулируется ответ, получаемый по запросу. Этот класс является абстрактным. В наследующих от него классах создаются отдельные его вер сии, поддерживающие конкретный протокол. Объект класса WebResponse обыч но получается в результате вызова метода GetResponse(), определенного в классе WebRequest. Этот объект будет экземпляром отдельного класса, производного от класса WebResponse и реализующего конкретный протокол. Методы, определенные в классе WebResponse, сведены в табл. 26.3, а свойства, объявляемые в этом классе, — в табл. 26.4. Значения этих свойств устанавливаются на основании каждого запроса в отдельности. Открытые конструкторы в классе WebResponse не определяются.
Таблица 26.3. Наиболее часто используемые методы, определенные в классе WebResponse
Метод | Описание |
---|---|
public virtual void Close() | Закрывает ответный поток. Закрывает также поток ввода ответа, возвращаемый методом GetResponseStream() |
public virtual Stream GetResponseStream() | Возвращает поток ввода, связанный с запрашиваемым URI. Из этого потока могут быть введены данные из запрашиваемого URI |
Таблица 26.4. Свойства, определенные в классе WebResponse
Свойство | Описание |
---|---|
public virtual long ContentLength { get; set; } | Получает или устанавливает длину принимаемого содержимого. Устанавливается равным -1, если данные о длине содержимого недоступны |
public virtual string ContentType { get; set; } | Получает или устанавливает описание принимаемого содержимого |
public virtual WebHeaderCollection Headers { get; } | Получает или устанавливает коллекцию заголовков, связанных с URI |
public virtual bool IsFromCache { get; } | Принимает логическое значение true, если запрос получен из кэша. А если запрос доставлен по сети, то принимает логическое значение false |
public virtual bool IsMutuallyAuthenticated { get; } | Принимает логическое значение true, если клиент и сервер опознают друг друга, а иначе — принимает логическое значение false |
public virtual Uri ResponseUri { get; } | Получает URI, по которому был сформирован ответ. Этот идентификатор может отличаться от запрашиваемого, если ответ был переадресован по другому URI |
Оба класса, HttpWebRequest и HttpWebResponse, наследуют от классов WebRequest и WebResponse и реализуют протокол HTTP. В ходе этого процесса в обоих классах вводится ряд дополнительных свойств, предоставляющих подробные сведения о транзакции по протоколу HTTP. О некоторых из этих свойств речь пойдет далее в настоящей главе. Но для выполнения простых операций в Интернете эти до полнительные свойства, как правило, не требуются.
Доступ к Интернету организуется на основе классов WebRequest и WebResponse. Поэтому, прежде чем рассматривать этот процесс более подробно, было бы полезно обратиться к простому примеру, демонстрирующему порядок доступа к Интернету по принципу запроса и ответа. Глядя на то, как эти классы применяются на практике, легче понять, почему они организованы именно так, а не как-то иначе.
В приведенном ниже примере программы демонстрируется простая, но весьма типичная для Интернета операция получения гипертекстового содержимого из кон кретного веб-сайта. В данном случае содержимое получается из веб-сайта издатель ства McGraw-Hill по адресу www.McGraw-Hill.com, но вместо него можно подставить адрес любого другого веб-сайта. В этой программе гипертекстовое содержимое выво дится на экран монитора отдельными порциями по 400 символов, чтобы полученную информацию можно было просматривать, не прибегая к прокрутке экрана.
// Пример доступа к веб-сайту.
using System;
using System.Net;
using System.IO;
class NetDemo {
static void Main() {
int ch;
// Сначала создать объект запроса типа WebRequest по указанному URI.
HttpWebRequest req = (HttpWebRequest)
WebRequest.Create("http://www.McGraw-Hill.com");
// Затем отправить сформированный запрос и получить на него ответ.
HttpWebResponse resp = (HttpWebResponse)
req.GetResponse();
// Получить из ответа поток ввода.
Stream istrm = resp.GetResponseStream();
/* А теперь прочитать и отобразить гипертекстовое содержимое,
полученное по указанному URI. Это содержимое выводится на экран
отдельными порциями по 400 символов. После каждой такой порции
следует нажать клавишу <ENTER>, чтобы вывести на экран
следующую порцию из 400 символов. */
for(int i=1; ; i++) {
ch = istrm.ReadByte ();
if(ch == -1) break;
Console.Write((char) ch);
if((i%400)==0) {
Console.Write("\nНажмите клавишу <Enter>.");
Console.ReadLine();
}
}
// Закрыть ответный поток. При этом закрывается также поток ввода istrm.
resp.Close();
}
}
Ниже приведена первая часть получаемого результата. (Разумеется, это содержи мое может со временем измениться в связи с обновлением запрашиваемого веб-сайта, и поэтому у вас оно может оказаться несколько иным.)
<html>
<head>
<title>Home - The McGraw-Hill Companies</title>
<meta name="keywords" content="McGraw-Hill Companies,McGraw-Hill, McGraw Hill,
Aviation Week, BusinessWeek, Standard and Poor's, Standard & Poor's,CTB/McGrawHill,
Glencoe/McGraw-Hill, The Grow Network/McGraw-Hill,Macmillan/McGraw-Hill,
McGraw-Hill Contemporary,McGraw-Hill Digital Learning,McGraw-Hill Professional
Development,SRA/McGraw
Нажмите клавишу <Enter>.
-Hill,Wright-Group/McGraw-Hill,McGraw-Hill Higher Education,McGraw-Hill/Irwin,
McGraw-Hill/Primis Custom Publishing,McGraw-Hill/Ryerson,Tata/McGraw-Hill,
McGraw-Hill Interamericana,Open University Press, Healthcare Information Group,
Platts, McGraw-Hill Construction, Information & Media Services" />
<meta name="description" content="The McGraw-Hill Companies Corporate Website." />
<meta http-equiv
Нажмите клавишу <Enter>.
Итак, выше приведена часть гипертекстового содержимого, полученного из веб сайта издательства 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(), определенный в классе WebRequest, может генерировать четыре исключения. Так, если протокол, указываемый в префиксе URI, не поддерживается, то генерируется исключение NotSupportedException. Если формат URI оказывает ся недействительным, то генерируется исключение UriFormatException. А если у пользователя нет соответствующих полномочий для доступа к запрашиваемому сете вому ресурсу, то генерируется исключение System.Security.SecurityException. Кроме того, метод Create() генерирует исключение ArgumentNullException, если он вызывается с пустой ссылкой, хотя этот вид ошибки не имеет непосредственного отношения к сетевому подключению.
При вызове метода 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 |
Как только будет выяснена причина ошибки, в программе могут быть предприня ты соответствующие действия.
Для соединения по протоколу 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 символов. После каждой такой порции
следует нажать клавишу <ENTER>, чтобы вывести на экран следующую
порцию, состоящую из 400 символов. */
for (int i=1; ; i++) {
ch = istrm.ReadByte();
if(ch == -1) break;
Console.Write((char) ch);
if((i%400)==0) {
Console.Write ("\nНажмите клавишу <Enter>.");
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, что и подтверждает приведенный выше ре зультат.
Ради краткости и ясности в программах большинства примеров из этой главы от сутствует полноценная обработка исключений. Но в реальных приложениях она про сто необходима.
Как следует из табл. 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("Локальный путь: " + sample.LocalPath);
Console.WriteLine("Запрос: " + sample.Query);
Console.WriteLine("Путь и запрос: " + sample.PathAndQuery);
}
}
Эта программа дает следующий результат.
Хост: HerbSchildt.com
Порт: 80
Протокол: http
Локальный путь: /somefile.txt
Запрос: ?ScmeQuery
Путь и запрос: /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}\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 chunked
Content-Type text/html
Date Sun, 06 Dec 2009 20:32:06 GMT
Server Sun-ONE-Web-Server/6.1
Для доступа к 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 <uri>");
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();
}
}
Иногда требуется знать, когда именно сетевой ресурс был обновлен в последний раз. Это нетрудно сделать, пользуясь сетевыми средствами класса 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 <uri>");
return;
}
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 <uri>");
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.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 рекомендуется использовать вместо классов WebRequest и WebResponse в том случае, если в приложении требуется лишь выгру жать или загружать данные из Интернета. Преимущество класса WebClient заклю чается в том, что он автоматически выполняет многие операции, освобождая от их программирования вручную.
В классе WebClient определяется единственный конструктор.
public WebClient()
Кроме того, в классе WebClient определяются свойства, сведенные в табл. 26.6, а также целый ряд методов, поддерживающих как синхронную, так и асинхронную пе редачу данных. Но поскольку рассмотрение асинхронной передачи данных выходит за рамки этой главы, то в табл. 26.7 приведены только те методы, которые поддерживают синхронную передачу данных. Все методы класса WebClient генерируют исключение WebException, если во время передачи данных возникает ошибка.
Таблица 26.6. Свойства, определенные в классе WebClient
Свойство | Описание |
---|---|
public string BaseAddress { get; set; } | Получает или устанавливает базовый адрес требуемого URI. Если это свойство установлено, то адреса, задаваемые в методах класса WebClient, должны определяться относительно этого базового адреса |
public RequestCachePolicy CachePolicy { get; set; } | Получает или устанавливает правила, определяющие, когда именно используется кэш |
public ICredentials Credentials { get; set; } | Получает или устанавливает мандат, т.е. учетные данные пользователя. По умолчанию это Свойство имеет пустое значение |
public Encoding Encoding { get; set; } | Получает или устанавливает схему кодирования символов при передаче строк |
public WebHeaderCollection Headers{ get; set; } | Получает или устанавливает коллекцию заголовков запроса |
public bool IsBusy{ get; } | Принимает логическое значение true, если данные по-прежнему передаются по запросу, а иначе — логическое значение false |
public IWebProxy Proxy { get; set; } | Получает или устанавливает прокси-сервер |
public NameValueCollection QueryString { get; set; } | Получает или устанавливает строку запроса, состоящую из пар “имя-значение”, которые могут быть присоединены к запросу. Строка запроса отделяется от URI символом ?. Если же таких пар несколько, то каждая из них отделяется символом @ |
public WebHeaderCollection ResponseHeaders{ get; } | Получает коллекцию заголовков ответа |
public bool UseDefaultCredentials { get; set; } | Получает или устанавливает значение, которое определяет, используется ли для аутентификации устанавливаемый по умолчанию мандат. Если принимает логическое значение true, то используется мандат, устанавливаемый по умолчанию, т.е. учетные данные пользователя, в противном случае этот мандат не используется |
Таблица 26.7. Методы синхронной передачи, определенные в классе WebClient
Метод | Определение |
---|---|
public byte[] DownloadData(string address) | Загружает информацию по адресу URI, обозначаемому параметром address. Возвращает результат в виде массива байтов |
public byte[] DownloadData(Uri address) | Загружает информацию по адресу URI, обозначаемому параметром address. Возвращает результат в виде массива байтов |
public void DownloadFile(string uri, string fileName) | Загружает информацию по адресу URI, обозначаемому параметром fileName. Сохраняет результат в файле fileName |
public void DownloadFile(Uri address, string fileName) | Загружает информацию по адресу URI, обозначаемому параметром address. Сохраняет результат в файле fileName |
public string DownloadString(string address) | Загружает информацию по адресу URI, обозначаемому параметром address. Возвращает результат в виде символьной строки типа string |
public string DownloadString(Uri address) | Загружает информацию по адресу URI, обозначаемому параметром address. Возвращает результат в виде символьной строки типа string |
public Stream OpenRead(string address) | Возвращает поток ввода для чтения информации по адресу URI, обозначаемому параметром address. По окончании чтения информации этот поток необходимо закрыть |
public Stream OpenRead(Uri address) | Возвращает поток ввода для чтения информации по адресу URI, обозначаемому параметром address. По окончании чтения информации этот поток необходимо закрыть |
public Stream OpenWrite(string address) | Возвращает поток вывода для записи информации по адресу URI, обозначаемому параметром address. По окончании записи информации этот поток необходимо закрыть |
public Stream OpenWrite(Uri address) | Возвращает поток вывода для записи информации по адресу URI, обозначаемому параметром address. По окончании записи информации этот поток необходимо закрыть |
public Stream OpenWrite(string address, string method) | Возвращает поток вывода для записи информации по адресу URI, обозначаемому параметром address. По окончании записи информации этот поток необходимо закрыть. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
public Stream OpenWrite(Uri address, string method) | Возвращает поток вывода для записи информации по адресу URI, обозначаемому параметром address. По окончании записи информации этот поток необходимо закрыть. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
public byte[] UploadData(string address, byte[] data) | Записывает информацию из массива data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ |
public byte[] UploadData(Uri address, byte[] data) | Записывает информацию из массива data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ |
public byte[] UploadData(string address, string method, byte[] data) | Записывает информацию из массива data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
public byte[] UploadData(Uri address, string method, byte[] data) | Записывает информацию из массива data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
public byte[] UploadFile(string address, string fileName) | Записывает информацию в файл fileName по адресу URI, обозначаемому параметром address. В итоге возвращается ответ |
public byte[] UploadFile(Uri address, string fileName) | Записывает информацию в файл fileName по адресу URI, обозначаемому параметром address. В итоге возвращается ответ |
public byte[] UploadFile(string address, string method, string fileName) | Записывает информацию в файл fileName по адресу URI, обозначаемому параметром address. В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
public byte[] UploadFile(Uri address, string method, string fileName) | Записывает информацию в файл fileName по адресу URI, обозначаемому параметром address. В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
public string UploadString(string address, string data) | Записывает строку data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ |
public string UploadString(Uri address, string data) | Записывает строку data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ |
public string UploadString(string addreds, string method, string data) | Записывает строку data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
public string UploadString(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(string address, string method, NameValueCollection data) | Записывает значения из коллекции data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
public byte[] UploadValues(Uri address, string method, NameValueCollection data) | Записывает значения из коллекции data по адресу URI, обозначаемому параметром address. В итоге возвращается ответ. В строке, передаваемой в качестве параметра method, указывается, как именно следует записывать информацию |
В приведенном ниже примере программы демонстрируется применение класса WebClient для загрузки данных в файл по указанному сетевому адресу.
// Использовать класс 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.McGrawHill.com и поме щает ее в файл data.txt. Обратите внимание на строки кода этой программы, в кото рых осуществляется загрузка информации. Изменив символьную строку uri, можно загрузить информацию по любому адресу URI, включая и конкретные файлы, доступ ные по указываемому URL.
Несмотря на то что классы WebRequest и WebResponse предоставляют больше возможностей для управления и доступа к более обширной информации, для многих приложений оказывается достаточно и средств класса WebClient. Этим классом осо бенно удобно пользоваться в тех случаях, когда требуется только загрузка информации из веб-ресурса. Так, с помощью средств класса WebClient можно получить из Интер нета обновленную документацию на приложение.