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

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

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

Label Message2;

HTTPpost CGIpost; String CGI;

public CheckOutFrame( String Title, String ItemList, String CGI )

{

super(Title); this.ItemList = ItemList; this.CGI = CGI; setLayout(gridbag);

Name = new TextField(25); Phone = new TextField(25); Done = new Button("Done"); LName = new Label("Your Name");

LPhone= new Label("Phone number");

Message1=new Label("Please enter in the above information and a"); Message2=new Label("sales agent will call to confirm your order"); Con.weightx=.2;

Con.weighty=.2;

Con.anchor = GridBagConstraints.CENTER; Con.fill = GridBagConstraints.NONE; Con.gridwidth = GridBagConstraints.REMAINDER; gridbag.setConstraints(Name, Con); gridbag.setConstraints(LName, Con); add(LName);

add(Name); gridbag.setConstraints(Phone, Con); gridbag.setConstraints(LPhone, Con); add(LPhone);

add(Phone); gridbag.setConstraints(Done, Con); gridbag.setConstraints(Message1, Con); gridbag.setConstraints(Message2, Con); add(Message1);

add(Message2);

add(Done);

pack();

resize(300,300);

show();

}

public boolean action(Event evt, Object arg) { if ("Done".equals(arg))

{

System.out.println(CGI);

CGIpost = new HTTPpost(CGI, "NAME: " + Name.getText() + "\nPHONE: " + Phone.getText() +"\nPURCHASES:\n" + ItemList +

"\n");

System.out.println(CGIpost.results());

dispose(); return true;

}

return false;

}

} // конец CheckOutFrame

Когда пользователь нажимает кнопку Done, мы вызываем HTTPpost и передаем URL CGIпрограмме наряду с данными, которые ввел пользователь. После этого программа завершается. CGI-программа обрабатывает полученные данные, сохраняя их в файле, или отправляет по почте отделу заказов.

Обработка принятых данных при помощи CGI-программы

Мы хотим использовать CGI-программу на сервере Web, чтобы или сохранить информацию о заказе пользователя, или отправить ее по почте в отдел заказов. Не забудьте, что эта программа

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

определена в теге <PARAM> HTML-документа, из которого вызывается класс Store. Реализация CGI может иметь специфические особенности платформы и в настоящее время не очень хорошо подходит для Java. Чтобы получить больше информации по этой теме, проконсультируйтесь с книгой "The Web Server Book" или с какой-нибудь книгой по программированию CGI. Существует несколько свободно доступных библиотек CGI, которые могут облегчить задачу написания CGIпрограммы, делающей то, что вам нужно. Если вы не знаете, откуда начать, связи с ресурсами программирования CGI вы можете найти на странице Online Companion.

Возможные улучшения

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

Поддержка интерфейса "drag and drop". Для этого нужно сделать пиктограмму корзины для покупок так, чтобы пользователь, выбирая товар для приобретения, мог переместить его на пиктограмму, после чего тот будет автоматически добавлен к списку товаров в корзине.

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

Справочные меню. Мы показали, как в нашей программе обрабатывается меню, и выполнить обработчика событий для справочного меню было бы довольно просто.

www.books-shop.com

Глава 18

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

Контракт

Свойства Разработка и исполнение

Взаимодействие с асинхронным сервером Создание шахматной доски

Связь шахматной доски с CIS Написание апплета

Возможные усовершенствования Окно login

Список текущих игроков

Что вы узнаете из этой главы

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

программирование сокетов;

асинхронная передача данных;

многопотоковость;

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

Контракт

Представьте, что некая недавно образованная компания, работающая на Интернет, решила создать бесплатное высококачественное развлечение для Интернет-сообщества с тем, чтобы привлечь пользователей к своим серверам, и заключила с нами контракт на выполнение этой работы. Мы решили, что для этой задачи подойдет программа игры в шахматы, позволяющая людям играть друг с другом. Мы будем использовать в качестве внутреннего интерфейса шахматный сервер (Chess Internet-Server, CIS), потому что он может выполнять такие функции, как установку связи, подбор игроков, ведение счета, проверку правильности ходов. Однако несколько ограничивает возможности сервера то, что его интерфейс является символьным. На рис. 18-1 изображена текстовая шахматная доска, предоставляемая сервером.

Рис. 18.1.

CIS распространяется бесплатно на условиях публичной лицензии GNU; его можно найти по адресу ftp://chess.onenet.net/pub/chess/Unix.

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

