Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Секреты программирования для Internet на Java

.pdf
Скачиваний:
181
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

OutputStream getOutputStream() Возвращает OutputStream, относящийся к данному сокету.

setSocketImplFactory(SocketImplFactory) Устанавливает фактор реализации сокета для данной системы.

СОВЕТ Если ваша операционная система не UNIX и вы загружаете апплет временного клиента с локального жесткого диска или с Companion CD-ROM, пример 13-1 не будет работать (так же как и другие примеры этой главы), если только у вас случайно не сервер времени. Чтобы посмотреть на этот апплет в работе, сходите на http://www.vmedia.com/vvc/onlcomp/java/ chapter13/example1/TimeApplet.htm.

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

Пример 13-1. Временной клиент. import java.applet.*; import java.awt.*;

import java.net.*; import java.io.*;

public class TimeApplet extends Applet { private Exception error;

private String output;

В примере, приведенном ниже, мы попытаемся установить связь с удаленным хостом на порту 13 - это порт сервера времени. Если соединение будет установлено, мы попытаемся связать InputStream и Socket. Если мы столкнемся с какой-то исключительной ситуацией (UnknownHostException или IOException), произойдет быстрый возврат. Соединившись с InputStream, мы прочтем строку и закроем связь:

public void start() { Socket TimeSocket;

InputStream TimeInput; try {

TimeSocket = new Socket(getCodeBase().getHost(),13,true); TimeInput = TimeSocket.getInputStream();

} catch (Exception e) { error = e; return;

}

output = readString(TimeInput); try {

TimeInput.close();

TimeSocket.close(); } catch (IOException e) {}

}

Теперь мы готовы к тому, чтобы прочесть выходные данные с сервера времени. Присвоим начальные значения массиву байтов, который должен содержать эти данные, и прочтем информацию из InputStream по одному байту в единицу времени. Когда поток заполнится, метод чтения InputStream вернет -1 (после того, как сокет закроется на удаленном хосте). Для простоты будем считать, что входные данные будут занимать меньше 50 байтов, хотя позже мы покажем вам лучшие способы получать неизвестное количество данных с сервера.

private String readString(InputStream in) { byte ary[] = new byte[50];

byte buf;

int count = 0; try {

buf = (byte)in.read(); while (buf!=-1) {

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

ary[count] = buf; count++;

buf = (byte)in.read();

}

} catch (IOException e) { error = e;

return null;

}

return new String(ary,0).trim();

}

Метод paint выдаст выходные данные с сервера времени, если все прошло хорошо, или информацию об ошибке, если что-то было не так:

public void paint(Graphics g) { g.setColor(Color.white); g.fillRect(0,0,499,249); g.setColor(Color.black); g.drawRect(0,0,499,249); if (error!=null) {

g.drawString(error.toString(),25,25);

g.drawString(error.getMessage(),25,25); } else {

g.drawString(output,25,25);

}

}

}

Ссокетами особенно легко работать на Java; вся работа сводится к оперированию с входными

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

Реализация сокетов

Есть один метод в классе Socket, который мы еще не использовали, - это метод setSocketImplFactory. Сам класс Socket может делать немного больше, чем создавать согласованный интерфейс для связи с удаленными хостами. Каждый Socket содержит частный SocketImpl - другой класс в пакете java.net.package, который реально выполняет работу по соединению и перемещению данных на удаленный хост и обратно. Определенный по умолчанию класс SocketImpl создан для того, чтобы работать с обычными данными по протоколу TCP/IP, но вы можете захотеть воспользоваться другим протоколом, как, например, Apple Talk или IPX.

Можно написать класс, реализующий SocketImplFactory, и интерфейс, позволяющий классу

Socket запросить SocketImpl. Новый класс SocketImplFactory создаст классы SockImpls, которые могут передавать данные по протоколам IPX или Apple Talk, но при этом могут использоваться стандартным классом Socket, не требуя от пользователей изучения целого набора новых методов. Такая техника послойного использования уровней абстракции вполне обычна при работе с сетевыми компьютерами.

