Izgradnja internetskog chat sustava

Možda ste vidjeli jedan od mnogih sustava chata utemeljenih na Javi koji su se pojavili na Webu. Nakon što pročitate ovaj članak, shvatit ćete kako oni funkcioniraju - i znati kako izgraditi vlastiti jednostavan sustav za chat.

Ovaj jednostavni primjer klijentsko-poslužiteljskog sustava namijenjen je demonstraciji načina izrade aplikacija koristeći samo streamove dostupne u standardnom API-ju. Chat koristi TCP / IP utičnice za komunikaciju i može se lako ugraditi u web stranicu. Kao referencu, pružamo bočnu traku koja objašnjava Java mrežne programske komponente koje su relevantne za ovu aplikaciju. Ako i dalje ubrzavate, prvo pogledajte bočnu traku. Ako ste već dobro upućeni u Javu, možete uskočiti i jednostavno se uputiti na bočnu traku za referencu.

Izgradnja klijenta za chat

Počinjemo s jednostavnim grafičkim klijentom za chat. Potrebna su dva parametra naredbenog retka - naziv poslužitelja i broj porta za povezivanje. Uspostavlja utičnicu, a zatim otvara prozor s velikim izlaznim područjem i malim ulaznim područjem.

Sučelje ChatClient

Nakon što korisnik upiše tekst u područje unosa i pritisne Return, tekst se prenosi na poslužitelj. Poslužitelj odzvanja na sve ono što klijent pošalje. Klijent prikazuje sve primljeno od poslužitelja u izlaznom području. Kada se više klijenata poveže s jednim poslužiteljem, imamo jednostavan sustav za chat.

Razredni chatClient

Ova klasa implementira chat klijenta, kako je opisano. To uključuje postavljanje osnovnog korisničkog sučelja, rukovanje korisničkom interakcijom i primanje poruka s poslužitelja.

import java.net. *; import java.io. *; import java.awt. *; javna klasa ChatClient proširuje Frame implementira Runnable {// public ChatClient (String title, InputStream i, OutputStream o) ... // public void run () ... // public boolean handleEvent (Event e) ... // public static void main (String args []) baca IOException ...}

ChatClientKlasa proteže Frame; to je tipično za grafičku primjenu. Implementiramo Runnablesučelje tako da možemo pokrenuti sustav Threadkoji prima poruke s poslužitelja. Konstruktor izvodi osnovno postavljanje GUI-ja, run()metoda prima poruke od poslužitelja, handleEvent()metoda obrađuje interakciju korisnika, a main()metoda izvodi početnu mrežnu vezu.

zaštićeni DataInputStream i; zaštićeni DataOutputStream o; zaštićeni izlaz TextArea; zaštićeni unos TextField; zaštićeni slušatelj niti; javni ChatClient (naslov niza, InputStream i, OutputStream o) {super (naslov); this.i = novi DataInputStream (novi BufferedInputStream (i)); this.o = novi DataOutputStream (novi BufferedOutputStream (o)); setLayout (novi BorderLayout ()); add ("Center", output = new TextArea ()); output.setEditable (false); add ("South", input = new TextField ()); paket (); pokazati (); input.requestFocus (); slušatelj = nova tema (ovo); listener.start (); }

Konstruktor uzima tri parametra: naslov prozora, ulazni tok i izlazni tok. U ChatClientkomunicira preko navedenih vodotoka; mi stvaramo međuspremnike tokova podataka i i o kako bismo osigurali učinkovite komunikacijske uređaje više razine preko tih tokova. Zatim postavljamo naše jednostavno korisničko sučelje, koje se sastoji od TextAreaizlaza i TextFieldulaza. Postavljamo i prikazujemo prozor i pokrećemo Threadslušatelj koji prihvaća poruke s poslužitelja.

public void run () {try {while (true) {String line = i.readUTF (); output.appendText (redak + "\ n"); }} catch (IOException ex) {ex.printStackTrace (); } napokon {slušatelj = null; input.hide (); potvrditi (); pokušajte {o.close (); } catch (IOException ex) {ex.printStackTrace (); }}}

Kad nit slušatelja uđe u metodu izvođenja, sjedimo u beskonačnoj petlji očitavajući Strings iz ulaznog toka. Kad Stringstigne, dodamo ga izlaznom području i ponovimo petlju. IOExceptionMože doći ako je veza s poslužiteljem je prekinuta. U tom slučaju ispisujemo iznimku i izvodimo čišćenje. Imajte na umu da će to biti signalizirano EOFExceptioniz readUTF()metode.

Kako očistiti, prvo dodijeliti naše slušatelja pozivanje na to Threadda null; ovo ukazuje na ostatak koda da je nit završena. Zatim sakrijemo polje za unos i pozivamo validate()tako da se sučelje ponovno postavi i zatvorimo OutputStreamo kako bismo osigurali da je veza zatvorena.

Imajte na umu da sve čišćenja izvodimo u finallyklauzuli, pa će se to dogoditi bez obzira pojavljuje li IOExceptionse ovdje ili ako je nit prisilno zaustavljen. Ne zatvaramo prozor odmah; pretpostavka je da će korisnik možda htjeti pročitati sesiju čak i nakon što je veza izgubljena.

javni logički handleEvent (Događaj 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 (""); povratak istinit; } else if ((e.target == this) && (e.id == Event.WINDOW_DESTROY)) {if (listener! = null) listener.stop (); sakriti (); povratak istinit; } return super.handleEvent (e); }

