Predavanje br. index|1|2|3|4|5|6|7|8|9|10|11|12|13|14|HOME


u pripremi

Dvanaesto predavanje – mrežno programiranje

Portovi – protokoli – internet adrese – kreiranje InetAddress objekata – parsiranje InetAddress objekata – URL -


Portovi

U pravilu (koje ima mnogo iznimaka) svako računalo ima samo jednu Internet adresu. Međutim, svako računalo često treba komunicirati s više od jednog računala istodobno. Na primjer, u isto vrijeme može se odvijati nekoliko ftp sesija, nekoliko web konekcija, chat i tako dalje.

Da bi se to omogućilo, mrežno sučelje računala podijeljeno je na 65536 ulaza, takozvanih portova. Port je apstrakcija. To nije nikakvi fizički ulaz kao što su serijski ili paralelni portovi na osobnim računalima. No podaci putuju Internetom u paketima, pri čemu svaki paket nosi ne samo adresu hosta nego i broj porta na koji treba stići. Host će na osnovi specificiranog porta odrediti kojem programu je namijenjen dotični paket podataka.

Ako želite, IP adresu možete zamišljati kao uličnu adresu, a portove kao brojeve stanova u kućama. Routeri koji transportiraju pakete brinu se samo o “uličnoj adresi”, dakle ne čitaju broj porta. To je prepušteno lokalnom računalu.

Na Unixu potrebne su vam root privilegije za osluškivanje konekcija na portovima od 0 do 1023. Konekcije na portovima od 1024 do 65535 može osluškivati svatko, dok god određeni port nije zauzet (na istom portu ne može više od jednog programa istodobno osluškivati konekcije). Na operacijskim sustavima Windows NT, Windows 95 i Mac bilo koji korisnik može bez posebnih privilegija osluškivati bilo koji port.

Bilo koji udaljeni host može ostvariti konekciju na poslužitelj koji osluškuje neki port ispod 1024. Nadalje, višestruke simultane konekcije mogu se ostvariti na udaljeni host i udaljeni port. Na primjer, web poslužitelj koji osluškuje (u pravilu) na portu 80 može istovremeno obrađivati desetke konekcija istodobno, sve usmjerene na port 80.

Ukratko, samo jedan process na lokalnom hostu može koristiti neki port. Naprotiv, mnogo udaljenih hostova može ostvarivati konekcije na jedan te isti (udaljeni) port.

Mnogi servisi rade na tzv. općepoznatim (well-known), portovima. To znači da protokoli specificiraju da neki servis može ili mora koristiti određeni port. Na primjer, http poslužitelji obično osluškuju na portu 80, SMTP poslužitelji na portu 25, Echo poslužitelji na portu 7, a Discard poslužitelji na portu 9. Nemaju svi servisi općepoznate portove. Na primjer NFS dozvoljava da se portovi otkrivaju u vrijeme izvršavanja.


Protokoli

Neformalno govoreći, protocol definira kakodva hosta međusobno komuniciraju. U radio komunikaciji, na primjer, protokoli specificiraju da po završetku poruke treba reći “over” ili “kraj”. Kod računalnih mreža protokol definira što jest, a što nije prihvatljivo za jednog ili drugog sudionika komunikacije u određenom vremenskom trenutku.

Na primjer, daytime protokol, specificiran u RFC 867, kaže da se klijent povezuje s poslužiteljem na portu 13. Poslužitelj tada kaže klijentu točno vrijeme u formatu koji je za čovjeka čitljiv, a nakon toga prekida konekciju.

S druge strane, time protokol, specificiran u RFC 868, propisuje binarnu reprezentaciju vremena koja je čitljiva za računala.

Daytime i time šalju istu informaciju. Međutim, oni koriste različite formate i različite protokole da bi je poslali.

Postoji onoliko različitih vrsta protokola koliko ima servisa koji se njima koriste. Lockstep protokoli traže jedan odgovor za svaki upit. Neki protokoli kao što je FTP koriste višestruke konekcija. Većina koristi samo jednu. Neki protokoli, kao HTTP dozvoljavaju samo jedan upit i jedan odgovor po konekciji. Drugi, kao FTP, dozvoljavaju višestruke konekcije i više odgovora unutar svake konekcije.


Internet adrese

Svako računalo na Internetu identificira se pomoću jedinstvene, četverobajtne IP adrese. Ona se obično zapisuje u tzv. dotted quad formatu kao npr. 161.53.8.14 gdje je svaki byte neoznačena vrijednost između 0 i 255.

Budući da je ovakve brojeve teško zapamtiti, adrese se mapiraju u imena kao “student.math.hr”, “jagor.srce.hr” i tako dalje. Međutim, bitna je numerička adresa, ne ime.

Klasa java.net.InetAddress predstavlja takve adrese. Između ostalog, ona sadrži i metode za konvertiranje numeričkih adresa u imena hostova i obrunuto.

public static InetAddress getByName(String host) throws UnknownHostException
public static InetAddress[] getAllByName(String host) throws UnknownHostException
public static InetAddress getLocalHost() throws UnknownHostException
public boolean isMulticastAddress()
public String getHostName()
public byte[] getAddress()
public String getHostAddress()
public int hashCode()
public boolean equals(Object obj)
public String toString()

Kreiranje InetAddress objekata

Klasa InetAddress je malo neobična jer nema ni jedan public konstruktor. Umjesto toga ime hosta ili dotted quad adresu u string formatu prosljeđujete statičkoj metodi InetAddress.getByName() kao u sljedećem primjeru:

 
try {
  InetAddress public = InetAddress.getByName("public.srce.hr");
  InetAddress jagor = InetAddress.getByName("161.53.2.130");
}
catch (UnknownHostException e) {
  System.err.println(e);
}  
 

Neki hostovi imaju više adresa. Ako pretpostavljate da je to slučaj sa hostom kojeg ispitujete, možete dobiti polje objekata tipa InetAddress pomoću statičke metode InetAddress.getAllByName(), na primjer ovako:

 

import java.net.*;

 

public class HostAddresses {

 

  public static void main (String args[]) {

 

    try {

      InetAddress[] addresses = InetAddress.getAllByName(args[0]);

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

        System.out.println(addresses[i]);

      }

    }

    catch (UnknownHostException e) {

      System.out.println("Ne mogu naci trazeni host");

    }

    catch (ArrayIndexOutOfBoundsException e) {

      System.out.println("Upisite ime trazenog hosta");

    }

 

  }

 

}

 

Imat ćemo:

 
% javac HostAddresses.java
% java HostAddresses student.math.hr
student.math.hr/161.53.8.14
student.math.hr/161.53.29.70
%
 

Konačno, statička metoda InetAddress.getLocalHost() vraća objekt tipa InetAddress koji sadrži adresu računala na kojem se program izvršava:

 
try {
  InetAddress me = InetAddress.getLocalHost();
}
catch (UnknownHostException e) {
  System.err.println(e);
}  

Parsiranje InetAddress objekata

Za objekt tipa InetAddress možete dobiti ime hosta kao string, IP adresu kao string ili kao polje byteova, a također možete ispitati je li to tzv. multicast adresa (adresa klase D, čija su prva četiri bita 1110). To se rješava sljedećim metodama:

 
public String getHostName()
public String getHostAddress()
public byte[] getAddress()
public boolean isMulticastAddress()
 

Sljedeći program ispisuje podatke o lokalnom hostu.

 

import java.net.*;

 

public class LocalHost {

 

