Получение текстового представления объекта
Метод toString возвращает текстовую строку, представляющую объект класса File:
public String toString();
Получение значений параметров аплета
Для получения значения заданного параметра найденного аплета вы можете воспользоваться методом getParameter:
String sParameter = currentApplet. GetParameter(“name”);
Здесь мы получаем значение параметра с именем NAME.
Получение значения хэш-кода
Метод hashCode возвращает значение хэш-кода, соответствующего объекту File:
public int hashCode();
Потоки в оперативной памяти
Операционные системы Microsoft Windows 95 и Microsoft Windows NT предоставляют возможность для программиста работать с оперативной памятью как с файлом. Это очень удобно во многих случаях, в частности, файлы, отображаемые на память, можно использовать для передачи данных между одновременно работающими задачами и процессами. Подробнее об этом вы можете прочитать в 27 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows NT для программиста. Часть 2”.
При создании приложений и аплетов Java вы также можете работать с объектами оперативной памяти, как с файлами, а точнее говоря, как с потоками. Так как аплетам запрещено обращаться к файлам, расположенным на локальном диске компьютера, при небходимости создания временных потоков ввода или вывода последние могут быть размещены в оперативной памяти.
Ранее мы отмечали, что в библиотеке классов Java есть три класса, специально предназначенных для создания потоков в оперативной памяти. Это классы ByteArrayOutputStream, ByteArrayInputStream и StringBufferInputStream.
Приложение Audio
Приложение Audio демонстрирует использование интерфейса AudioClip. В его окне (рис. 5.1) имеются три кнопки с названиями Play, Loop и Stop.
Рис. 5.1. Окно аплета Audio
Сразу после запуска аплета кнопка Stop находится в заблокированном состоянии. Если нажать кнопку Play или Loop, начнется, соответственно, однократное проигрывание или проигрывание в цикле файла с именем kaas.au, распложенного в том же каталоге, что и двоичный файл аплета Audio.
Когда начинается проигрывание звукового файла, кнопка Stop разблокируется, что позволяет остановить проигрывание.
Приложение CallCGI
В 29 томе “Библиотеки системного программиста” мы рассказывали о том, как с помощью расширений сервера Web, выполненных на основе интерфейса CGI и ISAPI можно обрабатывать данные из форм, расположенных в документах HTML. В частности, мы привели там исходные тексты программы controls.exe (составленной на языке программирования С), которая динамически создавала и отображала данные, введенные в форме. Внешний вид этой формы показан на рис. 3.6, воспроизведенном нами из указанного тома.
Рис. 3.6. Форма для ввода данных
Программа CGI controls.exe получала данные, введенные пользователем в этой форме, после чего динамически создавала документ HTML, в котором отображала состояние переменных серды, полученные данные в исходном и раскодированном виде, а также список значений полей (рис. 3.7).
Рис. 3.7. Документ HTML, сформрованный динамически программой CGI control.exe
Создавая приложение CallCGI, мы поставили перед собой задачу заменить форму приложением Java, которое вводит с клавиатуры текстовую строку полей и передает ее программе CGI controls.exe. Содержимое динамически сформированного программой CGI документа HTML приложение CallCGI отображает в своем консольном окне, как это показано на рис. 3.8.
Рис. 3.8. Отображение в окне приложения Java содержимого документа HTML, полученного от программы CGI
Приложение CDRotation
Задача отображения видеофильмов в окне Java настолько важна, что Microsoft включил в Visual J++ специальные средства для создания шаблона исходных текстов аплета с анимацией.
Если на третьем шаге системы автоматизированной создания исходных текстов аплетов Java Applet Wizard включить переключатель Yes в поле Would you like your applet to be multi-threaded, а также переключатель Yes в поле Would you like support for animation (рис. 4.3), для вас будут созданы исходные тексты аплета, в окне которого находится изображение земного шара, вращающегося вдоль вертикальной оси.
Рис. 4.3. Включение исходного текста для работы с анимацией в создаваемый аплет
Все, что вам остается сделать, это изменить созданные для вас исходные тексты таким образом, чтобы они соответствовали вашим потребностям. Именно так мы создали исходные тексты приложения CDRotation, в окне которого изображается вращающийся компакт-диск.
Когда будете запускать приложение CDRotation, обратите внимание, что в левом верхнем углу каждого кадра отображается его порядковый номер. Этот номер не нарисован в файлах кадров, а надписывается приложением после рисования очередного кадра. Такое невозможно, если располагать в документе HTML файл AVI или многосекционный файл GIF.
Приложение Combi
Приложение Combi имеет описанную выше структуру и потому способно работать как автономно, так и под управлением навигатора Internet.
После запуска приложение определяет и отображает в своем окне текущий режим работы, а также некоторую информацию о среде выполнения.
На рис. 7.1 показан внешний вид окна этого приложения, когда оно работает автономно.
Рис. 7.1. Окно автономно работающего приложения Combi
Приложение определяет платформу, на которой оно работает, название и версию операционной системы, каталог, в котором находится двоичный модуль приложения, и каталог, в котором расположены классы Java. Кроме того, сразу после запуска автономное приложение Combi, если оно выполняется в среде операционной системы Windows 95 или Windows NT, запускает программу калькулятора calc.exe, входящую в комплект этой операционной системы.
Когда приложение Combi работает как аплет, встроенный в документ HTML, его окно имеет вид, показанный на рис. 7.2.
Рис. 7.2. Окно приложения Combi, встроенного в документ HTML
Из-за ограничений, которые накладываются на аплеты, в данном случае нашему приложению доступна не вся информация о среде выполнения.
Кроме того, будучи загруженным с сервера Web, приложение не сможет запустить программу калькулятора, так как эта возможность для аплетов заблокирована. Однако при работе аплета в среде Microsoft Visual J++ запуск приложений (а также другие действия) разрешается, поэтому на экране появится окно клаькулятора.
Приложение DirectFileAccess
Для иллюстрации способов работы с классом RandomAccessFile мы подготовили приложение DirectFileAccess, в котором создается небольшая база данных. Эта база данных состоит из двух файлов: файла данных и файла индекса.
В файле данных хранятся записи, сосотящие из двух полей - текстового и числового. Текстовое поле с названием name хранит строки, закрытые смиволами конца строки “\r\n”, а числовое с названием account - значения типа int.
Дамп файла данных, создаваемого при первом запуске приложения DirectFileAccess, приведен на рис. 2.11.
Рис. 2.11. Дамп файла данных
Из этого дампа видно, что после первого запуска приложения в файле данных имеются следующие записи:
Номер записи | Смещение в файле данных | Поле name | Поле account | ||||
0 | 0 | Ivanov | 1000 | ||||
1 | 12 | Petrov | 2000 | ||||
2 | 24 | Sidoroff | 3000 |
При последующих запусках каждый раз в файл данных будут добавляться приведенные выше записи.
Так как поле name имеет переменную длину, для обеспечения возможности прямого доступа к записи по ее номеру необходимо где-то хранить смещения всех записей. Мы это делаем в файле индексов, дамп которого на момент после первого запуска приложения представлен на рис. 2.12.
Рис. 2.12. Дамп файла индекса
Файл индексов хранит 8-байтовые смещения записей файла данных в формате long. Зная номер записи, можнор легко вычислить смещение в файле индексов, по которому хранится смещение нужной записи в файле данных. Если извлечь это смещение, то можно выполнить позиционирование в файле данных с целью чтения нужной записи, что и делает наше приложение.
После добавления трех записей в базу данных приложение извлекает три записи в обратном порядке, то есть сначала запись с номером 2, затем с номером 1, и, наконец, с номером 0. Извлеченные записи отображаются в консольном окне приложения (рис. 2.13).
Рис. 2.13. Отображение записей базы данных приложением DirectFileAccess
Приложение DirList
В приложении DirList мы используем класс File для получения списка всех файлов и каталогов, расположенных в заданном каталоге.
После запуска приложение DirList предлагает ввести путь к каталогу и маску для имени файла (рис. 2.9).
Рис. 2.9. Работа приложения DirList
Если вместо маски задать символ ‘*’, как мы сделали это на рис. 2.9, приложение выведет полный список файлов и каталогов, выделив каталоги прописными буквами. В том случае, если будет задна другая маска, в окне появятся только такие файлы, которые содержат эту маску как подстроку (рис. 2.10).
Рис. 2.10. Просмотр содержимого каталога c:\dos с маской com
Приложение DrawImageObserver
Приложение DrawImageObserver рисует в своем окне изображение фона, такое же, как и в предыдущем приложении. При этом для ожидания процесса загрузки изображения перед рисованием фона мы используем интерфейс ImageObserver.
Если бы изображение фона, имеющее значительный размер, рисовалось без ожидания его загрузки, оно появлялось бы в окне аплета по частям. Наше приложение рисует его сразу, так как дожидается полной загрузки.
Приложение FileInfo
В приложении FileInfo мы демонстрируем способы работы с классом File.
После запуска наше приложение предлагает ввести путь к файлу (рис.2.8). Вы также можете ввести путь к каталогу.
Рис. 2.8. Работа приложения FileInfo
Далее приложение создает объект класса File, передавая введенную строку соответствующему конструктору, а затем, если указанный файл или каталог существует, отображает его параметры.
На рис. 2.8 видно, что в ответ на прглашение был введен путь к файлу autoexec.bat. Приложение вывело родительский каталог, в котором находится этот файл (диск c:), длину файла в байтах (235 байт), а также сообщило нам, что для файла разрешены операции чтения и записи.
Приложение HorzScroll
Практически в каждой книге, посвященной программированию на языке Java, вы найдете исходные тексты приложения, выполняющие горизонтальную прокрутку строки текста. Эффект бегущей строки достаточно широко используется для привлечения внимания пользователя.
Реализация эффекта бегущей его достаточно проста. Аплет создает задачу, которая периодически перерисовывает окно, вызывая метод repaint. Метод paint отображает строку в окне, каждый раз изменяя ее начальные координаты для получения эффекта сдвига.
Мы уверены, что вы сами легко справитесь с задачей создания бегущей строки, поэтому предложим вам кое-что иное. А именно, мы подготовили приложение, которое выписывает текстовую строку в своем окне последовательно буква за буквой. Когда вся строка будет нарисована, окно очищается и затем процесс возобновляется вновь (рис. 1.5).
Рис. 1.5. Окно аплета HorzScroll
В чем сложность создания такого аплета?
Главным образом в том, что все символы пропорционального шрифта имеют разную ширину, и вам нужно правильно вычислять координаты каждого нового символа.
Приложение ImageDraw
Теперь мы знаем все необходимое, чтобы приступить к рисованию растровых изображений в окне аплета. Приложение ImageDraw, о котором мы сейчас расскажем, рисует в своем окне четыре изображения: два изображения флоппи-диска и два - компакт-диска (рис. 4.1).
Рис. 4.1. Рисование растровых изображений в окне приложения ImageDraw
В верхнем левом углу окна аплета нарисованы исходные изображения. Справа вверху изображение компакт-диска нарисовано растянутым по горизонтали. Нижнюю часть окна аплета занимает пропорционально увеличенный рисунок флоппи-диска.
Приложение ImageDrawWait
В приложении ImageDrawWait мы демонстрируем использование класса MediaTracker для ожидания процесса завершения загрузки изображений, показанных на рис. 4.1. Эти изображения рисуются приложением ImageDrawWait не на белом фоне, а на фоне другого изображения.
Файл изображение фона имеет значительный размер, поэтому без применения техники ожидания завершения загрузки он будет появляться в окне по частям (именно так работает метод imageUpdate, определенный в классе Applet), а затем будут нарисованы остальные изображения. Наше приложение сначала дожидается завершения загрузки всех изображений, а затем рисует их, поэтому все изображения появятся практически одновременно и целиком.
В процессе загрузки окно приложения ImageDrawWait отображает сообщение о ходе загрузки (рис. 4.2).
Рис. 4.2. Сообщение о ходе процесса загрузки изображений
После загрузки в окне аплета рисуется изображение фона
Приложение InetAddressDemo
Приложение InetAddressDemo отображает имя и адрес IP локального узла, а затем запрашивает имя удаленного узла. Еси такой узел существует, для него определяется и отображается на консоли список адресов IP (рис.3.1).
Рис. 3.1. Работа приложения InetAddressDemo
Если же указано имя несуществующего узла, возникает исключение UnknownHostException, о чем на консоль выводится сообщение.
Приложение Inspector
Аплет Inspector располагается в одном документе HTML с приложениями Audio и Rectangles, которые уже были описаны в нашей книге (рис. 6.1).
Рис. 6.1. Документ HTML, в котором расположены три аплета - Inspector, Audio и Rectangles
В верхней части окна аплета Inspector расположены кнопки, дублирующие одноименные кнопки аплета Audio. С их помощью можно заставить аплет Audio проигрывать файл в однократном режиме или в цикле, а также остановить проигрывание.
В нижней части аплета Inspector отображается список имен аплетов, найденных в текущем документе HTML. Для списка используются первые строки описаний аплетов, полученные методом getAppletInfo.
Приложение MemStream
Аплет MemStream создает два потока в оперативной памяти - выходной и входной. Вначале во время инициализации метод init создает выходной поток и записывает в него текстовую строку “Hello, Java!”. Содержимое этого потока затем копируется в массив, и на базе этого массива создается входной поток.
Во время рисования окна аплета метод paint создает из только что упомянутого массива входной поток, читает из этого потока одну строку и отображает ее в окне аплета.
Аплет не может работать с обычными локальными файлами, поэтому для выполнения описанных выше действий необходимы потоки в оперативной памяти.
Приложение MultiTask
Система автоматизированной разработки аплетов Microsoft Visual J++ позволяет указать, что создаваемый аплет будет мультизадачным. Для этого на третьем шаге в поле Would you like your applet to be multi-threaded следует включить переключатель Yes (рис. 1.1).
Рис. 1.1. Добавление мультизадачности в создаваемый аплет
Включив указанный переключатель, выключите пока переключатель Would you like support for animation - анимацией мы займемся немного позже.
После завершения процедуры создания заготовки мультизадачного аплета выполните трансляцию и запустите аплет на выполнение. На экране появится текстовая строка с числом, значение которого быстро изменяется случайным образом.
Поставим теперь перед собой другую цель - создать аплет, запускающий на выполнение сразу две задачи. Наше следующее приложение MultiTask2 запускает одну задачу для рисования прямоугольников, а другую - для рисования закрашенных эллипсов (рис. 1.3).
Рис. 1.3. Окно аплета MultiTask2
Расположение, размеры и цвет прямоугольников и эллипсов выбирается случайным образом.
Приложение Rectangles
В предыдущем приложении задача выполняла периодическую перерисовку окна аплета, вызывая из метода run метод repaint. Такая методика приводит к сильному мерцанию окна аплета и поэтому во многих случаях нежелательна. Приложение Rectangles, постоянно отображающее в своем окне прямоугольники случайного размера, расположения и цвета (рис.1.2), использует другой способ. Оно запускает задачу, которая рисует в окне аплета непосредственно.
Рис. 1.2. Окно аплета Rectangles
В результате в каждый момент времени перерисовывается только часть окна аплета и мерцание отсутствует.
Приложение Scroller
Приложения, рассмотренные выше, демонстрируют различные методы реализации мультизадачности в Java, но едва ли вы найдете для них применение (разве лишь гипнотизирование пользователей). Ниже мы приведем исходные тексты приложения Scroller, которое имеет некоторую практическую ценность.
В своем окне приложение Scroller показывает строки текста, медленно всплывающие вверх (рис. 1.4). Вы можете использовать этот аплет для размещения рекламной информации на своем сервере. Всплывающий текст (как и всякое движение на сранице сервера Web) будет привлекать внимание пользователя.
Рис. 1.4. Окно аплета Scroller
Строки (в количестве 6 штук) можно задавать в параметрах аплета, редактируя текст документа HTML, содержащего этот аплет. Первая строка выделяется красным цветом.
Приложение ShowChart
Попробуем теперь на практике применить технологию передачи файлов из каталога сервера Web в аплет для локальной обработки. Наше следующее приложение с названием ShowChart получает небольшой текстовый файл с исходными данными для построения круговой диаграммы, содержимое которого представлено ниже:
10,20,5,35,11,10,3,6,80,10,20,5,35,11,10,3,6,80
В этом файле находятся численные значения углов для отдельных секторов диаграммы, причем сумма этих значений равна 360 градусам. Наш аплет принимает этот файл через сеть и рисует круговую диаграмму, показанную на рис. 3.2.
Рис. 3.2. Круговая диаграмма, построенная на базе исходных данных, полученных через сеть
Файл исходных данных занимает всего 49 байт, поэтому он передается по сети очень быстро. Если бы мы передавали графическое изображение этой диаграммы, статическое или динамическое, подготовленное, например, расширением сервера CGI или ISAPI, объем передаваемых по сети данных был бы намного больше.
Приложение Standard
Приложение Standard демонстрирует способы работы со стандартными потоками Java. Это консольное приложение, а не аплет.
При запуске приложение Standard выводит строку Hello, Java! И приглашение для ввода строки (рис. 2.4).
Рис. 2.4. Консольное окно приложения Standard
Если ввести любую текстовую строку и нажать клавишу <Enter>, введенная строка появится на консоли. Далее появится сообщение о том, что для завершения работы приложения нужно снова нажать клавишу <Enter>.
Приложение StreamDemo
Приложение StreamDemo было создано нами специально для того чтобы продемонстрировать наиболее распространенный способ работы с файлами через буферизованные форматированные потоки.
Действия, выполняемые этим приложением, достаточно просты. Вначале приложение выводит на консоль приглашение для ввода строки. Затем введенная строка записывается в файл через выходной буферизованный форматированный поток. После этого созданный файл читается через входной буферизованный поток. Его содержимое (введенная ранее строка) отображается на экране.
Приложение StreamToken
В приложении StreamToken мы демонстрируем использование класса StreamTokenizer для разбора входного потока.
Вначале приложение запрашивает у пользователя строку для разбора, записывая ее в файл. Затем этот файл открывается для чтения буферизованным потоком и разбирается на составные элементы. Каждый такой элемент выводится в отдельной строке, как это показано на рис. 2.6.
Рис. 2.6. Разбор входного потока в приложении StreamToken
Обратите внимание, что в процессе разбора значение 3.14 было воспринято как числовое, а 3,14 - нет. Это потому, что при настройке разборщика мы указали, что символ ‘.’ является обычным.
Приложение StringToken
Приложение StringToken получает одну строку из стандартного потока ввода и выполняет ее разбор с помощью класса StringTokenizer. Отдельные элементы строки выводятся на консоль в столбик (рис. 2.7).
Рис. 2.7. Разбор строки в приложении StringToken
В качестве разделителя заданы символы ",.; ", то есть запятая, точка, точка с запятой и пробел.
Приложение Synchro
Для иллюстрации способа синхронизации задач с помощью методов wait и notify мы подготовили приложение Synchro. Внешне окно аплета этого приложения выглядит точно так же, как и окно аплета Rectangles (рис. 1.2).
Напомним, что приложение Rectangles постоянно рисует закрашенные прямоугольники случайного размера, расположения и цвета, для чего в методе run организован бесконечный цикл с задержкой.
Приложение Synchro решает ту же самую задачу немного другим способом. Оно создает две задачи. Первая задача рисует в цикле прямоугольники, однако вместо задержки она ожидает извещение от другой задачи, вызывая для этого функцию wait. Вторая задача, опять же в цикле, создает извещения, вызывая с задержкой метод notify. Таким образом, вторая задача как бы управляет работой первой задачи.
Приложение URLDemo
В качестве практического примера применения класса URL мы создали приложение URLDemo. Это приложение вводит с консоли адрес URL текстового или двоичного файла, расположенного на сервере Web и создает для этого файла входной поток. С использованием данного потока приложение копирует файл на локальный диск компьютера в текущий каталог.
Приложения DatagramServer и DatagramClient
Приложения DatagramServer и DatagramClient иллюстрируют применение датаграммных сокетов для передачи данных от нескольких копий одного и того же клиента одному серверу с известным адресом и номером порта.
Клиентские приложения посылают серверу строки, которые пользователь вводит с клавиатуры. Сервер принимает эти строки, отображая их в своем консольном окне вместе с номером порта клиента (рис. 3.4).
Рис. 3.4. Передача данных между приложениями DatagramClient и DatagramServer через датаграммный сокет
Когда с консоли клиента будет введена строка “quit”, этот клиент и сервер завершает свою работу. Работа остальных клиентов также может быть завершена подобным образом, причем независимо от того, работает сервер, или нет.
Наши клиенты не получают от сервера никакого подтверждения в ответ на переданные ему пакеты. Вы можете изменить программу клиента, добавив такую возможность. Однако учтите, что так как датаграммные сокеты не гарантируют доставки пакетов, ожидание ответа может продлиться бесконечно долго.
Чтобы избежать этого, ваше приложение должно выполнять работу с сервером в отдельной задаче, причем главная задача должна ждать ответ ограниченное время. Все что вам нужно, чтобы реализовать работу подобным образом, вы найдете в первой главе нашей книги, посвященной мультизадачности в приложениях Java.
Приложения SocketServ и SocketClient
В качестве примера мы приведем исходные тексты двух приложений Java, работающих с потоковыми сокетами. Одно из этих приложений называется SocketServ и выполняет роль сервера, второе называется SocketClient и служит клиентом.
Приложение SocketServ выводит на консоль строку “Socket Server Application” и затем переходит в состояние ожидания соединения с клиентским приложением SocketClient.
Приложение SocketClient устанавливает соединение с сервером SocketServ, используя потоковый сокет с номером 9999 (этот номер выбран нами произвольно). Далее клиентское приложение выводит на свою консоль приглашение для ввода строк. Введенные строки отображаются на консоли и передаются серверному приложению. Сервер, получив строку, отображает ее в своем окне и посылает обратно клиенту. Клиент выводит полученную от сервера строку на консоли.
Когда пользователь вводит строку “quit”, цикл ввода и передачи строк завершается.
Весь процесс показан на рис. 3.3.
Рис. 3.3. Передача данных между приложениями SocketClient и SocketServ через потоковый сокет
Здесь в окне клиентского приложения мы ввели несколько строк, причем последняя строка была строкой “quit”, завершившая работу приложений.
Применение интерфейса ImageObserver
Второй способ ожидания завершения процесса загрузки изображений связан с интерфейсом ImageObserver. Определение этого интерфейса не занимает много места, поэтому мы приведем его полностью:
public interface java.awt.image.ImageObserver
{
// -------------------------------------------------------
// Биты флагов для параметра infoflags метода imageUpdate
// -------------------------------------------------------
public final static int ABORT;
public final static int ALLBITS;
public final static int ERROR;
public final static int FRAMEBITS;
public final static int HEIGHT;
public final static int PROPERTIES;
public final static int SOMEBITS;
public final static int WIDTH;
// -------------------------------------------------------
// Метод imageUpdate
// -------------------------------------------------------
public abstract boolean
imageUpdate(Image img, int infoflags, int x, int y,
int width, int height);
}
Как видите, в интерфейсе ImageObserver определен единственный метод imageUpdate и набор битовых флагов для этого метода.
Класс Component, от которого происходит класс Applet, реализует интерфейс ImageObserver:
public abstract class java.awt.Component
extends java.lang.Object
implements java.awt.image.ImageObserver
{
. . .
}
Этот интерфейс используется для отслеживания процесса загрузки и перерисовки изображений и других компонент, расположенных внутри компонента. В частности, он используется для отслеживания загрузки и рисования растровых изображений в окне аплета, чем мы и воспользуемся.
В процессе загрузки вызывается метод imageUpdate, поэтому чтобы отслеживать загрузку изображений, наш аплет должен переопределить этот метод.
Процедура ожидания загрузки изображений достаточно проста.
Прежде всего, аплет должен передать в последнем параметре методу drawImage ссылку на интерфейс ImageObserver, который будет применяться для отслеживания процесса загрузки:
g.drawImage(Img, x, y, width, height, this);
Здесь в качестве ссылки на интерфейс ImageObserver мы передали значение this. При этом будет применен интерфейс нашего аплета. Соответственно, нам нужно определить в классе аплета метод imageUpdate, который будет вызываться в процессе загрузки изображений.
Ниже мы привели возможный вариант реализации этого метода, который позже будет описан в деталях:
public boolean imageUpdate(Image img, int flags,
int x, int y, int w, int h)
{
// Проверяем, все ли биты изображения загружены
fAllLoaded = ((flags & ALLBITS) != 0);
// Если все, перерисовываем окно
if(fAllLoaded)
repaint();
// Если все биты загружены, дальнейшие вызовы
// метода imageUpdate не нужны
return !fAllLoaded;
}
Через первый параметр img методу imageUpdate передается ссылка на изображение, загрузка которого отслеживается.
Параметр flags отражает состояние процесса загрузки.
Через остальные параметры x, y, w и h передаются, соответственно, координаты и размеры изображения.
Основное, что должен делать метод imageUpdate для отслеживания процесса загрузки - это проверять флаги flags, дожидаясь установки нужных флагов.
Флаги определены следующим образом:
public final static int WIDTH;
public final static int HEIGHT = 2;
public final static int PROPERTIES = 4;
public final static int SOMEBITS = 8;
public final static int FRAMEBITS = 16;
public final static int ALLBITS = 32;
public final static int ERROR = 64;
public final static int ABORT = 128;
Ниже мы привели краткое описание перечисленных выше флагов.
Флаг |
Описание |
WIDTH |
Изображение загружено настолько, что стала доступна его ширина. Значение ширины изображения можно получить из параметра w метода imageUpdate |
HEIGHT |
Аналогично предыдущему, но для высоты изображения. Высоту изображения можно получить из параметра h метода imageUpdate |
PROPERTIES |
Стали доступны свойства изображения, которые можно получить методом getProperty класса Image. В нашей книге мы опустили описание этого метода |
SOMEBITS |
Стали доступны биты изображения для рисования в масштабе. Через параметры x, y, h и w передаются координаты и размеры прямоугольной области, которая ограничивает загруженную часть изображения |
FRAMEBITS |
Загружен очередной фрейм изображения, состоящего из нескольких фреймов. Параметры x, y, h и w следует игнорировать |
ALLBITS |
Изображение загружено полностью. Параметры x, y, h и w следует игнорировать |
ERROR |
При загрузке произошла ошибка |
ABORT |
Загрузка изображения была прервана или отменена |
Если вам нужно только дождаться завершения процесса загрузки, достаточно использовать флаг ALLBITS. Для проверки ошибок воспользуйтесь флагами ERROR и ABORT.
Применение класса MediaTracker
Для того чтобы выполнить ожидание загрузки нескольких изображений, проще воспользоваться классом MediaTracker, а не интерфейсом ImageObserver.
Как это сделать?
Обычно метод init аплета создает объект класса MediaTracker с помощью конструктора и добавляет в него все изображения, загрузки которых необходимо дождаться.
Применение мультизадачности для анимации
Одно из наиболее распространенных применений аплетов - это создание анимационных эффектов типа бегущей строки, мерцающих огней или аналогичных, привлекающих внимание пользователя. Для того чтобы достичь такого эффекта, необходим какой либо механизм, позволяющий выполнять перерисовку всего окна аплета или его части периодически с заданным временным интервалом.
Работа аплетов, так же как и обычных приложений операционной системы Microsoft Windows, основана на обработке событий. Для классического приложения Microsoft Windows событие - это приход сообщения в функцию окна. Основной класс аплета обрабатывает события, переопределяя те или иные методы базового класса Applet.
Проблема с периодическим обновлением окна аплета возникает из-за того, что в языке Java не предусмотрено никакого механизма для создания генератора событий, способного вызывать какой-либо метод класса аплета с заданным интервалом времени. Вы не можете поступить так, как поступали в этой ситуации, разрабатывая обычные приложения Microsoft Windows - создать таймер и организовать обработку периодически поступающих от него сообщений WM_TIMER.
Напомним, что перерисовка окна аплета выполняется методом paint, который вызывается виртуальной машиной Java асинхронно по отношению к выполнению другого кода аплета.
Можно ли воспользоваться методом paint для периодической перерисовки окна аплета, организовав в нем, например, бесконечный цикл с задержкой?
К сожалению, так поступать ни в коем случае нельзя. Метод paint после перерисовки окна аплета должен сразу возвратить управление, иначе работа аплета будет заблокирована.
Единственный выход из создавшейся ситуации - создание задачи (или нескльких задач), которые будут выполнять рисование в окне аплета асинхронно по отношению к работе кода аплета. Например, вы можете создать задачу, которая периодически обновляет окно аплета, вызывая для этого метод repaint, или рисовать из задачи непосредственно в окне аплета, получив предварительно для этого окна контекст отображения. В примерах аплетов, приведенных в нашей книге, мы будем использовать оба способа.
Принудительный сброс буферов
Еще один важный момент связан с буферизованными потоками. Как мы уже говорили, буферизация ускоряет работу приложений с потоками, так как при ее использовании сокращается количество обращений к системе ввода/вывода. Вы можете постепенно в течении дня добавлять в поток данные по одному байту, и только к вечеру эти данные будут физически записаны в файл на диске.
Во многих случаях, однако, приложение должно, не отказываясь совсем от буферизации, выполнять принудительную запись буферов в файл. Это можно сделать с помощью метода flush.
Приоритеты задач в приложениях Java
Если процесс создал несколько задач, то все они выполняются параллельно, причем время центрального процессора (или нескольких центральных процессоров в мультипроцессорных системах) распределяется между этими задачами.
Распределением времени центрального процессора занимается специальный модуль операционной системы - планировщик. Планировщик по очереди передает управление отдельным задачам, так что даже в однопроцессорной системе создается полная иллюзия параллельной работы запущенных задач.
Распределение времени выполняется по прерываниям системного таймера. Поэтому каждой задаче дается определенный интервал времени, в течении которого она находится в активном состоянии.
Заметим, что распределение времени выполняется для задач, а не для процессов. Задачи, созданные разными процессами, конкурируют между собой за получение процессорного времени.
Каким именно образом?
Приложения Java могут указывать три значения для приоритетов задач. Это NORM_PRIORITY, MAX_PRIORITY и MIN_PRIORITY.
По умолчанию вновь созданная задача имеет нормальный приоритет NORM_PRIORITY. Если остальные задачи в системе имеют тот же самый приоритет, то все задачи пользуются процессорным времени на равных правах.
При необходимости вы можете повысить или понизить приоритет отдельных задач, определив для них значение приоритета, соответственно, MAX_PRIORITY или MIN_PRIORITY. Задачи с повышенным приоритетом выполняются в первую очередь, а с пониженным - только при отсутствии готовых к выполнению задач, имеющих нормальный или повышенный приоритет.
Процесс
Процесс (process) - это объект, который создается операционной системой, когда пользователь запускает приложение. Процессу выделяется отдельное адресное пространство, причем это пространство физически недоступно для других процессов. Процесс может работать с файлами или с каналами связи локальной или глобальной сети. Когда вы запускаете текстовый процессор Microsoft Word for Windows или программу калькулятора, вы создаете новый процесс.
Процессы, задачи и приоритеты
Прежде чем приступить к разговору о мультизадачности, следует уточнить некоторые термины.
Обычно в любой мультизадачной операционной системе выделяют такие объекты, как процессы и задачи. Между ними существует большая разница, которую следует четко себе представлять.
Производные от класса InputStream
От класса InputStream производится много других классов, как это показано на рис. 2.2.
Рис. 2.2. Классы, производные от класса InputStream
Производные от класса OutputStream
Класс OutputStream предназначен для создания потоков вывода. Приложения, как правило, непосредственно не используют этот класс для операций вывода, так же как и класс InputStream для операций ввода. Вместо этого применяются классы, иерархия которых показана на рис. 2.3.
Рис. 2.3. Классы, производные от класса OutputtStream
Рассмотрим кратко назначение этих классов.
Произвольный доступ к файлам
В ряде случаев, например, при создании системы управления базой данных, требуется обеспечить произвольный доступ к файлу. Рассмотренные нами ранее потоки ввода и вывода пригодны лишь для последовательного доступа, так как в соответствующих классах нет средств позиционирования внутри файла.
Между тем библиотека классов Java содержит класс RandomAccessFile, который предназначен специально для организации прямого доступа к файлам как для чтения, так и для записи.
В классе RandomAccessFile определено два конструктора, прототипы которых показаны ниже:
public RandomAccessFile(String name, String mode);
public RandomAccessFile(File file, String mode);
Первый из них позволяет указывать имя файла, и режим mode, в котором открывается файл. Второй конструктор вместо имени предполагает использование объекта класса File.
Если файл открывается только для чтения, вы должны передать конструктору текстовую строку режима "r". Если же файл открывается и для чтения, и для записи, конструктору передается строка "rw".
Позиционирование внутри файла обеспечивается методом seek, в качестве параметра pos которому передается абсолютное смещение файла:
public void seek(long pos);
После вызова этого метода текущая позиция в файле устанавливается в соответствии со значением параметра pos.
В любой момент времени вы можете определить текущую позицию внутри файла, вызвав метод getFilePointer:
public long getFilePointer();
Еще один метод, который имеет отношение к позиционированию, называется skipBytes:
public int skipBytes(int n);
Он работает так же, как и одноименный метод для потоков - продвигает текущую позицию в файле на заданное количество байт.
С помощью метода close вы должны закрывать файл, после того как работа с им завершена:
public void close();
Метод getFD позволяет получить дескриптор файла:
public final FileDescriptor getFD();
С помощью метода length вы можете определить текущую длину файла:
public long length();
Ряд методов предназначен для выполнения как обычного, так и форматированного ввода из файла. Этот набор аналогичен методам, определенным для потоков:
public int read();
public int read(byte b[]);
public int read(byte b[], int off, int len);
public final boolean readBoolean();
public final byte readByte();
public final char readChar();
public final double readDouble();
public final float readFloat();
public final void readFully(byte b[]);
public final void readFully(byte b[], int off, int len);
public final int readInt();
public final String readLine();
public final long readLong();
public final short readShort();
public final int readUnsignedByte();
public final int readUnsignedShort();
public final String readUTF();
Существуют также методы, позволяющие выполнять обычную или форматированную запись в файл с прямым доступом:
public void write(byte b[]);
public void write(byte b[], int off, int len);
public void write(int b);
public final void writeBoolean(boolean v);
public final void writeByte(int v);
public final void writeBytes(String s);
public final void writeChar(int v);
public final void writeChars(String s);
public final void writeDouble(double v);
public final void writeFloat(float v);
public final void writeInt(int v);
public final void writeLong(long v);
public final void writeShort(int v);
public final void writeUTF(String str);
Имена приведенных методов говорят сами за себя, поэтому мы не будем их описывать.
Просмотр списка аплетов
Список класса Enumeration можно просмотреть в цикле только один раз, вызывая для получения очередного элемента списка метод nextElement:
while(eApplets.hasMoreElements())
{
Applet currentApplet = (Applet)(eApplets.nextElement());
. . .
}
Для проверки условия завершения цикла следует вызывать метод hasMoreElements, который возвращает значение true, если в процессе просмотра список еще не был опустошен.
Заметим, что в полученном списке будет ссылка и на тот аплет, который выполняет поиск остальных аплетов. При необходимости в процессе просмотра вы можете выделить “свой” аплет следующим образом:
if(currentApplet == this)
{
// Обработка “своего” аплета
}
Простейшие методы
Создав выходной поток на базе класса FileOutputStream, вы можете использовать для записи в него данных три разновидности метода write, прототипы которых представлены ниже:
public void write(byte b[]);
public void write(byte b[], int off, int len);
public void write(int b);
Первый из этих методов записывает в поток содержимое массива, ссылка на который передается через параметр, начиная с текущей позиции в потоке. После выполнения записи текущая позиция продвигается вперед на число записанных байт, которое при успешном завершении операции равно длине массива (b.length).
Второй метод позволяет дополнительно указать начальное смещение off записываемого блока данных в массиве и количество записываемых байт len.
Третий метод просто записывает в поток один байт данных.
Если в процессе записи происходит ошибка, возникает исключение IOException.
Для входного потока, созданного на базе класса FileInputStream, определены три разновидности метода read, выполняющего чтение данных:
public int read();
public int read(byte b[]);
public int read(byte b[], int off, int len);
Первая разновидность просто читает из потока один байт данных. Если достигнут конец файла, возвращается значение -1.
Вторая разновидность метода read читает данные в массив, причем количество прочитанных данных определяется размером массива. Метод возвращает количество прочитанных байт данных или значение -1, если в процессе чтения был достигнут конец файла.
И, наконец, третий метод позволяет прочитать данные в область массива, заданную своим смещением и длиной.
Если при чтении происходит ошибка, возникает исключение IOException.
Проверка существования файла или каталога
С помощью метода exists вы можете проверить существование файла или катлога, для которого был создан объект класса File:
public boolean exists();
Этот метод можно применять перед созданием потока на базе класса FileOutputStream, если вам нужно избежать случайной перезаписи существующего файла. В этом случае перед созданием выходного потока класса FileOutputStream следует создать объект класса File, указав конструктору путь к файлу, а затем проверить сущестование файла методом exists.
Проверка возможности чтения и записи
Методы canRead и canWrite позволяют проверить возможность чтения из файла и записи в файл, соответственно:
public boolean canRead();
public boolean canWrite();
Их полезно применять перед созданием соответствующих потоков, если нужно избежать возникновение исключений, связанных с попыткой выполнения доступа неразрешенного типа. Если доступ разрешен, эти методы возвращают значение true, а если запрещен - false.
Работа с файлами и каталогами при помощи класса File
В предыдущих разделах мы рассмотрели классы, предназначенные для чтения и записи потоков. Однако часто возникает необходимость выполнения и таких операций, как определение атрибутов файла, создание или удаление каталогов, удаление файлов, получение списка всех файлов в каталоге и так далее. Для выполнения всех этих операций в приложениях Java используется класс с именем File.
Работа с потоковыми сокетами
Как мы уже говорили, интерфейс сокетов позволяет передавать данные между двумя приложениями, работающими на одном или разных узлах сети. В процессе создания канала передачи данных одно из этих приложений выполняет роль сервера, а другое - роль клиента. После того как канал будет создан, приложения становятся равноправными - они могут передавать друг другу данные симметричным образом.
Рассмотрим этот процесс в деталях.
Работа со стандартными потоками
Приложению Java доступны три стандратных потока, которые всегда открыты: стандартный поток ввода, стандартный поток вывода и стандартный поток вывода сообщений об ошибках.
Все перечисленные выше потоки определены в классе System как статические поля с именами, соответственно, in, out и err:
public final class java.lang.System
extends java.lang.Object
{
public static PrintStream err;
public static InputStream in;
public static PrintStream out;
. . .
}
Заметим, что стандратные потоки, как правило, не используются аплетами, так как навигаторы Internet общаются с пользователем через окно аплета и извещения от мыши и клавиатуры, а не через консоль.
Реализация интерфейса Runnable
Описанный выше способ создания задач как объектов класса Thread или унаследованных от него классов кажется достаточнао естественным. Однако этот способ не единственный. Если вам нужно создать только одну задачу, работающую одновременно с кодом аплета, проще выбрать второй способ с использованием интерфейса Runnable.
Идея заключается в том, что основной класс аплета, который является дочерним по отношению к классу Applet, дополнительно реализует интерфейс Runnable, как это показано ниже:
public class MultiTask extends Applet implements Runnable
{
Thread m_MultiTask = null;
. . .
public void run()
{
. . .
}
public void start()
{
if (m_MultiTask == null)
{
m_MultiTask = new Thread(this);
m_MultiTask.start();
}
}
public void stop()
{
if (m_MultiTask != null)
{
m_MultiTask.stop();
m_MultiTask = null;
}
}
}
Внутри класса необходимо определить метод run, который будет выполняться в рамках отдельной задачи. При этом можно считать, что код аплета и код метода run работают одновременно как разные задачи.
Для создания задачи используется оператор new. Задача создается как объект класса Thread, причем конструктору передается ссылка на класс аплета:
m_MultiTask = new Thread(this);
При этом при запуске задачи управление получит метод run, определенный в классе аплета.
Как запустить задачу?
Запуск выполняется, как и раньше, методом start. Обычно задача запускается из метода start аплета, когда пользователь отображает страницу сервера Web, содержащую аплет. Остановка задачи выполняется методом stop.
Реализация мультизадачности в Java
Для создания мультизадачных приложений Java вы должны воспользоваться классом java.lang.Thread. В этом классе определены все методы, необходимые для создания задач, управления их состоянием и синхронизации.
Как пользоваться классом Thread?
Есть две возможности.
Во-первых, вы можете создать свой дочерний класс на базе класса Thread. При этом вы должны переопределить метод run. Ваша реализация этого метода будет работать в рамках отдельной задачи.
Во-вторых, ваш класс может реализовать интерфейс Runnable. При этом в рамках вашего класса необходимо определить метод run, который будет работать как отдельная задача.
Как вы скоро увидите, система автоматизированного создания приложений Java, входящая в состав Microsoft Visual J++, пользуется вторым из перечисленных выше способов. Этот способ удобен в тех случаях, когда ваш класс должен быть унаследован от какого-либо другого класса (например, от класса Applet) и при этом вам нужна мультизадачность. Так как в языке программирования Java нет множественного наследования, невозможно создать класс, для которого в качестве родительского будут выступать классы Applet и Thread. В этом случае реализация интерфейса Runnable является единственным способом решения задачи.
Синхронизация методов
Возможность синхронизации как бы встроена в каждый объект, создаваемый приложением Java. Для этого объекты снабжаются защелками, которые могут быть использованы для блокировки задач, обращающихся к этим объектам.
Чтобы воспользоваться защелками, вы можете объявить соответствующий метод как synchronized, сделав его синхронизированным:
public synchronized void decrement()
{
. . .
}
При вызове синхронизированного метода соответствующий ему объект (в котором он определен) блокируется для использования другими синхронизированными методами. В результате предотвращается одновременная запись двумя методами значений в область памяти, принадлежащую данному объекту.
Использование синхронизированных методов - достаточно простой способ синхронизации задач, обращающихся к общим критическим ресурсам, наподобие описанного выше банковского счета.
Заметим, что не обязательно синхронизовать весь метод - можно выполнить синхронизацию только критичного фрагмента кода.
. . .
synchronized(Account)
{
if(Account.check(3000000))
Account.decrement(3000000);
}
. . .
Здесь синхронизация выполняется для объекта Account.
Синхронизация задач
Мультизадачный режим работы открывает новые возможности для программистов, однако за эти возможности приходится расплачиваться усложнением процесса проектирования приложения и отладки. Основная трудность, с которой сталкиваются программисты, никогда не создававшие ранее мультизадачные приложения, это синхронизация одновременно работающих задач.
Для чего и когда она нужна?
Однозадачная программа, такая, например, как программа MS-DOS, при запуске получает в монопольное распоряжение все ресурсы компьютера. Так как в однозадачной системе существует только один процесс, он использует эти ресурсы в той последовательности, которая соответствует логике работы программы. Процессы и задачи, работающие одновременно в мультизадачной системе, могут пытаться обращаться одновременно к одним и тем же ресурсам, что может привести к неправильной работе приложений.
Поясним это на простом примере.
Пусть мы создаем программу, выполняющую операции с банковским счетом. Операция снятия некоторой суммы денег со счета может происходить в следующей последовательности:
на первом шаге проверяется общая сумма денег, которая хранится на счете;
если общая сумма равна или превышает размер снимаемой суммы денег, общая сумма уменьшается на необходимую величину;
значение остатка записывается на текущий счет.
Если операция уменьшения текущего счета выполняется в однозадачной системе, то никаких проблем не возникнет. Однако представим себе, что два процесса пытаются одновременно выполнить только что описанную операцию с одним и тем же счетом. Пусть при этом на счету находится 5 млн. долларов, а оба процесса пытаются снять с него по 3 млн. долларов.
Допустим, события разворачиваются следующим образом:
первый процесс проверяет состояние текущего счета и убеждается, что на нем хранится 5 млн. долларов;
второй процесс проверяет состояние текущего счета и также убеждается, что на нем хранится 5 млн. долларов;
первый процесс уменьшает счет на 3 млн. долларов и записывает остаток (2 млн. долларов) на текущий счет;
второй процесс выполняет ту же самую операцию, так как после проверки считает, что на счету по-прежнему хранится 5 млн. долларов.
В результате получилось, что со счета, на котором находилось 5 млн. долларов, было снято 6 млн. долларов, и при этом там осталось еще 2 млн. долларов! Итого - банку нанесен ущерб в 3 млн. долларов.
Как же составить программу уменьшения счета, чтобы она не позволяла вытворять подобное?
Очень просто - на время выполнения операций над счетом одним процессом необходимо запретить доступ к этому счету со стороны других процессов. В этом случае сценарий работы программы должен быть следующим:
процесс блокирует счет для выполнения операций другими процессами, получая его в монопольное владение;
процесс проводит процедуру уменьшения счета и записывает на текущий счет новое значение остатка;
процесс разблокирует счет, разрешая другим процессам выполнение операций.
Когда первый процесс блокирует счет, он становится недоступен другим процессам. Если второй процесс также попытается заблокировать этот же счет, он будет переведен в состояние ожидания. Когда первый процесс уменьшит счет и на нем останется 2 млн. долларов, второй процесс будет разблокирован. Он проверит остаток, убедится, что сумма недостаточна и не будет проводить операцию.
Таким образом, в мультизадачной среде необходима синхронизация задач при обращении к критическим ресурсам. Если над такими ресурсами будут выполняться операции в неправильной последовательности, это приведет к возникновению трудно обнаруживаемых ошибок.
В языке программирования Java предусмотрено несколько средств для синхронизации задач, которые мы сейчас рассмотрим.
Создание дочернего класса на базе класса Thread
Рассмотрим первый способ реализации мультизадачности, основанный на наследовании от класса Thread. При использовании этого способа вы определяете для задачи отдельный класс, например, так:
class DrawRectangles extends Thread
{
. . .
public void run()
{
. . .
}
}
Здесь определен класс DrawRectangles, который является дочерним по отношению к классу Thread.
Обратите внимание на метод run. Создавая свой класс на базе класса Thread, вы должны всегда определять этот метод, который и будет выполняться в рамках отдельной задачи.
Заметим, что метод run не вызывается напрямую никакими другими методами. Он получает управление при запуске задачи методом start.
Как это происходит?
Рассмотрим процедуру запуска задачи на примере класса DrawRectangles.
Вначале ваше приложение должно создать объект класса Thread:
public class MultiTask2 extends Applet
{
Thread m_DrawRectThread = null;
. . .
public void start()
{
if (m_DrawRectThread == null)
{
m_DrawRectThread = new DrawRectangles(this);
m_DrawRectThread.start();
}
}
}
Создание объекта выполняется оператором new в методе start, который получает управление, когда пользователь открывает документ HTML с аплетом. Сразу после создания задача запускается на выполнение, для чего вызывается метод start.
Что касается метода run, то если задача используется для выполнения какой либо периодической работы, то этот метод содержит внутри себя бесконечный цикл. Когда этот цикл завершается и метод run возвращает управление, задача прекращает свою работу нормальным, не аварийным образом. Для аварийного завершения задачи можно использовать метод interrupt.
Остановка работающей задачи выполняется методом stop. Обычно остановка всех работающих задач, созданных аплетом, выполняется методом stop класса аплета:
public void stop()
{
if (m_DrawRectThread != null)
{
m_DrawRectThread.stop();
m_DrawRectThread = null;
}
}
Напомним, что этот метод вызывается, когда пользователь покидает страницу сервера Web, содержащую аплет.
Создание каталогов
С помощью методов mkdir и mkdirs можно создавать новые каталоги:
public boolean mkdir();
public boolean mkdirs();
Первый из этих методов создает один каталог, второй - все подкаталоги, ведущие к создаваемому каталогу (то есть полный путь).
Создание объекта класса File
У вас есть три возможности создать объект класса File, вызвав для этого один из трех конструкторов:
public File(String path);
public File(File dir, String name);
public File(String path, String name);
Первый из этих конструкторов имеет единственный параметр - ссылку на строку пути к файлу или каталогу. С помощью второго конструктора вы можете указать отдельно каталог dir и имя файла, для которого создается объект в текущем каталоге. И, наконец, третий конструктор позволяет указать полный путь к каталогу и имя файла.
Если первому из перечисленных конструкторов передать ссылку со значением null, возникнет исключение NullPointerException.
Пользоваться конструкторам очень просто. Вот, например, как создать объект класса File для файла c:\autoexec.bat и каталога d:\winnt:
f1 = new File(“c:\\autoexec.bat”);
f2 = new File(“d:\\winnt”);
Создание объекта класса InetAddress для локального узла
Метод getLocalHost создает объект класса InetAddress для локального узла, то есть для той рабочей станции, на которой выполняется приложение Java. Так как этот метод статический, вы можете вызывать его, ссылаясь на имя класса InetAddress:
InetAddress iaLocal;
iaLocal = InetAddress.getLocalHost();
Создание объекта класса InetAddress для удаленного узла
В том случае, если вас интересует удаленный узел сети Internet или корпоративной сети Intranet, вы можете создать для него объект класса InetAddress с помощью методов getByName или getAllByName. Первый из них возвращает адрес узла, а второй - массив всех адресов IP, связанных с данным узлом. Если узел с указанным именем не существует, при выполнении методов getByName и getAllByName возникает исключение UnknownHostException.
Заметим, что методам getByName и getAllByName можно передавать не только имя узла, такое как “microsoft.com”, но и строку адреса IP в виде четырех десятичных чисел, разделенных точками.
После создания объекта класса InetAddress для локального или удаленного узла вы можете использовать другие методы этого класса.
Создание объекта класса MediaTracker
Объект класса MediaTracker создается следующим образом:
MediaTracker mt;
mt = new MediaTracker(this);
Конструктору класса MediaTracker передается ссылка на компонент, для которого необходимо отслеживать загрузку изображений. В данном случае это наш аплет, поэтому мы передаем конструктору значение this.
Создание потока для форматированного обмена данными
Оказывается, создание потоков, связанных с файлами и предназначенных для форматированного ввода или вывода, необходимо выполнять в несколько приемов. При этом вначале необходимо создать потоки на базе класса FileOutputStream или FileInputStream, а затем передать ссылку на созданный поток констркутору класса DataOutputStream или DataInputStream.
В классах FileOutputStream и FileInputStream предусмотрены конструкторы, которым в качестве параметра передается либо ссылка на объект класса File, либо ссылка на объект класса FileDescriptor, либо, наконец, текстовая строка пути к файлу:
public FileOutputStream(File file);
public FileOutputStream(FileDescriptor fdObj);
public FileOutputStream(String name);
Таким образом, если вам нужен выходной поток для записи форматированных данных, вначале вы создаете поток как объект класса FileOutputStream. Затем ссылку на этот объект следует передать конструктору класса DataOutputStream. Полученный таким образом объект класса DataOutputStream можно использовать как выходной поток, записывая в него форматированные данные.
Создание потоков, связанных с файлами
Если вам нужно создать входной или выходной поток, связанный с локальным файлом, следует воспользоваться классами из библиотеки Java, созданными на базе классов InputStream и OutputStream. Мы уже кратко рассказывали об этих классах в разделе “Классы Java для работы с потоками”. Однако методика использования перечисленных в этом разделе классов может показаться довольно странной.
В чем эта странность?
Говоря кратко, странность заключается в том, что для создания потока вам необходимо воспользоваться сразу несколькими классами, а не одним, наиболее подходящим для решения поставленной задачи, как это можно было бы предположить.
Поясним сказанное на примере.
Пусть, например, нам нужен выходной поток для записи форматированных данных (скажем, текстовых строк класса String). Казалось бы, достаточно создать объект класса DataOutputStream, - и дело сделано. Однако не все так просто.
В классе DataOutputStream предусмотрен только один конструктор, которому в качестве параметра необходимо передать ссылку на объект класса OutputStream:
public DataOutputStream(OutputStream out);
Что же касается конструктора класса OutputStream, то он выглядит следующим образом:
public OutputStream();
Так как ни в том, ни в другом конструкторе не предусмотрено никаких ссылок на файлы, то непонятно, как с использованием только одних классов OutputStream и DataOutputStream можно создать выходной поток, связанный с файлом.
Что же делать?
Сравнение адресов IP
И, наконец, метод equals предназначен для сравнения адресов IP как объектов класса InetAddress.
Сравнение объектов класса File
Для сравнения объектов класса File вы должны использовать метод equals:
public boolean equals(Object obj);
Заметим, что этот метод сравнивает пути к файлам и каталогам, но не сами файли или каталоги.
Стандартные потоки
Для работы со стандартными потоками в классе System имеется три статических объекта: System.in, System.out и System.err. По своему назначению эти потоки больше всего напоминают стандартные потоки ввода, вывода и вывода сообщений об ошибках операционной системы MS-DOS.
Поток System.in связан с клавиатурой, поток System.out и System.err - с консолью приложения Java.
Стандартный поток ввода
Стандартный поток ввода in определен как статический объект класса InputStream, который содержит только простейшие методы для ввода данных. Нужнее всего вам будет метод read:
public int read(byte b[]);
Этот метод читает данные из потока в массив, ссылка на который передается через единственный параметр. Количество считанных данных определяется размером массива, то есть значением b.length.
Метод read возвращает количество прочитанных байт данных или -1, если достигнут конец потока. При возникновении ошибок создается исключение IOException, обработку которого необходимо предусмотреть.
Стандартный поток вывода
Стандартный поток вывода out создан на базе класса PrintStream, предназначенного, как мы это отмечали раньше, для форматированного вывода данных различного типа с целью их визуального отображения в виде текстовой строки.
Для работы со стандартным потоком вывода вы будете использовать главным образом методы print и println, хотя метод write также доступен.
В классе PrintStream определено несколько реализаций метода print с параметрами различных типов:
public void print(boolean b);
public void print(char c);
public void print(char s[]);
public void print(double d);
public void print(float f);
public void print(int i);
public void print(long l);
public void print(Object obj);
public void print(String s);
Как видите, вы можете записать в стандартный поток вывода текстовое представление данных различного типа, в том числе и класса Object.
Метод println аналогичен методу print, отличаясь лишь тем, что он добавляет к записываемой в поток строке символ перехода на следующую строку:
public void println();
public void println(boolean b);
public void println(char c);
public void println(char s[]);
public void println(double d);
public void println(float f);
public void println(int i);
public void println(long l);
public void println(Object obj);
public void println(String s);
Реализация метода println без параметров записывает только символ перехода на следующую строку.
Стандртный поток вывода сообщений об ошибках
Стандртный поток вывода сообщений об ошибках err так же, как и стадартный поток вывода out, создан на базе класса PrintStream. Поэтому для записи сообщений об ошибках вы можете использовать только что описанные методы print и println.
Структура комбинированных приложений
Рассмотрим структуру комбинированного приложения Combi, полные исходные тексты которого мы привели ниже в этой главе.
Связь приложений Java с расширениями сервера Web
Итак, мы расказали вам, как приложения Java могут получать с сервера Web для обработки произвольные файлы, а также как они могут передавать данные друг другу с применением потоковых или датаграммных сокетов.
Однако наиболее впечатляющие возможности открываются, если организовать взаимодействие между приложением Java и расширением сервера Web, таким как CGI или ISAPI. В этом случае приложения или аплеты Java могли бы посылать произвольные данные расширению сервера Web для обработки, а затем получать результат этой обработки в виде файла.
О том, как сделать расширение сервера Web с применением интерфейса CGI или ISAPI вы можете узнать из 29 тома “Библиотеки системного программиста”, который называется “Сервер Web своими руками”. Если вы никогда раньше не создавали расширений сервера Web, мы настоятельно рекомендуем вам ознакомиться с этой книгой перед тем как продолжить работу над данным разделом.
Удаление файлов и каталогов
Для удаления ненужного файла или каталога вы должны создать соответствующий объект File и затем вызвать метод delete:
public boolean delete();
Универсальный адрес ресурсов URL
Адрес IP позволяет идентифицировать узел, однако его недостаточно для идентификации ресурсов, имеющихся на этом узле, таких как работающие приложения или файлы. Причина очевидна - на узле, имеющем один адрес IP, может существовать много различных ресурсов.
Для ссылки на ресурсы сети Internet применяется так называемый универсальный адрес ресуросв URL (Universal Resource Locator). В общем виде этот адрес выглядит следующим образом:
[protocol]://host[:port][path]
Строка адреса начинаетс с протокола protocol, который должен быть использован для доступа к ресурсу. Документы HTML, например, передаются из сервера Web удаленным пользователям с помощью протокола HTTP. Файловые серверы в сети Internet работают с протоколом FTP.
Для ссылки на сетевые ресурсы через протокол HTTP используется следующая форма универсального адреса ресурсов URL:
http://host[:port][path]
Параметр host обязательный. Он должен быть указан как доменный адрес или как адрес IP (в виде четырех десятичных чисел). Например:
http://www.microsoft.com
http://154.23.12.101
Необязательный параметр port задает номер порта для работы с сервером. По умолчанию для протокола HTTP используется порт с номером 80, однако для специализированных серверов Web это может быть и не так.
Номер порта идентифицирует программу, работающую в узле сети TCP/IP и взаимодействующую с другими программами, расположенными на том же или на другом узле сети. Если вы разрабатываете программу, передающую данные через сеть TCP/IP с использованием, например, интерфейса сокетов, то при создании канала связи с уделенным компьютером вы должны указать не только адрес IP, но и номер порта, который будет использован для передачи данных.
Ниже мы показали, как нужно указывать в адресе URL номер порта:
http://www.myspecial.srv/:82
Теперь займемся параметром path, определяющем путь к объекту.
Обычно любой сервер Web или FTP имеет корневой каталог, в котором расположены подкаталоги. Как в корневом каталоге, так и в подкаталогах сервера Web могут находиться документы HTML, двоичные файлы, файлы с графическими изображениями, звуковые и видео-файлы, расширения сервера в виде программ CGI или библиотек динамической компоновки, дополняющих возможности сервера (такие, как библиотеки ISAPI для сервера Microsoft Information Server).
Если в качестве адреса URL указать навигатору только доменное имя сервера, сервер перешлет навигатору свою главную страницу. Имя файла этой страницы зависит от сервера. Большинство серверов на базе операционной системы UNIX посылают по умолчанию файл документа с именем index.html. Сервер Microsoft Information Server может использовать для этой цели имя default.htm или любое другое, определенное при установке сервера, например, home.html или home.htm.
Для ссылки на конкретный документ HTML или на файл любого другого объекта необходимо указать в адресе URL его путь, включающий имя файла, например:
http://www.glasnet.ru/~frolov/index.html
http://www.dials.ccas.ru/frolov/bin/dbsp26.lzh
Корневой каталог сервера Web обозначается символом /. В спецификации протокола HTTP сказано, что если путь не задан, то используется корневой каталог.
Видео в окне аплета
Наиболее динамичные страницы сервера Web содержат анимационные изображения в виде небольших видеофильмов. Как мы рассказывали в 29 томе “Библиотеки системного программиста”, который называется “Сервер Web своими руками”, вы можете подготовить видеофильм как файл AVI или как многосекционный файл GIF.
Файл AVI представляет собой многопоточный файл, содержащий видео и звук. О том, как создавать такие файлы, мы рассказали в 15 томе “Библиотеки системного программиста” с называнием “Мультимедиа для Windows”. Файлы AVI можно создавать при помощи специального видеоадаптера, который способен оцифровывать сигнал с видеокамеры или видеомагнитофона, а также из отдельных изображений, составляющих кадры видеофильма.
Заметим, однако, что озвученный видеофильм в формате AVI продолжительностью в 1 минуту занимает мегабайты дискового пространства. При существующих на сегодняшний день скоростях передачи данных через Internet не имеет никакого смысла размещать на страницах сервера Web такие файлы.
Многосекционные файлы GIF не содержат звуковой информации и состоят обычно из одного-двух десятков кадров. Для каждого такого кадра вы можете задавать время отображения и координаты, где этот кадр будет отображаться. Можно также добиться зацикленного отображения видеофильма, созданного как многосекционный файл GIF.
Аплеты Java предоставляют вам еще одну возможность отображения небольших видеофильмов на страницах сервера Web.
Для реализации этой возможности вы должны подготовить и разместить в одном из каталогов сервера Web файлы отдельных кадров видеофильма в формате GIF или JPEG.
Аплет Java должен загрузить эти изображения, дождавшись окончания процесса загрузки, что можно сделать либо при помощи рассмотренного в этой главе класса MediaTracker либо при помощи интерфейса ImageObserver.
Как только все изображения будут полностью загружены, аплет может начинать их поочередное отображение в цикле. Этот цикл должен выполняться в отдельной задаче.
Так как аплет полностью контролирует отображение кадров фильма, он может реализовывать эффекты, недостижимые при использовании файлов AVI или многосекционных файлов GIF. Например, аплет может накладывать или смешивать кадры различных фильмов, рисовать поверх кадров произвольные изображения или делать надписи, масштабировать отдельные фрагменты кадров или весь кадр и так далее. Здесь все ограничивается главным образом вашей фантазией.
Так как мы уже научились выполнять все необходимые для показа видеофильма операции, перейдем сразу к исходным текстам приложения CDRotation.
Временная приостановка и возобновление работы
Методы suspend и resume позволяют, соответственно, временно приостанавливать и возобновлять работу задачи. Мы уже пользовались этими методами в приложении Rectangles для приостановки и возобновления работы задачи рисования прямоугольников.
Задача приостанавливалась, когда курсор мыши оказывался над окном аплета:
public boolean mouseEnter(Event evt, int x, int y)
{
if (m_Rectangles != null)
{
m_Rectangles.suspend();
}
return true;
}
Работа задачи возобновлялась, когда курсор мыши покидал окно аплета:
public boolean mouseExit(Event evt, int x, int y)
{
if (m_Rectangles != null)
{
m_Rectangles.resume();
}
return true;
}
Создание приложений на языке Java.
В 30 томе “Библиотеки системного программиста”, который называется “Microsoft Visual J++. Создание приложений на языке Java. Часть 1” мы научили вас создавать аплеты Java и размещать их на страницах сервера Web. Была рассмотрена общая структура аплета, работа с контекстом отображения, органами управления, панелями, шрифтами и многое другое. Теперь настало время поговорить о более сложных и весьма полезных возможностях, которые открываются перед разработчиком приложений Java.
Прежде всего, это мультизадачность. Практически любая современная операционная система, такая как Microsoft Windows, IBM OS/2 или UNIX, работает в мультизадачном режиме. Во многих случаях мультизадачность в целом благоприятно сказывается на производительности системы, так как во время ожидания одних задач свою работу могут выполнять другие задачи, готовые для этого.
Например, если вы работаете в сети Internet, то можете одновременно подключиться к нескольким серверам FTP и Web, перекачивая сразу несколько файлов и загружая несколько документов HTML. При этом еще можно отправлять или получать электронную почту. Так как скорость поступления данных из сети Internet составляет в среднем 1 Кбайт в секунду, то даже при использовании модемного соединения общая скорость передачи данных в этом случае будет выше, чем при поочередной работе с серверами FTP, Web или с почтовым сервером. Пока один из серверов находится в состоянии ожидания, вы будете получать данные от другого сервера.
Такое увеличение средней скорости передачи данных возможно из-за того, что при использовании протокола TCP/IP через общий канал могут одновременно передаваться пакеты данных, предназначенные для различных адресатов.
Если вы создаете приложения Java, вам доступны удобные средства организации мультзадачности, в том числе средства синхронизации задач. Последнее необходимо для того чтобы параллельно работающие задачи корректно обращались с критическими ресурсами, требующими последовательного обращения.
Заметим, что если вы собираетесь заниматься анимацией в окнах аплетов, вам в любом случае придется создавать мультизадачные приложения Java. Использование мультизадачности - единственный путь выполнения в приложениях Java любых периодических процедур, таких, например, как покадровое отображение мультфильма или медленный сдвиг текста для создания эффекта “бегущая строка”.
Отдельная глава будет посвящена организации файлового ввода и вывода в приложениях Java. Хотя аплеты не имеют доступа к файлам, расположенным на дисках локального компьютера, самостоятельные приложения Java могут обращаться с файлами свободно. Аплеты также могут иметь доступ к файлам, расположенным в каталогах сервера Web.
Много внимания в нашей книге мы уделим организации сетевого взаимодействия аплетов. Язык программирования Java был разработан специально для создания сетевых приложений, поэтому не удивительно, что в состав его библиотеки классов входят мощные средства, предназначенные для работы в сети. Мы, в частности, рассмотрим применение интерфейса потоковых и датаграмных сокетов.
Вы сможете более полно реализовать возможности аплетов, если сумеете организовать взаимодействие аплетов и расширений сервера Web, таких как программы CGI или приложения ISAPI. В нашей книге вы найдете описание практических способов организации такого взаимодействия.
Традиционно работа с растровыми графическими изображениями в приложениях Microsoft Windows или IBM OS/2 вызывала у программистов трудности, связанные с необходимостью разбора заголовков файлов графических изображений, реализации палитры и так далее. Библиотека классов Java содержит очень удобные и простые в использовании средства, избавляющие программистов от кошмарной работы с графическими файлами на низком уровне. Это особенно важно, так как аплеты часто применяются именно для усиления графического оформления страниц серверов Web. В нашей книге мы рассмотрим основные приемы работы с графическими изображениями. Отдельный раздел будет посвящена созданию анимационных изображений.
Мы расскажем вам также и о том, как аплеты Java работают со звуковыми файлами. И хотя средства, предоставляемые библиотекой классов Java для, работы со звуком, нельзя назвать богатыми, вы все же сможете применить аплеты для озвучивания своих документов HTML.
В отдельной главе нами будут рассмотрены приемы организации взаимодействия между несколькими аплетами, расположенными в одном документе HTML.
Заключительная глава нашей книги посвящена созданию комбинированных приложений Java, которые могут работать и как самостоятельные приложения, и как аплеты, встроенные в документы HTML.
Взаимодействие приложения Java и расширения сервера Web
Методика организации взаимодействия приложений Java и расширений сервера Web основана на применении классов URL и URLConnection.
Приложение Java, желающее работать с расширением сервера Web, создает объект класса URL для программы расширения (то есть для исполняемого модуля расширения CGI или библиотеки динамической компоновки DLL расширения ISAPI).
Далее приложение получает ссылку на канал передачи данных с этим расширением как объекта класса URLConnection. Затем, пользуясь методами getOutputStream и getInputStream из класса URLConnection, приложение создает с расширением сервера Web выходной и входной канал передачи данных.
Когда данные передаются приложением в выходной канал, созданный подобным образом, он попадает в стандартный поток ввода приложения CGI, как будто бы данные пришли методом POST из формы, определенной в документе HTML.
Обработав полученные данные, расширение CGI записывает их в свой стандартный выходной поток, после чего эти данные становятся доступны приложению Java через входной поток, открытый методом getInputStream класса URLConnection.
На рис. 3.5 показаны потоки данных для описанной выше схемы взаимодействия приложения Java и расширения сервреа Web с интерфейсом CGI.
Рис. 3.5. Взаимодействие приложения Java с расширением сервера Web на базе интерфейса CGI
Расширения ISAPI работают аналогично, однако они получают данные не из стандратного входного потока, а с помощью вызова специально предназначенной для этого функции интерфейса ISAPI. Вместо стандартного потока вывода также применяется специальная функция. Подробности вы можете узнать из 29 тома “Библиотеки системного программиста”.
Для каждого процесса операционная система
Для каждого процесса операционная система создает одну главную задачу (thread или task), которая является потоком выполняющихся по очереди команд центрального процессора. При необходимости главная задача может создавать другие задачи, пользуясь для этого программным интерфейсом операционной системы.
Все задачи, созданные процессом, выполняются в адресном пространстве этого процесса и имеют доступ к ресурсам процесса. Однако задача одного процесса не имеет никакого доступа к ресурсам задачи другого процесса, так как они работают в разных адресных пространствах. При необходимости организации взаимодействия между процессами или задачами, принадлежащими разным процессам, следует пользоваться системными средствами, специально предназначенными для этого.
Задачи-демоны
Вызвав для задачи метод setDaemon, вы превращаете обычную задачу в задачу-демон. Такая задача работает в фоновом режиме независимо от породившей ее задачи. Если задача-демон создает другие задачи, то они также станут получат статус задачи-демона.
Заметим, что метод setDaemon необходимо вызывать после создания задачи, но до момента ее запуска, то есть перед вызовом метода start.
С помощью метода isDaemon вы можете проверить, является задача демоном, или нет.
Загрузка и проигрывание звуковых файлов
Работа со звуковыми файлами во многом напоминает работу с растровыми графическими файлами. Вначале вы должны получить ссылку на интерфейс AudioClip, а затем, пользуясь его методами, вы сможете выполнять проигрывание содержимого этого файла.
Для получения интерфейса AudioClip вы должны воспользоваться одним из двух вариантов метода getAudioClip, определенных в классе Applet:
public AudioClip getAudioClip(URL url):
public AudioClip getAudioClip(URL url, String name);
Первый вариант метода предполагает указание адреса URL звукового файла через единственный параметр, второй допускает раздельное указание адреса URL каталога, содержащего файл, и имени файла.
В документации на метод getAudioClip сказано, что этот метод фактически не выполняет загрузку звуковых данных, а только возвращает ссылку на интерфейс AudioClip и немедленно возвращает управление. Загрузка звуковых данных выполняется методами, предназначенными для проигрывания файла.
Однако в книге “The Java Tutorial. Object-Oriented Programming for the Internet”, подготовленной специалистами группы JavaSoft, утверждается, что текущие реализации Java работают по другому: метод getAudioClip возвращает управление только после завершения загрузки звукового файла. Очевидно, вам не стоит полагаться на то, что так будет всегда. В тех случаях, когда нежелательно блокирование работы аплета на время загрузки звукового файла, загрузку и проигрывание следует выполнять в отдельной задаче.
Интерфейс AudioClip определен следующим образом:
public interface java.applet.AudioClip
{
public abstract void play();
public abstract void loop();
public abstract void stop();
}
Метод play запускает однократное проигрывание звукового файла, которое выполняется от начала файла и до его конца.
Метод loop запускает проигрывание звукового файла в цикле, которое будет продолжаться до тех пор, пока вы не остановите его, вызвав метод stop.
Метод stop, как нетрудно догадаться из его названия, останавливает проигрывание звукового файла, как однократное, так и выполняемое в цикле.
Загрузка и рисование растрового изображения
Загрузка растрового изображения из файла выполняется очень просто - с помощью метода getImage, определенного в классе Applet:
public Image getImage(URL url);
public Image getImage(URL url, String name);
Первый вариант метода предполагает использование только одного параметра - адреса URL файла графического изображения. Второй позволяет дополнительно указать относительное расположение файла изображения относительно адреса URL, например:
Image img;
img = getImage(“http://www.glasnet.ru/~frolov/pic”,
"cd.gif");
Если аплет желает загрузить изображение, расположенное в том же каталоге, что и он сам, это можно сделать следующим образом:
img = getImage(getCodeBase(), "cd.gif");
Метод getCodeBase, определенный в классе Applet, возвращает адрес URL аплета. Вместо него можно использовать метод getDocumentBase, который также определен в классе Applet и возвращает адрес URL документа HTML, содержащего аплет:
img = getImage(getDocumentBase(), "cd.gif");
В любом случае метод getImage создает объект класса Image.
Заметим, что на самом деле метод getImage вовсе не загружает изображение через сеть, как это можно было бы подумать. Он только создает объект класса Image. Реальная загрузка файла растрового изображения будет выполняться методом рисования drawImage, который определен в классе Graphics:
public abstract boolean
drawImage(Image img, int x, int y,
ImageObserver observer);
public abstract boolean
drawImage(Image img, int x, int y, Color bgcolor,
ImageObserver observer);
public abstract boolean
drawImage(Image img, int x, int y, int width, int height,
ImageObserver observer);
public abstract boolean
drawImage(Image img, int x, int y, int width, int height,
Color bgcolor, ImageObserver observer);
Как видите, существует четыре варианта этого метода.
В качестве первого параметра любому варианту метода передается ссылка на объект класса Image, полученный ранее с помощью метода getImage.
Параметры x и y задают координаты верхнего левого угла прямоугольной области, внутри которой будет нарисовано изображение. Эти параметры также задаются для любого варианта метода drawImage.
Параметр bgcolor задает цвет фона, на котором будет нарисовано изображение. Как мы говорили в 29 томе “Библиотеки системного программиста”, изображения GIF могут быть прозрачными. В этом случае цвет фона может иметь большое значение.
Если для рисования выбраны варианты метода drawImage с параметрами width и height, изображение будет нарисовано с масштабированием. При этом указанные параметры будут определять, соответственно, ширину и высоту изображения.
Параметр observer представляет собой ссылку на объект класса ImageObserver, который получит извещение при загрузке изображения. Обычно в качестве такого объекта используется сам аплет, поэтому данный параметр указывается как this.
Вот два примера использования метода drawImage:
g.drawImage(FloppyDiskImg, 25, 3, this);
g.drawImage(FloppyDiskImg, 25, 42, 200, 200, this);
В первой строке изображение FloppyDiskImg рисуется в точке с координатами (25, 3) без масштабирования, во второй - в точке с координатами (25, 42), причем высота и ширина нарисованного изображения будет равна 200 пикселам.
Метод drawImage запускает процесс загрузки и рисования изображения, а затем, не дожидаясь его завершения, возвращает управление. Так как загрузка файла изображения по сети может отнять немало времени, она выполняется асинхронно в отдельной задаче.
Закрывание потоков
Работая с файлами в среде MS-DOS или Microsoft Windows средствами языка программирования С вы должны были закрывать ненужные более файлы. Так как в системе интерпертации приложений Java есть процесс сборки мусора, возникает вопрос - выполняет ли он автоматическое закрывание потоков, с которыми приложение завершило работу?
Оказывается, процесс сборки мусора не делает ничего подобного!
Сборка мусора выполняется только для объектов, размещенных в оперативной памяти. Потоки вы должны закрывать явным образом, вызывая для этого метод close.
Запись данных в поток и чтение данных из потока
Для обмена данными с потоками можно использовать как простейшие методы write и read, так и методы, допускающие ввод или вывод форматированных данных. В зависимости от того, на базе какого класса создан поток, зависит набор доступных методов, предназначенных для чтения или записи данных.
Завершение работы сервера и клиента
После завершения передачи данных вы должны закрыть потоки, вызвав метод close:
is.close();
os.close();
Когда канал передачи данных больше не нужен, сервер и клиент должны закрыть сокет, вызвав метод close, определенный в классе Socket:
s.close();
Серверное приложение, кроме того, должно закрыть соединение, вызвав метод close для объекта класса ServerSocket:
ss.close();