Конспект лекций по курсу Выбранные вопросы информатики (часть 2) для специальности Информатика Графика

Вид материалаКонспект
Описание исходного текста аплета ShowChart
Поля класса ShowChart
Метод init
Метод paint
Приложения SocketServ и SocketClient
Описание исходного текста серверного приложения SocketServ
Описание исходного текста клиентского приложения SocketClient
Аплет Form
Исходные тексты аплета Form
Описание исходныех текстов аплета Form
Исходный текст программы CGI store.exe
Подобный материал:
1   ...   9   10   11   12   13   14   15   16   17

If codebase=. your browser recognized the applet tag, you would see an applet here.






Описание исходного текста аплета ShowChart

Аплет ShowChart получает содержимое файла исходных данных для построения круговой диаграммы с помощью класса URL. Как вы увидите, для получения содержимого этого файла оно создает поток ввода явным образом.

Поля класса ShowChart

В классе ShowChart определены пять полей.

URL SrcURL;

Object URLContent;

int errno = 0;

String str;

byte buf[] = new byte[200];

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

Поле str хранит принятую строку, которая предварительно записывается во временный буфер buf.

Метод init

Во время инициализации метод init создает объект класса URL для файла исходных данных:

SrcURL = new URL("art.txt");

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

Далее для нашего объекта URL мы создаем поток ввода и получаем содержимое файла (то есть исходные данные для построения диаграммы):

InputStream is = SrcURL.openStream();

is.read(buf);

Принятые данные записываются в буфер buf и затем преобразуются к типу String с помощью соответствующего конструктора:

str = new String(buf, 0);

Если при создании объекта класса URL возникло исключение, метод init записывает в поле errno код ошибки, равный 2, записывая при этом в строку состояния браузера сообщение "MalformedURLException exception".

В том случае, когда объект класса URL создан успешно, а исключение возникло в процессе чтения содержимого файла, в поле errno записывается значение 1, а в строку состояния браузера - сообщение "read exception".

Метод paint

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

showStatus(sChart);

Далее создается разборщик строки исходных данных:

StringTokenizer st =

new StringTokenizer(sChart, ",\r\n");

В качестве разделителей для этого разборщика указывается запятая, символ возврата каретки и перевода строки.

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

while(st.hasMoreElements())

{

. . .

}

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

rColor = (int)(255 * Math.random());

gColor = (int)(255 * Math.random());

bColor = (int)(255 * Math.random());

g.setColor(new Color(rColor, gColor, bColor));

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

String angle = (String)st.nextElement();

Далее с помощью конструктора класса Integer это значение преобразуется в численное:

AngleFromChart = new Integer(angle);

Рисование сектора круговой диаграммы выполняется с помощью метода fillArc:

g.fillArc(0, 0, 200, 200, PrevAngle, AngleFromChart.intValue());

В качестве начального значения угла сектора используется значение из переменной PrevAngle. Сразу после инициализации в эту переменную записывается нулевое значение.

Конечный угол сектора задается как AngleFromChart.intValue(), то есть указывается значение, полученное из принятого по сети файла исходных данных.

После завершения рисования очередного сектора круговой диаграммы начальное значение PrevAngle увеличивается на величину угла нарисованного сектора:

PrevAngle += AngleFromChart.intValue();


Приложения SocketServ и SocketClient

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

Приложение SocketServ выводит на консоль строку "Socket Server Application" и затем переходит в состояние ожидания соединения с клиентским приложением SocketClient.

Приложение SocketClient устанавливает соединение с сервером SocketServ, используя потоковый сокет с номером 9999 (этот номер выбран нами произвольно). Далее клиентское приложение выводит на свою консоль приглашение для ввода строк. Введенные строки отображаются на консоли и передаются серверному приложению. Сервер, получив строку, отображает ее в своем окне и посылает обратно клиенту. Клиент выводит полученную от сервера строку на консоли.

Когда пользователь вводит строку "quit", цикл ввода и передачи строк завершается.

Весь процесс показан на рис. 3 и 4.



Рис. 3. Окно клиентского приложения



Рис. 4. Окно серверного приложения

Здесь в окне клиентского приложения мы ввели несколько строк, причем последняя строка была строкой "quit", завершившая работу приложений.

Исходный текст серверного приложения SocketServ

Исходный текст серверного приложения SocketServ приведен в листинге 3.

Листинг 3. Файл SocketServ.java

import java.io.*;

import java.net.*;

import java.util.*;

public class SocketServ