  public static void main(String[] args) {

 

    try {

      InetAddress me = InetAddress.getLocalHost();

      System.out.println("Ime lokalnog hosta  -----> " + me.getHostName());

      System.out.println("Adresa lokalnog hosta ---> " + me.getHostAddress());

      byte[] address = me.getAddress();

      System.out.print("Adresa po byteovima -----> ");

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

        System.out.print(address[i] + " ");

      }

      System.out.println();

      if (me.isMulticastAddress())

         System.out.println("multicast");

      else

         System.out.println("nije multicast");

    }

    catch (UnknownHostException e) {

      System.err.println("Ne mogu naci trazeni host");

    }

  }

 

}

 

% javac LocalHost.java
% java LocalHost
Ime lokalnog hosta  -----> student.math.hr
Adresa lokalnog hosta ---> 161.53.8.14
Adresa po byteovima -----> -95 53 8 14
nije multicast
%
 

Primijetite da su byteovi koje vraća metoda getAddress() označeni iako se, prema konvenciji, za dotted quad adrese koriste neoznačeni byteovi.


URL

URL, kratica za "Uniform Resource Locator", je način za jednoznačno identificiranje lokacije nekog resursa na internetu. Tipični URL-ovi izgledaju ovako:

 
http://public.srce.hr/
file:///Macintosh%20HD/Java/Docs/JDK%201.1.1%20docs/api/java.net.InetAddress.html#_top_
http://www.macintouch.com:80/newsrecent.shtml
ftp://ftp.carnet.hr/pub/
mailto:Mladen.Vedris@student.math.hr
telnet://student.math.hr
 

Većina URL-ova može se rastaviti na sljedeće komponente (koje ne moraju sve biti prisutne u svakom URL-u):


Klasa URL

URL-ovi su u Javi predstavljeni klasom java.net.URL. Postoje konstruktori za kreiranje novog URL-a i metode za parsiranje različitih dijelova URL-a. Ipak, bitni dio ove klase su metode koje vam omogućuju da sa nekog URL-a dobijete InputStream i na taj način čitate podatke s poslužitelja.

Klasa URL usko je povezana s handlerima protokola i sadržaja. Cilj je odvojiti snimljene (downloaded) podatke od protokola koji je korišten za njihovo snimanje. Handler protokola je odgovoran za komuniciranje sa poslužiteljem, tj. prenošenje byteova od poslužitelja do klijenta. On obavlja potrebne “pregovore” (negotiations) oko poslužitelja i svih potrebnih headera. Njegov je posao dobaviti byteove traženih podataka. Handler sadržaja preuzima te byteove i prevodi ih u neku vrstu Java objekta kao što je InputStream ili ImageProducer.

Kad konstruirate objekt tipa URL, Java će potražiti handler protokola koji razumije “protokolski” dio URL-a, kao što je npr. "http" ili "mailto". Ako ne pronađe takav handler, izbacit će MalformedURLException. Koji su protokoli podržani, ovisi o implementaciji, no http i file su podržani uglavnom svagdje. Sun-ov JDK 1.1. razumije sljedećih deset:

Posljednjih pet su specifični Sun-ovi protokoli i koriste ih interno JDK i HotJava.


Konstruiranje URL objekata

Pogledajmo neke od konstruktora klase URL. Svi oni izbacuju MalformedURLException.

 
public URL(String u) throws MalformedURLException
public URL(String protocol, String host, String file) throws MalformedURLException
public URL(String protocol, String host, int port, String file) throws MalformedURLException
public URL(URL context, String u) throws MalformedURLException
 

Za potpuni, apsolutni URL kao što je
http://student.math.hr/~vedris/java/java-predavanja/java-predavanje-01.htm
konstruirate odgovarajući URL objekt ovako:

 
  URL u = null;
  try {
    u = new URL("http://student.math.hr/~vedris/java/java-predavanja/java-predavanje-01.htm ");
  }
  catch (MalformedURLException e) {
  
  }
 

You can also construct the URL by passing its pieces to the constructor, like this:

 
  URL u = null;
  try {
    u = new URL("http", "student.math.hr", 
                  "~vedris/java/java-predavanja/java-predavanje-01.htm");
  }
  catch (MalformedURLException e) {
  
  }
 

U pravilu ne morate specificirati port za URL. Većina protokola ima pretpostavljeni (default) URL , pa tako za http pretpostavljamo 80. Međutim, ako tražimo nešto što nije na default portu, poslužit ćemo se trećim konstruktorom:

 
  URL u = null;
  try {
    u = new URL("http", "student.math.hr", 80, 
                  "~vedris/java/java-predavanja/java-predavanje-01.htm");
  }
  catch (MalformedURLException e) {
  
  }
 

Konačno, mnoge HTML datoteke sadrže relativne URL-ove. Na primjer, URL ove stranice je
http://student.math.hr/~vedris/java/java-predavanja/java-predavanje-12.htm
Ako bismo htjeli mirorirati ove stranice na nekom drugom računalu, mogli bismo umjesto apsolutnih koristiti relativne URL-ove koji nasljeđuju ime hosta itd.. Na primjer, ako na ovoj stranici imamo link java-predavanje-02, onda on zapravo pokazuje na http://student.math.hr/~vedris/java/java-predavanja/java-predavanje-02.htm, međutim, na računalu regoc.srce.hr pokazivao bi na
http://regoc.srce.hr/~vedris/java/java-predavanja/java-predavanje-12.htm
i tako dalje. Četvrti od gore navedenih konstruktora kreira URL koji je relativan u odnosu na zadani URL. Na primjer:

 
  URL u1, u2;
  try {
    u1 = new URL("http://student.math.hr/~vedris/java/java-predavanja/java-predavanje-01.htm");
    u2 = new URL(u1, "java-predavanje-02.htm");
  }
  catch (MalformedURLException e) {
  
  }
 

To je posebno korisni kod parsiranja HTML-a.


Parsiranje URL objekata

Klasa java.net.URL koristi sljedeće metode za rastavljanje URLa na njegove komponente:

 
public String getProtocol()
public String getHost()
public int getPort() 
public String getFile()
public String getRef()
 

Na primjer,

 
 try {
    URL u = new URL("http://student.math.hr/~vedris/java/html/TricksterApplet.html#top");
    System.out.println("Protokol: " + u.getProtocol());
    System.out.println("Host    : " + u.getHost());
    System.out.println("Port    : " + u.getPort());
    System.out.println("File    : " + u.getFile());
    System.out.println("Anchor  : " + u.getRef());
  }
  catch (MalformedURLException e) {
  
  }
 

Ako port u URLu nije eksplicitno specificiran, vraća se -1. To ne znači da se pokušava napraviti konekcija na (nepostojeći) port –1, nego jednostavno da se koristi default port.

Ako ne postoji referenca (anchor), onda je ona naprosto null, pa treba uhvatiti NullPointerException ili, još bolje, ispitati da je non-null prije nego li je koristimo.

Konačno, ako je izostavljen file, kao na primjer u http://student.math.hr, odgovarajuća se vrijednost postavlja na "/".


Čitanje podataka sa URLa

Metoda openStream() otvara konekciju na poslužitelj kojeg URL specificira te vraća InputStream s podacima iz te konekcije. To omogućuje snimanje podataka sa poslužitelja. Svi headeri koji dolaze prije stvarnih podataka ili tražene datoteke bit će obrisani prije nego stream bude otvoren. Dobit ćete samo čiste podatke.

 
public final InputStream openStream() throws IOException

Stream ćete pročitati koristeći se metodama iz paketa java.io o kojem smo govorili u desetom predavanju. Primijetite da većina mrežnih konekcija predstavlja manje pouzdan i sporiji izvor podataka nego što su to datoteke. Bit će dakle potrebno izvršiti bufferiranje, koristeći se pri tom klasama BufferedInputStream ili BufferedReader.

