Programmering

Bygg et nettprat-system

Du har kanskje sett et av de mange Java-baserte chat-systemene som har dukket opp på nettet. Etter å ha lest denne artikkelen, vil du forstå hvordan de fungerer - og vite hvordan du bygger et enkelt chat-system.

Dette enkle eksemplet på et klient- / serversystem er ment å demonstrere hvordan du bygger applikasjoner ved å bruke bare strømmen som er tilgjengelig i standard API. Chatten bruker TCP / IP-kontakter for å kommunisere, og kan enkelt legges inn på en webside. Som referanse gir vi et sidefelt som forklarer Java-nettverksprogrammeringskomponenter som er relevante for dette programmet. Hvis du fremdeles kommer opp i fart, ta en titt på sidefeltet først. Hvis du allerede er kjent med Java, kan du hoppe rett inn og bare referere til sidefeltet for referanse.

Å bygge en chatklient

Vi starter med en enkel grafisk chatklient. Det tar to kommandolinjeparametere - servernavnet og portnummeret du skal koble til. Den lager en stikkontakt og åpner deretter et vindu med et stort utgangsområde og et lite inngangsområde.

ChatClient-grensesnittet

Etter at brukeren har skrevet inn tekst i inndataregionen og trykket på Return, overføres teksten til serveren. Serveren gir ekko tilbake alt som sendes av klienten. Klienten viser alt mottatt fra serveren i utdataområdet. Når flere klienter kobler seg til en server, har vi et enkelt chat-system.

Klasse ChatClient

Denne klassen implementerer chatklienten, som beskrevet. Dette innebærer å sette opp et grunnleggende brukergrensesnitt, håndtere brukerinteraksjon og motta meldinger fra serveren.

importer java.net. *; importer java.io. *; importer java.awt. *; offentlig klasse ChatClient utvider Frame implementerer Runnable {// public ChatClient (String title, InputStream i, OutputStream o) ... // public void run () ... // public boolean handleEvent (Event e) ... // public statisk ugyldig hoved (String args []) kaster IOException ...} 

De ChatClient klasse utvider Ramme; dette er typisk for en grafisk applikasjon. Vi implementerer Kjørbar grensesnitt slik at vi kan starte en Tråd som mottar meldinger fra serveren. Konstruktøren utfører det grunnleggende oppsettet av GUI, the løpe() metoden mottar meldinger fra serveren, handleEvent () metoden håndterer brukerinteraksjon, og hoved() metoden utfører den første nettverkstilkoblingen.

 beskyttet DataInputStream i; beskyttet DataOutputStream o; beskyttet TextArea-utgang; beskyttet TextField-inngang; beskyttet trådlytter; offentlig ChatClient (strengetittel, InputStream i, OutputStream o) {super (tittel); this.i = ny DataInputStream (ny BufferedInputStream (i)); this.o = ny DataOutputStream (ny BufferedOutputStream (o)); setLayout (ny BorderLayout ()); add ("Center", output = new TextArea ()); output.setEditable (false); legg til ("South", input = new TextField ()); pakke (); vise fram (); input.requestFocus (); lytter = ny tråd (dette); listener.start (); } 

Konstruktøren tar tre parametere: en tittel for vinduet, en inngangsstrøm og en utgangsstrøm. De ChatClient kommuniserer over de angitte strømmer; vi lager buffrede datastrømmer i og o for å tilby effektive kommunikasjonsfasiliteter på høyere nivå over disse strømningene. Deretter satte vi opp vårt enkle brukergrensesnitt, bestående av TextArea output og Tekstfelt inngang. Vi legger opp og viser vinduet, og starter en Tråd lytter som godtar meldinger fra serveren.

public void run () {try {while (true) {String line = i.readUTF (); output.appendText (linje + "\ n"); }} fange (IOException ex) {ex.printStackTrace (); } til slutt {lytter = null; input.hide (); validere (); prøv {o.close (); } fange (IOException ex) {ex.printStackTrace (); }}} 

Når lyttertråden går inn i kjøremetoden, sitter vi i en uendelig løkke og leser Strings fra inngangsstrømmen. Når en String ankommer, legger vi den til utgangsområdet og gjentar sløyfen. An IO Unntak kan oppstå hvis forbindelsen til serveren har gått tapt. I så fall skriver vi ut unntaket og utfører opprydding. Merk at dette blir signalisert av en EOFEeksepsjon fra readUTF () metode.

For å rydde opp, tilordner vi først lytterens referanse til dette Tråd til null; dette indikerer for resten av koden at tråden har avsluttet. Vi skjuler deretter inntastingsfeltet og ringer validere() slik at grensesnittet legges ut igjen, og lukk OutputStream o for å sikre at forbindelsen er stengt.

Merk at vi utfører hele oppryddingen i a endelig klausul, så dette vil skje om en IO Unntak oppstår her eller tråden stoppes med makt. Vi lukker ikke vinduet med en gang; antagelsen er at brukeren kanskje vil lese økten selv etter 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 sant; } annet hvis ((e.target == dette) && (e.id == Event.WINDOW_DESTROY)) {if (lytter! = null) listener.stop (); gjemme seg (); returner sant; } returner super.handleEvent (e); } 