U handleEvent()metodi moramo provjeriti dva značajna događaja korisničkog sučelja:

Prvi je akcijski događaj u TextField, što znači da je korisnik pritisnuo tipku Return. Kad uhvatimo ovaj događaj, napišemo poruku u izlazni tok, a zatim pozivamo flush()kako bismo osigurali da se odmah pošalje. Izlazni tok je a DataOutputStream, tako da možemo koristiti writeUTF()za slanje a String. Ako se IOExceptiondogodi, veza mora biti neuspješna, pa zaustavljamo nit slušatelja; ovo će automatski izvršiti sva potrebna čišćenja.

Drugi događaj je korisnik koji pokušava zatvoriti prozor. Na programeru je da se pobrine za ovaj zadatak; zaustavljamo nit slušatelja i skrivamo Frame.

public static void main (String args []) baca IOException {if (args.length! = 2) baciti novi RuntimeException ("Sintaksa: ChatClient"); Socket s = nova Socket (args [0], Integer.parseInt (args [1])); novi ChatClient ("Chat" + args [0] + ":" + args [1], s.getInputStream (), s.getOutputStream ()); }

main()Metoda započinje klijenta; osiguravamo da je dostavljen točan broj argumenata, otvaramo a Socketnavedenom hostu i portu i stvaramo ChatClientspojene na tokove utičnice. Stvaranje utičnice može dovesti do iznimke koja će izaći iz ove metode i biti prikazana.

Izgradnja višenitnog poslužitelja

Sada razvijamo chat poslužitelj koji može prihvatiti više veza i koji će emitirati sve što pročita s bilo kojeg klijenta. Ožičeno je za čitanje i pisanje Strings u UTF formatu.

U ovom programu postoje dvije klase: glavna klasa, ChatServerje poslužitelj koji prihvaća veze od klijenata i dodjeljuje ih novim objektima za rukovanje vezama. Predavanje ChatHandlerzapravo obavlja posao preslušavanja poruka i njihovog emitiranja svim povezanim klijentima. Jedna nit (glavna nit) obrađuje nove veze, a ChatHandlerza svakog klijenta postoji nit ( klasa).

Svako novo ChatClientpovezat će se s ChatServer; ovo ChatServerće prenijeti vezu na novu instancu ChatHandlerklase koja će primati poruke od novog klijenta. Unutar ChatHandlerklase održava se popis trenutnih voditelja; broadcast()metoda koristi ovaj popis za prijenos poruka svim povezanim ChatClients.

Razred ChatServer

Ova se klasa bavi prihvaćanjem veza od klijenata i pokretanjem niti obrađivača za njihovu obradu.