Несвязываемые датаграммы

Протокол TCP/IP поддерживает также доставку несвязываемых датаграмм и их возврат с помощью UDP (User Datagram Packet, "Пакет пользовательских датаграмм"). Датаграммы UDP, так же как сокеты TCP, позволяют связаться с удаленным хостом по протоколу TCP/IP. В отличие от сокетов TCP, осуществляющих связь с логическим соединением, датаграммы UDP связываются без логического соединения. Если сокет TCP можно сравнить с телефонным звонком, то датаграмму UDP можно сравнить с телеграммой. Доставка датаграммы UDP не гарантирована, и даже если такая датаграмма доставлена, нет гарантии, что она доставлена правильно. Если вы решили применить в своей программе датаграммы, вам придется иметь дело с утерянными или неупорядоченными пакетами.

Из-за ненадежности UDP большинство программистов при написании сетевых приложений

www.books-shop.com

предпочитают работать с сокетами TCP. Тем не менее вы можете решить использовать датаграммы, если захотите написать клиент Java для существующего UDP-сервера, например NFS версии 2. Кроме того, у датаграмм служебный заголовок (overhead) занимает меньше места, чем у сокета TCP, потому что при посылке или получении датаграмм не нужны механизмы ни подтверждения связи (handshaking), ни управления потоком данных (flow control). Кроме того, датаграммы UDP можно использовать на сервере Java, посылающем периодически обновляемую информацию клиентам. Если сервер вместо того, чтобы устанавливать TCP-соединение для каждого клиента, будет просто посылать датаграммы UDP, он будет выполнять меньше работы и, соответственно, работать быстрее.

Два класса в Java ассоциируются с датаграммами UDP - DatagramPacket и DatagramSocket. Класс DatagramPacket используется для посылки или получения датаграмм UDP. Конструкторы и методы для DatagramPacket приводятся в табл. 13-3.

Таблица 13-3. Конструкторы и методы класса DatagramPacket

Конструкторы и методы

Описание

DatagramPacket(byte[], int,

Создает пакет для посылки датаграммы. Содержимое массива byte

InetAddress, int)

посылается на порт удаленного хоста, заданный вторым целым и

 

InetAddress. Первое целое задает число посылаемых байтов.

InetAddress getAddress()

Возвращает интернетовский адрес пакета.

int getPort()

Возвращает номер порта пакета.

byte[] getData()

Возвращает данные пакета.

int getLength()

Возвращает длину пакета.

Когда вы сконструируете класс DatagramPacket, вам понадобится DatagramSocket, чтобы посылать и получать датаграммы. Эти сокеты привязаны к определенному порту на вашем компьютере, совсем как сокеты TCP. Номера портов, меньшие 1024, зарезервированы для пользователя root на компьютерах с UNIX. Конструкторы и методы для класса DatagramSocket приводятся в табл. 13-4.

Таблица 13-4. Конструкторы и методы класса DatagramSocket

Конструкторы и Описание методы

DatagramSocket()

Создает сокет UDP на незаданном свободном порту.

DatagramSocket(int)

Создает сокет UDP на заданном порту.

receive(DatagramPacket)

Ждет получения датаграммы и копирует данные в специальный

 

DatagramPacket.

send(datagramPacket)

Посылает DatagramPacket.

getLocalPort()

Возвращает номер локального порта, к которому привязан сокет.

close()

Закрывает сокет.

Мы воспользуемся этими классами для реализации UDP-клиента. Соединимся с UDP эхосервером, который на большинстве машин с UNIX находится на порту 7. Эхо-сервер принимает каждую полученную UDP-датаграмму и возвращает ее копию в виде другой UDP-датаграммы туда, откуда первая датаграмма была послана. Вот текст программы нашего эхо-клиента.