Program iz sljedećeg primjera čita niz URLa sa komandne linije. Iz svakog argumenta pokušava formirati URL, povezati se na specificirani poslužitelj i snimiti podatke koje će onda ispisati na System.out.

import java.net.*;
import java.io.*;
 
public class Webcat {
 
  public static void main(String[] args) {
 
    for (int i = 0; i < args.length; i++) {
      try {
        URL u = new URL(args[i]);
        InputStream is = u.openStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        String theLine;
        while ((theLine = br.readLine()) != null) {
          System.out.println(theLine);
        }
      }
      catch (MalformedURLException e) {
        System.err.println(e);
      } 
      catch (IOException e) {
        System.err.println(e);      
      } 
      
    }
 
  }
 
}
 
% javac Webcat.java
% java Webcat "http://student.math.hr/~vedris/java/html/TricksterApplet.html"
<APPLET CODE="TricksterApplet.class"
CODEBASE="http://student.math.hr/~vedris/java/classes"
ARCHIVE="Trickster.jar"
WIDTH=1 HEIGHT=1>
</APPLET>
%

Utičnice (sockets)

Prije nego se podaci šalju preko Interneta s jednog hosta na drugi uz pomoć TCP/IP, oni se pakiraju u pakete različitih, ali konačnih veličina koji se nazivaju datagrami. Veličina datagrama varira od nekoliko desetaka byteova pa do oko 60,000 byteova. Sve što je veće od toga, a često i ono što je manje od toga, treba podijeliti u manje dijelove prije odašiljanja. Prednost takvog slanja podataka je u tome što ako se jedan paket putem izgubi, on se može poslati ponovo bez da se opet šalju svi ostali paketi. Također, ako paketi stignu izvan poretka, oni se mogu pravilno poredati i kod primatelja.

Ipak, sve je ovo transparentno za Java programera. Hostov ugrađeni mrežni softver će transparentno obaviti razdiobu podataka u pakete na strani pošiljatelja te ih ponovo spojiti na strani primatelja. Umjesto toga, Java programer se susreće s vrlo visokom apstrakcijom koju nazivamo utičnica (socket). Utičnica predstavlja pouzdanu konekciju za prijenos podataka između dva hosta. Ona vas izolira od detalja kodiranja paketa, gubitka i ponovnog slanja pošiljki, te uspostavljanja poretka među pristiglim paketima.

Utičnica obavlja sljedeće četiri fundamentalne operacije:

  1. uspostavljanje veze s udaljenim računalom
  2. slanje podataka
  3. primanje podataka
  4. prekidanje veze

Utičnica ne može biti priključena na više hostova istodobno.


Klasa Socket

Klasa java.net.Socket omogućuje izvođenje svih četiriju fundamentalnih operacija na utičnicama. Možete ostvariti konekciju na udaljeno računalo, slati i primati podatke te prekinuti konekciju.

Konekcija se obavlja uz pomoć konstruktora. Svakom objektu tipa Socket pridružen je točno jedan udaljeni host. Da biste ostvarili konekciju na drugi host, morate kreirati novi objekti tipa Socket.

public Socket(String host, int port) throws UnknownHostException, IOException
public Socket(InetAddress address, int port) throws IOException
public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException
public Socket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
 

Slanje i primanje podataka obavlja se pomoću izlaznih i ulaznih streamova. Sljedeće metode daju odgovarajuće streamove za danu utičnicu.

 
public InputStream getInputStream() throws IOException
public OutputStream getOutputStream() throws IOException
 

Sljedeća metoda zatvara utičnicu:

 
public synchronized void close() throws IOException
 

Postoji i nekoliko metoda koje postavljanju različite opcije vezane uz utičnicu, no uglavnom ćete otkriti da su default vrijednosti sasvim zadovoljavajuće.

 
public void setTcpNoDelay(boolean on) throws SocketException
public boolean getTcpNoDelay() throws SocketException
public void setSoLinger(boolean on, int val) throws SocketException
public int getSoLinger() throws SocketException
public synchronized void setSoTimeout(int timeout) throws SocketException
public synchronized int getSoTimeout() throws SocketException
public static synchronized void setSocketImplFactory(SocketImplFactory fac) throws IOException
 

Sljedeće metoda daju različite informacije o utičnici:

 
public InetAddress getInetAddress()
public InetAddress getLocalAddress()
public int getPort()
public int getLocalPort()
 

Na kraju, tu je i uobičajena toString() metoda:

 
public String toString()

Konstruiranje Socket objekata

Pogledajmo public, non-deprecated konstruktore klase Socket.

 
public Socket(String host, int port) throws UnknownHostException, IOException
public Socket(InetAddress address, int port) throws IOException
public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
public Socket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException
 

Potrebno je, dakle, specificirati u najmanju ruku host i port na koji se želite konektirati. Host može biti specificiran bilo kao string, npr "student.math.hr" ili kao objekt tipa InetAddress. Port mora biti cijeli broj između 1 i 65535. Ne može biti bilo koji, već ga trebate znati isto kao i ime hosta. Na primjer,

 
Socket webStudent = new Socket("student.math.hr", 80); 
 

Posljednja dva konstruktora također specificiraju host i port sa kojega ostvarujete konekciju. Na sistemu sa više IP adresa, kao što su mnogi web poslužitelji, to vam omogućuje da odaberete svoje mrežno sučelje i IP adresu. Možete specificirati i broj porta, ali kako svaki pojedini port može biti zauzet, najbolje je staviti port 0. To će reći sistemu da izabere bilo koji slobodni port. Ako želite znati s kojeg porta ste napravili konekciju, pozvat ćete metodu getLocalPort().

 
Socket webStudent = new Socket("student.math.hr", 80, "pc-mladen.srce.hr", 0); 
 

Ovi konstruktori neće samo kreirati novi objekt tipa Socket. Oni će također pokušati ostvariti konekciju na specificirani udaljeni poslužitelj. Svi oni zato odbacuju IOException u slučaju da se konekcija iz bilo kojeg razloga ne može uspostaviti.


Port Scanner

Ne možete se naprosto konektirati na bilo koji port na bilo kojem hostu. Konekcija je moguća samo na one portove na kojima udaljeno računalo osluškuje dolazne konekcije. Konstruktore utičnica možete koristiti da biste ustanovili na kojim portovima računalo osluškuje. Pogledajte primjer:

 
import java.net.*;
import java.io.IOException;
 
public class PortScanner {
 
  public static void main(String[] args) {
 
    for (int i = 0; i < args.length; i++) {
      try {
        InetAddress ia = InetAddress.getByName(args[i]);
        scan(ia);
      }
      catch (UnknownHostException e) {
        System.err.println(args[i] + " is not a valid host name.");
      }
    }
 
  }
 
  public static void scan(InetAddress remote) {
 
    // Do I need to synchronize remote?
    // What happens if someone changes it while this method
    // is running?
 
    String hostname = remote.getHostName();
    // Skanirat cemo samo portove od 78 do 81. 
    // Potpuno skaniranje islo bi od 0 do 65535. Ne cinite to bez dozvole vlasnika hosta!
    for (int port = 78; port < 82; port++) {
      try {
        Socket s = new Socket(remote, port); 
        System.out.println("Server slusa na portu " + port
         + " racunala " + hostname);
        s.close();
      }
      catch (IOException e) {
        System.out.println("Racunalo ne slusa na portu " + port);
      }
    }
 
  }
 
