Назад Содержание Далее

Сеть

В этом разделе расматриваются два аспекта сетевого программирования. Первый из них касается доступа из приложений Java к файлам, расположенным на сервере Web, второй - создания серверных и клиентских приложений с использованием сокетов.

Необходимо напомнить, что из соображений безопасности алпетам полностью запрещен доступ к локальным файлам рабочей станции, подключенной к сети. Тем не менее, аплет может работать с файлами, расположенными на серверах Web. При этом можно использовать входные и выходные потоки.

Для чего аплетам обращаться к файлам сервера Web?

Таким аплетам можно найти множество применений.

Представьте себе, например, что вам нужно отображать у пользователя диаграмму, исходные данные для построения которой находятся на сервере Web. Эту задачу можно решить, грубо говоря, двумя способами.

Первый заключается в том, что вы создаете расширение сервера Web в виде приложения CGI или ISAPI, которое на основании исходных данных динамически формирует графическое изображение диаграммы в виде файла GIF и посылает его пользователю.

Однако на пути решения задачи с помощью расширения сервера Web вас поджидают две неприятности. Во-первых, создать из программы красивый цветной графический файл в стандарте GIF не так-то просто - вы должны разобраться с форматом этого файла и создать все необходимые заголовки. Во-вторых, графический файл занимает много места и передается по каналам Internet достаточно медленно - средняя скорость передачи данных в Internet при обычном модемном соединении составляет 3-4 Кбайт в секунду.

В то же время файл с исходными данными может быть очень компактным. Возникает вопрос - нельзя ли передавать через Internet только исходные данные, а построение графической диаграммы выполнять на рабочей станции пользователя?

В этом заключается второй способ, который предполагает применение аплетов. Ваше приложение может, например, получать через сеть файл исходных данных, а затем на основании содержимого этого файла рисовать в своем окне цветную круговую диаграмму. Объем передаваемых данных при этом по сравнению с использованием расширения сервера Web сокращается в десятки и сотни раз.

Помимо работы с файлами, расположенными на сервере Web, будет рассмотрен вопрос создания каналов между приложениями Java, работающими на различных компьютерах в сети, с использованием сокетов.

Сокеты позволяют организовать тесное взаимодействие аплетов и полноценных приложений Java, при котором аплеты могут предавать друг другу данные через сеть Internet. Это открывает широкие возможности для обработки информации по схеме клиент-сервер, причем в роли серверов здесь может выступать любой компьютер, подключенный к сети, а не только сервер Web. Каждая рабочая станция может выступать одновременно и в роли сервера, и в роли клиента.

Протоколы TCP/IP

Наиболее часто в схеме TCP/IP используется сочетание трех протоколов: IP, TCP и UDP, поэтому они будут рассмотрены подробно. При разработке сетевых приложений понимание их взаимодействия очень существенно.

Internet Protocol (IP)

В основе TCP/IP лежит IP. Все данные передаются по Internet в виде пакетов IP (IP packets). Этот пакет является основной единицей передачи данных IP. Протокол IP характеризуется как ненадежный протокол без логического соединения. Поскольку логическое соединение не устанавливается, протокол IP не производит обмен служебной информацией перед передачей данных на другой компьютер - пакеты просто пересылаются. IP является ненадежным, поскольку он не выполняет повторную посылку потерянных пакетов и не обнаруживает ошибок при пересылке данных. Эти задачи должны быть решены протоколами более высокого уровня, например TCP. IP определяет глобальную схему адресации, называемую 1Р-адресом. IP-адрес (IP address) - это 32-разрядное число, причем каждый адрес уникален в пределах Internet. Заданный IP-пакет может быть направлен по назначению, заданному IP-адресом в заголовке пакета. Обычно IP-адреса записывают в виде четырех чисел от 0 до 255, разделенных точками (например, 124.148.157.6). Адрес в виде 32-разрядного числа вполне подходит для компьютеров, но людям обычно трудно его запоминать. Поэтому была разработана система под названием DNS (Domain Name System - доменная система имен), которая ставит в соответствие IP-адресам мнемонические обозначения, и наоборот.. Благодаря ей можно вместо адреса 128.148.157.6 использовать имя www.netspace.org. Важно иметь в виду, что доменные имена не используются и не распознаются протоколом IP. Если приложение должно переслать данные на другую машину в Internet, оно сначало должно транслировать доменное имя в IP-адрес при помощи DNS. Принимающее приложение может проделать обратную операцию: при помощи DNS выяснить доменное имя по 1Р-адресу. Между доменными именами и IP-адресами нет взаимно-однозначного соответствия: доменное имя может относиться к нескольким IP-адресам, а IP-адрес может соответствовать нескольким доменным именам.

