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 |