Voogude kasutamise abil saab
sarnaselt andmeid lugeda nii failist, Internetist, sõnest, baidimassiivist,
teiselt lõimelt kui ka klaviatuurilt. Samuti saab voo abil andmeid ühtmoodi
väljastada. Vaid voo loomisel piisab märkida, kuhu ta suunatakse ning meetodid
lugemiseks ning kirjutamiseks on edaspidi sarnased. Kui on vaja andmete
päritolu- või sihtkohta muuta, piisab vaid muutusest voo loomisel. Nii saab
näiteks Internetiühenduseta arvutis katsetada võrgust lugevat programmi, andes
talle tegelikult andmed ette failist.
Põhilised sisendi-väljundi klassid
asuvad paketis java.io. Tähtede lugemiseks mõeldud klassid
lõppevad sõnaga Reader, kirjutamiseks — Writer. Näiteks failist tähtede
lugemiseks sobib FileReader, faili kirjutamiseks FileWriter. Baitide kirjutamiseks on OutputStream,
lugemiseks InputStream. Faili puhul siis vastavalt FileOutputStream ning FileInputStream.
Sisimas käib andmevahetus baitide
kaupa, kasutamismugavuse huvides on aga loodud selle ümber mitmesuguseid kesti.
Nii aitab näiteks DataOutputStream muuta baitideks nii täisarvud, reaalarvud
kui tõeväärtused.
Baidivoo abil
saab enese käsutusse soovikohase Internetti välja pandud faili. Luues
aadressile vastava URLi objekti ning avades selle ühenduse, võib ühenduselt
küsida sisendvoo, millelt saab baidi kaupa vastava faili andmeid lugeda. Siin
näites kirjutan saabuvad baidid töökataloogis asuvasse faili, programmi töö
tulemusena saan kaugel asuvast failist omale koopia.
import java.io.*;
import java.net.URL;
public class Voog3c{
public static
void main(String argumendid[]) throws IOException{
String
aadress="http://www.tpu.ee/plogo.GIF";
InputStream
sisse=
new
URL(aadress).openConnection().getInputStream();
OutputStream
valja=new FileOutputStream("pilt.gif");
int
nr=sisse.read();
while(nr>=0){
valja.write(nr);
nr=sisse.read();
}
sisse.close();
valja.close();
}
}
Teksti saab rea kaupa lugeda klassi BufferedReader abil. Selle konstruktor vajab
parameetriks klassi Reader järglast, näiteks FileReaderit, kui soovitakse failist lugeda. Voo
lõpu puhul antakse lugemisel rea väärtuseks null (tühiväärtus).
import java.io.*;
public class Voog4{
public static
void main(String argumendid[]) throws IOException{
BufferedReader
sisse=new BufferedReader(
new
FileReader("andmed.txt")
);
String
rida=sisse.readLine();
System.out.println("Faili esimene rida on: "+rida);
System.out.println("Nüüd järjestikku käik faili read:");
while(rida!=null){
System.out.println(rida);
rida=sisse.readLine();
}
}
}
InputStreami (baidivoo) saab Readeriks (tähevoog) muundada klassi InputStreamReader abil. Näiteks võrgust lugemisel annab
pistik vaid baidivoo, sellest teksti lugemisel on aga soovitav muuta see enne
tekstivooks.
Socket sc=new Socket("madli.ut.ee", 13);
BufferedReader sisse=new BufferedReader(
new
InputStreamReader(sc.getInputStream())
);
Teksti
soovitatakse kirjutada klassi PrintWriter abil. Tema loomisel võib talle parameetriks anda nii OutputStreami kui Writeri. PrintWriter tunneb ise ära, millise vooga on tegemist ning vastavalt sellele saadab
sinna andmeid.
import java.io.*;
public class Voog5{
public static
void main(String argumendid[]) throws IOException{
PrintWriter
valja=new PrintWriter(new FileWriter("ruudud.txt"));
for(int nr=1;
nr<=100; nr++){
valja.println(nr*nr);
}
valja.close();
}
}
Tavaliselt kogub PrintWriter välja saadetavad andmed kokku ning alles
puhvri täitumisel või voo sulgemisel saadab andmed kohale. Nii kulub vähem
ressursse, sest sageli (näiteks Internetis) kulub ühe baidi või terve bloki
andmete saatmiseks ühepalju energiat, sest ülekantavad blokid on kindla pikkusega
ning juhul kui saadetakse vähem andmeid kui blokki mahub, siis täidetakse
ülejäänud bloki sisu "aherainega".
Andmete teele saatmiseks saab voole
öelda flush(). Et teade alati kohe peale kirjutamist
teele läheks, selleks tuleb PrintWriteri konstruktorisse lisada true.
Mitmetes operatsioonisüsteemides
saab klahvistikult lugeda ning ekraanile saata andmeid voona, ka Java keelde on
selline lähenemine üle võetud. Nendele voogudele pääseb ligi klassi System väljade vastavalt in ja out kaudu. Nii tuleb tulemuse
tekstiekraanile väljastamiseks saata seal paiknevad tähed voogu System.out. Klaviatuurilt lugemiseks aga tuleb püüda
baite voost System.in. Klaviatuurilt saab väärtusi sisestada
vaid sõnena (erinevalt näiteks C-st või Pascalist, kus võib kohe ette määrata,
millise tüübi sisse andmed loetakse). Juhul kui meil on vaja sisestatut interpreteerida mõne teise
tüübina, siis tuleb tüüp vastavate meetodite abil muuta. Sõne püüab
täisarvuliseks objektiks muuta klassi Integer meetod parseInt.
import java.io.*;
public class Sisse1{
public static
void main(String argumendid[]) throws Exception{
int arv;
double
distants;
String s;
PrintWriter
valja=new PrintWriter(System.out, true);
BufferedReader
sisse=new BufferedReader(
new
InputStreamReader(System.in)
);
valja.println("Osalejate arv:");
s=sisse.readLine();
arv=Integer.parseInt(s);
valja.println("Vahemaa:");
distants=Double.parseDouble(sisse.readLine());
valja.println("Kokku läbiti "+arv*distants+" km");
}
}
Väljund:
D:\Kasutajad\jaagup\java>javac
Sisse1.java
D:\Kasutajad\jaagup\java>java
Sisse1
Osalejate arv:
5
Vahemaa:
22.2
Kokku liiguti 111.0 km
D:\Kasutajad\jaagup\java>
Käsklus import java.io.* lubab kasutada kõiki paketti java.io kuuluvaid klasse. Siin programmis on
neist kasutatud PrintWriter, BufferedReader ja InputStreamReader. InputStreamReader'it kasutatakse selles progammis
sisendvoost System.in saabuva BufferedReader'ile "söödavaks muutmiseks".
Kuna sõne pikkusel Java keeles pole
piirangut (2 miljardit tähte mida neljabaidine täisarv lubab on tunduvalt
rohkem, kui tavaliste tekstide puhul võib ette tulla), siis saab ka suuremad
andmed (näiteks failitäie teksti) sõnesse paigutada. Sõnesse kirjutamiseks sobib
StringWriter, kuhu saab kirjutusvoo suunata samuti
nagu mõne muu Writeri (näiteks FileWriteri) sisse. Sõne saab sellest kätte meetodiga toString(). StringReaderi abil saab pikast sõnest lugeda nagu
voost, konstruktorina tuleb talle anda sõne, mille andmeid soovitakse voona
lugema hakata.
import
java.io.*;
public
class SonevooLugeja{
public static void main(String[] argumendid)
throws IOException{
StringReader sisendvoog=new StringReader(
"Tekst,\nmis simuleerib\nsisestust
failist");
BufferedReader sisse=new
BufferedReader(sisendvoog);
String rida=sisse.readLine();
while(rida!=null){
System.out.println(rida);
rida=sisse.readLine();
}
sisse.close();
}
}
D:\Kasutajad\jaagup\java>java
SonevooLugeja
Tekst,
mis simuleerib
sisestust failist
Ka zip-faile saab Java abil luua
ilma, et peaks ise faili struktuuri uurima. ZipOutputStreamile tuleb anda väljundvoog pakitud andmete
väljasaatmiseks, teda ennast võib aga kasutada nagu iga muud väljundvoogu,
näiteks PrintWriteri abil temasse andmeid kirjutades. Meetod putNextEntry teatab, et nüüd hakkavad tulema järgmise
arhiivi lisatava faili andmed. Parameetriks tuleb anda ZipEntry, mis sisaldab andmeid lisatava faili kohta,
lihtsamal juhul vaid selle nime.
import java.io.*;
import java.util.zip.*;
public class Voog6{
public static
void main(String argumendid[]) throws IOException{
ZipOutputStream zo=new ZipOutputStream(
new
FileOutputStream("nimed.zip")
);
PrintWriter
valja=new PrintWriter(zo, true);
zo.putNextEntry(new ZipEntry("nimed.txt"));
valja.println("Juku");
valja.println("Kaarel");
zo.putNextEntry(new ZipEntry("kirjeldus.txt"));
valja.println("Korvpallimeeskonna varumängijad");
valja.close();
}
}
Sarnasel põhimõttel on võimalik
kirjutada ka jar-formaadis arhiivifaile. Samuti saab ise luua filtri, mis
andmed meile sobivalt muudab. Kui kirjutada filtrit, mis saadaks tekstist edasi
vaid numbrid, tuleb lihtsalt iga tähe puhul vaadata, kas tegemist on numbriga
ning siis vastavalt ta kas edasi saata või mitte.
SequenceInputStream aitab kokku
liita mitmest voost tulevad andmed. Senikaua võetakse esimesest voost, kuni see
on tühi, siis minnakse järgmise voo juurde. Viimase voo ammendumisega saab ka
SequenceInputStream tühjaks.
Voogu on võimalik kirjutada ja sealt
lugeda ka terveid objekte (ehk isendeid) klasside ObjectInputStream ning
ObjectOutputStream abil. Nii saab säilitada või mujale mööda voogu edasi anda
näiteks punkte, pilte või ka keerulisemate komponentide olekuid ilma, et peaks
teadmagi, kuidas komponendid tehtud on.
import java.io.*;
import java.awt.Point;
import java.util.Date;
public class Voog7{
public
static void main(String argumendid[]) throws IOException{
ObjectOutputStream valja=new ObjectOutputStream(
new
FileOutputStream("objektid.dat")
);
valja.writeObject(new Point(3, 2));
valja.writeObject(new String("Kirjutamise aeg"));
Date
praegu=new Date();
valja.writeObject(praegu);
valja.close();
}
}
import java.io.*;
import java.awt.Point;
import java.util.Date;
public class Voog7a{
public
static void main(String argumendid[]) throws Exception{
ObjectInputStream sisse=new ObjectInputStream(
new
FileInputStream("objektid.dat")
);
Point
p=(Point)sisse.readObject();
String
s=(String)sisse.readObject();
Date
aeg=(Date)sisse.readObject();
sisse.close();
System.out.println(p+" "+s+" "+aeg);
}
}
Ka klasse (ning koos nendega alamprogramme) on võimalik voogu mööda
transportida. Nii on võimalik omale võrku mööda kohale laadida meetodid, mida
kohalikus masinas olemas pole.
Nii failide kui kataloogidega
tegelemiseks on Java keeles klass File. Selle abil saab kontrollida faili pikkust,
loomise ning muutmise aega. Samuti faile luua, kustutada ning ümber
nimetada. Saab kontrollida, kas fail on
olemas, kas sinna saab lugeda või kirjutada. Kataloogi puhul saab küsida samas
kataloogis asuvate failide nimesid, luua alamkatalooge.
Klassi Fail1 main-meetodis uuritakse, kas fail nimega “nimed.txt” leidub. Juhul kui jah,
siis kirjutatakse välja ta nimi, pikkus ning viimane muutmisaeg.
import java.io.*;
import java.util.Date;
public class Fail1{
public static void main(String argumendid[])
throws IOException{
File fail=new
File("nimed.txt");
if(fail.exists()){
System.out.println(
"Faili "+fail.getName()+" pikkus on "+fail.length()+
"
baiti. Viimati muudeti seda "+new Date(fail.lastModified())
);
}
}
}
Kataloogiosuti
luuakse nagu failiosuti, s.t. antakse konstruktorisse vastava faili või
kataloogi nimi. Ühe punktiga tähistatakse jooksvat kataloogi ning kahe punktiga
ülemkataloogi. Alles spetsiifiliste meetodite rakendamisel kontrollitakse, kas
tegemist on faili või kataloogiga, s.t. list() saab öelda vaid kataloogidele, meetod väljastab
selles kataloogis asuvate failide nimed sõnemassiivina.
import java.io.*;
public class Kataloog1{
public static
void main(String argumendid[]){
File
kataloog=new File("."); // . on jooksev kataloog
String
failid[]=kataloog.list();
System.out.println("Kodukataloogis asuvad failid on:");
for(int
i=0;i<failid.length; i++){
System.out.println(failid[i]);
}
System.out.println("Laiendiga .txt on neist:");
for(int i=0;
i<failid.length; i++){
if(failid[i].endsWith(".txt"))
System.out.println(failid[i]);
}
}
}
Uue kataloogi
aitab luua käsk mkdir(). Vastloodud kataloogi saab kasutada nagu
iga muud juba olemas olevat kataloogi, s.t. sinna faile kirjutada ning sealt
lugeda. File.separator annab faili otsingutee eraldaja vastavalt
operatsioonisüsteemile (s.t. "\" Dos/Windowsi ning "/" Unixi
puhul). Kui soovitakse korraga luua mitu (rohkem kui üks) üksteise sees asuvat
kataloogi, siis selleks on käsk mkdirs().
import java.io.*;
public class Kataloog2{
public static
void main(String argumendid[]) throws IOException{
File
kataloog=new File("uus");
kataloog.mkdir();
PrintWriter
valja=new PrintWriter(
new
FileWriter("uus"+File.separator+"katse.txt")
);
valja.println("Fail katsetamiseks");
valja.close();
}
}
Nii faile kui
katalooge saab ka kustutada. Kataloogi kustutamiseks peab ta enne olema seest
tühi. Kõigepealt kustutatakse kataloogis uus asuvad failid ning seejärel
kataloog ise.
import java.io.*;
public class Kataloog3{
public static
void main(String argumendid[]){
File
kataloog=new File("uus");
if(kataloog.isDirectory()){
String[]
failid=kataloog.list();
for(int i=0;
i<failid.length; i++){
new
File(kataloog+File.separator+failid[i]).delete();
System.out.println(failid[i]);
}
kataloog.delete();
}
}
}
Kui soovida teada, palju on mõne kataloogi all andmeid ning kui palju saaks
kettale katakoogi kustutamisel vaba ruumi juurde, siis ei piisa vaid kataloogi
sees olevate failide suuruste kokku liitmisest. Ka ei piisa vaid sellest kui
otsida üles vaid kataloogis olevad alamkataloogid ning nende seest failide
pikkused kokku liita. Ka alamkataloogides võivad omakorda olla alamkataloogid
ja nii päris mitu taset edasi. Et kõigile neile ligi pääseda, tuleb kasutada
mõnd kavalamat võtet. Üheks võimaluseks on teha alamprogramm, millele antakse
ette kataloogi nimi. Alamprogrammi ülesandeks oleks teha etteantud kataloogi
failidega soovitud töö (näiteks lugeda kokku failide maht) ning juhul, kui töö
käigus selgub, et etteantud kataloogis on omakorda alamkatalooge, siis paluda
uuesti seesama alamprogramm välja kutsuda ning paluda tal vahepeal leitud
alamkataloogiga sama töö ära teha. Kui alamprogrammile antakse ette vaid faile
sisaldav kataloog, siis pole tal põhjust kusagile sügavamale minna. Kui
kataloogi sisse on sattunud vaid faile (ning mitte teisi katalooge) sisaldavaid
alamkatalooge, siis juhtub selle alamprogrammi täitmine olema kõige rohkem
kahes kohas korraga: üks eksemplar uurimas alamkataloogi faile ning teine
ootamas alamkataloogi nime juures, et saaks ülemkataloogi uurimisega edasi
minna. Kui aga üksteise sees olevaid katalooge on rohkem, siis peab ka rohkem
alamprogrammi eksemplare olema käiku pandud – iga taseme jaoks üks.
import
java.io.*;
class
Faililoetelu1{
static void trykiKataloog(String
katalooginimi){
String failid[]=new
File(katalooginimi).list();
for(int i=0; i<failid.length; i++){
String
failinimi=katalooginimi+File.separator+failid[i];
System.out.println(failinimi);
if(new File(failinimi).isDirectory()){
trykiKataloog(failinimi);
}
}
}
public static void main(String
argumendid[]){
trykiKataloog("d:\\temp");
}
}
D:\Kasutajad\jaagup\java>java
Faililoetelu1
d:\temp\1art-a.html
d:\temp\naited
d:\temp\naited\graafika
d:\temp\naited\graafika\hulknurk.txt
d:\temp\naited\naidete_kirjeldus.txt
Alamkataloogide andmete kätte saamiseks on ka põhimõtte poolest küllalt
teistsugune võimalus olemas. Siin tehakse mällu loetelu kataloogide nimedest,
mida pole veel jõutud läbi vaadata. Iga kord, kui jõutakse läbi vaatamata
kataloogini, pannakse selle nimi meelde. Kui parasjagu uuritud kataloog läbi
saab, võetakse meelest järgmise kataloogi nimi ning asutakse selle sisu uurima.
Esimest lähenemist nimetatakse rekursiooniks. Seda saab enamasti küllalt
lühidalt kirja panna, samas võib mõnikord aga masinale raskeks käivitada
osutuda, kui andmeid palju ning samast alamprogrammist palju eksemplare tööle
pannakse. Tuhatkond korda kipub Pentium-100 arvutile piir olema, millest alates
juba uute alamprogrammi eksemplaride väljakutse raskeks kipub minema. Kui aga
otsitavate kataloogide nimed lihtsalt meeles hoida, siis ei saa arvuti
ressursid mitte nii kergesti otsa.
Nimede meeles pidamiseks on appi võetud klass LinkedList. Tegu on kui
massiiviga, kuid siin pole vaja ette öelda, palju elemente loetellu tuleb ning
lisamisel või eemaldamisel muudetakse nimistu suurust automaatselt. Käsuga add lisatakse leitud kataloogi nimi listi
lõppu, käsuga removeFirst küsitakse välja esimene ning eemaldatakse
see loetelust.
import
java.io.*;
import
java.util.LinkedList;
class
Faililoetelu2{
static void trykiKataloog(String
katalooginimi){
LinkedList l=new LinkedList();
l.add(katalooginimi);
while(l.size()>0){
String
failinimi=(String)l.removeFirst();
System.out.println(failinimi);
if(new File(failinimi).isDirectory()){
String[] failid=new
File(failinimi).list();
for(int i=0; i<failid.length; i++){
l.add(failinimi+File.separator+failid[i]);
}
}
}
}
public static void main(String
argumendid[]){
trykiKataloog("d:\\temp");
}
}
D:\Kasutajad\jaagup\java>java
Faililoetelu2
d:\temp
d:\temp\1art-a.html
d:\temp\naited
d:\temp\naited\graafika
d:\temp\naited\naidete_kirjeldus.txt
d:\temp\naited\graafika\hulknurk.txt
Kui kahe
katalooge uuriva programmi väljundeid võrrelda, siis on näha, et esimesel juhul
trükitakse iga alamkataloogi sisu sinna järele, kus kataloogi enesegi nimi on.
Teisel juhul aga pannakse kataloogi nimi meelde ning alles pärast hakatakse
selle sisu uurima. Sellepärast siin näites kataloogis graafika paikneva faili
hulknurk.txt nimi ongi alles pärast eelmist faili. Kui andmepuu juhtub suurem
olema, siis võib pealtnäha kokkukuuluvate andmete kaugus üksteisest päris
suureks osutuda. Kui andmete vaatamise järjekorral pole tähtsust (näiteks
failisuuruste summa arvutamise juures), siis pole vaja lasta end häirida.
Samuti on niimoodi ühe kataloogi seest leitud failid ilusti koos.
Voogude abil saab andmeid vahetada
ühtmoodi mitmesuguste lähte- ning sihtkohtade vahel. Põhiliselt on tegemist
faili, klaviatuuri/ekraani, mälu, toru ning võrguga, kuid vajadusel saab ka
muid ühendusi (näiteks printeriga) luua. Voos liiguvad andmed jadana. Voo
sisimas on tegemist baidijadaga, kuid neid saab interpreteerida vastavalt
kasutaja vajadustele. Reader- ja Writer klassid on tähtede (sõnede) lugemiseks ja
kirjutamiseks. DataInputStreami ja DataOutputStreami abil saab lugeda ja kirjutada lihttüüpe.
Sõnede ridade kaupa lugemisel on lõputunnuseks tühiväärtus null. Baitide puhul väljastatakse lõpu puhul
–1. ObjectInputStreami ning ObjectOutputStreami abil saab lugeda ja kirjutada objekte.
Filtrite abil saab kasutada näiteks pakitud faile, samuti sidet kodeerida. Ka
filtreid saab ise luua.
Voogude abil saab vaid järjest
lugeda või kirjutada. Andmevahetuse mitmed meetodid võivad heita erindeid.
Seetõttu tuleb tegelda nende püüdmise või edasisaatmisega.
Failide ja kataloogide kohta andmete
küsimiseks, nende kustutamiseks ning ümber nimetamiseks on klass File.
Üksteises alanevaid katalooge aitab uurida rekursioon.
Liitmistehted
·
Väljasta
tervitus tekstifaili.
·
Väljasta
tekstifaili arvud ühest sajani.
·
Väljasta
tekstifaili arvude ruudud ühest sajani.
·
Väljasta
tekstifaili viis juhuslike liidetavatega liitmisülesannet.
·
Väljasta
tekstifaili kasutaja pool määratud arv juhuslike liidetavatega
liitmisülesannet, mille vastus ei ületaks kümmet.
Hinnetetabel
·
Programm
loob kasutaja poolt määratud värvi taustaga tühja HTML-tekstifaili.
·
Tekstifailis
on õpilaste nimed ning nende hinded. Programm loob nende põhjal HTML-faili, kus
nimed ning nendele vastavad hinded on tabelis.
·
Tekstifailis
on igal real õpilase nimi ning hinded ainete kaupa. Ainete nimed on faili
esimesel real. Igale õpilasele luuakse HTML leht, kus pealkirjaks on õpilase
nimi ning tabelis ained ning nendes ainetes olevad hinded. Lisaks juhtleht
viitega iga õpilase lehele.
Arvutid saab ühendada võrguks.
Kohalik võrk võib koosneda kasvõi vaid kahest arvutist, mis omavahel ühendatud
on ning mille abil saab teineteisele teateid ja dokumente saata. Võrgu kaudu
saab transportida kõiksugu infot, mida on võimalik elektrisignaalideks
muundada. Näiteks teksti, pilti ja heli. Üle võrgu transportides on tähtis ka
ülekandmise aeg, et teine pool oleks valmis saadetavat infot vastu võtma.
Kahe kasutaja omavahelise reaalajas
suhtlemise puhul on tähtis, et nad üheaegselt masina taha satuksid. Enamike
teenuste korral aga töötab programm serveris pidevalt, kasutaja saab end vaid
sobival ajal sinna ühendada ning siis andmeid saata ja küsida.
Füüsiliselt on arvutid ühendatud
mitmesuguste juhtmetega (Ethernet, “keerupaar”, valguskaabel), juhtmed omakorda
“sõlmede” (hub, switch) abil. Ühendusprotokollide abil on korraldatud nii, et
programmidel andmete transpordiks piisab vaid teada arvutile antud aadressi (IP
numbrit) või nime. Arvuti saab enese poole pöördumiseks kasutada nime
localhost. Nii saab võrguprogramme katsetada ka ilma masinat võrku ühendamata.
Andmekoguse edasiandmiseks piisab
andmeportsioni teele saatmisest koos sihtarvuti aadressiga, kus sihtarvuti
programm selle kinni püüab (ühenduseta protokoll UDP datagrammide saatmiseks).
Kui soovitakse andmete kohalejõudmist kontrollida või soovitakse muul põhjusel
kahepoolset andmesidet, siis tuleks kasutada ühendusega protokolli (TCP), kus
arvutite vahel luuakse ettekujutatav ühendusliin, mille kaudu saab andmeid
saata ja lugeda.
Ühest arvutist võib samaaegselt olla
selliseid ühendusi mitu. Näiteks FTP abil kopeeritakse faili teise arvutisse,
Telneti aknast loetakse kirju ning jututoas suheldakse. Sel juhul on FTP
programmil ühendus arvutiga kuhu kopeeritakse, Telneti programmil ühendus kirju
hoidva masina vastava programmiga ning seiluri rakendil või muul jututoa
klientprogrammil ühendus jututoa serveriga, kes kasutajatelt saabuvad teated
kinni püüab ning teistele teateid kuulama määratud kasutajatele edasi saadab.
Ka ühel programmil võib samaaegselt olla mitu ühendust teiste programmidega.
Nii on jututoa keskuseks oleva masina programmil eraldi ühendus samaaegselt
kõigi jututuppa meldinud kasutajate programmidega. Ta saab neilt saabuvaid
andmeid lugeda ning omalt poolt kasutajatele saata. Serveripoolse programmi
kood peab olema nii kirjutatud, et ta suudab samaaegselt mitmelt kasutajalt
teateid lugeda ning otsustada, millisele liinile mida saata ning mida näiteks
kettale logifailidesse kirjutada.
Ühenduse loomiseks seab ühes arvutis
olev programm end ootele ning teisest arvutist saab ootajaga ühendust võtta.
Kui nad ilusti ühele ajale sattusid, nii et ootaja jõudis ära oodata, millal
teine ühendust palub, siis luuakse ilus kahepoolne sidekanal, mille kaudu
saavad programmid andmeid vahetada.
Ühes masinas võib korraga
suhtluspartnerit oodata mitu programmi. Masinas on ligikaudu 64000 väratit ehk
porti, mille abil saab ühendust pidada. Tavalises lauaarvutis sellist ühenduste
hulka harilikult vaja ei lähe, kuid näiteks asutuse serveris võib ühekorraga
töös olevate ühenduste arv päris suureks minna.
Serverarvutis on harilikult alati töös mõned standardsed programmid, mis
pakuvad kasutajatele teenuseid. Põhiliste teenuste juurde on välja kujunenud
väratid, mille juures nad kasutajat ootavad. Serverarvutis kehtivat kellaaega
näiteks teatab programm, mis peab väljastpoolt tuleva kasutajaga ühendust
värati nr. 13 abil. Kasutajate kohta andmeid jagav finger ootab tavaliselt
väratis number 79 ning www lehekülgi näidatakse värati 80 kaudu. Neid numbreid
saab vajadusel muuta, kuid sel juhul peab kasutajale eraldi ütlema, kus kohast
millist programmi otsida. Ühe värati
kaudu saab teenust pakkuda (ehk partnerit oodata) üldjuhul vaid üks programm.
Üks programm aga võib vajadusel kasutada ka mitut väratit. Näiteks FTP
ühenduses kasutatakse kahte väratit: ühte teadete ja käskluste ning teist
andmefailide edastamiseks.
Järgnev programm küsib
masina madli.ut.ee kellaaega.
import java.net.*;
import java.io.*;
public class Kell1{
public static
void main(String argumendid[]) throws Exception{
Socket sc=new
Socket("madli.ut.ee", 13);
BufferedReader
sisse=new BufferedReader(
new
InputStreamReader(sc.getInputStream())
);
System.out.println(sisse.readLine());
}
}
ning töö
tulemusena vastati Tue Sep 21
17:38:45 1999 , mis oli
parasjagu täiesti õige aeg.
Sideliini mängib klass nimega Socket ehk maakeeli pistik. Socket sc loob muutuja nimega sc tüübist Socket ning
new
Socket("madli.ut.ee", 13);
loob uue
pistikliidese kohalikus masinas töötava programmi ning masina madli.ut.ee
vahel. Ühendus luuakse Tartu Ülikooli masinas väratis nr. 13 ootava
teenust pakkuva programmiga. Pistikliidesest lugemiseks luuakse isend nimega sisse
tüübist BufferedReader. Lause sc.getInputStream() annab pistiku sc voo,
kust saab lugeda. InputStreamReader muudab voost saabuvad baidid tähtedeks
ning BufferedReaderi abil hakkatakse ridu lugema. Eemal masinas ootav programm on loodud nii, et kui keegi
temaga ühendust võtab, siis pärast ühenduse moodustamist saadab ta ühendust
võtnud programmile oma kellaaega teatava lause ning lõpetab ühenduse. Käsu sisse.readLine() tulemusena loetakse sisendvoost
üks rida ning see satub System.out.println meetodi parameetriks, kust kaudu see
ekraanile trükitakse.
Kui soovida serverist kasutaja kohta
teavet saada, siis aitab programm nimega finger. Kui ta on serverisse tööle
pandud, siis talle kasutaja nime ütlemisel vastab ta näiteks, millal kasutaja
viimati kirju lugenud on ning millal üldse masinasse loginud. Järgnevalt
programmilõik, mis küsib serverilt lin2.tpu.ee infot jaagupi-nimelise kasutaja
kohta.
import java.net.*;
import java.io.*;
public class Fingerinfo{
public static
void main(String argumendid[]) throws Exception{
Socket sc=new
Socket("lin2.tpu.ee", 79);
BufferedReader
sisse=new BufferedReader(
new
InputStreamReader(sc.getInputStream())
);
PrintWriter
valja=new PrintWriter(sc.getOutputStream(), true);
valja.println("jaagup");
String
rida=sisse.readLine();
while(rida!=null){
System.out.println(rida);
rida=sisse.readLine();
}
}
Ning tulemus nägi
ekraanil välja järgmine:
C:\jaagup\vork>java Fingerinfo
Login: jaagup Name: Jaagup Kippar
Directory: /home2/jaagup Shell: /bin/bash
Office: L51
On since Tue Sep 21 19:06 (EEST) on
ttypa from math6
4 minutes 3
seconds idle
Mail last read Tue Sep 21 19:08 1999 (EEST)
Plan:
Loodusteaduslike ainete eriala tudeng.
Muu programmiosa on kellaaja küsimisega sarnane. Andmete saatmiseks
kasutati klassi PrintWriter, kes saadab andmed pistiku väljundvoo
kaudu teisele osapoolele. Sõna true konstruktori teise parameetrina tähendab,
et teade saadetakse kohe välja. Võrdlus !=null kontrollib, et seal rida ikka olemas oli
ning järgnev käsk trükib selle rea ekraanile. Kui voog on suletud, siis pole
sealt enam midagi tulemas ning meie programm läheb sellest while tsüklist edasi
ning lõpetab oma töö.
Nõnda väratisse kirjutada ning
lugeda saab ka programmi Telnet abil. Teda saab kasutada nii serverisse (nt.
lin2.tpu.ee) sisse loginuna (samuti Telneti abil) kui ka kohalikust masinast
otse mingi masina mingisse väratisse ühendust võttes. Kella küsimine näeb telneti
abil välja järgmine:
[jaagup@minitorn jaagup]$ telnet madli.ut.ee 13
Trying 193.40.5.124...
Connected to madli.ut.ee.
Escape character is '^]'.
Wed Oct 11 19:16:07 2000
Connection closed by foreign host.
[jaagup@minitorn jaagup]$
Esimesel real on
ees operatsioonisüsteemi viip ning selle järele kirjutati telnet madli.ut.ee
13. Järgnev
rida teatab, et programm püüab ühendust saada masinaga 130.40.5.124. Tegemist
on sama Tartu masinaga. Igal masinal on lisaks nimele veel number. Nimega saab
lihtsalt elu mugavamaks teha, numbreid on keerulisem pähe õppida. Masinatel aga
omavahel numbritest kergem aru saada. Connected rida teatab, et ühendus on
saadud ning veel järgmine, et ühenduse katkestamiseks tuleb üheaegselt vajutada
CTRL ning ]. Seejärel tuleb sealtpoolselt programmilt saabunud vastus.
Connection closed on jällegi telneti programmi poolne teade ühenduse lõppemise
kohta. Ka fingerit saab telneti abil kasutada, siis tuleb vaid väratiks valida
79 ning pärast ühenduse loomist tippida sisse inimese nimi, kelle kohta infot
soovitakse saada. Nii saab telneti-nimelist programmi kasutada oma elu
mugavamaks muutmisel ja programmide töö kontrollimisel, kuid soovides lasta
omatehtud programmidel ühendust pidada ja mujalt andmeid saada, peame
paratamatult ühenduse loomise ning teadete vahetamise ise programmi sisse
kirjutama.
Ka ise saame luua väratit kuulavaid
programme, mis sinna uudistama tulnuga suhtlevad. Järgnev programm kirjutab
kõigile, kes ennast selle masina, kus programm töötab, väratisse nr. 3001
ühendavad, et arvuti on korras.
import java.io.*;
import java.net.*;
public class Teade1{
public static
void main(String argumendid[]) throws IOException{
ServerSocket
ss=new ServerSocket(3001);
while(true){
Socket sc=ss.accept();
PrintWriter
valja=new PrintWriter(sc.getOutputStream(), true);
valja.println("Arvuti on korras");
sc.close();
}
}
}
Ning nagu teisest masinast proovides näha, programm töötab.
[jaagup@minitorn tarkvara]$ telnet math6.tpu.ee 3001
Trying 193.40.238.56...
Connected to math6.tpu.ee.
Escape character is '^]'.
Arvuti on korras
Connection closed by foreign host.
Proovida saab ka
tegelikult samast masinast, kui masina nimeks panna localhost ning sellisel juhul ei pea arvuti isegi mitte võrgus olema.
Kui ühenduse pidamiseks oli klass Socket (pistik), mille sisend- ning väljundvoo
abil sai teateid lugeda ning saata, siis ühenduse loomiseks peab ühes otsas
olema klass ServerSocket. Konstruktoris antakse ette, millist
väratit ta kuulab ning siis, kui programmis saadetakse talle teade accept(), jätab ta programmi täitmise seniks
seisma, kuni väljapoolt keegi selle väratiga ühendust tahab võtta (n.ö. kuulab
kuni keegi uksele koputab). Siis väljastab ServerSocket pistiku, mille abil saab koputanuga
ühendust pidada. Edaspidine pistikust lugemine ning sinna kirjutamine on
sarnane kui eelmiste programmidegi puhul.
Nüüd näide programmist, mis vastab
sõltuvalt kasutaja nimele:
import java.io.*;
import java.net.*;
public class Teade2{
public static void main(String argumendid[])
throws IOException{
ServerSocket
ss=new ServerSocket(3001);
while(true){
Socket
sc=ss.accept();
PrintWriter
valja=new PrintWriter(sc.getOutputStream(), true);
BufferedReader sisse=new BufferedReader(
new
InputStreamReader(sc.getInputStream())
);
valja.println("Mis su nimi on?");
String
nimi=sisse.readLine();
if(nimi.equals("Mari")){
valja.println("Tule homme minu juurde!");
} else{
valja.println("Mind
pole homme kodus.");
}
sc.close();
}
}
}
Selle programmi juures aga tekib probleem: ajal, kui keegi kasutaja oma
nime sisse tipib, on programm selle koha peal paigal ning samaaegselt ei saa
teise kasutajaga suhelda. Kui aga juhtuks sel ajal Mari ühendust võtma, siis
peaks ju ka tema ootama ning sellest oleks ju ometi kahju. Analoogiliselt: kui
säärase põhimõttega töötaks ka pangaautomaatide programm, siis saaks korraga
vaid üks kasutaja oma rahaga opereerida ning teised peaksid tema järele ootama.
Olgugi, et kasutaja on võibolla Tartus ning ootaja Raplas, kuid kui nad
kasutavad võrgu kaudu sama programmi teenuseid, peab programm suutma nende
mõlemaga tegelda.
Järgnev programm peaks hakkama saama
ka juhul, kui mõni kasutaja end külge ühendab ning siis paigale unustab.
import java.io.*;
import java.net.*;
public class Teade3{
public static
void main(String argumendid[]) throws IOException{
ServerSocket
ss=new ServerSocket(3001);
while(true){
new
Teate3loim(ss.accept());
}
}
}
class Teate3loim extends Thread{
Socket sc;
public
Teate3loim(Socket uus_sc){
sc=uus_sc;
start();
}
public void
run(){
try{
PrintWriter
valja=new PrintWriter(sc.getOutputStream(), true);
BufferedReader sisse=new BufferedReader(
new
InputStreamReader(sc.getInputStream())
);
valja.println("Mis su nimi on?");
String
nimi=sisse.readLine();
if(nimi.equals("Mari")){
valja.println("Tule homme minu juurde!");
} else{
valja.println("Mind pole homme kodus.");
}
sc.close();
}catch(Exception e){
System.out.println("Probleem: "+e);
}
}
}
Kui üldjuhul käivitatakse programmi
käske ükshaaval ning järgmist käsku alustades võib kindel olla, et eelmisega
seotud toimingud on lõpetatud, siis mitme kasutaja üheaegseks teenindamiseks
tuleb sellest rahulolust loobuda. Kuigi iga kasutaja puhul täidetakse tema
jaoks tarvilikke toiminguid ükshaaval, tegeldakse samal ajal ka teise
kasutajaga. Lihtsamate programmide puhul võib eeldada ja loota, et samaaegsed
toimingud üksteist ei sega, kuid veidi hiljem tuleb hakata neid omadusi arvesse
võtma.
Siin luuakse iga kasutaja jaoks omaette lõim(Thread) ehk iseseisvalt töötav käsujada. Selle abil saab
programm korraga nagu "mitmes kohas" olla. Peaprogrammi (Teade3) osaks jääb vaid lasta ServerSocket'il väratit kuulata ning kui keegi ühendust võtab, siis luua uus lõime (Teate3Loim) eksemplar ehk isend temaga suhtlemiseks.
Lõimeklassi meetodisse run()
kirjutatud koodi saab käima panna sõltumatult programmi muudest tegemistest.
Selleks tuleb lõimeklassi isendil käivitada meetod start() ning siis juba Java virtuaalmasin ise hoolitseb selle eest, et loodud uus
lõim saaks oma run() meetodisse kirjutatud koodi iseseisvalt
täita. Lõimeklassi isend luuakse peaprogrammis new
Teate3loim(ss.accept());
Selle käsu peale eraldatakse uuele isendile mälu ning käivitatakse konstruktor.
Viimases jäetakse lõime isendisse meelde accept-käsuga loodud ühendus ning
edasi kutsutakse välja lõimeklassi sisene meetod start(), et Java virtuaalmasin teaks hakata selle isendi
sees olevat run meetodit käivitama.
Muutuja sc on
kirjeldatud väljapool meetodeid selle pärast, et ta kehtiks kogu isendi ulatuses,
s.t. nii konstruktoris kui meetodis run. Konstruktoris hakkab see osutama
peaprogrammilt antud pistikule ning run-meetodis loetakse ning kirjutatakse sc
kaudu viidatud ühenduse abil.
try-catch püünis run meetodi sees töötleb tekkivaid
eriolukordi, praegusel juhul lihtsalt trükib välja sõna "probleem"
ning seejärel kirjelduse, mis erindiga kaasa anti. Erind tekib näiteks juhul,
kui ühendus ära kaob. Kinni püütakse erindid sellepärast, et nad programmi tööd
ei hakkaks segama. Kuna run meetod kaetakse algse Thread klassiga võrreldes üle
ning algses meetodis pole lubatud erindeid välja heita, siis ei tohi ka üle
kaetud meetodisse erindit välja lubavat throws-klauslit kirjutada. Vähemasti
seetõttu tulebki püünist kasutada.
Järgnev programm
on väike Telneti-jututuba:
import java.io.*;
import java.net.*;
import java.util.Vector;
public class Jututuba{
public static void main(String
argumendid[]) throws IOException{
ServerSocket ss=new
ServerSocket(3001);
Vector uhendused=new Vector();
while(true){
Socket sc=ss.accept();
uhendused.add(sc);
new JututoaLoim(sc,
uhendused);
}
}
}
class JututoaLoim extends Thread{
Vector v;
Socket sc;
public JututoaLoim(Socket
uus_sc, Vector uus_v){
v=uus_v;
sc=uus_sc;
start();
}
public void run(){
try{
BufferedReader sisse=new
BufferedReader(
new
InputStreamReader(sc.getInputStream())
);
boolean veel=true;
while(veel){
String
rida=sisse.readLine();
System.out.println(rida);
if(rida.startsWith(".ots"))veel=false;
for(int i=0;
i<v.size(); i++){
Socket
skt=(Socket)v.elementAt(i);
PrintWriter valja=new
PrintWriter(skt.getOutputStream(), true);
valja.println(rida);
}
}
sc.close();
} catch(Exception e){
System.out.println("Probleem: "+e);
}
v.remove(sc);
}
}
Jututoa töö nägemiseks
tuleb kõigepealt serveripoolne programm
tööle panna ning seejärel võib klient end külge ühendada. Windowsi Telneti
puhul saab Connect menüüst valida Remote System ning sealt sättida, millise
nimega masinasse ning väratisse ühenduda soovitakse. Sama masina nimi on
localhost, väratis 3001 ootab me loodud serverprogramm. Kui Terminal menüü alt lülitada
Local Echo, siis on ka tippimise ajal näha, mida kirjutatakse. Muul juhul
tulevad nähtavale vaid serveri poolt saadetud teated.
Järgmisena külge ühendunud klient hakkab teksti nägema alates hetkest, kui ühendus on loodud. Siin paistab teise kliendi esimene saadetud teade olema "Terekest". Esimese kliendi aknas paistab see erinevalt muust tekstist olema ühekordselt. Serverprogramm on koostatud nii, et kõik tipitav tekst on konsoolil näha.
Võrreldes
eelmise programmiga on juurde tulnud Vector ühenduste andmete hoidmiseks.
Programmeerija jaoks käitub see samamoodi kui eelpool kataloogi nimede
meelespidamiseks kasutatud LinkedList. Tegemist on muutuva suurusega
massiiviga, kuhu saame elemente lisada ning eemaldada. Vector hoiab andmeid
teadmises, et need on kõige ülema klassi ehk Object isendid. Massiivi lisades muudab ta kõik muud
automaatselt Objectiks, välja võttes peame ütlema, milliseks
tüübiks me teda muundada soovime. Juhul kui muundamine on võimalik, toimib kõik
ilusti. Kui kusagilt kasutaja juurest tuleb uus rida teksti, saadetakse see
tekst kõikidele kasutajatele, kelle pistik on massiivis. Kui kasutaja kirjutab
.ots, siis pääseb ta tsüklist välja, ühendus katkestatakse ning tema pistik
eemaldatakse massiivist.
Ka rakendid ehk appletid ehk rakendikäituri sees töötavad programmikesed
saavad Interneti kaudu andmeid liigutada. Et aga neid võiks julgelt käivitada,
selleks on neile lisaks muudele piirangutele (nad ei pääse ligi failidele ega
arvuti seadmetele) ka võrgu kasutamise juures piirang: nad saavad ühendust
pidada vaid selle masina väratitega, kust rakendi enese programm
rakendikäiturisse tõmmatud on. Kui on vaja muude masinatega suhelda, siis tuleb
rakendi klasse hoidvasse masinasse tööle panna programm, mis siis juba vajaliku
masinaga ühendust peab. Kui taolist turvapiirangut poleks, siis saaks
programmeerija kirjutada rakendi, mis kasutajale teadmata näiteks võõrasse
masinasse sisse murrab. Samas võõrast masinast sissetungijat otsima hakates
jõutaks rakendi kasutajani, kes aga milleski süüdi pole. Kuna brauserid on
arvutites levinud, siis on kerge programme kasutada rakenditena. Siis võib
kasutaja asuda ükskõik millise Internetti ühendatud arvuti taha ja programmi
kasutada. Kui ka andmed salvestatakse serveris, siis saab tööd rahumeeli
jätkata samast kohast, kus eelmisel korral pooleli jäi ning andmed tulevad
võrku pidi kohale.
Teinud eraldiasuvas arvutis programmi valmis ning soovides loodud
serverprogrammi avalikku serverisse tööle panna, ei pruugi tolle serveri
administraator nimetatud soovi kuigi rõõmsalt vastu võtta. Isegi siis, kui
serverprogramm pole kirjutatud pahatahtlikult ning on küllalt tähelepanelikult
vigade suhtes üle vaadatud, võib seal küllalt kergesti leiduda apsakaid, mis
võivad Interneti pealt tulevatel huvilistel serveri tööd tublisti häirida või
mõnel juhul suisa suuta serverarvuti administreerimine üle võtta.
·
Väratit
kuulav programm teatab ühendusevõtjale tema järjekorranumbri
·
Programm
küsib ühendusevõtjalt nime. Juhul, kui sellist nime veel kirjas ei ole, siis
lisatakse see nimi nimede faili.
·
Programm
küsib ühendusevõtjalt kasutajanime ning parooli. Kui sellise kasutajanime ning
parooliga kasutaja leidub, siis
väljastatakse serverarvuti kellaaeg. Olemasoleva kasutajanime kuid vale
parooli puhul antakse veateade. Puuduva kasutajanime puhul lisatakse kasutaja
soovi korral koos sisestatava parooliga andmebaasi. Järgmisel korral selle nime
ja parooliga sisse meldides väljastatakse talle kellaaeg.
Töö serveripooleks eelpool vaadeldud programm, mis kasutajatelt saabuvad
teated kõigile edasi saadab.
·
Loo
tekstiväljaga aken sinna teadete saatmiseks.
·
Loo
tekstialaga aken jututoast tulevate teadete lugemiseks
·
Ühenda need
programmid kokku, pannes teadete lugemise omaette lõime.
·
Jututoas on
näha kasutaja nimi.