  public static void scan(String remote) throws UnknownHostException {
 
    // Why throw the UnknownHostException? Why not catch it like I did
    // in the main() method?
    InetAddress ia = InetAddress.getByName(remote);
    scan(ia);
 
  }
 
}
 

Izvršit ćemo ovu ograničenu verziju PortScannera koja ispituje samo portove od 78 do 81. Primijetit ćete da računalo sluša na portu 80. To je standardni port za web poslužitelj.

 
% javac PortScanner.java
% java PortScanner localhost
Racunalo ne slusa na portu 78
Racunalo ne slusa na portu 79
Server slusa na portu 80 racunala localhost
Racunalo ne slusa na portu 81
% 
 

Upozorenje: Ne pokušavajte usmjeriti kompletni PortScanner prema mašini koja nije vaša vlastita bez dozvole vlasnika / sistem inženjera, jer će se to smatrati hakerskim napadom i ugrožavanjem sigurnosti računala!!!


Čitanje ulaza sa utičnice

Jednom kad je utičnica konektirana, možete slati podatke na poslužitelj putem izlaznog streama ili ih primati sa poslužitelja pomoću ulaznog streama. Koje točno podatke šaljete ili primate obično ovisi o protokolu.

Metoda getInputStream() vraća objekt tipa InputStream koji čita podatke iz utičnice. Možete koristiti uobičajene metode klase InputStream o kojima ste učili u desetom predavnju. Uglavnom će biti potrebno povezati InputStream sa nekim drugim ulaznim streamom ili readerom kako bi se lakše moglo rukovati podacima.

Na primjer, sljedeći fragment koda radi konekciju na daytime poslužitelj na portu 13 računala student.math.hr i ispisuje podatke koje od njega dobije.

   try {
      Socket s = new Socket("student.math.hr", 13);
      InputStream is = s.getInputStream();
      InputStreamReader isr = new InputStreamReader(is);
      BufferedReader br = new BufferedReader(isr);
      String theTime = br.readLine();
      System.out.println(theTime);
    }
    catch (IOException e) {
      return (new Date()).toString();
    }

Klijent Daytime

Pogledajmo sada kompletan program, Daytime klijent, koji se konektira na daytime poslužitelj računala student.math.hr na portu 13 i ispisuje podatke koje od njega dobije.

 
import java.net.*;
import java.io.*;
import java.util.Date;
 
 
public class Daytime {
 
  InetAddress server;
  int port = 13;
 
  public static void main(String[] args) {
 
    try {
      Daytime d = new Daytime("student.math.hr");
      System.out.println(d.getTime());
    }
    catch (IOException e) {
      System.err.println(e);
    }
 
  }
 
  public Daytime() throws UnknownHostException {
 
    this (InetAddress.getLocalHost());
 
  }
 
  public Daytime(String name) throws UnknownHostException {
    this(InetAddress.getByName(name));
  }
 
  public Daytime(InetAddress server) {
    this.server = server;
  }
 
  public String getTime() {
    if (server == null) return (new Date()).toString();
    try {
      Socket s = new Socket(server, port);
      InputStream is = s.getInputStream();
      InputStreamReader isr = new InputStreamReader(is);
      BufferedReader br = new BufferedReader(isr);
      StringBuffer sb = new StringBuffer();
      String theLine;
      while ((theLine = br.readLine()) != null) {
        sb.append(theLine + "\r\n");
      }
      return sb.toString();
    }
    catch (IOException e) {
      return (new Date()).toString();
    }
 
  }
 
}
 
% javac Daytime.java
% java Daytime
Tue Jan 22 16:16:31 MET 2002
%

Pisanje izlaza preko utičnice

Metoda getOutputStream() vraća objekt tipa OutputStream pomoću kojeg se ispisuju podaci na utičnicu. Možete koristiti sve uobičajene metoed klase OutputStream o kojima ste učili u desetom predavanju. Uglavnom će biti potrebno povezati OutputStream sa nekim drugim izlaznim streamom ili readerom kako bi se lakše moglo rukovati podacima.

Na primjer, sljedeći program se konektira na discard poslužitelj na portu 9 računala student.math.hr i šalje mu podatke koje čita sa standardnog ulaza System.in.

   byte[] b = new byte[128];
    try {
      Socket s = new Socket("student.math.hr", 9);
      OutputStream out = s.getOutputStream();
      while (true) {
        int n = System.in.available();
        if (n > b.length) n = b.length;
        int m = System.in.read(b, 0, n);
        if (m == -1) break;
        out.write(b, 0, n);
      }
      s.close();
    }
    catch (IOException e) {
    }

Klijent Discard

Pogledajmo sada kompletan program, Discard klijent, koji se konektira na Discard poslužitelj računala student.math.hr na portu 9 i šalje mu podatke koje čita sa standardnog ulaza System.in.

 
import java.net.*;
import java.io.*;
 
 
public class Discard extends Thread {
 
  InetAddress server;
  int port = 9;
  InputStream theInput;
 
  public static void main(String[] args) {
 
    try {
      Discard d = new Discard("student.math.hr");
      d.start();
    }
    catch (IOException e) {
      System.err.println(e);
    }
 
  }
 
  public Discard() throws UnknownHostException {
    this (InetAddress.getLocalHost(), System.in); 
  }
 
  public Discard(String name) throws UnknownHostException {
    this(InetAddress.getByName(name), System.in);
  }
 
  public Discard(InetAddress server) {
    this(server, System.in);
  }
 
  public Discard(InetAddress server, InputStream is) {
    this.server = server;
    theInput = System.in;
  }
 
  public void run() {
    byte[] b = new byte[128];
    try {
      Socket s = new Socket(server, port);
      OutputStream out = s.getOutputStream();
      while (true) {
        int n = theInput.available();
        if (n > b.length) n = b.length;
        int m = theInput.read(b, 0, n);
        if (m == -1) break;
        out.write(b, 0, m);
      }
      s.close();
    }
    catch (IOException e) {
    }
  }
 
}
 
% javac Discard.java
% java Discard
asdf
<Ctrl-C>
%

Čitanje i pisanje kroz utičnicu

Nije uobičajeno da se s utičnice samo čita ili da se na nju samo piše. Većina protokola traži da klijent i čita i piše. Neki protokoli traže da čitanje i pisanje bude naizmjenično, npr.

write
read
write
read
write
read
 

Drugi protokoli, npr. HTTP 1.0, traže višestruke ispise nakon kojih slijede višestruka čitanja, npr.:

 
write
write
write
read
read
read
read
 

Neki protokoli dozvoljavaju da klijentovi upiti i poslužiteljevi odgovori budu slobodno izmiješani.

Java ne stavlja nikakve restrikcije na čitanje i pisanje po utičnicama. Jedan thread može čitati s utičnice, a drugi može po njoj istodobno pisati. (Primijetite da to nije isto kao kad jedan thread čita datoteku, a drugi piše u nju.)


Čitanje i pisanje utičnice za HTTP

Program u sljedećem primjeru šalje upit HTTP poslužitelju koristeći izlazni stream utičnice, a zatim čita odgovor koristeći ulazni stream utičnice. HTTP poslužitelji sami zatvaraju konekciju kad pošalju odgovor.

 
import java.net.*;
import java.io.*;
 
 
public class Grabber {
 
