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; i<v.size(); i++){

           Socket skt=(Socket)v.elementAt(i);

           PrintWriter valja=new PrintWriter(

                             skt.getOutputStream(), true);

           valja.println(rida);

         }

      }

      sc.close();

      v.remove(sc);

    } catch(Exception e){

      System.out.println("Probleem: "+e);

    }

  }

}

 

Võrreldes eelmise programmiga on juurde tulnud Vector ühenduste andmete hoidmiseks. 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.

Rakend ja võrk

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 millesti 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 jatkata samast kohast, kus eelmisel korral pooleli jäi ning andmed tulevad võrku pidi kohale.

 

Kokkuvõte.

 

Otse Internetti ühendatud arvutil on oma tunnus, mille kaudu saab tema poole pöörduda. Kahepoolse ühendusliini loomiseks peab ühe arvuti väratis ServerSocket ootama kuni temaga ühendust võetakse. Ühenduse võtja peab teadma, millise arvuti millise järjenumbriga väratis teda oodatakse. Et serveris töötav programm saaks samaaegselt suhelda mitme kliendiga, selleks on mõistlik luua igaks suhtluseks omaette lõim. Rakendid saavad ühendust pidada vaid selle arvutiga, kust nad pärit on.

 

 

 

 

Lõimed

 

Väikses lihtsas programmis on enamasti üks tööjärg. Käske täidetakse üksteise järel. Enne ei alustata uut toimingut, kui eelmine on valmis. Sellist tööjärge nimetatakse lõimeks (thread).

Lõime peatumine

Lõim võib peatuda näiteks ootamaks sisestust kasutajalt, andmeid Internetist või on ta lihtsalt mingiks ajaks seisma pandud. Siis ei toimu programmis enne midagi, kui ollaks ootamise kohast üle saanud. Ühelõimelises programmis saab vigu leida, kontrollides iga käsu järel et see oleks täidetud korrektselt. Pole karta, et muutujad "salapäraselt" oma väärtusi muudaksid.

 

import java.io.*;

public class Loim1{

  public static void main(String argumendid[]) throws Exception{

    System.out.println("Oodatakse reavahetust");

    new BufferedReader(new InputStreamReader(System.in)).readLine();

    System.out.println("Lihtsalt 5 sekundiline paus");

    Thread.sleep(5000);

    System.out.println("Programmi ots");

  }

}

 

Mitmelõimeline programm

Kui programm peab sooritama korraga mitu teineteisest sõltumatut tegevust, on mõistlik nad tööle panna eraldi lõimedena, et peatus ühes lõimes ei takistaks teise töö sooritamist. Näiteks nagu kasutajatega suhtleval Interneti serveril on mõistlik luua iga kasutaja jaoks omaette lõim, et server saaks samaaegselt teenindada mitut kasutajat ilma et programmi looja peaks muretsema arvuti tööaja jagamise üle kasutajate vahel.

Eraldi lõime on mõistlik panna tegevused, mis võtavad aega, kuid mida saab siiski suhteliselt sõltumatult teha. Mõnikord saab iseseisva tööotsa jooksma panna, samas programmi sees muid asju ajada. Kui aga ühel hetkel pole võimalik enam ilma selle iseseisva tööotsa tulemusteta edasi töötada, siis tuleb tema tulemusi ootama jääda. Selline olukord võib näiteks juhtuda, kui saadame Internetis asuvasse andmebaasi päringu vabade bussipiletite kohta. Siis saame lasta programmi kasutajal sisestada oma andmed. Kui andmed on sees, aga aegu pole veel saabunud, siis tuleb jääda neid ootama.

Uue lõime loomiseks tuleb luua klassi Thread alamklass ning seal sees asuvas meetodis nimega run() kirjeldada käsud, mis eraldi lõimes käivitada tuleb.

 

class Numbrid extends Thread{

  public void run(){

    for(int i=0; i<10; i++){

      System.out.println(i);

      try{

        Thread.sleep(1000);

      }catch(Exception e){}

    }

  }

}

 

                Lõime käivitamiseks tuleb luua lõimeklassi isend ning siis käskude käivitamiseks ütelda sellele start(). Nagu näitest näha, lõpetab programm töö alles siis, kui kõik lõimed on oma käskudega hakkama saanud.

 

class Numbrilugeja1{

  static void main(String argumendid[]){

    Thread t=new Numbrid();

    t.start();

    System.out.println("Numbreid hakati lugema");

  }

}

Lõimest võib soovi korral luua ka mitu eksemplari ning need siis käima panna.

class Numbrilugeja2{

  static void main(String argumendid[]) throws Exception{

    new Numbrid().start();   

    Thread.sleep(300);

    new Numbrid().start();

  }

}

