2a9cfd0b

Описание исходного текста приложения


Внутри метода main мы создали ссылки на выходной поток OutStream и входной поток InStream:

DataOutputStream OutStream;

DataInputStream  InStream;

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

byte bKbdInput[] = new byte[256];

Способ вывода приглашения и получения строки с клавиатуры ничем не отличается от использованного в предыдущем приложении, поэтому для экономии места мы не будем на нем останавливаться. Скажем только, что введенная строка записывается в поле sOut типа String.

Создание выходного потока для записи строки выполняется следующим образом:

OutStream = new DataOutputStream(

  new BufferedOutputStream(

  new FileOutputStream("output.txt")));

Вначале с помощью конструктора создается объект класса FileOutputStream - поток, связанный с файлом output.txt. Далее на базе этого потока создается буферизованный поток типа BufferedOutputStream. И, наконец, на базе буферизованного потока создается форматированный поток OutStream класса DataOutputStream.

Заметим, что конструктор класса FileOutputStream создает файл output.txt, если он не существует, и перезаписывает существующий файл. Если вам нужно исключить случайную перезапись существующего файла, необходимо воспользоваться классом File, о котором мы еще будем рассказывать.

Для записи строки sOut в выходной поток мы вызываем метод writeBytes:

OutStream.writeBytes(sOut);

После записи выполняется сброс содержимого буферов и закрытие потока:

OutStream.flush();



OutStream.close();

При закрытии потока содержимое буферов сбрасывается автоматически. Мы вызвали метод flush только для иллюстрации.

При работе с потоками могут возникать различные исключения. Наш обработчик этих исключений просто записывает в стандратный поток вывода название возникшего исключения:

catch(Exception ioe)

{

  System.out.println(ioe.toString());

}

На следующем этапе приложение открывает файл output.txt для чтения буферизованным форматированным потоком:

InStream = new DataInputStream(

  new BufferedInputStream(

  new FileInputStream("output.txt")));

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

Для чтения строки из входного потока мы применили метод readLine:

System.out.println(InStream.readLine());

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

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

InStream.close();

На последнем этапе приложение ожидает ввода с клавиатуры и затем завершает свою работу.


После ввода строки с клавиатуры и записи ее в файл через поток наше приложение создает входной буферизованный поток, как это показано ниже:
InStream = new DataInputStream(
  new BufferedInputStream(
  new FileInputStream("output.txt")));