  public static void main(String[] args) {
 
    int port = 80;
 
    for (int i = 0; i < args.length; i++) {
      try {
         URL u = new URL(args[i]);
         if (u.getPort() != -1) port = u.getPort();
         if (!(u.getProtocol().equalsIgnoreCase("http"))) {
           System.err.println("Sorry. I only understand http.");
           continue;
         }
         Socket s = new Socket(u.getHost(), port);
         OutputStream theOutput = s.getOutputStream();
         // no auto-flushing
         PrintWriter pw = new PrintWriter(theOutput, false);
         // native line endings are uncertain so add them manually
         pw.print("GET " + u.getFile() + " HTTP/1.0\r\n");
         pw.print("Accept: text/plain, text/html, text/*\r\n");
         pw.print("\r\n");
         pw.flush();
         InputStream in = s.getInputStream();
         InputStreamReader isr = new InputStreamReader(in);
         BufferedReader br = new BufferedReader(isr);
         int c;
         while ((c = br.read()) != -1) {
           System.out.print((char) c);
         }
      }
      catch (MalformedURLException e) {
        System.err.println(args[i] + " is not a valid URL");
      }
      catch (IOException e) {
        System.err.println(e);
      }
 
    }
 
  }
 
}
 

Pogledajte poziv programa i odgovor sa tipičnim početkom, karakterističnim za HTTP poslužitelje:

 
% java Grabber "http://student.math.hr:80/~vedris/java/html/TricksterApplet.html"
HTTP/1.1 200 OK
Date: Tue, 22 Jan 2002 15:32:38 GMT
Server: Apache/1.2.6
Last-Modified: Tue, 13 Nov 2001 13:09:48 GMT
ETag: "213be-97-3bf11b9c"
Content-Length: 151
Accept-Ranges: bytes
Connection: close
Content-Type: text/html
 
<APPLET CODE="TricksterApplet.class"
CODEBASE="http://student.math.hr/~vedris/java/classes"
ARCHIVE="Trickster.jar"
WIDTH=1 HEIGHT=1>
</APPLET>
%

 

Čitanje i pisanje po utičnici za Echo

The echo protocol simply echoes back anything its sent. The following echo client reads data from an input stream, then passes it out to an output stream connected to a socket, connected to a network echo server. A second thread reads the input coming back from the server. The main() method reads some file names from the command line and passes them into the output stream.

 
import java.net.*;
import java.io.*;
import java.util.*;
 
 
public class Echo {
 
  InetAddress server;
  int port = 7;
  InputStream theInput;
 
  public static void main(String[] args) {
 
    if (args.length == 0) {
      System.err.println("Usage: java Echo file1 file2...");
      System.exit(1);
    }
 
    Vector v = new Vector();
    for (int i = 0; i < args.length; i++) {
      try {
        FileInputStream fis = new FileInputStream(args[i]);
        v.addElement(fis);
      }
      catch (IOException e) {
      }
    }
 
    InputStream in = new SequenceInputStream(v.elements());
 
    try {
      Echo d = new Echo("metalab.unc.edu", in);
      d.start();
    }
    catch (IOException e) {
      System.err.println(e);
    }
 
  }
 
  public Echo() throws UnknownHostException {
    this (InetAddress.getLocalHost(), System.in); 
  }
 
  public Echo(String name) throws UnknownHostException {
    this(InetAddress.getByName(name), System.in);
  }
 
  public Echo(String name, InputStream is) throws UnknownHostException {
    this(InetAddress.getByName(name), is);
  }
 
  public Echo(InetAddress server) {
    this(server, System.in);
  }
 
  public Echo(InetAddress server, InputStream is) {
    this.server = server;
    theInput = is;
  }
 
  public void start() {
    try {
      Socket s = new Socket(server, port);
      CopyThread toServer = new CopyThread("toServer", 
       theInput, s.getOutputStream());
      CopyThread fromServer = new CopyThread("fromServer", 
       s.getInputStream(), System.out);
      toServer.start();
      fromServer.start();
    }
    catch (IOException e) {
      System.err.println(e);
    }
  }
 
}
 
class CopyThread extends Thread {
 
  InputStream in;
  OutputStream out;
 
  public CopyThread(String name, InputStream in, OutputStream out) {
    super(name);
    this.in = in;
    this.out = out;
  }
 
  public void run() {
    byte[] b = new byte[128];
    try {
      while (true) {
        int n = in.available();
        if (n == 0) Thread.yield();
        else System.err.println(n);
        if (n > b.length) n = b.length;
        int m = in.read(b, 0, n);
        if (m == -1) {
          System.out.println(getName() + " done!");
          break;
        }
        out.write(b, 0, n);
      }
    }
    catch (IOException e) {
    }
  }
 
}
 
 

 

Server Sockets

Utičnice poslužitelja (server sockets)

There are two ends to each connection: the client, that is the host that initiates the connection, and the server, that is the host that responds to the connection. Clients and servers are connected by sockets.

On the server side instead of connecting to a remote host, a program waits for other hosts to connect to it. A server socket binds to a particular port on the local machine. Once it has successfully bound to a port, it listens for incoming connection attempts. When it detects a connection attempt, it accepts the connection. This creates a socket between the client and the server over which the client and the server communicate.

Multiple clients can connect to the same port on the server at the same time. Incoming data is distinguished by the port to which it is addressed and the client host and port from which it came. The server can tell for which service (like http or ftp) the data is intended by inspecting the port. It can tell which open socket on that service the data is intended by looking at the client address and port stored with the data.

No more than one server socket can listen to a particular port at one time. Therefore, since a server may need to handle many connections at once, server programs tend to be heavily multi-threaded. Generally the server socket listening on the port will only accept the connections. It then passes off the actual processing of connections to a separate thread.

Incoming connections are stored in a queue until the server can accept them. (On most systems the default queue length is between 5 and 50. Once the queue fills up further incoming connections are refused until space in the queue opens up.)


 

Klasa ServerSocket

The java.net.ServerSocket class represents a server socket. It is constructed on a particular port. Then it calls accept() to listen for incoming connections. accept() blocks until a connection is detected. Then accept() returns a java.net.Socket object you use to perform the actual communication with the client.

There are three constructors that let you specify the port to bind to, the queue length for incoming connections, and the IP address to bind to:

 
public ServerSocket(int port) throws IOException
public ServerSocket(int port, int backlog) throws IOException
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
 

The accept() and close() methods provide the basic functionality of a server socket.

 
public Socket accept() throws IOException
public void close() throws IOException
 

On a server with multiple IP addresses, the getInetAddress() method tells you which one this server socket is listening to. The getLocalPort() method tells you which port you're listening to.

 
public InetAddress getInetAddress()
public int getLocalPort()
 

There are three methods to set and get various options. The defaults are generally fine.

 
public synchronized void setSoTimeout(int timeout) throws SocketException
public synchronized int getSoTimeout() throws IOException
public static synchronized void setSocketFactory(SocketImplFactory fac) throws IOException
 

Finally, there's the usual toString() method:

 
public String toString()
 

 

Lokalni PortScanner

You can attempt to determine which ports are currently occupied by trying to create server sockets on all of them, and seeing where that operation fails.

 
import java.net.*;
import java.io.IOException;
 
 
public class LocalPortScanner {
 
  public static void main(String[] args) {
 
    // first test to see whether or not we can bind to ports
    // below 1024
    boolean rootaccess = false;
    for (int port = 1; port < 1024; port += 50) {
      try {
        ServerSocket ss = new ServerSocket(port);
        // if successful
        rootaccess = true;
        ss.close();
        break;
      }
      catch (IOException e) {
      }
    }
    
    int startport = 1;
    if (!rootaccess) startport = 1024;
    int stopport = 65535;
    
    for (int port = startport; port <= stopport; port++) {
      try {
        ServerSocket ss = new ServerSocket(port);
        ss.close();
      }
      catch (IOException e) {
        System.out.println("Port " + port + " is occupied.");
      }
    
    }
 
  }
 
}
 

 