Eraldi lõimes käivitatavad käsud võib lõimeklassi alamklassi asemel kirjutada ka lihtsalt liidest Runnable realiseerivasse klassi. Sel juhul ei hõlma Thread ülemklassi kohta ning lõimeklass võib oma oskusi kergesti ka muudest klassidest pärida.

class Numbrid2 implements Runnable{

  public void run(){

    for(int i=0; i<10; i++){

      System.out.println(i);

      try{

        Thread.sleep(1000);

      }catch(Exception e){}

    }

  }

}

Sellise lõime käivitamiseks tuleb luua isend tüübist Thread, millele konstruktoris ette anda liidest Runnable realiseeriv objekt, kus sees kirjas, mida lõim tegema peab.

 

class Numbrilugeja3{

  static void main(String argumendid[]) throws Exception{

    new Thread(new Numbrid2()).start();

  }

}

Selliselt võib üks klass olla näiteks raami alamklass, samaaegselt realiseerides liidest Runnable ning võimaldades oma meetodit run() iseseisvas lõimes käivitada. Nii on tehtud allpool.

Järgnevas näites on lõime alamklass Pall2. Igal pallil on koordinaadid x ja y ning iga sammuga muutuvad need dx ja dy võrra. Iga pall liigub omaette lõimes, s.t. programmi muudest osadest sõltumatult. Pall alandab enese prioriteeti, s.t. et protsessori tööaja jagamisel loeb ta end keskmiselt vähem tähtsamaks. Prioriteedi alandamine on vajalik selleks, et pallide liigutamine ei hakkaks märkimisväärselt aeglustama teisi lõimi, näiteks ekraanile joonistamist. Muidu võib juhtuda, et suure hulga pallide puhul jäävad ülejäänud tegevused suhteliselt unarusse.  Käsk yield tähendab, et lõim annab oma tööjärje üle järgmisele ning asub ise järjekorda uuesti protsessori aega ootama.

Raami alamklass Loim2 loob enesele viis palli isendit ning laseb nad liikuma. Loim2 ise realiseerib liidest Runnable, s.t., et tema run-meetodit on samuti võimalik panna tööle eraldi lõimes. Selle lõime ülesandeks on joonistada iga natukese aja tagant uus ekraanipilt vastavalt pallide  uutele koordinaatidele.

 

import java.awt.*;

public class Loim2 extends Frame implements Runnable{

 Pall2[] pallid;

 public Loim2(){

   pallid=new Pall2[5];

   for(int i=0; i<pallid.length; i++)

     pallid[i]=new Pall2();

   setSize(200, 200);

   setVisible(true);

   new Thread(this).start();

 }

 public void joonista(){

   Image pilt=createImage(200, 200);

   Graphics piltg=pilt.getGraphics();

   for(int i=0; i<pallid.length; i++){

     piltg.drawOval(pallid[i].x()-10, pallid[i].y()-10, 20, 20);

   }

   this.getGraphics().drawImage(pilt, 0, 0, this);

 }

 public void run(){

   while(true){

     joonista();

     Thread.yield();

     try{Thread.sleep(100);}catch(Exception e){}

   }

 }

 public static void main(String argumendid[]){

   new Loim2();

 }

}

 

class Pall2 extends Thread{

  double x=200, y=200, dx, dy;

  int vasak=20, ulal=50, parem=200, all=200;

  public Pall2(){

    dx=5*Math.random();

    dy=5*Math.random();

    start();

  }

  public int x(){

    return (int)x;

  }

  public int y(){

    return (int)y;

  }

  public void run(){

    setPriority(Thread.NORM_PRIORITY-2);

    while(true){

      if(x>parem)dx=-Math.abs(dx);

      if(y>all)dy=-Math.abs(dy);

      if(x<vasak)dx=Math.abs(dx);

      if(y<ulal)dy=Math.abs(dy);

      x+=dx;

      y+=dy;

      yield();

      try{Thread.sleep(100);}catch(Exception e){}

    }

  }

}

 

Ootamine

Java keeles saab üht lõime panna teise järele ootama käsu join abil. Iseseisval lõimel enne kümneni lugeda ning alles siis minnakse peaprogrammi tööjärjega edasi.

 

public class Loim3{

  public static void main(String argumendid[]) throws Exception{

    Thread nr=new Numbrid();

    nr.start();

    Thread.sleep(2000);

    System.out.println("Ootan, kuni numbrid loetud");

    nr.join();

    System.out.println("Ongi loetud, saab muud edasi teha");

  }

}

 

Ootama saab jääda ka suvalise objekti taha, käsuga wait. Tööjärg läheb sellest kohast edasi alles siis, kui muu lõime poolt on vastava objekti taha ootama jäänu edasi lastud käsuga notifyAll. Siin näites loob main-meetodis olev peaprogramm raami tekstikasti ning nupuga ning jääb siis luku taha ootama. Nupule vajutus vabastab luku taga ootaja ning peaprogramm saab edasi minna. Nii saab ära oodata kasutaja vastuse ning sellega siis edasi tegutseda.

 

