Programování

Budování internetového chatovacího systému

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ětivas 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ětivas 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 ChatClients.

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ětivas.

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 ChatHandlermanipulá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 ChatHandlers 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.