Konstruktori klase ServerSocket

The java.net.ServerSocket class has three constructors that let you specify the port to bind to, the queue length for incoming connections, and the IP address to bind to:

 
public ServerSocket(int port) throws IOException
public ServerSocket(int port, int backlog) throws IOException
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
 

Normally you only specify the port you want to listen on, like this:

 
  try {
    ServerSocket ss = new ServerSocket(80);
  }
  catch (IOException e) {
    System.err.println(e);
  }
 

When you create a ServerSocket object, it attempts to bind to the port on the local host given by the port argument. If another server socket is already listening to the port, then a java.net.BindException, a subclass of java.io.IOException, is thrown. No more than one process or thread can listen to a particular port at a time. This includes non-Java processes or threads. For example, if there's already an HTTP server running on port 80, you won't be able to bind to port 80.

On Unix systems (but not Windows or the Mac) your program must be running as root to bind to a port between 1 and 1023.

0 is a special port number. It tells Java to pick an available port. You can then find out what port it's picked with the getLocalPort() method. This is useful if the client and the server have already established a separate channel of communication over which the chosen port number can be communicated.

For example, the ftp protocol uses two sockets. The initial connection is made by the client to the server to send commands. One of the commands sent tells the server the name of the port on which the client is listening. The server then connects to the client on this port to send data.

 
  try {
    ServerSocket ftpdata = new ServerSocket(0);
    int port = ftpdata.getLocalPort();
  }
  catch (IOException e) {
    System.err.println(e);
  }
 

 

Podešavanje duljine repa

 
public ServerSocket(int port, int backlog) throws IOException
 

The operating system stores incoming connections for each port in a first-in, first-out queue until they can be accepted. The default queue length varies from operating system to operating system. However, it tends to be between 5 and 50. Once the queue fills up further connections are refused until space opens up in the queue. If you think you aren't going to be processing connections very quickly you may wish to expand the queue when you construct the server socket. For example,

 
  try {
    ServerSocket httpd = new ServerSocket(80, 50);
  }
  catch (IOException e) {
    System.err.println(e);
  }
 

Each system has a maximum queue length beyond which the queue cannot be expanded. If you ask for a queue longer than the maximum queue size on a particular platform, (generally around fifty to a hundred) then the queue is merely expanded to its maximum size.

Once the server socket has been constructed, you cannot change the queue length it uses.


 

Odabir lokalne adrese

Many hosts have more than one IP address. This is especially common at web server farms where a single machine is shared by multiple sites. By default, a server socket binds to all available IP addresses. That is it accepts connections addressed to any of the local IP addresses on a given port. However you can modify that behavior with this constructor:

 
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
 

You must also specify the queue length in this constructor.

 
  try {
    InetAddress ia = InetAddress.getByName("199.1.32.90");
    ServerSocket ss = new ServerSocket(80, 50, ia);
  }
  catch (IOException e) {
    System.err.println(e);
  }
 

How would you bind to some but not all IP addresses on the server?


 

Ispis podataka na klijent

The following simple program repeatedly answers client requests by sending back the client's address and port. Then it closes the connection.

 
import java.net.*;
import java.io.*;
 
public class HelloServer {
 
  public final static int defaultPort = 2345;
 
  public static void main(String[] args) {
  
    int port = defaultPort;
    
    try {
      port = Integer.parseInt(args[0]);
    }
    catch (Exception e) {
    }
    if (port <= 0 || port >= 65536) port = defaultPort;
    
    try {
      ServerSocket ss = new ServerSocket(port);
      while (true) {
        try {
          Socket s = ss.accept();
          PrintWriter pw = new PrintWriter(s.getOutputStream());
          pw.println("Hello " + s.getInetAddress() + " on port " + s.getPort());
          pw.println("This is " + s.getLocalAddress() + " on port " + s.getLocalPort());
          pw.flush();
          s.close();
        }
        catch (IOException e) {
        }
      }
    }
    catch (IOException e) {
      System.err.println(e);
    }
 
  }
  
}
 

Pokrenem li ovaj program na lokalnom računalu, npr. pc-mladen.srce.hr i, dok je on aktivan, napravim s računala student.mat.hr telnet na pc-mladen.srce.hr 2345, dobit ću sljedeće:

 
% telnet pc-mladen.srce.hr 2345 
Trying...
Connected to pc-mladen.srce.hr.
Escape character is '^]'.
Hello student.math.hr/161.53.8.14 on port 51546
This is pc-mladen.srce.hr/161.53.2.93 on port 2345
Connection closed by foreign host.
% telnet pc-mladen.srce.hr 2345
Trying...
Connected to pc-mladen.srce.hr.
Escape character is '^]'.
Hello student.math.hr/161.53.8.14 on port 51547
This is pc-mladen.srce.hr/161.53.2.93 on port 2345
Connection closed by foreign host.
 

Primijetite da se dolazni port promijeni kod svakog novog telneta. Kad HelloServer prestane s radom, port koji je on osluškivao bit će opet slobodan, što možete provjeriti pomoću programa LocalPortScanner. Također, pokušam li sada napraviti telnet na port 2345, dobit ću:

 
% telnet pc-mladen.srce.hr 2345
Trying...
telnet: Unable to connect to remote host: Connection refused
%
 

Kad HelloServer prestane s radom, port koji je on osluškivao bit će opet slobodan, što možete provjeriti pomoću programa LocalPortScanner.


 

Čitanje podataka pomoću utičnice poslužitelja

The port scanner pretty much exhausts what you can do with just the constructors. Almost all ServerSocket objects you create will use their accept() method to connect to a client.

 
public Socket accept() throws IOException
 

There are no getInputStream() or getOutputStream() methods for ServerSocket. Instead you use accept() to return a Socket object, and then call its getInputStream() or getOutputStream() methods.