Еще более важно отметить, что вообще на данные DNS полагаться нельзя. За поддержку записей DNS отвечают различные системы, разбросанные по всему миру. Можно легко обмануть сервер DNS, а также создать сервер DNS, содержащий ложную информацию. Ранние реализации Java даже имели пробел в системе безопасности, который образовался из-за неоправданного доверия к системе DNS.

Протокол TCP

Большинство приложений Internet в качестве реализации транспортного уровня используют протокол TCP. Он обеспечивает надежную связь на основе логического соединения с непрерывным потоком данных. Эти характеристики подразумевают следующее:

  1. Надежный (reliable). Если сегмент TCP (TCP segment) - мельчайшая единица передачи данных TCP - потерян или поврежден, реализация TCP обнаружит это и повторно передаст необходимый сегмент.
  2. На основе логического соединения (connection-oriented). Перед началом передачи данных TCP устанавливает с удаленной машиной соединение, обмениваясь с ней служебной информацией. Этот процесс обычно называется квитированием (handshaking). B конце соединения аналогичное завершающее квитирование разрывает связь.
  3. С непрерывным потоком данных (continuous-stream). TCP обеспечивает механизм передачи, позволяющий пересылать произвольное количество байт. После установления соединения сегменты TCP обеспечивают для уровня приложений эмуляцию непрерывного потока данных.
Нетрудно понять, почему TCP используется большинством приложений Internet. TCP облегчает создание сетевого приложения, освобождая разра- ботчика от необходимости обдумывать, как разбить данные по пакетам и обеспечить коррекцию ошибок. При этом TCP требует существенных накладных расходов, так что, возможно, некоторым программистам потребуется создать свои процедуры, более эффективно обеспечивающие надежный обмен данными в соответствии с запросами конкретных приложений. Кроме того, повторная пересылка потерянных данных может быть нежелательна для данного приложения, поскольку данные могут устареть. В таких случаях альтернативой служит протокол UDP, рассмотренный в следующем разделе. Важным понятием, определяемым в протоколе TCP, является nopm (port). Порты разграничивают отдельные потоки информации, которые параллельно существуют на одной машине. Для приложений-серверов, ожидающих контактов со стороны клиентов TCP, можно организовать порт, где будет создано соединение. Понятие порта тесно связано с программной абстракцией, называемой сокетом.

Протокол UDP

UDP представляет собой альтернативу TCP, требующую меньших накладных расходов. В отличие отТСР, UDP имеет следующие характеристики:

  1. Ненадежный (unreliable). UDP не имеет ни встроенного механизма обнаружения ошибок, ни средств повторной пересылки поврежденных или потерянных данных.
  2. Без установления логического соединения (connectionless). Перед пересылкой данных UDP не устанавливает логического соединения. Информация пересылается в предположении, что принимающая сторона ее ожидает.
  3. Основанный на сообщениях (message-oriented). UDP позволяет приложениям пересылать информацию в виде сообщений, передаваемых посредством дейтаграмм (datagram), которые являются единицами передачи данных в UDP. Приложение должно самостоятельно распределить данные по отдельным дейтаграммам.
Для некоторых приложений UDP подходит лучше, чем TCP. Например, в протоколе NTP (Network Time Protocol - протокол передачи времени) потерянный пакет с инфомацией о текущем времени к моменту повторной передачи содержал бы неверные данные. Сетевая файловая система (Network File System, NFS) более эффективно обеспечивает надежность на уровне приложения, и потому использует UDP. Как и в TCP, в UDP применяется схема адресации с использованием портов, позволяющая нескольким приложениям параллельно принимать и посылать данные. В то же время порты UDP отличаются от портов TCP. Например, одно приложение может отзываться на номер 512 порта UDP, а при этом другой независимый сервис может обрабатывать порт 512, относящийся к TCP.

URL (Uniform Resource Locator -унифицированный указатель ресурсов)