import java.net. *; import java.io. *; uvoz java.util. *; javna klasa ChatServer {// javni ChatServer (int port) baca IOException ... // javna statička void glavna (String args []) baca IOException ...}

Ova je klasa jednostavna samostalna aplikacija. Mi isporučujemo konstruktor koji izvodi sav stvarni posao za klasu i main()metodu koja ga zapravo pokreće.

javni ChatServer (int port) baca IOException {ServerSocket server = novi ServerSocket (port); while (true) {Socket client = server.accept (); System.out.println ("Prihvaćeno od" + client.getInetAddress ()); ChatHandler c = novi ChatHandler (klijent); c.start (); }}

Ovaj konstruktor, koji izvodi sav posao poslužitelja, prilično je jednostavan. Stvorimo a, ServerSocketa zatim sjedimo u petlji i prihvaćamo klijente accept()metodom ServerSocket. Za svaku vezu kreiramo novu instancu ChatHandlerklase, predajući novu Socketkao parametar. Nakon što smo kreirali ovaj rukovatelj, započinjemo s njegovom start()metodom. Ovo pokreće novu nit za obradu veze, tako da naša glavna petlja poslužitelja može nastaviti čekati na nove veze.

public static void main (String args []) baca IOException {if (args.length! = 1) baciti novi RuntimeException ("Sintaksa: ChatServer"); novi ChatServer (Integer.parseInt (args [0])); }

main()Postupak stvara instancu ChatServer, prolazak naredbenog retka priključak kao parametar. Ovo je luka na koju će se klijenti povezati.

Razred ChatHandler

Ova se klasa bavi rukovanjem pojedinačnim vezama. Moramo primiti poruke od klijenta i ponovno ih poslati svim ostalim vezama. Održavamo popis veza u a

static

Vector.

import java.net. *; import java.io. *; uvoz java.util. *; javna klasa ChatHandler proširuje nit {// javni ChatHandler (Socket s) baca IOException ... // public void run () ...}

Proširujemo Threadklasu kako bismo omogućili odvojenoj niti za obradu pridruženog klijenta. Konstruktor prihvaća a Socketkojem se pridružujemo; run()postupak, nazvan po novu temu, obavlja stvarnu obradu klijent.

 protected Socket s; protected DataInputStream i; protected DataOutputStream o; public ChatHandler (Socket s) throws IOException { this.s = s; i = new DataInputStream (new BufferedInputStream (s.getInputStream ())); o = new DataOutputStream (new BufferedOutputStream (s.getOutputStream ())); } 

The constructor keeps a reference to the client's socket and opens an input and an output stream. Again, we use buffered data streams; these provide us with efficient I/O and methods to communicate high-level data types -- in this case, Strings.

protected static Vector handlers = new Vector (); public void run () { try { handlers.addElement (this); while (true) { String msg = i.readUTF (); broadcast (msg); } } catch (IOException ex) { ex.printStackTrace (); } finally { handlers.removeElement (this); try { s.close (); } catch (IOException ex) { ex.printStackTrace(); } } } // protected static void broadcast (String message) ... 

The run() method is where our thread enters. First we add our thread to the Vector of ChatHandlers handlers. The handlers Vector keeps a list of all of the current handlers. It is a static variable and so there is one instance of the Vector for the whole ChatHandler class and all of its instances. Thus, all ChatHandlers can access the list of current connections.

Note that it is very important for us to remove ourselves from this list afterward if our connection fails; otherwise, all other handlers will try to write to us when they broadcast information. This type of situation, where it is imperative that an action take place upon completion of a section of code, is a prime use of the try ... finally construct; we therefore perform all of our work within a try ... catch ... finally construct.

The body of this method receives messages from a client and rebroadcasts them to all other clients using the broadcast() method. When the loop exits, whether because of an exception reading from the client or because this thread is stopped, the finally clause is guaranteed to be executed. In this clause, we remove our thread from the list of handlers and close the socket.

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); } c.o.flush (); } catch (IOException ex) { c.stop (); } } } } 

This method broadcasts a message to all clients. We first synchronize on the list of handlers. We don't want people joining or leaving while we are looping, in case we try to broadcast to someone who no longer exists; this forces the clients to wait until we are done synchronizing. If the server must handle particularly heavy loads, then we might provide more fine-grained synchronization.

Unutar ovog sinkroniziranog bloka dobivamo jedan Enumerationod trenutnih rukovatelja. EnumerationKlasa pruža zgodan način za prolazak kroz sve elemente Vector. Naša petlja jednostavno zapisuje poruku u svaki element Enumeration. Imajte na umu da ako se tijekom zapisivanja u a dogodi iznimka ChatClient, tada nazivamo stop()metodu klijenta ; ovo zaustavlja klijentovu nit i stoga izvodi odgovarajuće čišćenje, uključujući uklanjanje klijenta iz rukovatelja.