www.books-shop.com

пользователя) в виде апплета, мы одновременно решаем две задачи: во-первых, клиент может работать на любом компьютере, на котором установлен Java, во-вторых, у пользователей не возникает проблем с его инсталляцией. Ограничения, введенные в Netscape Navigator и связанные с безопасностью апплетов, не мешают нам благодаря тому, что сервер и клиент могут быть установлены на одном и том же компьютере.

Свойства

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

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

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

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

разработку процедуры общения с сервером;

создание общей шахматной доски;

создание класса, соединяющего шахматную доску с CIS.

Клиент служит связующим звеном между CIS и пользователем; он должен принимать пожелания пользователя и переводить их на язык команд CIS, и наоборот - принимать выходные данные CIS и переводить их в дружественный для пользователя формат.

Взаимодействие с асинхронным сервером

Из главы 13 вы знаете, как с помощью сокетов устанавливать соединение между апплетом и сервером. Однако мы еще не затрагивали режима асинхронного соединения. Простой сервер, например Finger, обслуживает синхронные потоки данных - он всегда ожидает ввода команды и только после этого передает данные. Некоторые серверы являются асинхронными - они умеют высылать данные в любой подходящий момент времени. К ним относится и наш шахматный сервер. Это значит, что, если один пользователь пошлет данные серверу для другого пользователя, данные будут переданы этому пользователю без всякого предупреждения.

Мы решили разработать специальный приемник данных, работающий в отдельном потоке. Благодаря этому мы будем уверены, что, как бы апплет не был сильно занят в текущий момент, данные, пришедшие из сети, будут приняты. Приемник данных считывает данные в виде значений типа String, получаемых из потока InputStream, разделяя строки при появлении символов EOL или EOF. Давайте опишем интерфейс Listener, позволяющий это делать:

package ventana.io;

public interface Listener {

public void receiveInput(InputStreamHandler i, Object o);

}

Этот метод позволяет приемнику данных - классу InputStreamHandler - передавать значения String, считанные из входного потока InputStream, другому объекту с помощью метода receiveInput. Listener способен принимать любые объекты, но в данном случае мы будем передавать через него только значения String. Кроме того, мы возвращаем ссылку на

www.books-shop.com

InputStreamHandler. Она пригодится, если нам понадобится использовать и различать одновременно несколько потоков

InputStreamHandler: package ventana.io; import java.io.*;

public class InputStreamHandler implements Runnable {<Com private Thread engine;

private DataInputStream in; private Listener client;

public InputStreamHandler(InputStream in, Listener client) {

this.in = new DataInputStream(in); this.client = client;

engine = new Thread(this); engine.start();

}

public void close() { if (in!=null) {

try {in.close();} catch(IOException e) {}

}

engine.stop();

}

public void run() {

if (in == null) {return;} String s;

try {s = in.readLine();} catch (IOException e) { return;

}}

while (s != null && engine.isAlive()) { client.receiveInput(this,s); try {s = in.readLine();}

catch (IOException e) {s = null;}

}

}

}

Когда объект InputStreamHandler сконструирован, он создает DataInputStream на базе переданного ему объекта InputStream и запускает поток. Метод run считывает строки из DataInputStream и передает их клиенту Listener с помощью метода receiveInput. Если происходит исключение IOException, InputStreamHandler просто прекращает считывание данных.

Создание шахматной доски

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

Наша шахматная доска является расширением класса Canvas, который, в свою очередь, является более-менее пригодной для работы реализацией абстрактного класса Component. Мы решили не использовать для доски класс Container по двум причинам. Во-первых, изображения шахматных фигур не принадлежат классу Component и, следовательно, не могут напрямую добавляться к объекту Container. Во-вторых, современная реализация Java API для Microsoft Windows неправильно работает в том случае, если объект Container содержит большое количество объектов Component.

Класс ChessBoard несет ответственность за прорисовку шахматной доски и генерацию ходов. Общие методы класса ChessBoard перечислены в табл. 18-1.

 

Таблица 18-1. Общие методы класса ChessBoard

Метод

Описание

setImage(Image, String) Задает квадрат, указанный данной строкой и содержащий данное

www.books-shop.com

 

изображение, и перерисовывает квадрат. Строка должна

 

представлять собой шахматное обозначение поля, например "А1".

endGame()

Заканчивает шахматную партию.

setGenerateMoves(boolean) Указывает, разрешить ли пользователю делать ход.

setOrientation

