Распределенность
Язык Java предназначен для создания программ, которые работают в распределенной среде Internet на базе протоколов TCP/IP. На самом деле, доступ к ресурсам за помощью URL отличается от доступа к файлу. Кроме того, в Java присутствует явный уклон передачи сообщений в границах внутреннего адресного пространства. Это позволяет обеспечить удаленное исполнение процедур. Эти интерфейсы включены в пакет RMI (remote method invocation). Этот уклон дает высокий уровень абстракции в программировании для среды клиент/сервер.
Java-программы несут в себе значительный обзор информации о типах времени исполнения (run-time type information), которое используется для разрешения доступа к объектам во время работы программы. Это позволяет обеспечить безопасную и оптимальную компоновку. При таком способе достигается безопасность среды исполнения программы.
Доступность инструментария и эффективность разработок
Отмеченная выше простота программирования на Java является причиной того, что разработки на Java будут стоить дешевле аналогичных на более сложных языках программирования. Этому же способствует и переносимость программ на Java, поскольку ликвидируются затраты, связанные с адаптацией программ на конкретных платформах. К тому же интегрированные программы оболочки для разработки Java программ стоят намного дешевле (70-100 долларов), чем аналогичные продукты С++,Ъ (примерно 1000 долларов). А набор инструментария для пакетной компиляции Java программ JDK (Java Development Kit) является к тому же freeware. Поэтому платформу Java можно рекомендовать как идеальную для создания некоммерческих программных продуктов.
Перспективы развития
Программы на Java могут найти различное развитие в учебном процессе: интерактивные обучающие программы (HTML в совокупности с Java), программы-тесты и особенно деловые игры. Дополнительные преимущества можно получить, если писать эти программы в виде апплетов, которые инициализируются с Web сервером внутренней сети Intranet. Такой способ можно использовать, инсталлируя программы на многих компьютерах – пользователь просто запускает Web -браузер и загружает нужную страничку. Для тестовых программ, написанных на Java с использованием архитектуры клиент/сервер, можно повысит степень конфиденциальности. База данных тестовых вопросов располагается на сервере в каталоге с ограниченным доступом. Когда пользователь загружает апплет, он автоматически подключается к программам-серверам, которые исполняются на сервере и могут выдавать вопросы из базы данных в соответствии с запросом пользователя. При таком способе исключается кража базы данных, так как отсутствует физический способ к серверу у пользователя.
Немаловажный вопрос – обучающие деловые игры. Под такой игрой будем понимать игру, в которой участвует неограниченное количество пользователей, целью которых является обмен информации между собой. Система безопасности Java накладывает ограничения, вследствие чего апплет может восстанавливать соединения только с хостом., с которого он был загружен, и ни с каким другим. Но эти ограничения легко обходятся: на сервере исполняется программа-сервер, с которой соединяются все клиенты и через которую осуществляется обмен информации. Таким образом, такая программа должна иметь архитектуру клиент/сервер. В качестве примера приведу упрощенную chat -программу. В ней реализованы базовые принципы для создания деловой игры.
Серверная частичная программа взята из книги Нотон П., Шилдт Г. «Полный справочник по Java»: - Киев, «Диалектика», 1997 и является практически универсальной для программ такого класса. Возможны легкие доработки этой программы в сторону расширения количества протокольных команд. Программа работает следующим образом: пользователь запускает апплет из сервера, вводит свой идентификатор и видит перечень идентификаторов пользователей, которые подключены к серверу. После чего пользователь имеет возможность обмениваться сообщениями с другими.
Программа состоит из четырех классов: Server, ClientConnection, Client и ServerConnection. Первые два класса относятся к серверной части, вторые – к клиентской.
Класс Server import java.net.*;
import java.io.*;
import java.util.*; public class Server implements Runnable {
private int port = 6564;
private Hashtable idcon = new Hashtable();
private int id = 0;
static final String CRLF = "\r\n"; synchronized void addConnection(Socket s) {
ClientConnection con = new ClientConnection(this, s, id);
id++;
} synchronized void set(String the_id, ClientConnection con) {
idcon.remove(the_id) ;
con.setBusy(false);
Enumeration e = idcon.keys();
while (e.hasMoreElements()) {
String id = (String)e.nextElement();
ClientConnection other = (ClientConnection) idcon.get(id);
if (!other.isBusy())
con.write("add " + other + CRLF);
}
idcon.put(the_id, con);
broadcast(the_id, "add " + con);
} synchronized void sendto(String dest, String body) {
ClientConnection con = (ClientConnection)idcon.get(dest);
if (con != null) {
con.write(body + CRLF);
}
} synchronized void broadcast(String exclude, String body) {
Enumeration e = idcon.keys();
while (e.hasMoreElements()) {
String id = (String)e.nextElement();
if (!exclude.equals(id)) {
ClientConnection con = (ClientConnection) idcon.get(id);
con.write(body + CRLF);
}
}
} synchronized void delete(String the_id) {
broadcast(the_id, "delete " + the_id);
} synchronized void kill(ClientConnection c) {
if (idcon.remove(c.getId()) == c) {
delete(c.getId());
}
} public void run() {
try {
ServerSocket acceptSocket = new ServerSocket(port);
System.out.println("Server listening on port " + port);
while (true) {
Socket s = acceptSocket.accept();
addConnection(s);
}
} catch (IOException e) {
System.out.println("accept loop IOException: " + e);
}
} public static void main(String args[]) {
new Thread(new Server()).start();
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
}
}
}
Этот небольшой класс реализует программу-сервер. Точка входа программы – функция main. В программе используется главный поток, в котором создается объект стандартного класса ServerSocket. Этот объект присоединяется к определенному порту, и в цикле осуществляется проверка на подключение клиента к порту. Во время такого подключения создается объект типа ClientConnection и обрабатываются протокольные команды. Класс Server является универсальным и ничего не знает про сообщения, которыми обмениваются игроки (клиенты). В нем реализовываются только подключение/отключение клиентов, передача строки определенному клиенту и функция broadcast, которая согласно названию передает сообщения всем зарегистрированным клиентам.
Класс Client Connection: import java.net.*;
import java.io.*;
import java.util.*; class ClientConnection implements Runnable {
private Socket sock;
private DataInputStream in;
private OutputStream out;
private String host;
private Server server;
private static final int bufsize = 8192;
private byte buffer[] = new byte[bufsize];
private static final String CRLF = "\r\n";
private String name = null;
private String id;
private boolean busy = false; public ClientConnection(Server srv, Socket s, int i) {
try {
server = srv;
sock = s;
in = new DataInputStream(s.getInputStream());
out = s.getOutputStream();
host = s.getInetAddress().getHostName();
id = "" + i;
// tell the new one who it is...
write("id " + id + CRLF); new Thread(this).start();
} catch (IOException e) {
System.out.println("failed ClientConnection " + e);
}
} public String toString() {
return id + " " + host + " " + name;
} public String getHost() {
return host;
} public String getId() {
return id;
} public boolean isBusy() {
return busy;
} public void setBusy(boolean b) {
busy = b;
} public void close() {
server.kill(this);
try {
sock.close(); // closes in and out too.
} catch (IOException e) {
}
} public void write(String s) {
byte buf[] = new byte[s.length()];
s.getBytes(0, buf.length, buf, 0);
try {
out.write(buf, 0, buf.length);
} catch (IOException e) {
close();
}
} private String readline() {
try {
return in.readLine();
} catch (IOException e) {
return null;
}
} static private final int NAME = 1;
static private final int QUIT = 2;
static private final int TO = 3;
static private final int DELETE = 4; static private Hashtable keys = new Hashtable();
static private String keystrings[] = {
"", "name", "quit", "to", "delete"
};
static {
for (int i = 0; i < keystrings.length; i++)
keys.put(keystrings[i], new Integer(i));
} private int lookup(String s) {
Integer i = (Integer) keys.get(s);
return i == null ? -1 : i.intValue();
} public void run() {
String s;
StringTokenizer st;
out:
while ((s = readline()) != null) {
st = new StringTokenizer(s);
String keyword = st.nextToken();
switch (lookup(keyword)) {
default:
System.out.println("bogus keyword: " + keyword + "\r");
break;
case NAME:
name = st.nextToken() +
(st.hasMoreTokens() ? " " + st.nextToken(CRLF) : "");
System.out.println("[" + new Date() + "] " + this + "\r");
server.set(id, this);
break;
case QUIT:
break out;
case TO:
String dest = st.nextToken();
String body = st.nextToken(CRLF);
server.sendto(dest, body);
break;
case DELETE:
busy = true;
server.delete(id);
break;
}
}
close();
}
}
Класс ClientConnection реализовывает обмен информации с конкретным клиентом. В нем сохраняется объект класса Socket, к которому подключен этот клиент, и создается поток, в котором осуществляются периодические попытки чтения сообщений. Если эта попытка завешается успешно, то приходит сообщение от клиента, это сообщение анализируется и осуществляется соответствующая реакция. Для осуществления этой реакции методы класса Server, объект которого передается классу ClientConnection в качестве параметра конструктора. Кроме того, в этом классе существует метод, который осуществляет передачу информации клиенту без посредников. Когда серверу необходимо передать информацию конкретному клиенту он вызывает этот метод.
Класс Client:
import java.util.*;
import java.io.*;
import java.net.*;
import java.awt.*;
import java.applet.*; public class Client extends Applet {
private ServerConnection server;
private String serverName;
private boolean single = false;
private boolean seen_pass = false;
private boolean name_set = false;
private String name;
private String others_name;
private Panel topPanel;
private Label prompt;
private TextField namefield;
private Button done;
private TextField chatfield;
private List idList;
private TextArea dialogArea; public void init() {
setLayout( new BorderLayout() );
serverName = getCodeBase().getHost();
if (serverName.equals(""))
serverName = "localhost";
prompt = new Label("Enter id:");
namefield = new TextField(30);
topPanel = new Panel();
topPanel.setBackground(new Color(255, 255, 200));
topPanel.add(prompt);
topPanel.add(namefield);
add("North", topPanel);
idList = new List(10, false);
add("West", idList );
dialogArea = new TextArea();
dialogArea.setEditable( false );
add("Center", dialogArea );
} public void start() {
try {
showStatus("Connecting to " + serverName);
server = new ServerConnection(this,serverName);
server.start();
showStatus("Connected: " + serverName);
} catch (Exception e) {
single = true; }
} public void stop() {
if (!single)
server.quit();
} void add(String id, String hostname, String name) {
delete(id); // in case it is already there.
idList.addItem("(" + id + ") " + name + "@" + hostname);
} void delete(String id) {
for (int i = 0; i < idList.countItems(); i++) {
String s = idList.getItem(i);
s = s.substring(s.indexOf("(") + 1, s.indexOf(")"));
if (s.equals(id)) {
idList.delItem(i);
break;
}
}
if (idList.countItems() == 0)
showStatus("Wait for other players to arrive.");
}
private String getName(String id) {
for (int i = 0; i < idList.countItems(); i++) {
String s = idList.getItem(i);
String id1 = s.substring(s.indexOf("(") + 1, s.indexOf(")"));
if (id1.equals(id)) {
return s.substring(s.indexOf(" ") + 3, s.indexOf("@"));
}
}
return null;
}
void chat(String id, String s) {
dialogArea.appendText(id + ": " + s+ "\n");//append
showStatus(id + ": " + s);
}
void quit(String id) {
showStatus(id + " just quit.");
delete(id);
} private void nameEntered(String s) {
if (s.equals(""))
return;
System.out.println(s);
name = s;
if( !single )
{
server.setName(name);
showStatus("Wait for other players to arrive.");
}
prompt.setText("You say:");
name_set = true; } public boolean action(Event evt, Object arg) {
System.out.println("a");
if(evt.id == Event.ACTION_EVENT)
if (evt.target == namefield){
if( name_set ){
dialogArea.appendText(name + ": " + namefield.getText()+"\n");
if(!single)
server.sendTo((String)arg, idList.getSelectedItem());
}
else nameEntered((String)arg);
namefield.setText("");
} return true;
}
}
Этот класс является расширением класса Applet. В этом классе создается объект класса ServerConnection, через который осуществляется обмен информацией с сервером. Графический интерфейс такого класса представляет три области: поле ввода, в которое сначала вводится имя пользователя, а потом – сообщения, адресованные другим клиентам, список клиентов, в котором нужно выбирать адресата, и текстовая область, в которой печатается текст диалога. Метод action реализует обработку событий ввода текста в область ввода. Методы add, delete, chat, nameEntered, quit являются реакцией на следующие события: подключение нового клиента, исчезновение клиента, отправки сообщения от клиента, идентификация и выход. Эти методы вызываются классом ServerConnection при отправлении соответствующих сообщений, или же в середине апплета, если источником событий является сам клиент.
Класс Server Connection: import java.io.*;
import java.net.*;
import java.util.*;
class ServerConnection implements Runnable {
void sendTo(String s, String id)
{
if( id!= null )
out.println("to "+id+" "+s);
}
private static final int port = 6564;
private static final String CRLF = "\r\n";
private DataInputStream in;
private PrintStream out;
private String id, toid = null;
private Client client;
public ServerConnection(Scrabblet sc, String site) throws IOException {
Socket server = new Socket(site, port);
in = new DataInputStream(server.getInputStream());
out = new PrintStream(server.getOutputStream(), true);
}
public ServerConnection( Client c,String site) throws IOException {
client = c;
Socket server = new Socket(site, port);
in = new DataInputStream(server.getInputStream());
out = new PrintStream(server.getOutputStream(), true);
}
private String readline() {
try {
return in.readLine();
} catch (IOException e) {
return null;
}
}
void setName(String s) {
out.println("name " + s);
}
void delete() {
out.println("delete " + id);
}
void setTo(String to) {
toid = to;
}
void send(String s) {
if (toid != null)
out.println("to " + toid + " " + s);
}
void chat(String s) {
send("chat " + id + " " + s);
}
void quit() {
send("quit " + id); // tell other player
out.println("quit");
stop();
}
private Thread t;
void start() {
t = new Thread(this);
t.start();
}
void stop() {
t.stop();
}
private static final int ID = 1;
private static final int ADD = 2;
private static final int DELETE = 3;
private static final int CHAT = 4;
private static final int QUIT = 5;
private static Hashtable keys = new Hashtable();
private static String keystrings[] = {
"", "id", "add", "delete","chat",
"quit"
};
static {
for (int i = 0; i < keystrings.length; i++)
keys.put(keystrings[i], new Integer(i));
}
private int lookup(String s) {
Integer i = (Integer) keys.get(s);
return i == null ? -1 : i.intValue();
}
public void run() {
String s;
StringTokenizer st;
while ((s = readline()) != null) {
st = new StringTokenizer(s);
String keyword = st.nextToken();
switch (lookup(keyword)) {
default:
System.out.println("bogus keyword: " + keyword + "\r");
break;
case ID:
id = st.nextToken();
break;
case ADD: {
String id = st.nextToken();
String hostname = st.nextToken();
String name = st.nextToken(CRLF);
client.add(id, hostname, name);
}
break;
case CHAT: {
String from = st.nextToken();
client.chat(from, st.nextToken(CRLF));
}
break;
case QUIT: {
String from = st.nextToken();
client.quit(from);
}
break;
}
}
}
} Этот класс является зеркальным отображением класса ClientConnection. Но он более тесно переплетен с классом Client. В нем реализован поток, в котором клиент, подсоединенный к серверу осуществляет анализ приходящих сообщений и вызывает необходимые методы класса Client. Кроме того в нем существуют методы, которые выполняются классом Client для отправления информации на сервер.
Эта программа может быть легко модифицирована в направлении расширения количества протокольных команд и усложнения клиента за движение добавления специфических реакций на новые команды. В классе ClientConnection может быть усложнена реакция на подсоединение клиента. Например, клиенту может выдаваться информация о начальных условиях игры. Таким образом, данная программа может служить скелетом для создания более сложных. Можно кое-что урезать в классах Client и ServerConnection, оставив в них лишь базовые функции для того, чтобы затем расширять их путем механизма объектного упорядочения.
|