{

public static void main(String args[])

{

byte bKbdInput[] = new byte[256];

ServerSocket ss;

Socket s;

InputStream is;

OutputStream os;

try

{

System.out.println("Socket Server Application");

}

catch(Exception ioe)

{

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

}

try

{

ss = new ServerSocket(9999);

s = ss.accept();

is = s.getInputStream();

os = s.getOutputStream();

byte buf[] = new byte[512];

int lenght;

while(true)

{

lenght = is.read(buf);

if(lenght == -1)

break;

String str = new String(buf, 0);

StringTokenizer st;

st = new StringTokenizer(str, "\r\n");

str = new String((String)st.nextElement());

System.out.println("> " + str);

os.write(buf, 0, lenght);

os.flush();

}

is.close();

os.close();

s.close();

ss.close();

}

catch(Exception ioe)

{

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

}

try

{

System.out.println("Press to terminate application...");

System.in.read(bKbdInput);

}

catch(Exception ioe)

{

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

}

}

}

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

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

Массив bKbdInput размером 256 байт предназначен для хранения строк, введенных при помощи клавиатуры.

В переменную ss класса ServerSocket будет записана ссылка на объект, предназначенный для установления канала связи через потоковый сокет (но не ссылка на сам сокет):

ServerSocket ss;

Ссылка на сокет, с использованием которого будет происходить передача данных, хранится в переменной с именем s класса Socket:

Socket s;

Кроме того, мы определили переменные is и os, соответственно, классов InputStream и OutputStream:

InputStream is;

OutputStream os;

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

После отображения на консоли строки названия приложения, метод main создает объект класса ServerSocket, указывая конструктору номер порта 9999:

ss = new ServerSocket(9999);

Конструктор возвращает ссылку на объект, с использованием которого можно установить канал передачи данных с клиентом.

Канал устанавливается методом accept:

s = ss.accept();

Этот метод переводит приложение в состояние ожидания до тех пор, пока не будет установлен канал передачи данных.

Метод accept в случае успешного создания канала передачи данных возвращает ссылку на сокет, с применением которого нужно принимать и передавать данные.

На следующем этапе сервер создает входной и выходной потоки, вызывая для этого методы getInputStream и getOutputStream, соответственно:

is = s.getInputStream();

os = s.getOutputStream();

Далее приложение подготавливает буфер buf для приема данных и определяет переменную length, в которую будет записываться размер принятого блока данных:

byte buf[] = new byte[512];

int lenght;

Теперь все готово для запуска цикла приема и обработки строк от клиентского приложения.

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

lenght = is.read(buf);

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

Метод read возвращает размер принятого блока данных или -1, если поток исчерпан. Мы воспользовались этим обстоятельством для завершения цикла приема данных:

if(lenght == -1)

break;

После завершения приема блока данных мы преобразуем массив в текстовую строку str класса String, удаляя из нее символ перевода строки, и отображаем результат на консоли сервера:

System.out.println("> " + str);

Затем полученная строка отправляется обратно клиентскому приложению, для чего вызывается метод write:

os.write(buf, 0, lenght);

Методу write передается ссылка на массив, смещение начала данных в этом массиве, равное нулю, и размер принятого блока данных.

Для исключения задержек в передаче данных из-за накопления данных в буфере (при использовании буферизованных потоков) необходимо принудительно сбрасывать содержимое буфреа метдом flush:

os.flush();

И хотя в нашем случае мы не пользуемся буферизованными потоками, мы включили вызов этого метода для примера.

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

Наше приложение явням образом закрывает входной и выходной потоки данных, сокет, а также объект класса ServerSocket, с использованием которого был создан канал передачи данных:

is.close();

os.close();

s.close();

ss.close();

Исходный текст клиентского приложения SocketClient

Исходный текст клиентского приложения SocketClient приведен в листинге 4.

Листинг 4. Файл SocketClient.java

import java.io.*;

import java.net.*;

import java.util.*;

public class SocketClient

{

public static void main(String args[])

{

byte bKbdInput[] = new byte[256];

Socket s;

InputStream is;

OutputStream os;

try

{

System.out.println("Socket Client Application" + "\nEnter any string or" + " 'quit' to exit...");

}

catch(Exception ioe)

{

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

}

try

{

s = new Socket("localhost",9999);

is = s.getInputStream();

os = s.getOutputStream();

byte buf[] = new byte[512];

int length;

String str;

while(true)

{

length = System.in.read(bKbdInput);

if(length != 1)

{

str = new String(bKbdInput, 0);

StringTokenizer st;

st = new StringTokenizer(str, "\r\n");

str = new String((String)st.nextElement());

System.out.println("> " + str);

os.write(bKbdInput, 0, length);

os.flush();

length = is.read(buf);

if(length == -1)

break;

str = new String(buf, 0);

st = new StringTokenizer(str, "\r\n");

str = new String((String)st.nextElement());

System.out.println(">> " + str);

if(str.equals("quit"))

break;

}

}

is.close();

os.close();

s.close();

}

catch(Exception ioe)

{

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

}

try

{

System.out.println("Press to " + "terminate application...");

System.in.read(bKbdInput);

}

catch(Exception ioe)

{

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

}

}

}

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