Пример 13-2. Апплет датаграммы. import java.applet.*;

import java.awt.*; import java.net.*;

public class DatagramApplet extends Applet { private InetAddress addr;

private String message; private TextArea output; public void init() {

try {

String me = detCodeBase().getHost(); addr = InetAddress.getByName(me);

} catch (Exception e) { handleException(e);

}

www.books-shop.com

message = "abcde";

output = new TextArea(5,60); output.setEditable(false); add(output);

show();

}

public void start() {

byte b[] = new byte[message.length()]; message.getBytes(0,message.length(),b,0); try {

DatagramSocket ds

ds = new DatagramSocket(); DatagramPacket outPacket;

outPacket = new DatagramPacket(b,b.length,addr,7); b = new byte[message.length()];

DatagramPacket inPacket;

inPacket = new DatagramPacket(b,b.length); ds.send(outPacket); output.appendText("Sent: "+message+"\n"); ds.receive(inPacket);

b = inPacket.getData();

output.appendText("Rec'd: "+new String(b,0)+"\n"); ds.close();

} catch (Exception e) { handleException(e);

}

}

public void stop() { output.setText("");

}

public void handleException(Exception e) { System.out.println(e.toString()); e.printStackTrace();

}

}

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

Потоки

Java API для управления входными и выходными данными программ использует потоки. Поток - это, по сути, метод передачи данных от программы на какое-то внешнее устройство, например в файл или сеть. На одном конце вы помещаете нечто, и оно приходит на другой конец (рис. 13- 1). Основными классами потоков являются InputStream и OutputStream, входящие в пакет java.io. В данном разделе мы также рассмотрим богатый пакет расширенных типов потоков, которые значительно упрощают передачу данных по сравнению с использованием непосредственно байтов.

Рис. 13.1.

Входные потоки

www.books-shop.com

При передаче входных данных с удаленного устройства возникают некоторые трудности. Большинство входных потоков реально передается через устройство, которое может работать значительно медленнее, чем использующая их программа. Не самым лучшим (хотя и самым простым) программистским решением было бы ждать медленной или уже закончившейся передачи данных. Скорость связи по сети варьируется в широких пределах, часто при прохождении данных передача зависает на несколько секунд или минут. Класс InputStream построен таким образом, чтобы относительно легко уменьшить подобные задержки.

Класс InputStream определяет метод, возвращающий число байтов, которое можно прочесть из потока без блокировки, то есть за то свободное время, пока приходят новые данные. Методы чтения потока осуществляют блокировку до тех пор, пока не станет доступна хотя бы часть из запрошенных данных. Это означает, что если вы попытаетесь прочесть данные, которые еще не появились, то поток, управляющий проходом данных, дождется, пока хотя бы часть данных придет, после чего продолжит работу. Если данные так и не придут, поток будет ждать до тех пор, пока не закончится время работы сокета. Если достигнут конец потока данных, метод чтения возвращает -1. При возникновении ошибки все методы выдают исключение IOException. Методы класса InputStream приводятся в табл. 13-5.

 

Таблица 13-5. Методы класса InputStream

Методы

Описание

int available()

Возвращает число байтов, которое можно прочесть в потоке данных без

 

блокировки.

int read()

Возвращает следующий байт из потока данных.

int read(byte[])

Заполняет все свободное место в массиве байтов потока данных и

 

возвращает число реально прочитанных данных.

int

Заполняет все свободное место в массиве байтов потока данных. Первое

read(byte[],int,int)

целое указывает на смещение, с которого нужно начинать читать, второе

 

целое указывает максимальное число байтов, которые нужно прочесть.

long skip(long n)

Пропускает n байтов входных данных и возвращает число реально

 

пропущенных байтов.

close()

Закрывает поток.

mark(int)

Отмечает текущую позицию в потоке данных. Целый аргумент обозначает

 

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

 

отметки.

reset()

