Du har måske set et af de mange Java-baserede chat-systemer, der er dukket op på Internettet. Efter at have læst denne artikel forstår du, hvordan de fungerer - og ved, hvordan du bygger et simpelt chat-system.
Dette enkle eksempel på et klient / serversystem er beregnet til at demonstrere, hvordan man bygger applikationer ved hjælp af kun de streams, der er tilgængelige i standard API. Chatten bruger TCP / IP-stikkontakter til at kommunikere og kan let integreres i en webside. Som reference giver vi et sidebjælke, der forklarer Java-netværksprogrammeringskomponenter, der er relevante for denne applikation. Hvis du stadig kommer op i fart, skal du først kigge på sidepanelet. Hvis du allerede er velbevandret i Java, kan du dog hoppe lige ind og bare henvise til sidepanelet til reference.
Opbygning af en chatklient
Vi starter med en simpel grafisk chatklient. Det tager to kommandolinjeparametre - servernavnet og portnummeret, der skal oprettes forbindelse til. Det opretter en stikforbindelse og åbner derefter et vindue med et stort outputområde og et lille inputområde.
ChatClient-grænsefladen
Når brugeren har skrevet tekst i inputområdet og trykket på Retur, overføres teksten til serveren. Serveren gentager alt, hvad der sendes af klienten. Klienten viser alt modtaget fra serveren i outputområdet. Når flere klienter opretter forbindelse til en server, har vi et simpelt chat-system.
Klasse ChatClient
Denne klasse implementerer chatklienten som beskrevet. Dette indebærer oprettelse af en grundlæggende brugergrænseflade, håndtering af brugerinteraktion og modtagelse af meddelelser fra serveren.
import java.net. *; import java.io. *; import java.awt. *; offentlig klasse ChatClient udvider Ramme implementerer Kan køres {// public ChatClient (String title, InputStream i, OutputStream o) ... // public void run () ... // public boolean handleEvent (Event e) ... // public statisk ugyldigt hoved (String args []) kaster IOException ...}
Det ChatClient
klasse udvides Ramme
; dette er typisk for en grafisk anvendelse. Vi implementerer Kan køres
interface, så vi kan starte en Tråd
der modtager meddelelser fra serveren. Konstruktøren udfører den grundlæggende opsætning af GUI, løb()
metoden modtager meddelelser fra serveren handleEvent ()
metoden håndterer brugerinteraktion og hoved ()
metode udfører den oprindelige netværksforbindelse.
beskyttet DataInputStream i; beskyttet DataOutputStream o; beskyttet TextArea output; beskyttet TextField input; beskyttet trådlytter; offentlig ChatClient (streng titel, InputStream i, OutputStream o) {super (titel); this.i = ny DataInputStream (ny BufferedInputStream (i)); this.o = ny DataOutputStream (ny BufferedOutputStream (o)); setLayout (ny BorderLayout ()); tilføj ("Center", output = ny TextArea ()); output.setEditable (falsk); tilføj ("Syd", input = ny TextField ()); pakke (); at vise (); input.requestFocus (); lytter = ny tråd (dette); listener.start (); }
Konstruktøren tager tre parametre: en titel til vinduet, en inputstrøm og en outputstrøm. Det ChatClient
kommunikerer over de specificerede streams; vi opretter buffrede datastrømme i og o for at levere effektive kommunikationsfaciliteter på højere niveau over disse streams. Derefter oprettede vi vores enkle brugergrænseflade, der består af TextArea
output og Tekstfelt
input. Vi layouter og viser vinduet og starter en Tråd
lytter, der accepterer meddelelser fra serveren.
public void run () {try {while (true) {String line = i.readUTF (); output.appendText (linje + "\ n"); }} fange (IOException ex) {ex.printStackTrace (); } endelig {lytter = null; input.hide (); validere (); prøv {o.close (); } fange (IOException ex) {ex.printStackTrace (); }}}
Når lyttertråden går ind i køremetoden, sidder vi i en uendelig sløjfe-læsning Snor
s fra inputstrømmen. Når en Snor
ankommer, føjer vi det til outputområdet og gentager sløjfen. En IOUndtagelse
kan opstå, hvis forbindelsen til serveren er gået tabt. I så fald udskriver vi undtagelsen og udfører oprydning. Bemærk, at dette vil blive signaleret af en EOFException
fra readUTF ()
metode.
For at rydde op tildeler vi først vores lytterhenvisning til dette Tråd
til nul
; dette indikerer for resten af koden, at tråden er afsluttet. Vi skjuler derefter indtastningsfeltet og ringer valider ()
så grænsefladen er lagt ud igen, og luk OutputStream
o for at sikre, at forbindelsen er lukket.
Bemærk, at vi udfører hele oprydningen i en langt om længe
klausul, så dette vil ske, om en IOUndtagelse
forekommer her, eller tråden stoppes med magt. Vi lukker ikke vinduet med det samme; antagelsen er, at brugeren måske vil læse sessionen, selv efter at forbindelsen er mistet.
public boolean handleEvent (Event e) {if ((e.target == input) && (e.id == Event.ACTION_EVENT)) {try {o.writeUTF ((String) e.arg); o.flush (); } fange (IOException ex) {ex.printStackTrace (); listener.stop (); } input.setText (""); returner sandt; } ellers hvis ((e.target == dette) && (e.id == Event.WINDOW_DESTROY)) {if (listener! = null) listener.stop (); skjule (); returner sandt; } returner super.handleEvent (e); }
I handleEvent ()
metode, skal vi kontrollere, om der er to vigtige UI-begivenheder:
Den første er en handlingsbegivenhed i Tekstfelt
, hvilket betyder, at brugeren har trykket på Retur-tasten. Når vi fanger denne begivenhed, skriver vi beskeden til outputstrømmen og ringer derefter op Flush()
for at sikre, at den sendes straks. Outputstrømmen er en DataOutputStream
, så vi kan bruge skrivUTF ()
at sende en Snor
. Hvis en IOUndtagelse
opstår forbindelsen skal være mislykket, så vi stopper lyttertråden; dette udfører automatisk al nødvendig oprydning.
Den anden begivenhed er, at brugeren forsøger at lukke vinduet. Det er op til programmøren at tage sig af denne opgave; stopper vi lyttertråden og skjuler Ramme
.
offentlig statisk ugyldig hoved (String args []) kaster IOException {if (args.length! = 2) throw new RuntimeException ("Syntax: ChatClient"); Socket s = ny Socket (args [0], Integer.parseInt (args [1])); ny ChatClient ("Chat" + args [0] + ":" + args [1], s.getInputStream (), s.getOutputStream ()); }
Det hoved ()
metode starter klienten; vi sikrer, at det korrekte antal argumenter er leveret, vi åbner en Stikkontakt
til den angivne vært og port, og vi opretter en ChatClient
forbundet til stikkontakten. Oprettelse af stikkontakten kan muligvis kaste en undtagelse, der forlader denne metode og vises.
Opbygning af en multitrådet server
Vi udvikler nu en chat-server, der kan acceptere flere forbindelser, og som sender alt, hvad den læser fra enhver klient. Det er fast kablet at læse og skrive Snor
s i UTF-format.
Der er to klasser i dette program: hovedklassen, ChatServer
, er en server, der accepterer forbindelser fra klienter og tildeler dem til nye objekt til forbindelseshåndterer. Det ChatHandler
klasse gør faktisk arbejdet med at lytte efter beskeder og udsende dem til alle tilsluttede klienter. Én tråd (hovedtråden) håndterer nye forbindelser, og der er en tråd (den ChatHandler
klasse) for hver klient.
Hvert nyt ChatClient
vil oprette forbindelse til ChatServer
; det her ChatServer
vil give forbindelsen til en ny forekomst af ChatHandler
klasse, der modtager meddelelser fra den nye klient. Indenfor ChatHandler
klasse opretholdes en liste over de nuværende håndtere; det udsende()
metode bruger denne liste til at sende en besked til alle tilsluttede ChatClient
s.
Klasse ChatServer
Denne klasse beskæftiger sig med at acceptere forbindelser fra klienter og starte håndteringstråde til at behandle dem.
import java.net. *; import java.io. *; importer java.util. *; offentlig klasse ChatServer {// offentlig ChatServer (int-port) kaster IOException ... // offentlig statisk ugyldig main (String args []) kaster IOException ...}
Denne klasse er en simpel enkeltstående applikation. Vi leverer en konstruktør, der udfører alt det aktuelle arbejde for klassen, og a hoved ()
metode, der faktisk starter den.
offentlig ChatServer (int-port) kaster IOException {ServerSocket-server = ny ServerSocket (port); while (true) {Socket client = server.accept (); System.out.println ("Accepteret fra" + client.getInetAddress ()); ChatHandler c = ny ChatHandler (klient); c.start (); }}
Denne konstruktør, der udfører alt serverens arbejde, er ret simpelt. Vi opretter en ServerSocket
og derefter sidde i en løkke, der accepterer klienter med acceptere()
metode til ServerSocket
. For hver forbindelse opretter vi en ny forekomst af ChatHandler
klasse, passerer det nye Stikkontakt
som parameter. Når vi har oprettet denne handler, starter vi den med dens Start()
metode. Dette starter en ny tråd til at håndtere forbindelsen, så vores hovedserver-loop kan fortsætte med at vente på nye forbindelser.
offentlig statisk ugyldig hoved (String args []) kaster IOException {if (args.length! = 1) throw new RuntimeException ("Syntax: ChatServer"); ny ChatServer (Integer.parseInt (args [0])); }
Det hoved ()
metoden opretter en forekomst af ChatServer
, videresender kommandolinjeporten som parameter. Dette er den port, som klienterne vil oprette forbindelse til.
Klasse ChatHandler Denne klasse beskæftiger sig med håndtering af individuelle forbindelser. Vi skal modtage meddelelser fra klienten og sende dem igen til alle andre forbindelser. Vi fører en liste over forbindelserne i en statisk
Vektor
.
import java.net. *; import java.io. *; importer java.util. *; offentlig klasse ChatHandler udvider tråd {// offentlig ChatHandler (stikkontakter) kaster IOException ... // offentlig ugyldig kørsel () ...}
Vi udvider Tråd
klasse for at tillade en separat tråd at behandle den tilknyttede klient. Konstruktøren accepterer en Stikkontakt
som vi knytter os til; det løb()
metode, kaldet af den nye tråd, udfører den faktiske klientbehandling.
beskyttede stikkontakter; beskyttet DataInputStream i; beskyttet DataOutputStream o; offentlig ChatHandler (stikkontakter) kaster IOException {this.s = s; i = ny DataInputStream (ny BufferedInputStream (s.getInputStream ())); o = ny DataOutputStream (ny BufferedOutputStream (s.getOutputStream ())); }
Konstruktøren holder en henvisning til klientens stikkontakt og åbner en input og en output-stream. Igen bruger vi buffrede datastrømme; disse giver os effektive I / O og metoder til at kommunikere datatyper på højt niveau - i dette tilfælde Snor
s.
beskyttede statiske Vector-håndtere = nye Vector (); public void run () {prøv {handlers.addElement (dette); while (true) {String msg = i.readUTF (); udsendelse (msg); }} fange (IOException ex) {ex.printStackTrace (); } endelig {handlers.removeElement (dette); prøv {s.close (); } fange (IOException ex) {ex.printStackTrace (); }}} // beskyttet statisk ugyldig udsendelse (strengbesked) ...
Det løb()
metoden er hvor vores tråd kommer ind. Først tilføjer vi vores tråd til Vektor
af ChatHandler
s håndterere. Handlerne Vektor
holder en liste over alle de nuværende håndtere. Det er en statisk
variabel, og så er der en forekomst af Vektor
for det hele ChatHandler
klasse og alle dens forekomster. Således alle ChatHandler
s kan få adgang til listen over aktuelle forbindelser.
Bemærk, at det er meget vigtigt for os at fjerne os fra denne liste bagefter, hvis vores forbindelse mislykkes; Ellers vil alle andre handlere prøve at skrive til os, når de sender information. Denne type situation, hvor det er bydende nødvendigt, at en handling finder sted efter afslutningen af en sektion af kode, er en primær anvendelse af prøv ... endelig
konstruere; derfor udfører vi alt vores arbejde inden for en prøv ... fang ... endelig
konstruere.
Selve denne metode modtager meddelelser fra en klient og udsender dem igen til alle andre klienter, der bruger udsende()
metode. Når sløjfen afsluttes, hvad enten på grund af en undtagelseslæsning fra klienten, eller fordi denne tråd stoppes, bliver langt om længe
klausul garanteres at blive udført. I denne klausul fjerner vi vores tråd fra listen over håndterere og lukker soklen.
beskyttet statisk ugyldig udsendelse (strengmeddelelse) {synkroniseret (handlers) {Enumeration e = handlers.elements (); mens (e.hasMoreElements ()) {ChatHandler c = (ChatHandler) e.nextElement (); prøv {synkroniseret (c.o) {c.o.writeUTF (besked); } c.o.flush (); } fange (IOException ex) {c.stop (); }}}}
Denne metode sender en besked til alle klienter. Vi synkroniserer først på listen over håndterere. Vi vil ikke have, at folk slutter sig til eller forlader, mens vi går i loop, hvis vi prøver at udsende til nogen, der ikke længere eksisterer; dette tvinger kunderne til at vente, indtil vi er færdige med at synkronisere. Hvis serveren skal håndtere særligt tunge belastninger, kan vi muligvis give mere finkornet synkronisering.
Inden for denne synkroniserede blok får vi en Optælling
af de nuværende håndtere. Det Optælling
klasse giver en bekvem måde at gentage gennem alle elementerne i en Vektor
. Vores sløjfe skriver ganske enkelt beskeden til hvert element i Optælling
. Bemærk, at hvis en undtagelse opstår, mens du skriver til en ChatClient
, så ringer vi til klientens hold op()
metode; dette stopper klientens tråd og udfører derfor den passende oprydning, herunder fjernelse af klienten fra håndterere.