I handleEvent () metode, må vi sjekke for to viktige UI-hendelser:

Den første er en actionhendelse i Tekstfelt, som betyr at brukeren har trykket på Retur-tasten. Når vi får tak i denne hendelsen, skriver vi meldingen til utgangsstrømmen og ringer flush () for å sikre at den sendes umiddelbart. Utgangsstrømmen er en DataOutputStream, slik at vi kan bruke writeUTF () å sende en String. Hvis en IO Unntak oppstår forbindelsen må ha mislyktes, så vi stopper lyttertråden; dette utfører automatisk all nødvendig opprydding.

Den andre hendelsen er at brukeren prøver å lukke vinduet. Det er opp til programmereren å ta seg av denne oppgaven; vi stopper lyttertråden og skjuler Ramme.

public static void main (String args []) kaster IOException {if (args.length! = 2) throw new RuntimeException ("Syntax: ChatClient"); Stikkontakt s = ny stikkontakt (args [0], Integer.parseInt (args [1])); ny ChatClient ("Chat" + args [0] + ":" + args [1], s.getInputStream (), s.getOutputStream ()); } 

De hoved() metoden starter klienten; vi sørger for at riktig antall argumenter er levert, vi åpner en Stikkontakt til den angitte verten og porten, og vi oppretter en ChatClient koblet til stikkontaktenes strømmer. Å opprette kontakten kan føre til et unntak som vil avslutte denne metoden og vises.

Å bygge en flertrådet server

Vi utvikler nå en chatserver som kan godta flere tilkoblinger, og som vil kringkaste alt den leser fra en hvilken som helst klient. Det er fast kablet å lese og skrive Strings i UTF-format.

Det er to klasser i dette programmet: hovedklassen, ChatServer, er en server som godtar tilkoblinger fra klienter og tildeler dem til nye tilkoblingshåndteringsobjekter. De ChatHandler klasse gjør faktisk arbeidet med å lytte etter meldinger og kringkaste dem til alle tilkoblede klienter. En tråd (hovedtråden) håndterer nye forbindelser, og det er en tråd (den ChatHandler klasse) for hver klient.

Hvert nytt ChatClient vil koble til ChatServer; dette ChatServer vil gi forbindelsen til en ny forekomst av ChatHandler klasse som vil motta meldinger fra den nye klienten. Innen ChatHandler klasse opprettholdes en liste over nåværende håndtere; de kringkaste() metoden bruker denne listen til å overføre en melding til alle tilkoblede ChatClients.

Klasse ChatServer

Denne klassen er opptatt av å godta forbindelser fra klienter og starte håndteringstråder for å behandle dem.