Возвращает поток данных к последней отмеченной позиции.

boolean

Показывает, поддерживает ли данный поток данных отметку и методы

markSupported()

сброса.

Выходные потоки

Записать данные в поток гораздо проще, чем получить входные данные. Класс OutputStream состоит из трех методов записи, метода flush, записывающего все выходящие из буфера данные в поток, и метода close, закрывающего поток. Методы записи осуществляют блокировку до тех пор, пока данные реально не запишутся. Эти методы приведены в табл. 13-6. При возникновении ошибки все эти методы выдают IOException.

 

Таблица 13-6. Методы класса OutputStream

Метод

Описание

close()

Закрывает поток.

flush()

Освобождает выводной буфер от всех данных.

write(int)

Записывает байт в поток.

write(byte[])

Записывает массив байтов в поток.

write(byte[],int,int) Записывает подмассив данного массива в поток. Первое целое указывает смещение, второе целое указывает длину подмассива.

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

www.books-shop.com

Пример 13-3. Апплет клиента Finger. import java.applet.*;

import java.awt.*; import java.net.*; import java.io.*;

Вместо drawString воспользуемся полем TextArea, потому что сервер Finger часто возвращает несколько строк выходной информации. Для простоты зададим имя пользователя, о котором мы запрашиваем информацию, в качестве параметра к Web-странице. Поскольку здесь используется стандартный выходной поток, то перед посылкой мы должны конвертировать строку в массив байтов, хотя ниже в этой главе мы обсудим подклассы класса OutputStream, которые могут записывать переменные типа String непосредственно. Байт EOL используется при синтаксическом анализе выходных данных с сервера Finger для установления конца строки:

public class FingerApplet extends Applet { private TextArea output;

private byte user[]; private byte EOL; public void init() {

String userStr

userStr = getParameter("USER")+"\n"; user = new byte[userStr.length()];

userStr.getBytes(0,userStr.length(),user,0); output = new TextArea(10,80); output.setEditable(false);

add(output);

show();

String eolStr = new String("\n"); byte eolAry[] = new byte[1]; eolStr.getBytes(0,1,eolAry,0); EOL = eolAry[0];

}

Раздел программы, приведенный ниже, почти идентичен TimeClientApplet - серверу Finger, находящемуся на порту 79, и нам понадобятся как класс OutputStream, так и класс InputStream. После установления соединения имя разыскиваемого пользователя посылается в виде массива байтов. Ниже приведен еще один метод, readText, копирующий содержимое InputStream в TextArea:

public void start() { Socket FingerSocket;

InputStream FingerInput;

OutputStream FingerOutput; try {

FingerSocket = new Socket(getCodeBase().getHost(),79); FingerInput = FingerSocket.getInputStream(); FingerOutput = FingerSocket.getOutputStream(); FingerOutput.write(user);

} catch (Exception e) { output.appendText(e.toString()+"\n"); output.appendText(e.getMessage()+"\n"); return;

}

readText(FingerInput,output);

}

Теперь мы готовы к приему данных с сервера Finger. Для хранения каждой строки данных воспользуемся массивом байтов. При достижении символа EOL или при переполнении длины массива (задаваемой числом колонок в поле TextArea) копируем массив байтов в TextArea и создаем новый пустой массив байтов. Это более сложный способ оперирования с неизвестным количеством данных, чем тот, который использовался в первом примере, когда мы просто задали верхнюю границу и создали единичный массив:

www.books-shop.com

private void readText(InputStream in, TextArea text) { int bufsize = text.getColumns();

byte ary[] = new byte[bufsize]; byte buf;

int count = 0; try {

buf = (byte)in.read(); while (buf!=-1) {

ary[count] = buf; count++;

if (buf==EOL || count==bufsize) { String aryStr

aryStr = new String(ary,0); text.appendText(aryStr);

ary = new byte[80]; count = 0;

}

buf = (byte)in.read();

}

} catch (IOException e) { text.appendText(e.toString()+"\n"); text.appendText(e.getMessage()+"\n"); return;

}

text.appendText(new String(ary,0));

}

Когда апплет заканчивается, поле TextArea просто очищается для последующего применения:

public void stop() { output.setText("");

}

}