Внутри метода main клиентского приложения SocketClient определены переменные для ввода строки с клавиатуры (массив bKbdInput), сокет s класса Socket для работы с сервером SocketServ, входной поток is и выходной поток os, которые связаны с сокетом s.

После вывода на консоль приглашающей строки клиентское приложение создает сокет, вызывая конструктор класса Socket:

s = new Socket("localhost",9999);

В процессе отладки мы запускали сервер и клиент на одном и том же узле, поэтому в качестве адреса сервера указана строка "localhost". Номер порта сервера SocketServ равен 9999, поэтому мы и передали конструктору это значение.

После создания сокета наше клиентское приложение создает входной и выходной потоки, связанные с этим сокетом:

is = s.getInputStream();

os = s.getOutputStream();

Теперь клиентское приложение готово обмениваться данными с сервером.

Этот обмен выполняется в цикле, условием завершения которого является ввод пользователем строки "quit".

Внутри цикла приложение читает строку с клавиатуры, записывая ее в массив bKbdInput:

length = System.in.read(bKbdInput);

Количество введенных символов сохраняется в переменной length.

Далее если пользователь ввел строку, а не просто нажал на клавишу , эта строка отображается на консоли и передается серверу:

os.write(bKbdInput, 0, length);

os.flush();

Сразу после передачи сбрасывается буфер выходного потока.

Далее приложение читает ответ, посылаемый сервером, в буфер buf:

length = is.read(buf);

Напомним, что наш сервер посылает клиенту принятую строку в неизменном виде.

Если сервер закрыл канал, то метод read возвращает значение -1. В этом случае мы прерываем цикл ввода и передачи строк:

if(length == -1)

break;

Если же ответ сервера принят успешно, принятые данные записываются в строку str, которая отображается на консоли клиента:

System.out.println(">> " + str);

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

is.close();

os.close();

s.close();


Аплет Form

На примере аплета Form мы покажем, как приложения Java могут взаимодействовать с расширениями сервера Web, такими как программы CGI или приложения ISAPI.

В окне нашего аплета находится форма, содержащая два однострочных поля редактирования, кнопку и многострочное поле редактирования (рис. 5).



Рис. 5. Окно аплета Form

Эта форма предназначена для добавления записей в базу данных, содержащую электронные почтовые адреса. Заполнив поля имени и адреса E-Mail, пользователь должен нажать кнопку Send. При этом введенная информация будет передана расширению сервера CGI, который запишет ее в базу данных, а затем отправит обратно аплету. Сохраненные записи, полученные от программы CGI, аплет FORM отобразит в многострочном поле редактирования, как это показано на рис. 5.

Исходные тексты аплета Form

Исходные тексты аплета Form представлены в листинге 5.

Листинг 5. Файл Form.java

import java.applet.*;

import java.awt.*;

import java.net.*;

import java.io.*;

import java.util.*;

public class Form extends Applet

implements Runnable

{

private Thread m_store = null;

TextField txtName;

TextField txtEMail;

TextArea txta;

Button btnGetText;

public void init()

{

Label lbName;

Label lbEMail;

Label lbPress;

lbName = new Label("Enter your name:");

lbEMail = new Label("Enter your E-Mail address:");

add(lbName);

txtName = new TextField("Your name", 40);

add(txtName);

add(lbEMail);

txtEMail = new TextField("your@email", 40);

add(txtEMail);

btnGetText = new Button("Send!");

add(btnGetText);

txta = new TextArea(8, 65);

add(txta);

setBackground(Color.yellow);

}

public void paint(Graphics g)

{

setBackground(Color.yellow);

Dimension dimAppWndDimension = getSize();

g.setColor(Color.black);

g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1);

}

public boolean action(Event evt, Object obj)

{

Button btn;

if(evt.target instanceof Button)

{

btn = (Button)evt.target;

if(evt.target.equals(btnGetText))

{

startTransaction();

}

else

return false;

return true;

}

return false;

}

void startTransaction()

{

m_store = new Thread(this);

m_store.start();

}

public void stop()

{

if (m_store != null)

{

m_store.stop();

m_store = null;

}

}

public void run()

{

URL u;

URLConnection c;

PrintStream ps;

DataInputStream is;

try

{

String szSourceStr = txtName.getText() + ", " + txtEMail.getText();

String szReceived;

String szURL = "ripts/store.exe";

u = new URL(szURL);

c = u.openConnection();

ps = new PrintStream(c.getOutputStream());

ps.println(szSourceStr);

ps.close();

is = new DataInputStream(c.getInputStream());

szReceived = is.readLine();

is.close();

txta.appendText(szReceived + "\r\n");

repaint();

}

catch (Exception ioe)

{

showStatus(ioe.toString());

stop();

}

}

}