Далее для этого потока создается разборщик, который оформлен в отдельном классе TokenizerOfStream, определенном в нашем приложении:
TokenizerOfStream tos = new TokenizerOfStream();
Вслед за этим мы вызываем метод TokenizeIt, определенный в классе TokenizerOfStream, передавая ему в качестве параметра ссылку на входной поток:
tos.TokenizeIt(InStream);
Метод TokenizeIt выполняет разбор входного потока, отображая результаты разбора на консоли. После выполнения разбора входной поток закрывается методом close:
InStream.close();
Самое интересное в нашем приложении связано, очевидно, с классом TokenizerOfStream, поэтому перейдем к его описанию.
В этом классе определен только один метод TokenizeIt:
public void TokenizeIt(InputStream is)
{
  . . .
}
Получая в качестве параметра ссылку на входной поток, он прежде всего создает для него разборщик класса StreamTokenizer:
StreamTokenizer stok;
stok = new StreamTokenizer(is);
Настройка параметров разборщика очень проста и сводится к вызовам всего двух методов:
stok.slashSlashComments(true);
stok.ordinaryChar('.');
Метод slashSlashComments включает режим распознавания комментариев в стиле языка программирования С++, а метод ordinaryChar объявляет символ ‘.’ обычным символом.
После настройки запускается цикл разбора входного потока, причем условием завершения цикла является достижение конца этого потока:
while(stok.nextToken() != StreamTokenizer.TT_EOF)
{
  . . .
}
В цикле анализируется содержимое поля ttype, которое зависит от типа элемента, обнаруженного во входном потоке:
switch(stok.ttype)
{
  case StreamTokenizer.TT_WORD:
  {
    str = new String("\nTT_WORD >" + stok.sval);
    break;
  }
  case StreamTokenizer.TT_NUMBER:
  {
    str = "\nTT_NUMBER >" + Double.toString(stok.nval);


Внутри функции main мы определили несколько переменных.
Массив bKbdInput хранит строку, введенную с клавиатуры.
Массив buf используется для хранения строк (команд), которые сервер получает от клиентских приложений.
В переменной s класса DatagramSocket хранится ссылка на датаграмный сокет, который будет использован для приема команд от клиентских приложений.
Переменная pinp класса DatagramPacket хранит пакеты, полученные сервером из сети.
Переменные SrcAddress (класса InetAddress) и SrcPort типа int хранят, соответственно, адрес и порт узла, отправившего пакет.
Первое, что делает сервер - это создание датаграммного сокета:
s = new DatagramSocket(9998);
Конструктору передается номер порта 9998, на котором сервер будет принимать пакеты данных от клиентских приложений.
После создания сокета сервер создает объекта класса DatagramPacket, в который будут записываться приходящие от клиентов пакеты:
pinp = new DatagramPacket(buf, 512);
Конструктор пакета получает ссылку на массив buf, в который нужно будет записывать приходящие по сети данные, и размер этого массива, равный 512 байт.
Далее сервер запускает цикл приема пакетов и отображения их содержимого.
Пакет принимается простым вызовом метода receive из класса DatagramSocket:
s.receive(pinp);
Этот метод блокирует работу вызвавшей его задачи до тех пор, пока по данному сокету не будет получен пакет данных.
Когда пакет будет получен, наше приложение определяет адрес и порт отправителя:
SrcAddress = pinp.getAddress();
SrcPort    = pinp.getPort();
Эта информация может пригодиться, если вы будете посылать отправителю ответный пакет.
Наше приложение ответные пакеты не посылает. Оно преобразует принятые данные в текстовую староку класса String, добавляет к этой строке номер порта отправителя и отображает эту информацию на консоли:
System.out.println(">  " + str + " < " + "port: " +
  SrcPort);
Цикл приема команд завершается, если от клиента пришла строка “quit”:
if(str.equals("quit"))
  break;
Перед тем как завершить свою работу, наше приложение закрывает датаграммный сокет, вызывая для этого метод close:
s.close();


Внутри метода main определен массив bKbdInput, предназначенный для хранения данных, введенных с клавиатуры, переменная length, в которой хранится размер этих данных, рабочая строка str класса String, датаграммный сокет s и пакет pout класса DatagramPacket.
Прежде всего приложение определяет адрес узла, на котором оно выполняется, вызывая метод getLocalHost:
InetAddress OutAddress = InetAddress.getLocalHost();
Этот адрес будет использован для формирования передаваемого пакета данных.
Затем клиент создает датаграммный сокет, применяя для этого конструктор без параметров:
s = new DatagramSocket();
Напомним, что в этом случае для сокета выделяется любой свободный порт.
На следующем шаге приложение формирует передаваемый пакет, вызывая конструктор класса DatagramPacket:
pout = new DatagramPacket(bKbdInput, bKbdInput.length,
  OutAddress, 9998);
Этому конструктору указывается адрес массива, содержащего введенные с клавиатуры данные, размер этого массива, адрес локального узла, на который нужно передать пакет, и номер порта серверного приложения.
Теперь все готово для запуска цикла передачи команд от клиента к серверу.
В этом цикле выполняется чтение строки с клавиатуры, причем размер прочитанной строки сохраняется в переменной length:
length = System.in.read(bKbdInput);
Далее, если строка не состоит из одного лишь символа перехода на новую строку, она отображается на косоли и посылается серверу методом send:
s.send(pout);
После того как пользователь введет строку “quit”, цикл завершается. Вслед за этим приложение закрывает датаграммный сокет:
s.close();

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