Местонахождение компьютера в сети однозначно определяется 1Р-адресами, а конкретный сервис - номером порта UDP или TCP. URL же обеспечивает глобальную схему идентификации на уровне приложения. Каждый, кто использовал браузер Web, наверняка видел многочисленные URL, хотя общая форма его записи неочевидна. URL создавался для обеспечения общей формы идентификации ресурсов, причем он разрабатывался в достаточно общем виде, чтобы удовлетворить потребности приложений, которые населяли Web в течение десятилетий. Синтаксис URL достаточно гибок для того, чтобы подстроиться под протоколы, которые появятся в будущем.

Синтаксис URL

Первичным понятием URL является схема (scheme), которая обычно соответствует протоколу прикладной программы. Схемой может быть, например, http, ftp, telnet или gopher. Синтаксис остальной части URL зависит от схемы. Схема отделяется двоеточием:

название_схемы:параметры_схемы

Например, mailto:KosKV@mail.ru означает "послать сообщение пользователю KosKV на компьютер mail.ru", а ftp://KosKV@koskv.narod.ru/ означаетчает "открыть FTP-соединение с компьютером koskv.narod.ru и зарегистрироваться под именем KosKV.

Общий вид URL

Большинство URL записывается в виде

название_схемы://узел:порт/имя_файла#внутренняя_ссьшка

название_схемы - это название схемы URL, например, http, ftp или gopher; узел - доменное имя или IP-адрес компьютера; порт - номер порта, используемый данным сервисом. Поскольку большинство прикладных протоколов задают стандартный порт, этот номер и разделительное двоеточие можно опустить (кроме случаев, когда используется нестандартный номер порта). имя_файла задает запрашиваемый ресурс, который чаще всего является файлом. Этот фрагмент также может вызывать запуск определенной программы на сервере, и обычно включает путь к определенному файлу в системе, внутренняя ссьшка - это обычно именованная позиция в файле формата HTML. Именованная позиция (named anchor) позволяет URL указывать на конкретное место страницы HTML. Обычно она не используется, так что ее и разделитель # можно опустить. Следует иметь в виду, что этот общий вид является упрощением, охватывающим лишь наиболее употребительные случаи. Более полную информацию об URL можно получить из следующего ресурса: http://www.netspace.org/users/dwb/urI-guide.html

Java и URL

Java обеспечивает мощные и элегантные средства создания сетевых приложений-клиентов, позволяя при помощи сравнительно небольших программ обращаться к ресурсам Internet. Источник этих возможностей - классы URL и URLConnection из пакета java.net.

Менеджер безопасности браузера вообще запрещает апплетам открывать сетевые соединения с любыми компьютерами, кроме того, с которого поступил апплет. Это относится ко всем сетевым средствам, описанным в этой и следующих главах. Такая особенность системы безопасности существенно ограничивает сферу деятельности апплетов. На приложения таких ограничений не накладывается.

Класс URL

Этот класс позволяет создать структуру, содержащую всю необходимую информацию для доступа к сетевому ресурсу. После создания объекта URL можно обращаться к различным элементам в соответствии с общим форматом. Объект URL позволяет также получить данные с удаленного компьютера. Класс URL имеет четыре конструктора:

public URL(String spec) throws MalformedURLException;
public URL(String protocol, String host, String file)
throws MalformedURLException;
public URL(String protocol, String host, int port, String file)
throws MalformedURLException;
public URL(URL context, String spec)
throws MalformedURLException;

Первый из них, применяемый наиболее часто, создает объект URL при помощи простого описания, например:

URL myURL  =  new  URL("http://www.jahoo.com/");

Второй и третий варианты конструктора позволяют явно задать отдельные элементы URL. Последний конструктор дает возможность использовать относительный uRL. Относительный URL содержит только часть информации, остальная часть берется из URL, относительно которого адресуется ресурс. Такой способ часто применяется на страницах HTML; например, ссылка more.html означает "загрузить файл more.html с той же машины и из того же каталога, где находится текущий документ".

Примеры конструкторов:

URL firstURLObject = new URL("http://www.yahoo.com/"); 
URL secondURLObject = new URL("http","www.yahoo.com","/");
URL  thirdURLObject = new  URL("http","www.yahoo.com",80,"/");
URL fourthURLObject = new URL(firstURLObject,"text/suggest.html");

