Možná jste viděli jeden z mnoha chatovacích systémů založených na prostředí Java, které se objevily na webu. Po přečtení tohoto článku pochopíte, jak fungují - a víte, jak vytvořit vlastní vlastní chatovací systém.
Tento jednoduchý příklad systému klient / server má demonstrovat, jak vytvářet aplikace pomocí pouze streamů dostupných ve standardním API. Chat používá ke komunikaci zásuvky TCP / IP a lze jej snadno vložit na webovou stránku. Pro informaci uvádíme postranní panel vysvětlující komponenty Java síťového programování, které jsou relevantní pro tuto aplikaci. Pokud se stále dostáváte do tempa, nejprve se podívejte na postranní panel. Pokud se již v Javě dobře orientujete, můžete skočit přímo dovnitř a jednoduše se podívat na postranní panel.
Budování chatovacího klienta
Začínáme s jednoduchým grafickým klientem chatu. Trvá dva parametry příkazového řádku - název serveru a číslo portu, ke kterému se chcete připojit. Vytvoří soketové připojení a poté otevře okno s velkou výstupní oblastí a malou vstupní oblastí.
Rozhraní ChatClient
Poté, co uživatel zadá text do vstupní oblasti a stiskne Return, text se přenese na server. Server odráží zpět vše, co je odesláno klientem. Klient zobrazí vše přijaté ze serveru ve výstupní oblasti. Když se k jednomu serveru připojí více klientů, máme jednoduchý chatovací systém.
Třída ChatClient
Tato třída implementuje chatovacího klienta, jak je popsáno. To zahrnuje nastavení základního uživatelského rozhraní, zpracování interakce s uživatelem a příjem zpráv ze serveru.
importovat java.net. *; importovat java.io. *; importovat java.awt. *; public class ChatClient extends Frame implements Runnable {// public ChatClient (String title, InputStream i, OutputStream o) ... // public void run () ... // public boolean handleEvent (Event e) ... // public static void main (String args []) vyvolá IOException ...}
The ChatClient
třída se prodlužuje Rám
; to je typické pro grafickou aplikaci. Realizujeme Spustitelný
rozhraní, abychom mohli spustit a Vlákno
který přijímá zprávy ze serveru. Konstruktor provede základní nastavení grafického uživatelského rozhraní, běh()
metoda přijímá zprávy ze serveru, handleEvent ()
metoda zpracovává interakci uživatele a hlavní()
metoda provádí počáteční síťové připojení.
chráněný DataInputStream i; chráněný DataOutputStream o; chráněný výstup TextArea; chráněný vstup TextField; chráněný posluchač vláken; public ChatClient (název řetězce, InputStream i, OutputStream o) {super (název); this.i = new DataInputStream (nový BufferedInputStream (i)); this.o = new DataOutputStream (nový BufferedOutputStream (o)); setLayout (nový BorderLayout ()); add ("Center", output = new TextArea ()); output.setEditable (false); add ("South", input = new TextField ()); balíček (); ukázat (); input.requestFocus (); posluchač = nové vlákno (toto); listener.start (); }
Konstruktor má tři parametry: název okna, vstupní proud a výstupní proud. The ChatClient
komunikuje přes zadané proudy; vytváříme datové toky ve vyrovnávací paměti i a o, abychom poskytli efektivní komunikační prostředky na vyšší úrovni přes tyto toky. Poté jsme nastavili naše jednoduché uživatelské rozhraní, které se skládá z TextArea
výstup a Textové pole
vstup. Rozložíme a ukážeme okno a začneme a Vlákno
posluchač, který přijímá zprávy ze serveru.
public void run () {try {while (true) {String line = i.readUTF (); output.appendText (řádek + "\ n"); }} catch (IOException ex) {ex.printStackTrace (); } konečně {listener = null; input.hide (); ověřit (); zkuste {o.close (); } catch (IOException ex) {ex.printStackTrace (); }}}
Když vlákno posluchače vstoupí do metody běhu, sedíme v nekonečné smyčce čtení Tětiva
s ze vstupního proudu. Když Tětiva
přijde, připojíme ji k oblasti výstupu a opakujeme smyčku. An IOException
mohlo dojít, pokud došlo ke ztrátě připojení k serveru. V takovém případě vytiskneme výjimku a provedeme vyčištění. Všimněte si, že to bude signalizováno znakem Výjimka EOFE
z readUTF ()
metoda.
Abychom uklidili, nejprve k tomu přiřadíme náš posluchačský odkaz Vlákno
na nula
; to označuje zbytku kódu, který podproces ukončil. Potom skryjeme vstupní pole a zavoláme ověřit()
tak, aby bylo rozhraní znovu rozloženo, a zavřete Výstupní proud
o zajistit, aby bylo spojení uzavřeno.
Všimněte si, že provádíme veškeré čištění v a Konečně
klauzule, takže k tomu dojde, zda IOException
dojde zde nebo je vlákno násilně zastaveno. Okno nezavřeme okamžitě; předpokládá se, že uživatel může chtít relaci číst i po ztrátě připojení.
public boolean handleEvent (Událost e) {if ((e.target == input) && (e.id == Event.ACTION_EVENT)) {try {o.writeUTF ((String) e.arg); o.flush (); } catch (IOException ex) {ex.printStackTrace (); listener.stop (); } input.setText (""); návrat true; } else if ((e.target == this) && (e.id == Event.WINDOW_DESTROY)) {if (listener! = null) listener.stop (); skrýt (); návrat true; } návrat super.handleEvent (e); }
V handleEvent ()
metoda, musíme zkontrolovat dvě významné události uživatelského rozhraní:
První je akční událost v Textové pole
, což znamená, že uživatel stiskl klávesu Return. Když tuto událost zachytíme, napíšeme zprávu do výstupního proudu a poté zavoláme flush ()
zajistit okamžité odeslání. Výstupní proud je a DataOutputStream
, abychom mohli použít writeUTF ()
poslat Tětiva
. Pokud IOException
nastane spojení muselo selhat, tak zastavíme vlákno posluchače; toto automaticky provede všechny potřebné vyčištění.
Druhou událostí je uživatel, který se pokouší zavřít okno. Je na programátorovi, aby se o tento úkol postaral; zastavíme vlákno posluchače a skryjeme Rám
.
public static void main (String args []) vyvolá IOException {if (args.length! = 2) vyvolá novou RuntimeException ("Syntax: ChatClient"); Socket s = new Socket (args [0], Integer.parseInt (args [1])); new ChatClient ("Chat" + args [0] + ":" + args [1], s.getInputStream (), s.getOutputStream ()); }
The hlavní()
metoda spustí klienta; zajistíme, aby byl zadán správný počet argumentů, otevřeme a Zásuvka
na zadaného hostitele a port a vytvoříme a ChatClient
připojeno k proudům zásuvky. Vytvoření soketu může vyvolat výjimku, která ukončí tuto metodu a bude zobrazena.
Vytváření vícevláknového serveru
Nyní vyvíjíme chatovací server, který může přijímat více připojení a který bude vysílat vše, co čte z jakéhokoli klienta. Je pevně připojen ke čtení a psaní Tětiva
s ve formátu UTF.
V tomto programu jsou dvě třídy: hlavní třída, ChatServer
, je server, který přijímá připojení od klientů a přiřazuje je k novým objektům obslužné rutiny připojení. The ChatHandler
třída vlastně dělá práci poslechu zpráv a jejich vysílání všem připojeným klientům. Jedno vlákno (hlavní vlákno) zpracovává nová připojení a existuje vlákno (vlákno ChatHandler
třídy) pro každého klienta.
Každý nový ChatClient
se připojí k ChatServer
; tento ChatServer
předá připojení nové instanci souboru ChatHandler
třída, která bude přijímat zprávy od nového klienta. V rámci ChatHandler
třída, je zachován seznam aktuálních obslužných programů; the přenos()
metoda používá tento seznam k přenosu zprávy všem připojeným ChatClient
s.
Třída ChatServer
Tato třída se zabývá přijetím připojení od klientů a spuštěním vláken obslužné rutiny pro jejich zpracování.
importovat java.net. *; importovat java.io. *; import java.util. *; public class ChatServer {// public ChatServer (int port) throws IOException ... // public static void main (String args []) throws IOException ...}
Tato třída je jednoduchá samostatná aplikace. Dodáváme konstruktor, který provádí veškerou skutečnou práci pro třídu, a hlavní()
metoda, která to vlastně spouští.
public ChatServer (int port) hodí IOException {ServerSocket server = nový ServerSocket (port); while (true) {Socket client = server.accept (); System.out.println ("Přijato z" + client.getInetAddress ()); ChatHandler c = nový ChatHandler (klient); c.start (); }}
Tento konstruktor, který provádí veškerou práci serveru, je poměrně jednoduchý. Vytvoříme a ServerSocket
a pak sedět ve smyčce přijímat klienty s akceptovat()
metoda ServerSocket
. Pro každé připojení vytvoříme novou instanci ChatHandler
třídy, absolvování nové Zásuvka
jako parametr. Poté, co jsme vytvořili tuto obslužnou rutinu, začneme s ní Start()
metoda. Tím se spustí nové vlákno pro zpracování připojení, aby naše hlavní smyčka serveru mohla i nadále čekat na nová připojení.
public static void main (String args []) vyvolá IOException {if (args.length! = 1) vyvolá novou RuntimeException ("Syntax: ChatServer"); nový ChatServer (Integer.parseInt (args [0])); }
The hlavní()
metoda vytvoří instanci ChatServer
, předáním portu příkazového řádku jako parametru. Toto je port, ke kterému se klienti připojí.
Třída ChatHandler Tato třída se zabývá zpracováním jednotlivých připojení. Musíme přijímat zprávy od klienta a znovu je odesílat na všechna ostatní připojení. Udržujeme seznam připojení v a statický
Vektor
.
importovat java.net. *; importovat java.io. *; import java.util. *; public class ChatHandler extends Thread {// public ChatHandler (Socket s) throws IOException ... // public void run () ...}
Prodlužujeme Vlákno
třída umožňující samostatnému vláknu zpracovat přidruženého klienta. Konstruktor přijímá a Zásuvka
ke kterému se připojujeme; the běh()
metoda, kterou volá nové vlákno, provádí skutečné zpracování klienta.
chráněné zásuvky; chráněný DataInputStream i; chráněný DataOutputStream o; public ChatHandler (Socket s) hodí IOException {this.s = s; i = nový DataInputStream (nový BufferedInputStream (s.getInputStream ())); o = nový DataOutputStream (nový BufferedOutputStream (s.getOutputStream ())); }
Konstruktor udržuje odkaz na soket klienta a otevírá vstupní a výstupní proud. Opět používáme datové toky ve vyrovnávací paměti; poskytují nám efektivní I / O a metody pro komunikaci datových typů na vysoké úrovni - v tomto případě Tětiva
s.
protected static Vector handlers = new Vector (); public void run () {try {handlers.addElement (this); while (true) {String msg = i.readUTF (); vysílání (zpráva); }} catch (IOException ex) {ex.printStackTrace (); } konečně {handlers.removeElement (this); zkuste {s.close (); } catch (IOException ex) {ex.printStackTrace (); }}} // chráněné vysílání statické prázdnoty (řetězcová zpráva) ...
The běh()
metoda je místo, kde naše vlákno vstupuje. Nejprve přidáme naše vlákno do Vektor
z ChatHandler
manipulátory. Psovodi Vektor
udržuje seznam všech aktuálních obslužných programů. Je to statický
proměnná a tak existuje jedna instance Vektor
pro celek ChatHandler
třída a všechny její instance. Tedy vše ChatHandler
s může přistupovat k seznamu aktuálních připojení.
Všimněte si, že je pro nás velmi důležité, abychom se později odstranili z tohoto seznamu, pokud naše připojení selže; jinak se nám všichni ostatní zpracovatelé pokusí napsat, když vysílají informace. Tento typ situace, kdy je bezpodmínečně nutné, aby k akci došlo po dokončení části kódu, je hlavním využitím zkuste ... konečně
postavit; proto provádíme veškerou naši práci v rámci a zkuste ... chytit ... konečně
postavit.
Tělo této metody přijímá zprávy od klienta a rebroadcastuje je všem ostatním klientům pomocí přenos()
metoda. Při ukončení smyčky, ať už kvůli čtení výjimky z klienta nebo kvůli zastavení tohoto vlákna, Konečně
je zaručeno provedení klauzule. V této klauzuli odstraníme naše vlákno ze seznamu obslužných rutin a zavřeme soket.
protected static void broadcast (String message) {synchronized (handlers) {Enumeration e = handlers.elements (); while (e.hasMoreElements ()) {ChatHandler c = (ChatHandler) e.nextElement (); try {synchronized (c.o) {c.o.writeUTF (message); } fl. fl. (); } catch (IOException ex) {c.stop (); }}}}
Tato metoda vysílá zprávu všem klientům. Nejprve synchronizujeme seznam manipulátorů. Nechceme, aby se lidé připojili nebo odcházeli, zatímco jsme smyčkovali, pro případ, že bychom se pokusili vysílat někomu, kdo již neexistuje; to nutí klienty počkat, dokud synchronizaci neskončíme. Pokud server musí zvládnout obzvláště velká zátěž, pak bychom mohli zajistit jemnější synchronizaci.
V rámci tohoto synchronizovaného bloku dostaneme Výčet
současných zpracovatelů. The Výčet
třída poskytuje pohodlný způsob iterace všemi prvky a Vektor
. Naše smyčka jednoduše zapíše zprávu všem prvkům Výčet
. Všimněte si, že pokud dojde k výjimce během zápisu do ChatClient
, pak voláme klientovi stop()
metoda; to zastaví podproces klienta a proto provede příslušné vyčištění, včetně odebrání klienta z obslužných rutin.