Разнообразие потоков

Пакет java.io предоставляет гораздо больше возможностей, чем просто классы InputStream и OutputStream. Оба эти класса содержат по нескольку подклассов в API, позволяющих программистам взаимодействовать с потоками на гораздо более высоком уровне, в результате чего проще выполнять синтаксический анализ и посылать данные. Эти подклассы кратко описаны в табл. 13-7. Если не оговорено специально, каждый входной поток имеет соответствующий выходной поток.

Таблица 13-7. Специализированные подклассы класса InputStream

Подкласс

Описание

FilteredInputStream

Отфильтрованный входной поток байтов.

BufferedInputStream

Входной поток байтов с буфером. Использование буфера может ускорить

 

доступ к потоку, поскольку байты записываются порциями. Этот класс

 

является подклассом класса FilteredInputStream.

FileInputStream

Позволяет потокам соединяться с файлами.

PipedInputStream

В сочетании с PipedOutputStream разрешает направленный в одну

 

сторону поток данных между программными потоками.

LineNumberInputStream Следит за номерами строк потока. Этот класс является подклассом класса

FilterInputStream и не имеет соответствующего OutputStream.

PushbackInputStream Позволяющий перемотать поток на начало. Этот класс является подклассом класса FilterInputStream и не имеет соответствующего

OutputStream.

SequenceInputStream Позволяет связать вместе несколько потоков InputStream и прочесть данные из них в режиме карусели. Не имеет соответствующего

OutputStream.

StringBufferInputStream Позволяет обращаться с переменными типа String так же, как с

www.books-shop.com

 

InputString. Не имеет соответствующего OutputStream.

ByteArrayInputStream

Позволяет обращаться с массивом байтов так же, как с InputString.

PrintStream

Позволяет печатать простые типы данных как текст. Не имеет

 

соответствующего OutputStream.

Большинство специализированных потоков из описанных в табл. 13-7 можно построить над обычными потоками. Например, для создания BufferOutputStream из обычного OutputStream нужно сделать следующее:

OutputStream out = mySocket.getOutputStream(); BufferedOutputStream bufferOut;

bufferOut = new BufferedOutputStream(out);

Теперь все, что вы напишете с помощью потока bufferOut, будет вписано в начальный OutputStream и при этом будет находиться в буфере.

Потоки данных

Из всего разнообразия потоков двумя наиболее важными являются DataInputStream и DataOutputStream, которые можно построить над обычными потоками InputStream и OutputStream. Эти потоки позволят вам как брать переменные примитивных типов Java (int, String и т. д.) прямо из потока, не заботясь о массиве байтов, так и записывать переменные прямо в поток. Воспользовавшись DataInputStream, мы можем сократить метод readString из апплета клиента времени. Вместо того чтобы считывать данные из потока в массив байтов и затем помещать их в строку, мы можем считывать строку прямо из потока с помощью метода readLine.

Пример 13-4. Апплет клиента времени, использующий DataInputString. private String readString(InputStream in) {

DataInputStream dataIn;

dataIn = new DataInputStream(in); try {

return dataIn.readLine(); } catch (IOException e) {

error = e; return null;

}

}

В табл. 13-8 перечислены новые методы, добавляющиеся с потоком DataInputStream. Все эти методы выдают исключение IOException при возникновении ошибки. Методы readFully выдают java.io.EOException, если встречается EOF до наполнения массива байтов. Эти методы отличаются от методов read(byte[]) и read(byte[],int,int) исходного потока InputStream тем, что они осуществляют блокировку до тех пор, пока не станут доступными все запрошенные данные или пока не встретится EOF. Аналогично, метод skipBytes блокирует до тех пор, пока не будут пропущены все запрошенные байты.

 