importer java.net. *; importer java.io. *; importer java.util. *; offentlig klasse ChatServer {// offentlig ChatServer (int-port) kaster IOException ... // offentlig statisk ugyldig hoved (String args []) kaster IOException ...} 

Denne klassen er en enkel frittstående applikasjon. Vi leverer en konstruktør som utfører alt det faktiske arbeidet for klassen, og a hoved() metode som faktisk starter den.

 offentlig ChatServer (int-port) kaster IOException {ServerSocket-server = ny ServerSocket (port); while (true) {Socket client = server.accept (); System.out.println ("Akseptert fra" + client.getInetAddress ()); ChatHandler c = ny ChatHandler (klient); c.start (); }} 

Denne konstruktøren, som utfører alt arbeidet til serveren, er ganske enkelt. Vi lager en ServerSocket og deretter sitte i en løkke og akseptere klienter med aksepterer() Metode av ServerSocket. For hver forbindelse oppretter vi en ny forekomst av ChatHandler klasse, passerer det nye Stikkontakt som parameter. Etter at vi har opprettet denne håndtereren, starter vi den med dens start() metode. Dette starter en ny tråd for å håndtere tilkoblingen, slik at hovedserverløkken vår kan fortsette å vente på nye tilkoblinger.

public static void main (String args []) kaster IOException {if (args.length! = 1) throw new RuntimeException ("Syntax: ChatServer"); ny ChatServer (Integer.parseInt (args [0])); } 

De hoved() metoden oppretter en forekomst av ChatServer, sender kommandolinjeporten som parameter. Dette er porten som klienter vil koble til.

Klasse ChatHandler

Denne klassen er opptatt av å håndtere individuelle forbindelser. Vi må motta meldinger fra klienten og sende dem på nytt til alle andre forbindelser. Vi fører en liste over forbindelsene i a

statisk

Vector.

importer java.net. *; importer java.io. *; importer java.util. *; offentlig klasse ChatHandler utvider tråd {// offentlig ChatHandler (stikkontakter) kaster IOException ... // offentlig ugyldig kjøring () ...} 

Vi utvider Tråd klasse for å tillate en egen tråd å behandle den tilknyttede klienten. Konstruktøren godtar a Stikkontakt som vi knytter oss til; de løpe() metoden, kalt av den nye tråden, utfører den faktiske klientbehandlingen.

 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 referanse til klientens kontakt og åpner en inngang og en utgangsstrøm. Igjen bruker vi bufrede datastrømmer; disse gir oss effektive I / O og metoder for å kommunisere datatyper på høyt nivå - i dette tilfellet, Strings.

beskyttede statiske Vector-håndtere = nye Vector (); public void run () {try {handlers.addElement (this); while (true) {String msg = i.readUTF (); kringkasting (msg); }} fange (IOException ex) {ex.printStackTrace (); } til slutt {handlers.removeElement (dette); prøv {s.close (); } fange (IOException ex) {ex.printStackTrace (); }}} // beskyttet statisk ugyldig kringkasting (strengmelding) ... 

De løpe() metoden er der tråden vår kommer inn. Først legger vi til tråden vår i Vector av ChatHandlers håndterere. Behandlerne Vector holder en liste over alle nåværende håndtere. Det er en statisk variabel og så er det en forekomst av Vector for helheten ChatHandler klasse og alle dens forekomster. Dermed alle ChatHandlers har tilgang til listen over gjeldende tilkoblinger.

Merk at det er veldig viktig for oss å fjerne oss fra denne listen etterpå hvis forbindelsen mislykkes. Ellers vil alle andre håndtere prøve å skrive til oss når de sender informasjon. Denne typen situasjoner, der det er viktig at en handling finner sted når en seksjon er fullført, er en primær bruk av prøv ... endelig konstruere; vi utfører derfor alt vårt arbeid innen en prøv ... fang ... endelig konstruere.

Kroppen til denne metoden mottar meldinger fra en klient og sender dem på nytt til alle andre klienter som bruker kringkaste() metode. Når sløyfen går ut, enten på grunn av et unntak som leses fra klienten eller fordi denne tråden er stoppet, blir endelig klausul blir garantert utført. I denne paragrafen fjerner vi tråden vår fra listen over håndterere og lukker kontakten.

beskyttet statisk ugyldig kringkasting (strengmelding) {synkronisert (handlers) {Enumeration e = handlers.elements (); mens (e.hasMoreElements ()) {ChatHandler c = (ChatHandler) e.nextElement (); prøv {synkronisert (c.o) {c.o.writeUTF (melding); } c.o.flush (); } fange (IOException ex) {c.stop (); }}}} 

Denne metoden sender en melding til alle klienter. Vi synkroniserer først på listen over håndterere. Vi vil ikke at folk blir med eller drar mens vi sløyfer, i tilfelle vi prøver å kringkaste til noen som ikke lenger eksisterer; dette tvinger kundene til å vente til vi er ferdig med å synkronisere. Hvis serveren må håndtere spesielt tunge belastninger, kan vi gi mer finkornet synkronisering.

Innenfor denne synkroniserte blokken får vi en Oppregning av dagens håndtere. De Oppregning klasse gir en praktisk måte å gjenta gjennom alle elementene i en Vector. Vår sløyfe skriver ganske enkelt meldingen til hvert element i Oppregning. Merk at hvis et unntak oppstår mens du skriver til en ChatClient, så ringer vi kundens Stoppe() metode; dette stopper klientens tråd og utfører derfor riktig opprydding, inkludert å fjerne klienten fra håndtererne.

$config[zx-auto] not found$config[zx-overlay] not found