Interneti vahendid Javas Võrgu võimalused 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. Java keeles loodi köigepealt vahendid Internetti ühendatud arvutite omavaheliseks suhtlemiseks, nüüd luuakse tasapisi vahendeid ka muude ühendusvõimaluste jaoks. Siin kursuses vaatame Interneti abil loodud ühendusi. 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. Andmebaaside korral aga töötab programm serveris pidevalt, kasutaja saab end vaid sobival ajal sinna ühendada ning siis andmeid saata ja küsida. Ühenduse põhimõte 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). Arvuti saab enese poole pöördumiseks kasutada nime localhost. Nii saab võrguprogramme katsetada ka ilma end 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. Siin loengus vaatamegi kahepoolse pistikliidese kasutamist. Ü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 brauseri 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 standartsed 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) 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. Kellaaja küsimine eemal asuvast arvutist 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 vastas ta mulle 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 loon isendi nimega sisse tüübist BufferedReader. Lause sc.getInputStream() annab pistiku sc voo, kust saan lugeda. InputStreamReader muudab voost saabuvad baidid tähtedeks ning BufferedReaderi abil hakkan 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. Voog nii kirjutamiseks kui lugemiseks 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 kasutasin 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öö. Telnet-ühendus 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. Postimehelt 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 kirjutasin 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. 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. Omatehtud serveriprogramm 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), kelle sisend- ning väljundvoo abil sai teateid lugeda ning saata, siis ühenduse loomiseks peab ühes otsas olema klass ServerSocket. Ta 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: sel 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. Eraldi lõim 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); } } } Siin luuakse iga kasutaja jaoks omaette lõim. Selle abil saab programm korraga nagu "mitmes kohas" olla. Peaprogrammi osaks jääb vaid lasta ServerSocket'il väratit kuulata ning kui keegi ühendust võtab, siis luua uus lõime eksemplar ehk isend temaga suhtlemiseks. Lõimeklassi meetodisse run() kirjutatud programmikoodi 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. 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 ta osutama peaprogrammilt antud pistikule ning run-meetodis loetakse ning kirjutatakse tema 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üüan nad sellepärast, et nad programmi tööd ei hakkaks segama. Lihtne jututuba 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(); //ühenduste hoidla 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; iparem)dx=-Math.abs(dx); if(y>all)dy=-Math.abs(dy); if(xvanasumma){ PrintWriter faili=new PrintWriter(new FileWriter(failinimi)); faili.println(uusnimi); faili.println(uussumma); faili.close(); } } } sc.close(); }catch(Exception e){ } } } Kokkuvõte Lõim on programmi tööjärg. Ühes lõimes täidetakse käske järgemööda. Lõime saab ootama panna käsuga Thread.sleep, andes parameetriteks ootamise aja millisekundites. Samuti saab lõime panna ootama ükskõik millise objekti taha. Sellisese ootamise lõppemiseks peab miski ütlema sellele objektile notifyAll, mille tulemusena lastakse objekti taga ootaval lõimel edasi töötada. Lõim jääb ootama ka näiteks kasutajalt sisestust või võrgust andmeid oodates. Programmisiseste suhteliselt sõltumatute tegevuste korral saab need panna jooksma eraldi lõime. Sel juhul jagatakse protsessori tööaega lõimede vahel. Lõimede prioriteeti saab muuta, andes mõnele lõimele enam või vähem ressursse kui teistele. Kui on vaja hoolitseda, et mingid tegevused ei toimuks üheaegselt, saab neile seada monitori.