Таблица 13-8. Методы класса DataInputStream

Метод

Описание

boolean readBoolean()

Читает булевскую переменную.

int readByte()

Читает байт (8 бит).

readUnsignedByte()

Читает байт без знака (8 бит).

readShort()

Читает короткое целое (16 бит).

readUnsignedShort()

Читает короткое целое без знака (16 бит).

readChar()

Читает символ (16 бит).

readInt()

Читает целое (32 бита).

readLong()

Читает двойное целое (64 бита).

readFloat()

Читает число с плавающей точкой (32 бита).

readDouble()

Читает число двойной точности с плавающей точкой (64 бита)

www.books-shop.com

readLine()

Читает строку с исключениями \r, \n, \r\n или EOF.

readUTF()

Читает строку в формате UTF.

readFully(byte[])

Считывает байты в специальный массив байтов; блокирует до заполнения

 

массива.

readFully(byte[],int,int) Считывает байты в специальный массив байтов; блокирует до заполнения подмассива.

skipBytes(int n) Пропускает n байтов; блокирует, пока не пропущены все байты.

СОВЕТ При использовании readByte и парного ему writeByte не требуется конвертировать байты в сетевой порядок и обратно. Байты записываются и читаются строго в том порядке, в каком они находятся в виртуальной машине Java.

Потоки DataOutputStream работают так же, как и DataInputStream, только в обратном порядке. Можно записать переменные, за исключением строк, прямо в поток, не конвертируя их предварительно в массивы байтов. В табл. 13-9 приводятся новые методы, добавляемые классом

DataOutputStream.

 

Таблица 13-9. Методы класса DataOutputStream

Метод

Описание

writeBoolean(boolean) Записывает булевскую переменную.

writeByte(int)

Записывает байт (8 бит).

writeChar(int)

Записывает символ (16 бит).

writeShort(short)

Записывает короткое целое (16 бит).

writeInt(int)

Записывает целое (32 бита).

writeLong(long)

Записывает двойное целое (64 бита).

writeFloat(float)

Записывает число с плавающей точкой (32 бита).

writeDouble(double)

Записывает число двойной точности с плавающей точкой (64 бита).

writeBytes(String)

Записывает строку в байтах.

writeChars(String)

Записывает строку в символьной форме.

writeUTF(String)

Записывает строку в формате UTF.

Имейте в виду, что методы DataInputStream не выполняют синтаксический анализ входящего текста на обнаружение символов, похожих на простые типы Java, а методы DataOutputStream не записывают текстовое представление проходящих через них переменных. Эти методы читают и записывают переменные в поток в точности так, как они представлены в виртуальной памяти Java в машине.

Например, когда вы вызываете readInt, он читает 32 бита из потока, после чего возвращает целое, соответствующее этим 32 битам. Он не анализирует строку текстовых данных, чтобы обнаружить нечто, похожее на целое число. Аналогично, метод writeInt потока DataOutputStream не запишет в поток число "10", если вы передаете целое, значение которого не равно 10. Он запишет последовательность из 32 бит, которая при интерпретации как целое число выдаст значение 10. Если вы хотите анализировать синтаксис текста для выявления чисел, вы можете либо считать текстовые данные в строку и воспользоваться StringTokenizer, либо воспользоваться классом StreamTokenizer, который мы обсудим в следующем разделе.

Разбор данных текстового потока

Класс StreamTokenizer, аналогично классу DataInputStream, строится над обычным потоком InputStream. StreamTokenizer отличается от других расширений типов InputStream тем, что он не является непосредственно подклассом класса InputStream, - если вы пользуетесь классом StreamTokenizer, вы не сможете использовать для него обычные методы InputStream. Но зато Stream Tokenizer позволит вам прочитать некоторые простые типы данных Java, а именно строки и числа с плавающей точкой двойной точности, из текстового потока данных. Вам понадобится это свойство для связи с сервером, передающим данные в текстовом виде, например MUD.