For example,

 
  try {
    ServerSocket ss = new ServerSocket(2345);
    Socket s = ss.accept();
    PrintWriter pw = new PrintWriter(s.getOutputStream());
    pw.println("Hello There!");
    pw.println("Goodbye now.);
    s.close();
  }
  catch (IOException e) {
    System.err.println(e);
  }
 

Notice in this example, I closed the Socket s, not the ServerSocket ss. ss is still bound to port 2345. You get a new socket for each connection but it's easy to reuse the server socket. For example, the next code fragment repeatedly accepts connections:

 
  try {
    ServerSocket ss = new ServerSocket(2345);
    while (true) {
      Socket s = ss.accept();
      PrintWriter pw = new PrintWriter(s.getOutputStream());
      pw.println("Hello There!");
      pw.println("Goodbye now.);
      s.close();
    }
  }
  catch (IOException e) {
    System.err.println(e);
  }
 

 

Interakcija s klijentom

More commonly, a server needs to both read a client request and write a response. The following program reads whatever the client sends and then sends it back to the client. In short this is an echo server.

 
import java.net.*;
import java.io.*;
 
 
public class EchoServer {
 
  public final static int defaultPort = 2346;
 
  public static void main(String[] args) {
  
    int port = defaultPort;
    
    try {
      port = Integer.parseInt(args[0]);
    }
    catch (Exception e) {
    }
    if (port <= 0 || port >= 65536) port = defaultPort;
    
    try {
      ServerSocket ss = new ServerSocket(port);
      while (true) {
        try {
          Socket s = ss.accept();
          OutputStream os = s.getOutputStream();
          InputStream is = s.getInputStream();
          while (true) {
            int n = is.read();
            if (n == -1) break;
            os.write(n);
            os.flush();
          }
        }
        catch (IOException e) {
        }
      }
    }
    catch (IOException e) {
      System.err.println(e);
    }
 
  }
  
}
 

Na jednom računalu, npr pc-mladen.srce.hr pokrenem EchoServer:

 
% javac EchoServer
% java EchoServer
 . . . 
%
 

S drugog računala, npr. student.math.hr napravim telnet na pc-mladen.srce.hr 2346. Imamo:

 
telnet pc-mladen.srce.hr 2346
Trying...
Connected to pc-mladen.srce.hr.
Escape character is '^]'.
jedan
jedan
dva
dva
^]
Connection closed by foreign host.
 

Ako ne možete izaći iz telneta, prekinite EchoServer na lokalnom računalu sa <Ctrl-C>, pa će se telnet automatski otkvačiti.


 

Dodavanje threadova poslužitelju

The last two programs could only handle one client at a time. That wasn't so much of a problem for HelloServer because it had only a very brief interaction with each client. However the EchoServer might hang on to a connection indefinitely. In this case, it's better to make your server multi-threaded. There should be a loop which continually accepts new connections. However, rather than handling the connection directly the Socket should be passed to a Thread object that handles the connection.

The following example is a threaded echo program.

 
import java.net.*;
import java.io.*;
 
 
public class ThreadedEchoServer extends Thread {
 
  public final static int defaultPort = 2347;
  Socket theConnection;
  
  public static void main(String[] args) {
  
    int port = defaultPort;
    
    try {
      port = Integer.parseInt(args[0]);
    }
    catch (Exception e) {
    }
    if (port <= 0 || port >= 65536) port = defaultPort;
    
    try {
      ServerSocket ss = new ServerSocket(port);
      while (true) {
        try {
          Socket s = ss.accept();
          ThreadedEchoServer tes = new ThreadedEchoServer(s);
          tes.start();
        }
        catch (IOException e) {
        }
      }
    }
    catch (IOException e) {
      System.err.println(e);
    }
 
  }
  
  public ThreadedEchoServer(Socket s) {
    theConnection = s;
  }
  
  public void run() {
    try {
      OutputStream os = theConnection.getOutputStream();
      InputStream is = theConnection.getInputStream();
      while (true) {
        int n = is.read();
        if (n == -1) break;
        os.write(n);
        os.flush();
      }
    }
    catch (IOException e) {
    }
    
  }
  
}
 

Na lokalnom računalu, npr pc-mladen.srce.hr:

 
% javac ThreadedEchoServer.java
% java ThreadedEchoServer
. . .
%
 

Na računalu student.math.hr:

 

% telnet pc-mladen.srce.hr 2347
Trying...
Connected to pc-mladen.srce.hr.
Escape character is '^]'.
jedan-student
jedan-student
dva-student
dva-student
^]
Connection closed by foreign host.
%
 

Istodobno, na računalu regoc.srce.hr:

 
% telnet pc-mladen.srce.hr 2347
Trying 161.53.2.93...
Connected to pc-mladen.srce.hr.
Escape character is '^]'.
jedan-regoc
jedan-regoc
dva-regoc
dva-regoc
^]
Connection closed by foreign host.
%
 

Ako ne možete izaći iz telneta, prekinite EchoServer na lokalnom računalu sa <Ctrl-C>, pa će se telnet automatski otkvačiti.

Note that explicit yields are not required because all the different threads will tend to block on calls to read() and accept().


 

Dodavanje Thread Poola poslužitelju