Исходный текст документа HTML, который был подготовлен для нас системой Java Workshop, мы немного отредактировали, изменив параметр CODEBASE (листинг 6).


Описание исходныех текстов аплета Form

При инициализации метод init создает все необходимые органы управления и добавляет их в окно аплета.

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

if(evt.target.equals(btnGetText))

{

startTransaction();

}

Метод startTransaction, определенный в нашем приложении, создает и запускает на выполнение поток, который и будет взаимодействовать с программой CGI:

void startTransaction()

{

m_store = new Thread(this);

m_store.start();

}

При этом в качестве отдельного потока, работающего одновременно с кодом аплета, выступает метод run. Именно в нем сосредоточена вся логика обмена данными с сервером Web.

Так как в процессе взаимодействия могут возникать различные исключения, мы предусмотрели их обработку при помощи блока try-catch:

URL u;

URLConnection c;

PrintStream ps;

DataInputStream is;

try

{

. . .

}

catch (Exception ioe)

{

showStatus(ioe.toString());

stop();

}

Название возникшего исключения будет отображено в строке состояния браузера.

Теперь о том, что делает метод run после получения управления.

Первым делом он извлекает из однострочных текстовых полей имя и электронный адрес, объединяя их и записывая полученную текстовую строку в поле szSourceStr:

String szSourceStr = txtName.getText() + ", " + txtEMail.getText();

В строке szURL находится адрес URL программы CGI:

String szURL = "ripts/store.exe";

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

На следующем этапе метод run создает для программы CGI объект класса URL и открывает с ним соединение:

u = new URL(szURL);

c = u.openConnection();

Пользуясь этим соединением, метод run создает форматированный поток вывода, записывает в него строку имени и электронного адреса, а затем закрывает поток:

ps = new PrintStream(c.getOutputStream());

ps.println(szSourceStr);

ps.close();

Переданные таким образом данные попадут в стандартный поток ввода программы CGI, откуда она их и прочитает.

Сделав это, программа CGI запишет в стандартный выходной поток строку ответа, которую необходимо прочитать в методе run нашего аплета. Для этого мы открываем входной поток, создаем на его основе форматированный входной поток данных, читаем одну строку текста и закрываем входной поток:

is = new DataInputStream(c.getInputStream());

String szReceived;

szReceived = is.readLine();

is.close();

Сразу после этого программа CGI завершит свою работу и будет готова к обработке новых запросов на добавление записей. Что же касается метода run, то он добавит полученную от расширения сервера текстовую строку в многострочное окно редактирования, как это показано ниже, а затем инициирует перерисовку окна аплета:

txta.appendText(szReceived + "\r\n");

repaint();

Заметим, что использованный нами способ передачи данных подходит только для латинских символов. Если вам нужно передавать символы кириллицы, следует преобразовывать их из кодировки UNICODE, например, в гексадецимальную кодировку, а в программе CGI выполнять обратное преобразование. Аналогичную методику можно применять и для передачи произвольных двоичных данных.

Исходный текст программы CGI store.exe

Исходный текст программы CGI store.exe очень прост и показан в листинге 7.

Листинг 7. Файл store.c

#include

#include

#include

#include

#include

#include

void main(int argc, char *argv[])

{

int nInDatasize;

char * szMethod;

char szBuf[2000];

FILE *fDatabase;

CRITICAL_SECTION csAddRecord;

szMethod = getenv("REQUEST_METHOD");

if(!strcmp(szMethod, "POST"));

{

nInDatasize = atoi(getenv("CONTENT_LENGTH"));

fread(szBuf, nInDatasize, 1, stdin);

szBuf[nInDatasize] = '\0';

InitializeCriticalSection(&csAddRecord);

EnterCriticalSection(&csAddRecord);

fDatabase = fopen("c:\\EMAIL.DAT", "a+");

if(fDatabase != NULL)

{

fputs(szBuf, fDatabase);

fclose(fDatabase);

}

LeaveCriticalSection(&csAddRecord);

DeleteCriticalSection(&csAddRecord);

printf("Content-type: text/plain\r\n\r\n");

printf("Stored information: %s", szBuf);

}

}

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

Свою работу программа CGI начинает с анализа переменной среды REQUEST_METHOD. Убедившись, что при запуске программы ей передали данные методом POST, программа определяет размер этих данных исходя из содержимого переменной среды CONTENT_LENGTH.

Далее программа считывает соответствующее количество байт данных из стандартного потока ввода, записывает их в файл. Затем, после добавления заголовка "Stored information:", программа CGI записывает полученную строку в стандартный выходной поток, передавая ее таким образом аплету Form.

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

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