Класс StreamTokenizer разделяет поток InputStream на куски, называемые токенами (tokens). Этот класс различает пять видов токенов: слова, числа, символы EOL, символы EOF, обычные символы. Пусть, например, поток содержит следующие данные:

You are user 9 of 100:

StreamTokenizer вернет из этого потока семь токенов. "You", "are", "user" и "of" будут

www.books-shop.com

восприняты как строки, "9" и "100" будут распознаны как числа, а ":" будет возвращено как обычный символ.

Рабочим методом класса StreamTokenizer является метод nextToken. Он читает следующий токен из потока и возвращает целое, указывающее, какой тип токена он нашел. Если токен является словом, числом, EOL или EOF, соответственно целое будет TT_WORD, TT_NUMBER, TT_EOL или TT_EOF. Эти переменные TT_* определены как статические целые в классе StreamTokenizer. Если токен является обычным символом, метод возвращает значение этого символа. Если типом токена является TT_WORD, реальная строка помещается в переменную sval класса StreamTokenizer. Если типом токена является TT_NUMBER, реальное значение типа double помещается в переменную nval класса StreamTokenizer, принадлежащую к этому типу.

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

Существует много способов использовать класс StreamTokenizer. Вы можете задать новый набор символов, распознаваемых как символы слов, как символы пробела или как обычные символы. Можно настроить класс StreamTokenizer так, чтобы он игнорировал комментарии.

Можно задать единичный символ как символ комментария, и каждый раз, как StreamTokenizer будет читать этот символ, он будет игнорировать его и все, что идет за ним, до ближайшего символа EOL. Этот класс может также понимать комментарии вида /*...*/ и // ... Можно даже задать новые символы разделения строк. Рассмотрим простой пример. Допустим, ваша входная информация выглядит следующим образом:

#Это комментарий Это группа слов? :Это целая строка!!!:

По умолчанию Stream Tokenizer поймет "#", "?" и "!" как обыкновенные символы, а все остальное - как слова. Допустим, мы создали StreamTokenizer и модифицировали его следующим образом:

InputStream in = mySocket.getInputStream(); StreamTokenizer st = new StreamTokenizer(in); st.commentChar('#');

st.wordChar('?','?');

st.quoteChar(':');

Теперь StreamTokenizer полностью проигнорирует первую строку, вернет "слов?" как целое слово и сочтет, что все, что находится между двоеточиями в третьей строке, является строкой. Для лучшего знакомства с методами класса StreamTokenizer рассмотрим табл. 13-10.

 

Таблица 13-10. Методы класса StreamTokenizer

Метод

Описание

int nextToken()

Читает следующий токен из входного потока и возвращает целое,

 

показывающее тип найденного токена.

commentChar(int)

Задает специальный символ для открывания комментария длиной в

 

одну строку.

quoteChar(int)

Задает специальный символ для разделения строк. nextToken, дойдя

 

до строки, разделенной этим символом, вернет сам символ (а не

 

TT_WORD) и поместит содержимое строки в переменную sval.

whitespaceChars(int,int)

Задает символы из области допустимых значений, которые должны

 

трактоваться как пробел.

wordChars(int,int)

Задает символы из области допустимых значений, которые должны

 

восприниматься как слова.

ordinaryChar(int)

Задает специальный символ, который должен распознаваться как

 

обычный; nextToken, дойдя до этого символа, вернет его значение.

ordinaryChars(int,int)

То же, что ordinaryChar(int), за исключением того, что определена

 

область допустимых значений.

resetSyntax()

Объявляет все символы специальными.

parseNumbers()

Требует, чтобы все числа синтаксически анализировались. Если

 

eollsSignificant(boolean) имеет значение true, nextToken должен

www.books-shop.com