Multi-threading is a good thing but it's still not a perfect solution. For example, let's take a look at the accept loop of the ThreadedEchoServer:

 
     while (true) {
        try {
          Socket s = ss.accept();
          ThreadedEchoServer tes = new ThreadedEchoServer(s);
          tes.start();
        }
        catch (IOException e) {
        }
 

Every time you pass through this loop, a new thread gets created. Every time a connection is finished the thread is disposed of. Spawning a new thread for each connection takes a non-trivial amount of time, especially on a heavily loaded server. It would be better not to spawn so many threads.

An alternative approach is to create a pool of threads when the server launches, store incoming connections in a queue, and have the threads in the pool progressively remove connections from the queue and process them. This is particularly simple since the operating system does in fact store the incoming connections in a queue. The main change you need to make to implement this is to call accept() in the run() method rather than in the main() method. The program below demonstrates.

 
import java.net.*;
import java.io.*;
 
 
public class PoolEchoServer extends Thread {
 
  public final static int defaultPort = 2347;
  ServerSocket theServer;
  static int numberOfThreads = 10;
  
  
  public static void main(String[] args) {
  
    int port = defaultPort;
    
    try {
      port = Integer.parseInt(args[0]);
    }
    catch (Exception e) {
    }
    if (port <= 0 || port >= 65536) port = defaultPort;
    
    try {
      ServerSocket ss = new ServerSocket(port);
      for (int i = 0; i < numberOfThreads; i++) {
        PoolEchoServer pes = new PoolEchoServer(ss); 
        pes.start();
      }
    }
    catch (IOException e) {
      System.err.println(e);
    }
 
  }
  
  public PoolEchoServer(ServerSocket ss) {
    theServer = ss;
  }
  
  public void run() {
  
    while (true) {
      try {
        Socket s = theServer.accept();
        OutputStream out = s.getOutputStream();
        InputStream in = s.getInputStream();
        while (true) {
          int n = in.read();
          if (n == -1) break;
          out.write(n);
          out.flush();
        } // end while
      } // end try
      catch (IOException e) {
      }       
    } // end while
    
  } // end run
    
}
 

In the program above the number of threads is set to ten. This can be adjusted for performance reasons. How would you go about testing the performance of this program relative to the one that spawns a new thread for each connection? How would you determine the optimum number of threads to spawn?


 

UDP

Što je UDP?

The User Datagram Protocol, UDP for short, provides unguaranteed, connectionless transmission of data across an IP network. By contrast, TCP, provides reliable, connection-oriented transmission of data.

Both TCP and UDP split data into packets called datagrams. However TCP includes extra headers in the datagram to enable retransmission of lost packets and reassembly of packets into the correct order if they arrive out of order. UDP does not provide this. If a UDP packet is lost, it's lost. It will not be retransmitted. Similarly, packets appear in the receiving program in the order they were received, not necessarily in the order they were sent.

Given these disadvantages you may well wonder why anyone would prefer UDP to TCP. The answer is speed. UDP can be up to three times faster than TCP; and there are many applications for which reliable transmission of data is not nearly as important as speed. For example lost or out of order packets may appear as static in an audio or video feed, but the overall picture or sound could still be intelligible.

Telephone vs. snail mail analogy.

Protocols that use UDP include NFS, FSP, and TFTP.


 

UDP klase

Java's support for UDP is contained in two classes, java.net.DatagramSocket and java.net.DatagramPacket. A DatagramSocket is used to send and receive DatagramPackets. Since UDP is connectionless, streams are not used. You must fit your data into packets of no more than about 60,000 bytes. You can manually split the data across multiple packets if necessary.

The DatagramPacket class is a wrapper for an array of bytes from which data will be sent or into which data will be received. It also contains the address and port to which the packet will be sent.

The DatagramSocket class is a connection to a port that does the sending and receiving. Unlike TCP sockets, there is no distinction between a UDP socket and a UDP server socket. The same DatagramSocket can both and receive. Also unlike TCP sockets, a DatagramSocket can send to multiple, different addresses. The address to which data goes is stored in the packet, not in the socket.

UDP ports are separate from TCP ports. Each computer has 65,536 UDP ports as well as its 65,536 TCP ports. You can have a ServerSocket bound to TCP port 20 at the same time as a DatagramSocket is bound to UDP port 20. Most of the time it should be obvious from context whether or not I'm talking about TCP ports or UDP ports.


 

Klasa DatagramPacket

The DatagramPacket class is a wrapper for an array of bytes from which data will be sent or into which data will be received. It also contains the address and port to which the packet will be sent.

 
public DatagramPacket(byte[] data, int length)
public DatagramPacket(byte[] data, int length, InetAddress host, int port)
 

You construct a DatagramPacket object by passing an array of bytes and the number of those bytes to send to the DatagramPacket() constructor like this:

 
  String s = "My first UDP Packet"
  byte[] b = s.getBytes();
  DatagramPacket dp = new DatagramPacket(b, b.length);
 

Normally you'll also pass in the host and port to which you want to send the packet:

 
try {
  InetAddress metalab = new InetAddess("metalab.unc.edu");
  int chargen = 19;
  String s = "My second UDP Packet"
  byte[] b = s.getBytes();
  DatagramPacket dp = new DatagramPacket(b, b.length, ia, chargen);
}
catch (UnknownHostException e) {
  System.err.println(e);
}
 

The byte array that's passed to the constructor is stored by reference, not by value. If you cahnge it's contents elsewhere, the contents of the DatagramPacket change as well.

DatagramPackets themselves are not immutable. You can change the data, the length of the data, the port, or the address at any time with these four methods:

 
public synchronized void setAddress(InetAddress host)
public synchronized void setPort(int port)
public synchronized void setData(byte buffer[])
public synchronized void setLength(int length)
 

You can retrieve these items with these four get methods:

 
public synchronized InetAddress getAddress()
public synchronized int getPort()
public synchronized byte[] getData()
public synchronized int getLength()
 

These methods are primarily useful when you're receiving datagrams.


 

Klasa DatagramSocket

The java.net.DatagramSocket class has three constructors:

 
public DatagramSocket() throws SocketException
public DatagramSocket(int port) throws SocketException
public DatagramSocket(int port, InetAddress laddr) throws SocketException
 

The first is used for datagram sockets that are primarily intended to act as clients; that is sockets that will send datagrams before receiving any. The secned two that specify the port and optionally the IP address of the socket, are primarily intended for servers that must run on a well-known port.

The LocalPortScanner developed earlier only found TCP ports. The following program detects UDP ports in use. As with TCP ports, you must be root on Unix systems to bind to ports below 1024.

 
import java.net.*;
import java.io.IOException;
 
 
public class UDPPortScanner {
 
  public static void main(String[] args) {
 
    // first test to see whether or not we can bind to ports
    // below 1024
    boolean rootaccess = false;
    for (int port = 1; port < 1024; port += 50) {
      try {
        ServerSocket ss = new ServerSocket(port);
        // if successful
        rootaccess = true;
        ss.close();
        break;
      }
      catch (IOException e) {
      }
    }
    
    int startport = 1;
    if (!rootaccess) startport = 1024;
    int stopport = 65535;
    
    for (int port = startport; port <= stopport; port++) {
      try {
        DatagramSocket ds = new DatagramSocket(port);
        ds.close();
      }
      catch (IOException e) {
        System.out.println("UDP Port " + port + " is occupied.");
      }
    
    }
 
  }
 
}
 

Since UDP is connectionless it is not possible to write a remote UDP port scanner. The only way you know whether or not a UDP server is listening on a remote port is if it sends something back to you.


 

Slanje UDP Datagrama

To send data to a particular server, you first must convert the data into byte array. Next you pass this byte array, the length of the data in the array (most of the time this will be the length of the array) and the InetAddress and port to which you wish to send it into the DatagramPacket() constructor. For example,

 
try {
  InetAddress metalab = new InetAddess("metalab.unc.edu");
  int chargen = 19;
  String s = "My second UDP Packet";
  byte[] b = s.getBytes();
  DatagramPacket dp = new DatagramPacket(b, b.length, metalab, chargen);
}
catch (UnknownHostException e) {
  System.err.println(e);
}
 

Next you create a DatagramSocket object and pass the packet to its send() method: For example,

 
try {
  DatagramSocket sender = new DatagramSocket();
  sender.send(dp);
}
catch (IOException e) {
  System.err.println(e);
}
 

 

Primanje UDP Datagrama

To receive data sent to you, you construct a DatagramSocket object on the port on which you want to listen. Then you pass an empty DatagramPacket object to the DatagramSocket's receive() method.

 
public synchronized void receive(DatagramPacket dp) throws IOException
 

The calling thread blocks until the a datagram is received. Then dp is filled with the data from that datagram. You can then use getPort() and and getAddress() to tell where the packet came from, getData() to retrieve the data, and getLength() to see how many bytes were in the data. If the received packet was too long for the buffer, then it's truncated to the length of the buffer. For example,

 
try {
  byte buffer = new byte[65536]; // maximum size of an IP packet
  DatagramPacket incoming = new DatagramPacket(buffer, buffer.length);
  DatagramSocket ds = new DatagramSocket(2134);
  ds.receive(dp);
  byte[] data = dp.getData();
  String s = new String(data, 0, data.getLength());
  System.out.println("Port " + dp.getPort() + " on " + dp.getAddress()
   + " sent this message:");
  System.out.println(s);
}
catch (IOException e) {
  System.err.println(e);
}
 

 

Slanje i primanje UDP Datagrama

Most of the time a program needs to both receive and send UDP datagrams. For example, the UDP echo service listens on port 7. When it receives a datagram it copies the data out of the datagram into a new datagram and sends it back to the sending host. (Why can't it just resend the same datagram?)

The following UDPEchoClient reads what the user types on System.in and sends it to an echo server specified on the commond line. Note that precisely because UDP is unreliable, you're not guaranteed that each line you type will in fact be echoed back. It may be lost going between client and server or returning from server to client.

 
import java.net.*;
import java.io.*;
 
 
public class UDPEchoClient extends Thread {
 
  public final static int port = 7;
  DatagramSocket ds;
  
  public static void main(String[] args) {
  
    InputStreamReader isr = new InputStreamReader(System.in);
    BufferedReader br = new BufferedReader(isr);
    String theLine;
    DatagramSocket ds = null;
    InetAddress server = null;
    try {
      server = InetAddress.getByName("metalab.unc.edu");
      ds = new DatagramSocket();
    }
    catch (IOException e) {
      System.err.println(e);
      System.exit(1);
    }
    
    UDPEchoClient uec = new UDPEchoClient(ds);
    uec.start();
    
    try {
      while ((theLine = br.readLine()) != null) {
        byte[] data = theLine.getBytes();
        DatagramPacket dp = new DatagramPacket(data, data.length, 
         server, port);  
        ds.send(dp);
        Thread.yield();
      }
    }
    catch (IOException e) {
      System.err.println(e);
    }
    uec.stop();
    
  }
  
  public UDPEchoClient(DatagramSocket ds) {
    this.ds = ds;
  }
  
  public void run() {
  
    byte[] buffer = new byte[1024]; 
    DatagramPacket incoming = new DatagramPacket(buffer, buffer.length);
    while (true) {
      try {
        incoming.setLength(bufffer.length);
        ds.receive(incoming);
        byte[] data = incoming.getData();
        System.out.println(new String(data, 0, incoming.getLength()));
      }
      catch (IOException e) {
        System.err.println(e);
      }
    }
  
  }
  
}