Szerveroldali program
A szerveroldali program három osztályt foglal magába:
- SimpleChatServer
- Ez tartalmazza a főprogramot, rátelepszik a figyelendő TCP portra, fogadja az új kapcsolatokat, s minden új kapcsolathoz létrehoz egy új szálat, mely az adatforgalmat bonyolítja.
- SimpleChatThread
- Ez tartja az egyes felhasználókkal a kapcsolatot, fogadja és feldolgozza a klienstől érkező üzeneteket, s amit szükséges, azt a SimpleChatBroadcaster-en keresztül továbbítja a többi felhasználóhoz.
- SimpleChatBroadcaster
- Ez az osztály tartja nyilván az aktív kapcsolatokat, s osztja szét köztük az üzeneteket.
Mi az amit tudnunk kell még mielőtt megírjuk a szerverprogramot? Csak néhány apró dolog a kliensoldal elkészítéséhez választott Flash socket-kezeléséről, amit ActionScript-ben az XMLSocket osztály valósít meg:
• az üzeneteket 0-s kódú karakter zárja, azaz a Flash minden socketen keresztül küldött szöveg végére illeszt egy 0-s kódú karaktert, s a kapott üzeneteket is e szerint értelmezi, vagyis akkor aktiválja az "üzenet érkezett" eseményt, ha a találkozik egy 0-s kódú karakterrel
• a socket-en átküldött szövegeket a Flash UTF-8 kódolásúnak feltételezi
• csak a 1023 feletti portszámok használhatók
Ha ezt a pár dolgot figyelembe vesszük, akkor már kezdhetjük is. Megjegyzem, hogy mivel az swf.hu nem egy Java-val foglalkozó portál, nem is folyok bele különösebben a Java program részleteibe.
SimpleChatServer.java
import java.net.*;
import java.io.*;
import java.util.*;
public class SimpleChatServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
SimpleChatThread thread = null;
SimpleChatBroadcaster broadcaster = null;
int port = 0;
boolean listen = true;
/* A konzolra irányuló kiírásokban a biztonság kedvéért nem használunk ékezeteket
(és angolul lesznek a szövegek), kevés konzol ismeri ugyanis a Unicode-ot, amit
a Java is használ, a kódlapokkal pedig most inkább ne molyoljunk :) */
System.out.println("Simple Chat Server v1.0, by Rakos Attila, 2004");
/* Nézzük meg a parancssorban kapott paramétereket */
if (args.length != 1) {
System.out.println("Wrong parameter count.");
System.out.println("Usage: java SimpleChatServer port_number");
System.exit(-1);
}
try {
/* Alakítsuk számmá a parancssori paramétert */
port = Integer.parseInt(args[0]);
}
catch (NumberFormatException e) {
/* Hát ez nem szám volt... */
System.out.println("Wrong parameter, it seems to be not a number.");
System.out.println("Usage: java SimpleChatServer port_number");
System.exit(-1);
}
/* Próbáljunk ráülni a megadott portra, ezen fogjuk fogadni a klienseket */
try {
serverSocket = new ServerSocket(port);
}
catch (IOException e) {
/* Ez nem jött össze... */
System.out.println("Couldn't initialize port " + args[0] + ".");
System.exit(-1);
}
System.out.println("Chat server is running on port " + args[0] + "...");
broadcaster = new SimpleChatBroadcaster();
/* Egy végtelen ciklusban dolgozzuk fel a bejövő kapcsolatokat */
while (listen) {
/* Várakozás a következő kapcsolatra */
socket = serverSocket.accept();
/* Az új kapcsolatnak létrehozunk egy új szálat, ami kiszolgálja */
thread = new SimpleChatThread(socket, broadcaster);
/* Iduljon az a szál */
thread.start();
}
/* Na, ez az ami sose fog lefutni :) Ha valakinek ez nem szimpatikus megoldás, akkor
rakjon a fenti ciklusba valamit, ami megfelelő felhasználói beavatkozásra átbillenti
a kilépési feltételt (ehhez állítani kell még a socket time-out-ján). Ctrl+C-vel így
is lelőhető a program :)*/
serverSocket.close();
}
}
SimpleChatBroadcaster.java
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.logging.*;
public class SimpleChatBroadcaster {
/* A connections-ben tartjuk nyilván az élő kapcsolatokat. A Vector elemei
SimpleChatThread objektumok */
private Vector connections = null;
/* A konstruktor */
public SimpleChatBroadcaster() {
connections = new Vector(10);
}
/* Új elemet ad hozzá a kapcsolatok listájához */
public synchronized void addConnection(SimpleChatThread aConnection) {
int i;
/* Hozzáadjuk a listához az új elemet */
connections.add(aConnection);
/* és a nyilvántartott kapcsolatokat értesítjük az új belépőről (kivéve persze
ez utóbbit, ő úgyis tudja magáról, hogy belépett :) */
for (i = 0; i < connections.size(); i++)
if (connections.get(i) != aConnection)
((SimpleChatThread)(connections.get(i))).sendUserConnected(aConnection.getNick());
}
/* Töröl egy elemet a kapcsolatok listájából */
public synchronized void removeConnection(SimpleChatThread aConnection) {
int i;
/* Töröljük a listából a megadott kapcsolatot */
connections.remove(aConnection);
/* és értesítjük a megmaradtakat is erről a szomorú eseményről :) */
for (i = 0; i < connections.size(); i++)
((SimpleChatThread)(connections.get(i))).sendUserDisconnected(aConnection.getNick());
}
/* A nyilvántartott kapcsolatok számát adja vissza */
public synchronized int numberOfConnections() {
return connections.size();
}
/* A nyilvántartott kapcsolatoknak elküld egy üzenetet (aMessage), melynek feladója aSender.
Az aIncludeSender paraméter mondja meg, hogy a feladó is megkapja-e az üzentete */
public synchronized void broadcastMessage(SimpleChatThread aSender, String aMessage, boolean aIncludeSender) {
int i;
for (i = 0; i < connections.size(); i++)
if (connections.get(i) != aSender || aIncludeSender)
((SimpleChatThread)(connections.get(i))).sendMessage(aSender, aMessage);
}
/* Egy tájékoztató üzentet küld szét a nyilvántartott kapcsolatoknak.
Az aIncludeSender paraméter mondja meg, hogy a feladó is megkapja-e az üzentete */
public synchronized void broadcastNotification(SimpleChatThread aSender, String aNotification, boolean aIncludeSender) {
int i;
for (i = 0; i < connections.size(); i++)
if (connections.get(i) != aSender || aIncludeSender)
((SimpleChatThread)(connections.get(i))).sendNotification(aNotification);
}
/* A nyilvántartott kapcsolatokhoz tartozó felhasználóneveket adja vissza egy String tömbben */
public synchronized String[] getUserList() {
int i;
String[] users = new String[connections.size()];
for (i = 0; i < connections.size(); i++)
users[i] = ((SimpleChatThread)(connections.get(i))).getNick();
return users;
}
/* A paraméterként megadott felhasználónév alapján visszaadja az ahhoz tartozó
kapcsolat-objektum azonosítóját (ha nincs, akkor null-t) */
public synchronized SimpleChatThread getConnectionByNick(String aNick) {
int i;
SimpleChatThread result = null;
for (i = 0; i < connections.size(); i++)
if (aNick.toUpperCase().equals(((SimpleChatThread)(connections.get(i))).getNick().toUpperCase())) {
result = (SimpleChatThread)connections.get(i);
break;
}
return result;
}
}
SimpleChatThread.java
import java.net.*;
import java.io.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
public class SimpleChatThread extends Thread {
private Socket socket;
private SimpleChatBroadcaster broadcaster;
/* Ezen keresztül küldjük a socket-re a kimenő adatokat */
private PrintStream output;
/* Ezen keresztül olvassuk a socket-en át érkező adatokat */
private BufferedReader input;
/* Ezek az XML kezeléshez kellenek */
private DocumentBuilderFactory dbf;
private DocumentBuilder db;
/* Ez tárolja a felhasználónevet */
private String nick;
/* A konstruktor */
public SimpleChatThread(Socket aSocket, SimpleChatBroadcaster aBroadcaster) {
super("SimpleChatThread");
/* Eltároljuk a paraméterként kapott objektumokat */
socket = aSocket;
broadcaster = aBroadcaster;
/* Létrehozzuk az XML értelmezéshez szükséges objektumpéldányokat */
dbf = DocumentBuilderFactory.newInstance();
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
/* A szál lényegi része */
public void run() {
String inputText;
Element data;
try {
/* Létrehozzuk a ki- és bemenetet kezelő objektumokat */
output = new PrintStream(socket.getOutputStream(), false);
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
/* Ha sikerült valamit olvasni a socket-ről (ami így elsőre a LOGIN üzenet kellene,
hogy legyen */
if ((inputText = readData()) != null) {
try {
/* Megpróbáljuk XML-ként értelmezni a fogadott adatokat */
data = db.parse(new InputSource(new StringReader(inputText))).getDocumentElement();
/* Ha az elemnév megfelelő és az elem még tartalmaz is adatot... */
if (data.getTagName() == "LOGIN" && data.hasChildNodes()) {
/* Eltároljuk a felhasználónevet */
nick = data.getFirstChild().getNodeValue().trim();
/* Ha üres a felhasználónév vagy van már ilyen... */
if (nick.length() == 0 || broadcaster.getConnectionByNick(nick) != null)
/* Akkor értesítést küldünk erről, majd mást nem csinálunk, így a kapcsolat lezárásra kerül */
sendStatus(102, "Van már ilyen néven belépett felhasználó vagy nincs megadva felhasználónév!");
else {
/* Nem volt még ilyen felhasználónév, küldünk erről egy nyugtázó üzenetet */
sendStatus(100, "Kerülj beljebb " + nick + "! Köszöntünk a chat-en!");
/* Mivel belépés utána kapcsolat már élőnek tekinthető, felvesszük a nyilvántartott
kapcsolatok listájába */
broadcaster.addConnection(this);
/* Amíg jön adat a socket-en át, addig olvasgatunk róla */
while ((inputText = readData()) != null) {
try {
/* Ha megvan egy adag adat, akkor próbáljuk XML-ként értelmezni */
data = db.parse(new InputSource(new StringReader(inputText))).getDocumentElement();
/* Ha üzenet érkezett a felhasználótól */
if (data.getTagName().equals("MESSAGE"))
processMessage(data);
else
/* Ha a felhasználó a belépett felhasználók listájára kiváncsi */
if (data.getTagName().equals("GET_USERLIST"))
processGetUserList(data);
else
/* Ha a felhasználó bontaná a kapcsolatot */
if (data.getTagName().equals("CLOSE"))
break;
else
/* Ha nem ismert az elemnév, akkor ezt tudatjuk a klienssel is */
sendStatus(104, "Ismeretlen típusú üzenet érkezett a klienstől.");
} catch (SAXException e) {
/* Valami gond volt az XML értelmezésével, akkor erről küldünk egy értesítést a kliensnek */
sendStatus(103, "Nem értelmezhető XML üzenet érkezett a klienstől.");
}
}
/* Töröljük a kapcsolatot a nyilvántartás listájából */
broadcaster.removeConnection(this);
}
}
} catch (SAXException e) {
/* Valami gond volt a login XML értelmezésével, úgyhogy küldünk erről egy üzenetet (majd
végül is bontjuk a kapcsolatot)*/
sendStatus(101, "Nem értelmezhető a bejelentkező üzenet!");
}
}
/* Lezárjuk a nyitott adatfolyamokat */
output.close();
input.close();
socket.close();
} catch (IOException e) {
/* Valami gond történt a ki- és bemenet inicializálása során */
e.printStackTrace();
}
}
/* Visszaadja a felhasználónevet */
public synchronized String getNick() {
return nick;
}
/* Beolvassa a következő üzenetet a socket-ről. Az üzenet végét a 0-s karakter jelzi */
private String readData() throws IOException {
StringBuffer s = new StringBuffer();
int ch;
while ((ch = input.read()) > 0)
s.append((char)ch);
return ch == 0 ? s.toString() : null;
}
/* Elküld egy üzenetet a kliensnek a paraméterként megadott fladóval és szöveggel */
public synchronized void sendMessage(SimpleChatThread aSender, String aMessage) {
Document doc;
Element elem;
doc = db.newDocument();
elem = doc.createElement("MESSAGE");
elem.setAttribute("SENDER", aSender.getNick());
elem.appendChild(doc.createTextNode(aMessage));
doc.appendChild(elem);
sendText(doc.getFirstChild().toString());
}
/* Tájékoztató üzenetet küld a kliensnek */
public synchronized void sendNotification(String aNotification) {
Document doc;
Element elem;
doc = db.newDocument();
elem = doc.createElement("NOTIFICATION");
elem.appendChild(doc.createTextNode(aNotification));
doc.appendChild(elem);
sendText(doc.getFirstChild().toString());
}
/* Üzenetet küld a kliensnek, hogy új felhasználó csatlakozott a chat-hez */
public synchronized void sendUserConnected(String aName) {
Document doc;
Element elem;
doc = db.newDocument();
elem = doc.createElement("USER_CONNECTED");
elem.setAttribute("NAME", aName);
doc.appendChild(elem);
sendText(doc.getFirstChild().toString());
}
/* Üzenetet küld a kliensnek, hogy egy felhasználó kilpett a chat-ről */
public synchronized void sendUserDisconnected(String aName) {
Document doc;
Element elem;
doc = db.newDocument();
elem = doc.createElement("USER_DISCONNECTED");
elem.setAttribute("NAME", aName);
doc.appendChild(elem);
sendText(doc.getFirstChild().toString());
}
/* Elküldi a kliensnek a belépett felhasználók listáját */
public synchronized void sendUserList(String[] aUsers) {
int i;
Document doc;
Element elem;
doc = db.newDocument();
elem = doc.createElement("USERLIST");
doc.appendChild(elem);
for (i = 0; i < aUsers.length; i++) {
elem = doc.createElement("USER");
elem.setAttribute("NAME", aUsers[i]);
doc.getFirstChild().appendChild(elem);
}
sendText(doc.getFirstChild().toString());
}
/* Hibaüzenetet küld a kliens számára */
private void sendStatus(int aStatusCode, String aStatusMessage) {
Document doc;
Element elem;
doc = db.newDocument();
elem = doc.createElement("STATUS");
elem.setAttribute("CODE", Integer.toString(aStatusCode));
elem.appendChild(doc.createTextNode(aStatusMessage));
doc.appendChild(elem);
sendText(doc.getFirstChild().toString());
}
/* Feldolgozza a klienstől érkezett új üzenetet és a broadcaster-en keresztül
továbbítja a többi felhasználóhoz */
private void processMessage(Element aData) {
String msg;
if (aData.hasChildNodes()) {
msg = aData.getFirstChild().getNodeValue().trim();
if (msg.length() != 0)
broadcaster.broadcastMessage(this, msg, true);
}
}
/* Feldolgozza a felhasználók listájának elküdésére vonatkozó kérést és a listát
elküldi a kliensnek */
private void processGetUserList(Element aData) {
sendUserList(broadcaster.getUserList());
}
/* Szöveget küld a socket-en keresztül, s a végére egy 0-s karaktert illeszt */
private void sendText(String aText) {
output.print(aText + "\0");
output.flush();
}
}
A forráskódot lefordítva, majd a SimpleChatServer.class osztályt elindítva (a kommunikációhoz használandó port számával mint paraméterrel) már fut is a chat-szerver és várja a kliensek jelentkezését.
Fordítás: javac SimpleChatServer.java SimpleChatBroadcaster.java SimpleChatThread.java
Futtatás: java SimpleChatServer 4321

|