запускає аплет із сервера, вводить свій ідентифікатор і бачить перелік ідентифікаторів користувачів, які під’єднані до сервера. Після чого користувач має змогу обмінюватись повідомленнями з іншими. Користувач одержує повідомлення в разі під’єднання або від’єднання користувачів.
Програма складається з 4 класів: Server, ClientConnection, Client та ServerConnection. Перші 2 класи відносяться до серверної частини, другі 2 - до клієнтської.
Клас 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 яка, як зрозуміло з назви, надсилає повідомлення усім зареєстрованим клієнтам.
Клас ClientConnection:
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("("