import java.awt.*;

import java.awt.event.*;

 

public class Loim4{

  public static void main(String argumendid[]) throws Exception{

    Frame f=new Frame("Sisestusraam");

    TextField tf=new TextField();

    Button b=new Button("OK");

    Object lukk=new Object();

    f.setLayout(new GridLayout(2, 1));

    f.add(tf);

    f.add(b);

    b.addActionListener(new Nupukuular4(lukk));

    f.setSize(200, 100);

    f.setVisible(true);

    synchronized(lukk){

      lukk.wait();

    }

    f.setVisible(false);

    System.out.println("Kirjutati "+tf.getText());

    System.exit(0);

  }

}

 

 

class Nupukuular4 implements ActionListener{

  Object lukk;

  public Nupukuular4(Object uuslukk){

    lukk=uuslukk;

  }

  public void actionPerformed(ActionEvent e){

    synchronized(lukk){

      lukk.notifyAll();

    }

  }

}

 

Sünkroniseerimine

Mõnikord tuleb hoolitseda, et andmetele pääseks korraga ligi vaid üks lõim. Samuti tuleb mõne sündumuse toimumise ajaks mõni muu tegevus peatada või ümber suunata. Nii nagu vaikust nõudvat helilindistust ei saa teha kui kõrval maasse auke raiutakse, nii ka lõimede juures saab määrata programmilõike, mida ei või täita samaaegselt. Monitor lubab korraga toimuda vaid ühel neist tegevustest. Kui üks monitori all olev programmilõik töötab, peavad ülejäänud sama monitori vajavad lõimed järjekorras ootama kuni monitor vabaneb. Monitori kehtestamiseks programmilõigule tuleb see panna synchronized-plokki, määrates milline objekt on ploki monitoriks. Kui meetodi piiritlejaks on syncronized, tähendab see, et meetodi monitoriks on isend, kelle meetod see on. See tähendab, et ühel isendil võib korraga töötada vaid üks synchornized-piiritlejaga märgitud meetod.

Järgnevas näites võtab serverprogramm vastu oksjonipakkumisi jättes meelde rohkem pakkunu nime ning pakutud summa. Selleks ajaks aga, kui kasutajale on öeldud parasjagu kõrgeim hind ning ta oma pakkumist mõtleb, peavad teised kasutajad jääma ootama. Pakkumisjärjekorrast järgmine saab alles siis kehtiva hinna teada ning oma pakkumist mõtlema hakata, kui eelmine oma pakkumise teinud on.

 

import java.net.*;

import java.io.*;

public class Loim5{

  public static void main(String argumendid[]) throws IOException{

    ServerSocket ss=new ServerSocket(3001);

    Object lukk=new Object();

    while(true){

      new Loim5abi(ss.accept(), lukk);

    }

  }

}

class Loim5abi extends Thread{

  Socket sc;

  Object lukk;

  static String failinimi="summa.txt";

  public Loim5abi(Socket usc, Object ulukk){

    sc=usc;

    lukk=ulukk;

    start();

  }

  public void run(){

    try{

      BufferedReader sisse=new BufferedReader(

        new InputStreamReader(sc.getInputStream())

      );

      PrintWriter valja=new PrintWriter(

        sc.getOutputStream(), true

      );

      valja.println("Palun teie nimi:");

      String uusnimi=sisse.readLine();

      valja.println("Oota praegust hinda");

      synchronized(lukk){

        BufferedReader failist=new BufferedReader(

          new FileReader(failinimi)

        );

        String vananimi=failist.readLine();

        int vanasumma=Integer.parseInt(failist.readLine());

        failist.close();

        valja.println("Viimati pakkus "+vananimi+" "+vanasumma+" krooni.");

        valja.println("Kirjuta suurem summa voi loobumiseks pass");

        String vastus=sisse.readLine();

        if(!vastus.equals("pass")){

          int uussumma=Integer.parseInt(vastus);

          if(uussumma>vanasumma){

            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.

 

Sisukord

 

Interneti vahendid Javas 1

Võrgu võimalused.. 1

Ühenduse põhimõte...... 1

Kellaaja küsimine eemal asuvast arvutist......... 2

Voog nii kirjutamiseks kui lugemiseks....................... 2

Telnet-ühendus....... 3

Omatehtud serveriprogramm.................... 3

Eraldi lõim.... 4

Lihtne jututuba....................... 5

Rakend ja võrk....................... 6

Kokkuvõte... 6

Lõimed.............. 6

Lõime peatumine.... 6

Mitmelõimeline programm..... 7

Ootamine...... 9

Sünkroniseerimine................. 9

Kokkuvõte. 11

Sisukord......... 11