Programmering

Opbygning af et internetchat-system

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 Snors 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 Snors 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 ChatClients.

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 Snors.

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