A megjelenítéshez és az eseménykezeléshez a következő standard csomagokat
kell importálnunk:
import java.awt.*; import java.awt.event.*; import javax.swing.*;Appletünk létrehozása után, az init metódusban felépítjük a felületet: public class Client extends JApplet implements ActionListener {
// Változók definiálása
// Szövegmezők
private JTextField nameField; // A felhasználói név beviteli mezője
private JPasswordField passField; // A jelszó beviteli mezője
private JTextArea infoField; // A kliens tájékoztató mezője
// Cimkék
private JLabel nameLabel; // A név cimkéje
private JLabel passLabel; // A jelszó cimkéje
private JLabel infoLabel; // A tájékoztatómező cimkéje
// Nyomógombok
private JButton logButton; // A Ki/Bejelentkezés gombja
// A felhasználói felületel kapcsolatos metódusok
// Az applet indítása
public void init() {
// A pakolási stratégia létrehozása és beállítása
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints constraints = new GridBagConstraints();
setFont(new Font("TimesRoman", Font.BOLD, 14));
getContentPane().setLayout(gridbag);
constraints.anchor = GridBagConstraints.WEST;
constraints.weighty = 0.0;
// 'név' cimke és szövegmező elkészítése
nameLabel = new JLabel("Név : ", Label.LEFT);
nameField = new JTextField(20);
constraints.gridwidth = 1;
constraints.weightx = 0.1;
addFormComponent(gridbag, nameLabel, constraints);
constraints.gridwidth = GridBagConstraints.REMAINDER;
constraints.fill = GridBagConstraints.HORIZONTAL;
addFormComponent(gridbag, nameField, constraints);
A további szövegmezők és a nyomógomb elhelyezése a fentiekhez hasonlóan
történik.
Kezelnünk kell a nyomógomb lenyomását. // Események kezelése
//
public void actionPerformed(ActionEvent event)
{
// Melyik gomb volt? A megfelelő kezelőeljárás hívása
if (event.getSource() == logonButton) {
infoField.append("A nyomógombot lenyomták\n");
}
}
A programhoz szükséges fájlok: 1/Client.java,
1/index.html,
1/Icon.gif |
A kapcsolat kezdeményezése egyszerűen következő:
clientSocket = new Socket(serverHost, 1234);Ahhoz, hogy az üzeneteket könnyebben tudjuk kezelni, a felépített kapcsolat input, illetve output streamből egy-egy kényelmes adatcsatornát is érdemes létrehoznunk: os = new PrintWriter(clientSocket.getOutputStream()); is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));A fentiek elvégzéséhez azonban a megfelelő osztályokat importálnunk kell. import java.net.*; import java.io.*;Ha sikeresen bejelentkeztünk, akkor a három újonnan bevezetett változó egyike sem null. Ekkor elküldhetjük első üzenetünket a szervernek. Ez az os.PrintWriter-be írással történik. Ügyelnünk kell azonban arra, hogy a stream tartalma csak a flush metódus hatására indul útjára. os.println("Kliens: Első üzenet"); os.flush();
A szerver válaszát az is olvasásával kaphatjuk meg.
String fromServer = is.readLine();
|
A következőkben egy ún. szerverprogramot fogunk írni, azaz egy olyan
alkalmazást, mely figyeli a számítógép valamelyik portját (az előzőekben
tárgyalt klienshez alkalmazkodva ez a port az 1234-es lesz), és
fogadja az oda érkező hívásokat. Egyelőre a kapott üzenetek feldolgozásával
nem nagyon fogunk törődni: egyszerűen megismételjük azokat egy Szerver:
taggal kiegészítve.
// A fogadóegység létrehozása
try {
serverSocket = new ServerSocket(1234);
}
catch (IOException e) {
System.err.println("Nem sikerült rácsatlakozni" +
" az 1234-es portra: "
+ e.getMessage());
System.exit(1);
}
Ha sikerült a megadott portra kapcsolódnunk, akkor indulhat egy végtelen
ciklus, és fogadhatjuk az érkező hívásokat. Egy hívás fogadása az accept
metódus hívásával történik.
A kapcsolatkezelő objektumhoz a könnyebb kommunikáció érdekében itt is létre kell hoznunk két stream-et, melyeket az egyszerűség kedvéért ugyanúgy nevezünk, mint az előzőekben. // Az első üzenet fogadása String inputLine = is.readLine(); // és megválaszolása (megismétlése) os.println(Szerver: + inputLine); os.flush();Természetesen a fentiek itt sem működnek a megfelelő osztályok importálása nélkül, ezért programunk elejére be kell szúrnunk az alábbi két sort: import java.net.*; import java.io.*;A program forráskódja: 3/Server.java |
| A kitűzött feladatnak megfelelően programunk grafikus felületét is
át kell alakítani. Biztosítani kell, hogy a felhasználó bejelentkezési
szándékán kívül közölhesse azt is, ha ki kíván jelentkezni.
Elegendő tehát már meglévő, BEJELENTKEZÉS feliratú gombunkat átalakítani, és feliratát sikeres bejelentkezés után KIJELENTKEZÉS-re változtatni, és viszont. // Sikeresen bejelentkeztünk?
if (ok) {
infoField.append("A bejelentkezés megtörtént\n");
// A 'Ki/Bejelentkezés' gomb szövegének cseréje
logButton.setText("KIJELENTKEZÉS");
}
Ahhoz, hogy a logButton lenyomásakor appletünk el tudja dönteni,
hogy kapcsolat kezdeményezését, vagy pedig lezárását várjuk tőle, tárolnunk
kell, hogy a kapcsolat él-e, vagy sem. Erre a kapcsolatot tartalmazó clientSocket
változót használjuk, amit az első kapcsolat kezdeményezése előtt null-ra
állítunk:
private Socket clientSocket = null; // A hálózati kapcsolat objektumaA gomb lenyomását feldolgozva elég erre a változóra tekintettel lennünk: if (clientSocket == null) {
// Ha nem, akkor létrehozzuk és bejelentkezünk
...
}
// Ha már volt kapcsolat, akkor megpróbáljuk bezárni
else
try{
closeSocket();
infoField.append("Kijelentkeztem\n");
} catch (Exception e) {
infoField.append("Nincs lezárható kapcsolat\n");
}
// A kapcsolat lezárása
private void closeSocket() {
try{
// A csatornák lezárása
os.close();
is.close();
clientSocket.close();
} catch (Exception e) {
infoField.append("Hiba a kapcsolat lezárásánál\n");
} finally {
// A kapcsolat változójának törlése
// (ez alapján döntjük el, hogy van-e élő kapcsolat)
clientSocket = null;
// A 'Ki/Bejelentkezés gomb visszaállítása
logButton.setText("BEJELENTKEZÉS");
}
}
Még egy metódust kell megírnunk, mégpedig a JApplet osztály destroy
metódusát. Erre azért van szükség, mert a fennálló kapcsolatot akkor is
le kell zárni, ha azt a felhasználó a program elhagyásával kezdeményezte.
// Az applet lezárása
public void destroy() {
// A kapcsolat lezárása (amennyiben volt)
if (clientSocket!=null) closeSocket();
}
A programhoz szükséges fájlok: 4/Client.java,
4/index.html,
4/Icon.gif |
| Mint már említettük, a kapcsolaton történő kommunikáció teljesen leköti
az előzőekben megírt szerverünk figyelmét. Azaz amíg az egyik klienst ki
nem szolgálja, addig nem fogad újabb hívást. Most ezt a problémát szeretnénk
kiküszöbölni.
Új szálat egy Thread, vagy abból származó objektum létrehozásával hozhatunk létre. A szál az objektum start metódusának hívásával kelthető életre. Ekkor a run metódusban található program indul el . Ezért a kapcsolat fogadása utáni részt az alábbi utasításra cseréljük: new ServerThread(clientSocket).start();Programunkból most már csak a kapcsolaton ténylegesen kommunikáló rész hiányzik. Mivel ez az, amit a program többi részével párhuzamosan szeretnénk végrehajtani, ez a rész a ServerThread osztály run metódusába kerül. Ide tulajdonképpen az előző szerverben látott kommunikáló részt kell írnunk. Még két apróbb módosítást javasolunk. Egyrészt a sendMsg metódus bevezetését, melybe a kommunikáció során előforduló os.println(message); os.flush();utasításokat emeljük ki. Másik módosításunk a logEvent naplózó metódus bevezetése. // Az adott esemény logolása
public void logEvent(String message) {
// Az aktuális időpont lekérdezése
Calendar actDate = Calendar.getInstance();
// A részletes dátum és idő kiírása
System.out.print(actDate.get(Calendar.YEAR) + ".");
System.out.print((actDate.get(Calendar.MONTH)+1) + ".");
System.out.print(actDate.get(Calendar.DATE) + ", ");
System.out.print(actDate.get(Calendar.HOUR) + ":");
System.out.print(actDate.get(Calendar.MINUTE) + ":");
System.out.print(actDate.get(Calendar.SECOND) + "> ");
// Az esemény kiírása, a kliens IP-számának megadásával
System.out.println("["+socket.getInetAddress()+"] "+message);
}
A program forráskódja: 4/Server.java |
A szerver által szolgáltatott információ lekérésére a GET üzenet szolgál. Válaszképpen a szerver vagy megkezdi az információ küldését, vagy hibát jelez: ##SYNTAX ERROR, ##NOT FOUND. Az információ továbbításának kezdetét a ##START üzenet jelzi. Az ezután következő sorok -- egyetlen kivétellel -- a szerver által szolgáltatott információ sorai, egészen a ##STOP üzenetet tartalmazó sorig. Az említett kivétel az az eset, amikor a sor a ##READ ERROR üzenetet tartalmazza. Ez nem jelenti feltétlenül az információfolyam végét, a szerver dönt, hogy megkísérli-e a további sorok előállítását. |
| A protokollt megvalósító program megírása leginkább szövegfeldolgozást
jelent, hiszen a másik féltől érkező egysornyi szöveget kell értelmezni
és az ahhoz tartozó választ generálni.
A kommunikáció protokollját megvalósító programot külön osztályba tesszük, melyet ServerProtocol-nak nevezünk. A protokoll szerinti megfelelő válasz generálásához szükség van néhány segédváltozóra, amelyek főként a kapott üzenetek megfelelő sorrendjének ellenőrzésére, illetve a bejelentkezés során szerzett információk tárolására szolgálnak. // Volt-e már USER kulcsszó private boolean hasUser; // Új felhasználó? private boolean newUser; // A bejelentkezés megtörtént már? private boolean logged; // A bejelentkezett felhasználó neve private String userName; // A bejelentkezett felhasználó adatai private User user;
|
| A protokoll szerint a kapcsolatot a kliens vezérli, ami a felhasználó
igényein kell alapuljon.
Ezért a felhasználó különböző cselekményeihez rendelt metódusokban kell a megfelelő kezdeményező programrészeket elhelyezni. Ezen kívül csupán egy feladatunk van, a kapcsolat lezárásának kezdeményezése. Minden olyan esetben, amikor a kliensprogram megszakítja a kapcsolatot el kell küldenünk a protokoll BYE üzenetét. Mivel a kapcsolat zárását önálló metódusba emeltük, a célt a closeSocket metódus kiegészítésével érhetjük el. private void closeSocket() {
try{
// A záróüzenet elküldése a szerver felé
os.println("BYE"); os.flush();
os.close(); // A csatornák lezárása
is.close();
clientSocket.close();
} catch (Exception e) {
infoField.append("Hiba a kapcsolat lezárásánál\n");
} finally {
// A kapcsolat változójának törlése
// (ez alapján döntjük el, hogy van-e élő kapcsolat)
clientSocket = null;
// A 'Ki/Bejelentkezés gomb visszaállítása
logButton.setText("BEJELENTKEZÉS");
// A tájékoztató szöveg kiírása
infoField.append("A kijelentkezés megtörtént\n");
}
}
A programhoz szükséges fájlok: 5/Client.java,
5/index.html,
5/Icon.gif |
Most tehát már rendelkezésünkre áll egy, az általunk definiált protokollt
teljes körűen ismerő szerver és egy ugyanilyen kliens. A felhasználó azonban
csak a kliensprogram bejelentkezését tudja kezdeményezni, az információ
lekérését nem. Ehhez elég egy újabb gomb és egy külön ablak felvétele.
private JTextArea frameText; // A szerver üzenetét megjelenítő mező private JFrame frameField; // A szervertől érkező üzenetet // megjelenítő ablak (frame) ... private JButton showButton; // A Megmutatás/Elrejtés gombjaA gomb eseményeinek kezeléséhez módosítanunk kell az applet actionPerformed metódusát. A módosítás egy új ág felvételét jelenti, amelyben a handleMessage metódust hívjuk meg: if (event.getSource() == showButton) handleMessage();Egy, a protokollban adott lehetőséget is érdemes lenne még kihasználnunk. Rendelkezésére áll ugyanis az ASK parancs, amellyel megtudható, hogy a szerver által közölt információt olvastuk-e már. Ennek megjelenítéséhez egy checkboxra van szükségünk: private JCheckBox changeField; // A változás (olvasta-e már) checkboxA változók létrehozása mellett nyilván az init metódust is módosítani kell, hiszen az új felületobjektumokat is fel kell helyezni. A változást jelző checkboxot az 1. lépésben ismertetett módon helyezzük fel, míg az ablak létrehozása és megjelenítése az alábbiak szerint történik: // Az üzenetmegjelenítő frame elkészítése
frameText = new JTextArea();
frameText.setEditable(false);
frameField = new JFrame("Újdonságok");
frameField.setSize(500, 300);
frameField.getContentPane().add("Center", frameText);
resize(500, 350);
További kiegészítések szükségesek ahhoz, hogy a MEGMUTAT gombot
a bejelentkezés előtt inaktívvá, míg utána aktívvá tegyük. Ennek érdekében
a log metódust kiegészítjük, hogy az alapállapotban inaktív gombot
engedélyezze:
showButton.setEnabled(true);Ha a kapcsolat megszakad, akkor ennek fordítottját kell elvégeznünk a showButton.setEnabled(false);parancs segítségével. Gondoskodni kell még a changeField checkbox beállításáról is. // Olvasta már a felhasználó a szerver üzenetét?
try{
changeField.setSelected(isNewMessage());
} catch (SystemErrorException e) {
infoField.append("Rendszerhiba: " +e.getMessage()+"\n");
closeSocket();
}
A closeSocket metódusban a checkbox törlésén kívül az üzenetmegjelenítő
ablak és a MEGMUTAT gomb megfelelő állapotát is be kell állítanunk.
private void closeSocket() {
try{
// A záróüzenet elküldése a szerver felé
os.println("BYE"); os.flush();
os.close(); // A csatornák lezárása
is.close();
clientSocket.close();
} catch (Exception e) {
infoField.append("Hiba a kapcsolat lezárásánál\n");
} finally {
// A kapcsolat változójának törlése
// (ez alapján döntjük el, hogy van-e élő kapcsolat)
clientSocket = null;
// A 'Ki/Bejelentkezés gomb visszaállítása
logButton.setText("BEJELENTKEZÉS");
// Ha az üzenetablak aktív, akkor eltüntetjük
// és a 'Megmutat' gomb szövegét lecseréljük
if (frameField.isShowing()) {
showButton.setText("MEGMUTAT");
frameField.setVisible(false);
}
// A 'Megmutat' gomb üzemen kívül helyezése
showButton.setEnabled(false);
// Az 'olvasta-e már' checkbox hamisra állítása
changeField.setSelected(false);
// A tájékoztató szöveg kiírása
infoField.append("A kijelentkezés megtörtént\n");
}
}
A Swing csomag lehetőségeit kihasználva felhasználói felületünket még elegánsabbá
tehetjük, ha az egyes képernyőelemekhez a setToolTipText metódus
segítségével rövid magyarázó szöveget rendelünk.
... nameField.setToolTipText("A név bevitelére szolgáló mező.");
...A programhoz szükséges fájlok: 6/Client.java, 6/index.html, 6/Icon.gif, 6/Icon1.gif |