Первые три оператора создают объекты URL, ссылающиеся на домашнюю страницу Yahoo, а четвертый создает ссылку на файл "text/suggest.html" относительно сервера Yahoo (то есть http://www.jahoo.com/text/suggest.html). Все конструкторы могут возбуждать исключение MaiformedURLException, которое полезно обрабатывать. Как это делается, показано ниже в примере из листинга 1. Заметим, что после создания объекта URL уже нельзя изменить ссылку на ресурс, можно лишь создать новый объект с новой ссылкой.

Подключение к URL

Создав объект URL, можно получить некоторые интересующие нас данные. Можно выбрать один из двух путей: непосредственно читать ресурс либо по Объекту URL создать объект URLConnection. Непосредственное чтение ресурса требует меньше программного текста, но это гораздо менее гибкое решение, к тому же допускающее доступ к ресурсу только для чтения. Этого не всегда достаточно, поскольку многие службы Web дают пользователю возможность записывать информацию, которая будет затем обработана приложением-сервером. В классе URL имеется метод openstream(), возвращающий объект inputstream, посредством которого можно считать ресурс байт за байтом. Обработка данных в виде потока байтов весьма утомительна, поэтому часто бывает удобно встроить полученный объект inputstream в объект Datainputstream, который обеспечивает построчный ввод. Подобная тактика программирования часто называется использованием надстройки (decorator), поскольку Datainputstream надстраивает inputstream, обеспечивая более развитый интерфейс. В следующем фрагменте текста непосредственно из объекта URL получен объект inputstream, который затем надстраивается:

URL whiteHouse = new URL("http://www.whitehouse.gov/");
InputStream undecoratedInput = whiteHouse.openStream();
DataInputStream decoratedInput = new DataInputStream(undecoratedInput);

Другой гибкий способ подключения к ресурсу состоит в использовании метода openConnection() класса URL. Этот метод возвращает объект URLConnection, который предоставляет ряд очень мощных методов для управления доступом к используемому ресурсу.

Например, в отличие от класса URL, класс URLConnection дает возможность сформировать как inputstream, так и outputstream. Методы доступа, определенные в протоколе HTTP, включают GET, и POST. При помощи метода GET приложение просто запрашивает ресурс и затем считывает ответ сервера. Метод POST часто используется для ввода данных в приложение-сервер. При этом запрашивается ресурс путем записи данных в тело запроса HTTP, а затем считывается ответ сервера. Чтобы воспользоваться методом POST, достаточно записать данные в объект Outputstream, полученный от URLConnection, прежде, чем выполнить чтение из соответствующего inputstream. Если же сначала считать данные, будет выполнен метод GET, так что последующие запросы на запись приведут к ошибке.

В следующем фрагменте текста иллюстрируется применение объекта URLConnection для удаленного обращения к серверу. Метод POST протокола HTTP выполняется путем записи в Outputstream, надстроенный экземпляром класса Printstream. Для тестирования этих методов существует CGI-сервер на www.javasoft.com. Следующий код обращается к приложению CGI, которое просто возвращает принятые данные в обратном порядке. Данные считываются посредством надстроенного inputstream.

URL reverseURL = new URL("http://www.javasoft.com/cgi-bin/backwards");
URLConnection reverseConn = reverseURL.openConnection();
PrintStream output = new PrintStream(reverseConn.getOutputStream());
DataInputStream input = new DataInputStream(reverseConn.getInputStream());
output.println("string=TexttoReverse");
String reversedText = input.readLine();

Классы, рассчитанные на HTTP

После краткого обзора классов URL и URLConnection возникает подозрение, что методы этих классов создавались в расчете на использование протокола HTTP. Если посмотреть на полные определения этих классов, это мнение подтвердится. Хотя помимо HTTP схема URL поддерживает много других протоколов, классы URL и URLConnection рассчитаны в основном на HTTP. Это не очень существенно, поскольку из всех стандартных протоколов для Web и Internet HTTP используется наиболее широко, но все же надо иметь в виду, что многие методы этих классов применимы только при работе по протоколу HTTP.

Сокеты TCP

Сокеты были первоначально разработаны в Калифорнийском Университете в Беркли в качестве средства для облегчения сетевого программирования. Принцип сокетов, появившийся сначала в ОС UNIX, был затем использован в различных средах, в том числе в языке jаvа.

Что такое сокет

Сокет (socket) - это описатель сетевого соединения с другим приложением. Сокет TCP использует протокол TCP, наследуя все свойства этого протокола. Для создания сокета TCP необходима следующая информация:

  1. IP-адрес локальной машины
  2. Номер порта TCP, который использует приложение на локальной машине
  3. IP-адрес машины, с которой устанавливается связь
  4. Номер порта TCP, на который отзывается приложение, ожидающее устанавления связи

Сокеты часто используются в приложениях клиент-сервер: централизованная служба ждет дистанционных обращений от различных машин с запросами конкретных ресурсов и обрабатывает запросы по мере поступления. Чтобы клиенты знали, как обращаться к серверу, стандартным прикладным протоколам назначены общеизвестные порты (well-known ports). В ОС UNIX порты с номерами меньше 1024 доступны только приложениям с привилегиями суперпользователя (таковым, например, является root). По общепринятым соглашениям, все общеизвестные порты находятся в этом диапазоне, чтобы можно было держать их под контролем. Некоторые общеизвестные порты перечислены в табл. 1.

Таблица 1. Общеизвестные порты TCP и службы
Порт Служба
21 FTP
23 Telnet
25 SMTP (протокол передачи почты в Internet)
79 Finger
80 HTTP

Приложение-клиент также должно привязаться (bind) к порту, чтобы начать обмен через сокет. Поскольку связь инициируется со стороны клиента, конкретный номер порта не имеет большого значения и может быть выбран непосредственно при необходимости установить соединение. Приложения-клиенты обычно запускаются рядовыми (не привилегированными) пользователями систем UNIX, и поэтому номера портов выбираются большими 1024. Это соглашение перешло и в другие операционные системы, так что, как правило, приложениям-клиентам даются динамически назначаемые порты (dynamically-allocated ports) с номерами, большими 1024.

Поскольку два приложения могут одновременно привязаться к одному порту на одной и той же машине, сокет служит уникальным идентификатором сетевого соединения. Надо понимать, что сервер способен обслуживать двух и более клиентов благодаря тому, что клиенты будут работать на разных машинах или разных портах, и таким образом обеспечивается уникальность соединения. Такая ситуация иллюстрируется рисунком 1.

Рис. 1. К серверу может подключиться несколько клиентов, использующих разные сокеты

Классы сокетов TCP в Java

В Java имеется два стандартных класса, которые позволяют создавать сетевые приложения на основе сокетов: java.net.Socket и java.net.ServerSocket. Класс socket применяется для организации обычного двустороннего обмена данными и имеет четыре конструктора:

public Socket(String host, int port) throws UnknownHostException, IOException;
public Socket(InetAddress address, int port) throws IOExceptlon;
public Socket(String host, int port, boolean stream) throws IOException;
public Socket(InetAddress address, int port, boolean stream) throws IOException;

Первый конструктор позволяет создать сокет, задав доменное имя компьютера, с которым следует установить связь (параметр типа string), и номер порта на этом компьютере. Второй создает сокет по объекту типа inetAddress. Третий и четвертый конструкторы аналогичны двум первым, но дополнительно позволяют задать булевское значение, указывающее, должен ли для реализации сокета быть применен протокол на основе потока данных (например, TCP). По умолчанию используется TCP, но если передано значение false, будет использован ненадежный протокол на основе дейтаграмм, например UDP.

Объект класса inetAddress содержит IP-адрес компьютера в сети. У него нет конструкторов, объявленных public, но есть ряд статических методов, которые возвращают экземпляры класса inetAddress. Таким образом, объект inetAddress можно создать путем обращения к статическому методу:

try {
InetAddress remoteOP =
 InetAddress.getByName("www.microsoft.com");
InetAddress[] allRemoteIPs =

  
InetAddress.getAllByName("www.microsoft.com");
InetAddress myIP = InetAddress.getLocalHost();
} catch(UnknownHostException excpt) {
System.err.println("Unknown host: " + excpt);
// "Неизвестный хост"
}

Первый метод возвращает объект inetAddress, содержащий IP-адрес серве ра www.microsoft.com. Второй создает массив объектов inetAddress, по од ному на каждый IP-адрес, ассоциированный с www.microsoft.com. Последний метод создает inetAddress с IP-адресом локальной машины. Все они могут возбуждать исключение unknownHostException, которое обрабатывается в приведенном примере.

Класс socket имеет также методы, позволяющие читать и писать в него: getinputstream() и getOutputstream(), которые возвращают соответственно потоки ввода и вывода. Чтобы облегчить написание приложений, эти потоки обычно надстраиваются другим потоковым объектом, а именно DataInputStream и PrintStream соответственно. И getInputStream(), и getOutputStream() могут возбуждать исключение IOException, которое должно быть перехвачено и обработано.

try   {
Socket netspace = 
new   Socket{"www.netspace.org",7);
DataInputStream input = new DataInputStream(netspace.getInputStream()};
PrintStream output =
 
  
new PrintStream(netspace.getOutputStream());
} catch(UnknownHostException expt) {
System.err.println("Unknown host: " + excpt);
//   "Неизвестный хост"
System.exit(1);
}   catch{IOException excpt)    {
System.err.println("Failed  I/O:   "  +  excpt.);
//   "Ошибка  ввода/вывода"
System.exit(1);
}

Теперь, чтобы послать однострочное сообщение и считать однострочный ответ, достаточно использовать надстроенный поток:

 output,printIn("test");
String testResponse = inpit.readLine();

Завершив обмен данными через сокет, необходимо сперва закрыть объекты inputstream и outputstream, а затем уже сам сокет:

output.close();
input.close();
netspace.close ();

Чтобы создать сервер TCP, необходимо уметь обращаться с классом, который позволяет привязаться к порту и ожидать подключения клиентов. При каждом подключении будет создан экземпляр класса socket.serverSocket имеет два конструктора:

public ServerSocket(int port) throws IOException;
public ServerSocket(int port, int count) throws IOException;

Первый из них создает сокет, подключенный к указанному порту. По умолчанию, в очереди ожидания соединения может находиться до 50 клиентов. Второй конструктор дает возможность задать длину очереди.

После создания объета ServerSocket можно использовать метод accept() для ожидания подключения клиентов. Этот метод блокируется до момента подключения клиента, а затем возвращает объект Socket для связи с клиентом. Блокировка (blocking) - термин, означающий, что процедура входит во внутренний бесконечный цикл, который прерывается при наступлении определенного события. Поток выполнения программы не идет далее блокированной процедуры до ее завершения, т. e. до наступления этого события.

Следующий текст создает объект ServerSocket с портом 2222, ожидает подключения и затем открывает потоки, через которые будет производиться обмен данными с подключившимся клиентом.

try{
ServerSocket  server = new  ServerSocket(2222);
Socket  clientConn =  server.accept();
DataInputStream input = new DataInputStream(clientConn.getInputStream());
PrintStream output = new PrintStream(clientConn.getOutputStream());
} catch(IOException excpt) {
System.err.println("Failed I/O: " + excpt);
// "Ошибка ввода/вывода"
System.exit(!);
}

После завершения связи с клиентом сервер должен закрыть потоки, а затем закрыть сокет, как сказано выше.

Пример создания приложения клиент-сервер

Зная основные элементы программирования сокетов TCP, разработаем настоящее приложение: создадим клиент и сервер для получения курса акций. Клиент будет обращаться к серверу за информацией об акциях, а сервер считывать данные из файла, периодически проверяя, был ли файл обновлен, и пересылать данные клиенту.

Проектирование прикладного протокола

При данных требованиях к системе протокол содержит шесть основных шагов:

  1. Клиент подключается к серверу.
  2. Сервер в ответ посылает сообщение, указывающее на корректность данных.
  3. Клиент запрашивает данные по заданной акции.
  4. Сервер отвечает.
  5. Шаги 2 и З повторяются до окончания диалога.
  6. Сеанс связи заканчивается.

Для реализации такой схемы разработаем более подробный протокол. Сервер ожидает клиента, слушая порт 1701. Когда подключается клиент, сервер отвечает сообщением:

   +HELLO<время>

Строка<время> указывает время последнего обновления данных по акциям. Затем клиент посылает запрос. В ответ сервер выдает необходимые данные.

< pre> STOCK: <идентификаторакции> +<идентификатор акции><данные>

   <p>Здесь <идентификатор акции> 
- это данные по которой необходимы клиенту. Этот 
идентификатор является последовательностью заглавныхбукв.<данные> - строка, характеризующая состояние данной акции. Клиент может получить данные по любой из акций, послав соответствующий запрос.

Если клиент послал запрос об акции, информацией о которой сервер не располагает, сервер отвечает строкой:

-ERR  UNKNOWN   STOCK  ID

Если клиент послал неизвестную серверу команду, сервер отвечает строкой:

-ERR UNKNOWN COMMAND

После того как клиент получил всю необходимую информацию, он завершает связь, и сервер подтверждает конец соединения:

QUIT 
+ВУЕ

Следующий пример демонстрирует обмен данными в соответствии с этим протоколом. Все ответы сервера, в отличие от запросов клиента, должны начинаться с символа + или -. В данном примере клиент запрашивает информацию о трех акциях: ABC, XYZ и AAM. Сервер имеет информацию только о последних двух.

+HELLO Tue, Jul 16, 2000 09:15:13 PDT 
STOCK: ABC
-ERR UNKNOWN STOCK ID
STOCK: XYZ
+XYZ Last: 20 7/8; Change -0 1/4; Volume 60,400
STOCK: AAM
+AAM Last 35; Change 0; Volume 2,500
QUIT
+BYE

Создание приложения-клиента

Текст приведен в листинге 1.

Метод main(): запуск клиента

В первую очередь метод main () проверяет, что приложение было запущено с необходимыми параметрами командной строки. Если это не так, приложение завершает работу. При нормальном запуске создается объект stockQuoteclient, которому передается массив args, и затем вызывается его метод printQuotes() с указанием направить данные на стандартный вывод.

Конструктор StockQuoteClient

Задачи конструктора: инициализировать структуры данных, подсоединиться к серверу, получить от него данные об акциях и завершить связь. Конструктор создает два массива: один для идентификаторов акций, другой остается неинициализированным до тех пор, когда в него будут помещены данные о каждой из акций.

Для установления связи с сервером в конструкторе используется метод contactServer(), который возвращает полученную при этом от сервера строку. Если связь установлена правильно, строка должна содержать время, указывающее актуальность данных. Конструктор разбирает строку, выделяя время, затем получаетданные посредством метода getQuotes() и завершает связь методом quitServer().

Метод contactServer(): установление связи

Как и в предыдущих примерах, метод открывает сокет для подключения к серверу. Затем создаются два потока для обмена данными. Наконец, он получает первый ответ сервера (например, +HELLO<время>) и возвращает ее в объекте типа string.

Метод getQuotes(): получение данных

Этот метод производит запрос информации о каждой из акций, идентификаторы которых заданы при запуске программы. Они хранятся в массиве stockIDs. Сначала вызывается короткий метод connectOK(), который просто убеждается, что объект socket и потоки не равны null. Затем просматривается массив stockIDs, и каждый его элемент посылается на сервер в составе запроса. Ответ считывается, и из него выделяется информация об акциях. Данные по каждой из акций сохраняются в соответствующих элементах массива stockinfo. Запросив информацию о каждой из акций, метод getQuotes() заканчивает работу.

Метод quitServer(): завершение связи

Это метод завершает связь с сервером, сперва посылая команду QUIT, если связь еще действует. Затем призводятся действия, необходимые при завершении связи через сокет: закрываются потоки, а затем и объект socket.

Метод printQuotes(): вывод информации

Метод выводит данные в заданный объект Printstream, например, в System.out. Он просматривает массив идентификаторов акций stockIDs и печатает значение соответствующего элемента массива stockInfo.

Разработка сервера курса акций

Приложение-сервер немного сложнее, чем клиент, пользующийся его услугами. Сервер состоит из двух классов. Первый загружает данные об акциях и ожидает подключения клиента. Когда клиент подключился, этот класс создает экземпляр второго класса, который реализует интерфейс Runnable, и передает созданному экземпляру объект socket, связанный с клиентом.

Этот второй объект - обработчик (handler) - работает в отдельном потоке выполнения, что позволяет серверу вернуться к ожиданию других клиентов вместо того, чтобы работать только с одним. Обработчик - это объект, который поддерживает связь с клиентом.

Очень распространенная схема создания сетевого сервера: использование многопотокового сервера для одновременного обслуживания нескольких клиентов. Текст этого приложения приведен в листинге 2.

Запуск сервера

Метод main() позволяет запускать сервер как приложение. Здесь создается объект StockQuoteServer. Затем исползуется метод serveQuotes( ) , чтобы приступить к ожиданию обращений со стороны клиентов. Конструктор сначала вызывает метод LoadQuotes( ) , который загружает данные об акциях. Конструктор удостоверяется, что эта процедура выполнена успешно, а в противном случает завершает приложение. В случае успеха он создает объект serverSocket на порту 1701. Теперь сервер ожидает обращений от клиентов.

Метод loadQuotes(): чтение данных об акциях

Этот метод использует объект java.io.File для получения объекта DataInputStream, При момощи которого считывается файл stockquotes.txt. loadQuotes( ) анализирует каждую вводимую строку, предполагая, что в каждой строке находятся данные об одной акции в виде:

< pre> <идентификатор акции><данные>


Метод подсчитывает количество строк, а данные помещает в объект StringBuffer. Он также сохраняет время последней модификации файла, полученное методом lastModified() класса File. Благодаря этому сервер может отслеживать момент последнего обновления данных. Метод loadQuotes() создает массив достаточного размера, чтобы хранить каждую строку файла в виде отдельного элемента типа string, a затем просматривает stringBuffer и помещает каждую строку в отдельный элемент массива. В объекте java.util.Date сохраняется текущая дата, что дает серверу возможность сообщать клиентам, когда были считаны данные об акциях.

В реальном приложении этот метод должен был бы получать данные из настоящего источника информации о курсах акций, но, поскольку в нашем распоряжении нет такой службы Internet, для примера подойдет обычный файл.

Метод serveQuotes(): ответ на обращения клиентов

В этом методе работает бесконечный цикл, который по мере обращения клиентов производит подключения. Метод блокируется при вызове метода accept() класса ServerSocket, который ожидает обращения клиента. Когда это происходит, проверяется, обновлялся ли файл с данными после последнего считывания. Если да, вызывается метод loadQoutes() для повторного считывания данных.

Затем serveQuotes() создает экземпляр класса StockQuoteHandier, передавая ему объект Socket, созданный при подключении клиента, и массив данных об акциях. Этот объект помещается в объект-поток Thread, после чего поток запускается на выполнение. Закончив эти действия, метод serveQuotes() возвращается к началу цикла, где снова ожидает обращений от новых клиентов.

Создание объекта StockQuotesHandler

Этот класс реализует интерфейс Runnable, так что он может работать в отдельном потоке выполнения. Конструктор просто присваивает переменным экземпляра значения, полученные в качестве параметров.

Метод run(): реализация обмена данными

Это метод открывает два потока для чтения данных от клиента и передачи данных ему. Поскольку все команды должны быть набраны заглавными буквами, метод переводит запрос клиента в верхний регистр, а затем сравнивает с двумя возможными командами STOCK: и QUIT.

Если принята команда STOCK:, предполагается, что остальная часть запроса является идентификатором акции. Выделив эту часть методом substring(), метод run() пересылает идентификатор методу getQuote(), чтобы получить соответствующие данные. getQuote() - несложный метод, который рассматривает данные, полученные из файла, в поисках заданного идентификатора. Если таковой найден, возвращается соответствующая строка. Если нет, возвращается сообщение об ошибке. Метод run() пересылает эту информацию клиенту.

Если получена команда QUIT, сервер посылает ответ +BYE и выходит из цикла. Затем он завершает связь, закрывая потоки ввода/вывода и объект socket. После этого метод run() заканчивает работу, давая возможность завершиться потоку, в котором он выполнялся.

Если запрос не соответствует ни одной из этих команд, сервер посылает сообщения об ошибке и ожидает следующей команды.

Запуск клиента и сервера

Для этого необходимо откомпилировать оба примера при помощи javac. Затем следует создать файл с информацией об акциях stockquotes.txt в требуемом формате, как указано в тексте сервера. Теперь можно запустить сервер интрепретатором Java, и он будет работать, пока выполнение не будет прервано системой.

Достаточно написать в командной строке

java <путь>StockQuoteServer 

где путь - место на жестком диске где находятся файлы сервера

Наконец, можно запустить приложение-клиент и посмотреть, как отвечает сервер. Можно запускать клиента с одним или более идентификаторами акций, которые есть в файле данных. Затем можно изменить файл данных и снова запустить приложение-клиент; оно покажет, что данные изменились. Например

java <путь>StockQuoteClient  <имя сервера> <имя акции>

где <имя сервера> - сетевой адрес, если и клиент и сервер запускаются на одном компьютере достаточно указать текущий TCP/IP адрес данного компьютера.

Назад Содержание Далее

Hosted by uCoz