Ориентация доски - белыми вниз (true) или белыми вверх (false).

boolean getOrientation()

Возвращает текущую ориентацию доски.

Класс ChessBoard содержит двумерный массив изображений. Это и есть шахматные поля. ChessBoard разрешает доступ к массиву полей методом setImage. Метод setImage не определяет индексы массива непосредственно, а позволяет указать в строке с шахматным обозначением, какое поле изменять. Переход от шахматных обозначений к индексам массива осуществляется внутренними средствами, что позволяет выполнять его по-разному, в зависимости от ориентации доски - белыми вниз или белыми вверх. Например, поле "А1" должно быть в левом нижнем углу, если наш пользователь играет белыми, но в правом верхнем углу, если он играет черными.

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

Вот текст программы для нашего класса ChessBoard:

import java.awt.*; import java.util.*;

public class ChessBoard extends Canvas { private Image[][] squares; private boolean up = true; private boolean generate = false; private Point selected;

private Color dark; private Color light; public ChessBoard() {

squares = new Image[8][8]; dark = Color.gray;

light = Color.lightGray; resize(320,320); repaint();

}

Метод EndGame вызывается, когда партия закончена. Он делает все поля темными, чтобы показать пользователю, что партия завершена, а затем перерисовывает доску. Наконец, он пресекает попытки пользователя делать еще ходы:

public void endGame() {

dark = Color.darkGray; light = Color.lightGray; repaint();

}

public void setGenerateMoves(boolean b) { generate = b;

}

public void setOrientation(boolean b) { up = b;

}

public boolean getOrientation() { return up;

}

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

www.books-shop.com

private String getSquare(int x, int y) { char ary[] = new char[2];

if (up) {

ary[0] = (char)('a'+x); ary[1] = (char)('1'+(7-y));

} else {

ary[0] = (char)('a'+(7-x)); ary[1] = (char)('1'+y);

}

return new String(ary);

}

Класс, ответственный за реальную игру в шахматы, использует метод setImage для того, чтобы вставлять в поля шахматные фигуры. Метод setimage берет изображение - возможно, это изображение фигуры - и строку, указывающую, в какое поле поместить изображение. Если новое изображение отличается от того, что находилось в поле, поле обновляется и перерисовывается. Чтобы удалить фигуру с поля, можно просто поставить в качестве изображения нулевой указатель (null):

public void setImage(Image i, String s) { int x = (int)(s.charAt(0)-'a'); int y = (int)(s.charAt(1)-1-'0'); if (up) {

y = 7-y;

}else {

x= 7-x;

if (squares[x][y]!=i) { squares[x][y] = i;

paintSquare(x,y,false,getGraphics());

}

public boolean mouseDown(Event evt, int px, int py) { if (!generate) {

return false;

}

int x = px/40; int y = py/40;

if (selected==null) {

selected = new Point(x,y); paintSquare(x,y,true,getGraphics());

}else if (selected.x==x && selected.y==y) { selected = null; paintSquare(x,y,false,getGraphics());

}else {

String move = getSquare(selected.x,selected.y); move = move+"-"+getSquare(x,y); System.out.println("New move: "+move);

Event e = new Event(getParent(),Event.ACTION_EVENT,move); getParent().deliverEvent(e);

paintSquare(selected.x,selected.y,false,getGraphics()); selected = null;

}

return true;

}

public synchronized Dimension preferredSize() { return new Dimension(320,320);

}

public synchronized Dimension minimumSize() { return new Dimension(320,320);

}

www.books-shop.com

Метод paintSquare отвечает за прорисовку отдельных полей. По заданному массиву индексов он закрашивает поле соответственно светлым или темным цветом (левое нижнее поле всегда светлое) и рисует изображение шахматной фигуры, если она есть на этом поле. Булевский аргумент указывает, надо ли рисовать красный кружок (обозначающий выделение поля):

public void paintSquare(int x, int y, boolean b, Graphics g) { if ((x%2==0 && y%2==0)||(x%2==1 && y%2==1)) {

g.setColor(light); } else {

g.setColor(dark);

}

g.fillRect(x*40,y*40,40,40); if (squares[x][y]!=null) {

g.drawImage(squares[x][y],x*40,y*40,this);

}

if (b) { g.setColor(Color.red);

g.fillOval(x*40+15,y*40+15,10,10);

}

}

public void paint(Graphics g) { for (int y=0; y<8; y++) {

for (int x=0; x<8; x++) { paintSquare(x,y,false,g);

}

}

}

}

Связь шахматной доски с CIS

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

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

Установка секундомера

Вместо того чтобы просто показывать, сколько осталось времени, воспользуемся возможностью языка Java работать с потоками и сделаем так, чтобы часы отсчитывали секунды для активного в данный момент игрока. Класс Label дает общее нередактируемое поле выходного текста. Расширим его в класс StopWatch (секундомер), чтобы получить метку, которая показывает текущее время и каждую секунду уменьшает переменную, содержащую величину времени. Общие методы класса StopWatch приводятся в табл. 18-2.

Таблица 18-2. Общие методы класса StopWatch

Метод Описание

set(int) Задает и показывает оставшееся время. start() Начинает отсчет времени.

stop() Прекращает отсчет времени. clear() Полностью останавливает часы.

Приведем программу для StopWatch:

www.books-shop.com

import java.awt.*;

public class StopWatch extends Label implements Runnable { private Thread engine;

private int time=0; public StopWatch() {

super();

engine = new Thread(this); engine.setPriority(3);

}

public StopWatch(int i) { this();

set(i);

}

Метод set устанавливает время, оставшееся на секундомере. Он должен быть синхронизирован, потому что разные пары игроков могут попытаться установить часы одновременно. Если время не отрицательно, переустанавливаем выходную метку, чтобы показать время:

public synchronized void set(int i) { time = 1;

if (time>0) {

int min = time/60; int sec = time%60; if (sec>9) {

setText(""+min+":"+sec);

}else { setText(""+min+":0"+sec);

}

}else {

setText("0:00");

}

}

public void start() {

if (engine!=null) {

if (!engine.isAlive()) { engine.start();

} else { engine.resume();

}

}

}

public void stop() {

if (engine!=null) { engine.suspend();

}

}

public void clear() {

if (engine!=null) { engine.stop(); engine = null;

}

}

public void run() {

while (engine!=null && engine.isAlive()) { set(time-1);

try {

engine.sleep(1000);

} catch (InterruptedException e) {}

}

}

}

www.books-shop.com

Реализация ChessFrame

Теперь мы можем приступить к реализации ChessFrame. В табл. 18-3 приведены общие методы класса ChessFrame.

 

Таблица 18-3. Методы ChessFrame

Метод

Описание

updateGame(String) Обновляет состояние кадра для отражения текущего состояния игры.

endGame()

Заканчивает текущую игру.

ChessFrame в первую очередь несет ответственность за разбор сообщений об обновлении игрового поля и выводе соответствующей информации. Сообщение об обновлении по умолчанию было приведено выше на рис. 18-1. CIS дает возможность выбрать из имеющегося набора стилей игровых досок. Стиль, выбранный нами под номером 12, посылает сообщение об обновлении в виде одной строки, содержащей несколько полей, разделенных пробелами:

<12> rnbqkbnr pppppppp ---- ----- ---- ---- PPPPPPPP RNBQKBNR W -1 1 1 1 1 0 1 donald donald 2 0 0 39 39 0 0 1 none (0:00) none 0

Первое поле показывает, что строка содержит сообщение об обновлении игровой доски в формате 12. Родитель ChessFrame будет использовать это поле для того, чтобы определить необходимость вызова метода updateGame. Следующие восемь полей содержат информацию о текущем состоянии доски. Каждой фигуре соответствует определенная буква; белые отмечаются прописными буквами, а черные - строчными. Дефисы обозначают клетки, не содержащие никаких фигур. Остальные поля мы обсудим позже, по мере рассмотрения текста метода updateGame.

Вот исходный текст класса ChessFrame:

import ChessBoard; import ChessClient; import PackerLayout; import java.awt.*; import java.util.*; import ventana.awt.*;

public class ChessFrame extends Frame { private ChessBoard board; private ChessClient parent; private String user;

private Panel players; private Panel whitePlayer; private Panel blackPlayer; private Label whiteName; private Label blackName; private StopWatch whiteTime; private StopWatch blackTime;

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

public ChessFrame(ChessClient parent, String user, Font fixed) { super("Chess Game");

this.user = user; this.parent = parent;

setLayout(new PackerLayout()); whitePlayer = new Panel(); whitePlayer.setLayout(new PackerLayout()); whiteName = new Label("-------"); whiteName.setFont(fixed); whiteName.setAlignment(Label.CENTER); whiteTime = new StopWatch();

www.books-shop.com