Predavanje br. index|1|2|3|4|5|6|7|8|9|10|11|12|13|14|HOME
u
pripremi
Portovi – protokoli – internet adrese – kreiranje
InetAddress objekata – parsiranje InetAddress objekata – URL -
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.
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.
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()
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);
}
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, 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):
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.
URL
objekataPogledajmo
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.
URL
objekataKlasa
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 "/".
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>
%
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:
Utičnica ne može biti priključena na više hostova istodobno.
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()
Socket
objekataPogledajmo
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.
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!!!
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();
}
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
%
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) {
}
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>
%
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.)
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>
%
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) {
}
}
}
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.)
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()
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.");
}
}
}
}
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);
}
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.
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?
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.
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);
}
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.
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()
.
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?
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.
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.
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.
DatagramPacket
s
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.
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.
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);
}
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);
}
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);
}
}
}
}