MREŽNO RAČUNARSTVO Soketi za klijente (iz 8. poglavlja u 4. izdanju) 1
Soketi za klijente podaci se šalju preko Interneta u paketima ograničene veličine, koji se nazivaju datagram-ima datagram ima header i payload header adresa i port kuda paket ide, adresa i port odakle dolazi itd. ostali podaci neophodni za pouzdani prenos payload sami podaci 2
Soketi za klijente podela podataka u pakete njihovo spajanje na odredištu izgubljeni i oštećeni paketi koje treba ponovo poslati paketi ne stižu dobrim redosledom, pa ih treba preurediti dakle, podela u paketa, generisanje zaglavlja, parsiranje zaglavlja dolazećih paketa, praćenje koji paketi jesu, a koji nisu pristigli tu ima dosta posla 3
Soketi za klijente Srećom, mi o tome ne moramo da brinemo. Soketi omogućavaju programeru da tretira mrežnu konekciju kao još jedan stream u koji može pisati bajtove, odnosno iz kojih može čitati bajtove Soketi štite programera od detalja nižeg nivoa, poput otkrivanja grešaka, veličina paketa, retransmisije paketa, itd. 4
Osnove soketa Soket predstavlja konekciju između dva hosta On može vršiti 7 osnovnih operacija: povezivanje na udaljenu mašinu (connect) slanje podataka (send) primanje podataka (receive) zatvaranje konekcije (close) povezivanje na port (bind to port) osluškivanje dolazećih podataka (listen) prihvatanje konekcija sa udaljenih mašina na povezanom portu 5
Socket klasa (i ServerSocket) klasa Socket, koju koriste i klijenti i serveri, ima metode koji odgovaraju prvim 4 od prethodnih 7 operacija Poslednje 3 operacije neophodne su samo serverima, koji čekaju da ih klijenti kontaktiraju. Ove operacije implementirane su klasom ServerSocket 6
uobičajeno korišćenje soketa za klijente Java programi obično koriste klijentske sokete na sledeći način program konstruktorom kreira novi soket soket pokušava da se konektuje na udaljeni host nakon što je konekcija uspostavljena, lokalni i udaljeni host uzimaju input i output stream-ove od soketa i koriste te streamove da šalju podatke jedan drugome. Konekcija je full-duplex, što znači da oba hosta mogu da primaju i šalju podatke istovremeno. Šta su podaci, zavisi od protokola; različite komande se šalju FTP serveru nego HTTP serveru kada je prenos podataka završen, jedna ili obe strane zatvaraju konekciju. Neki protokoli, poput HTTP 1.0 zahtevaju da se konekcija zatvori nakon obrade svakog zahteva. Drugi, poput FTP dopuštaju obradu većeg broja zahteva jednom konekcijom 7
klasa Socket java.net.socket osnovna klasa za izvršavanje client-side TCP operacija ostale klijentski orijentisane klase koje prave TCP mrežne konekcije (URL, URLConnection, Applet, JEditorPane) pozivaju metode ove klase interfejs koji klasa obezbeđuje programeru su stream-ovi. Stvarno čitanje i pisanje podataka preko soketa vrši se poznatim stream klasama 8
konstruktori jednostavni svaki dopušta da se zada host i port na koji želimo da se konektujemo host se može zadati kao InetAddress ili String portovi se uvek zadaju kao int vrednosti od 0 do 65535 2 konstruktora takođe zadaju lokalnu adresu i lokalni port sa kojih se šalju podaci (to je možda potrebno kada se želi izabrati jedan određeni mrežni interfejs sa kog se šalju podaci, ako ih je više) postoje i 2 konstruktora koja kreiraju nekonektovane sokete. Oni su korisni kada se žele postaviti opcije soketa pre pravljenja prve konekcije 9
public Socket(String host, int port) throws UnknownHostException, IOException kreira TCP soket ka zadatom portu i zadatom hostu i pokušava da se konektuje na udaljeni host try{ Socket tooreilly = new Socket("www.oreilly.com", 80); // send and receive data }catch(unkownhostexception ex){ System.err.println(ex); }catch(ioexception ex){ System.err.println(ex); } 10
host je hostname (String). Ako domain name server ne može da razreši hostname ili ne funkcioniše, izbacuje se UnkownHostException ako soket ne može biti otvoren iz drugih razloga, izbacuje se IOException. Mnogi su razlozi: taj host možda ne prihvata konekcije, dialup konekcija je možda pukla, ili problemi rutiranja sprečavaju pakete da stignu do odredišta ovaj konstruktor ne samo da kreira soket, već takođe pokušava da konektuje soket na udaljeni host, pa se ovaj objekat može koristiti za utvrđivanje da li su dopuštene konekcije na određeni port primer 1 (LowPortScanner) 11
Primer - objašnjenja na Unix sistemima, koji servisi su na kojim portovima može se videti u fajlu /etc/services program na Unix sistemima treba da nađe tačno portove iz tog fajla ne koristite LowPortScanner da probate na mašini koju ne posedujete, jer većina sistem administratora to smatra neprijateljskim činom 12
public Socket(InetAddress host, int port) throws IOException radi isto što i prethodni konstruktor (kreira TCP soket ka zadatom portu na zadatom hostu) i pokušava da se konektuje razlikuje se po tome što koristi InetAddress objekat za zadavanje hosta (umesto hostname) izbacuje IOException ako ne može da se konektuje, ne i UnknownHostException: ako je host nepoznat, saznaćemo kada kreiramo InetAddress objekat 13
primer upotrebe: try{ InetAddress oreilly = InetAddress.getByName("www.oreilly.com"); Socket oreillysocket = new Socket(oreilly, 80); // send and receive data } catch(unknownhostexception ex){ System.err.println(ex); } catch(ioexception ex){ System.err.println(ex); } U retkim situacijama kada otvarate mnogo soketa na istom hostu, efikasnije je konvertovati hostname u InetAddress i zatim koristiti InetAddress za kreiranje soketa. Primer 2 (HighPortScanner) 14
ostali konstruktori page 8 of 65 još 2 argumenta: lokalni mrežni interfejs i port ako se za port izabere 0, Java bira slučajan dostupan port između 1024 i 65535... page 10 of 65 konstruktori koji kreiraju sokete koji ne pokušavaju da se konektuju 15
Dobijanje informacija o soketu public InetAddress getinetaddress() koji je udaljeni host na koji je soket konektovan, ili, ako je konekcija zatvorena, na koji je soket bio konektovan dok je bio konektovan public int getport() koji je port na udaljenom hostu na koji je soket bio, je ili će biti konektovan public int getlocalport() (postoje 2 kraja konekcije: remote i local host) Za razliku od remote porta koji je (za klijenta) obično dobro poznat, local port se obično bira od strane sistema u vreme izvršavanja od dostupnih neiskorišćenih portova. Na ovaj način, mnogi različiti klijenti mogu pristupati istom servisu u isto vreme. Local port je ugrađen u IP pakete zajedno sa IP adresom local host-a, tako da server može poslati podatke nazad na pravi port klijenta. public InetAddress getlocaladdress() za koji mrežni interfejs je soket vezan. Ovo se obično koristi na hostu sa većim brojem mrežnih interfejsa primer 3, SocketInfo 16
public InputStream getinputstream() throws IOException vraća ulazni tok koji može čitati podatke iz soketa u program obično se olančava ovaj InputStream na filter tok ili čitač koji nudi veću funkcionalnost DataInputStream ili InputStreamReader, npr. pre čitanja ulaza. Zbog poboljšanja performansi, jako je dobra ideja baferisati ulaz olančavanjem na BufferedInputStream i/ili BufferedReader 17
daytime protokol (RFC 867) Sa ulaznim tokom, možemo čitati podatke iz soketa i početi eksperimentisanje sa nekim stvarnim Internet protokolima jedan od najjednostavnijih je daytime klijent otvara soket na portu 13 daytime servera kao odgovor, server šalje vreme u čitljivom formatu i zatvara konekciju Wed Nov 12 23:39:15 2003 linija koju je poslao server primer 4, DaytimeClient 18
primer 4, objašnjenja DaytimeClient čita hostname daytime servera iz komandne linije i koristi ga za konstruisanje novog Soketa koji se konektuje na port 13 servera ako se izostavi hostname, the National Institute of Standards and Technology server na time.nist.gov se koristi klijent zatim poziva thesocket.getinputstream() da dobije ulazni tok od soketa, i smešta taj tok u promenljivu timestream pošto daytime protokol specifikuje ASCII, DaytimeClient ne olančava čitač na tok. On samo čita bajtove u StringBuffer, jedan po jedan, prekidajući kada server zatvori konekciju pošto protokol to od njega zahteva. 19
primer 4, objašnjenja vremenski serveri na različitim host-ovima koriste različite formate. daytime protokol ne određuje format u kome se vraća vreme, osim da bude čitljiv zato, teško je konvertovati karaktere koje vrati server u Java Date na pouzdan način. Ako želimo da kreiramo Date objekat na osnovu vremena na serveru, lakše je koristiti time protokol iz RFC 868, jer on određuje format vremena 20
Time protocol (RFC 868) Kada se čitaju podaci sa mreže, bitno je imati na umu da ne koriste svi protokoli ASCII, čak ni tekst npr, time protokol zadat u RFC 868 zadaje da se vreme šalje kao broj sekundi od ponoći 1. januara 1900 po Griniču (GMT) Međutim, to se ne šalje kao ASCII string 2,524,521,600 ili -1297728000, već kao 32- bitni, neoznačeni, big-endian binarni broj 21
RFC podrazumeva da znamo da svi mrežni protokoli koriste big endian brojeve primer 5 (TimeClient) Pošto ovo nije tekst, naš program ne može čitati odgovor servera pomoću Reader-a niti bilo koje vrste readline() metoda. Java program koji se konektuje na time servere mora čitati neobrađene bajtove i interpretirati ih na odgovarajući način U ovom primeru, taj posao komplikuje nedostatak 32-bitnog neoznačenog celobrojnog tipa u Javi. Zato, bajtovi se moraju čitati jedan po jedan i ručno konvertovati u long korišćenjem bitskih operatora << i. Kada se radi o drugim protokolima, oni mogu baratati formatima podataka koji su još čudniji za Javu, npr. nekoliko mrežnih protokola koristi 64-bitne brojeve u fiksnom zarezu. Tu nema prečice koja će rukovati svim mogućim slučajevima. Prosto, mora se iskodirati sva matematika neophodna za rukovanje podacima u onom formatu koji server pošalje. 22
primer 5 objašnjenja program čita hostname servera i opcioni port iz komandne linije i koristi ih za konstruisanje novog Socket objekta koji se konektuje na taj server Ako korisnik izostavi hostname, koristi se time.nist.gov podrazumevani port je 37 klijent zatim poziva thesocket.getinputstream() da dobije ulazni tok, koji smešta u prom. raw 23
primer 5 objašnjenja 4 bajta se čitaju iz ovog toka i koriste za konstruisanje long-a koji predstavlja vrednost ta 4 bajta interpretiranu kao 32-bitni neoznačeni ceo broj ovo daje broj sekundi proteklih od 12:00 A.M. January 1, 1900 GMT (time protocol epoch) 2,208,988,800 sekundi se oduzima od tog broja da bi se dobio broj sekundi proteklih od 12:00 A.M. January 1, 1970 GMT (Java Date class epoch) ovaj broj se množi sa 1000 da bi se konvertovao u milisekunde konačno, taj broj milisekundi konvertuje se u Date objekat koji se štampa kako bi prikazao tekuće vreme i datum 24
public OutputStream getoutputstream() throws IOException vraća neobrađeni OutputStream za pisanje podataka iz naše aplikacije drugom kraju soketa obično olančavamo ovaj tok klasama DataOutputStream ili OutputStreamWriter pre njegovog korišćenja Za poboljšanje performansi, dobra ideja je baferisati ga, takođe. 25
Writer out; try{ Socket http = new Socket( www.oreilly.com,80); OutputStream raw = http.getoutputstream(); OutputStream buffered = new BufferedOutputStream(raw); out = new OutputStreamWriter(buffered, ASCII ); out.write( GET / HTTP 1.0\r\n\r\n ); // read the server response } catch(exception ex){ System.err.println(ex); } finally{ try{ out.close(); } catch(exception ex){} } 26
primer 6, echo protocol echo protokol, definisan u RFC 862, jedan je od najjednostavnijih interaktivnih TCP servisa klijent otvara soket na portu 7 echo servera i šalje podatke server šalje podatke nazad ovo se nastavlja dok klijent ne zatvori konekciju echo protokol je koristan za testiranje mreže, kako bismo bili sigurni da podaci nisu izopačeni pogrešnim ponašanjem rutera ili firewall-a. 27
primer 6, objašnjenja primer koristi getoutputstream() i getinputstream() da implementira jednostavni echo klijent. korisnik kuca ulaz u komandnoj liniji, koji se zatim šalje serveru server ga vraća nazad program se završava kada korisnik ukuca tačku u posebnoj liniji echo protokol ne određuje kodiranje karaktera. Zapravo, on zadaje da su podaci poslati serveru tačno oni koje server vraća. Server vraća neobrađene bajtove, ne karaktere koje oni predstavljaju. Tako, ovaj program koristi podrazumevano karaktersko kodiranje i line separator klijentskog sistema za čitanje iz System.in, slanje podataka udaljenom sistemu i ispis izlaza na System.out. Kako echo server vraća upravo ono što je poslato, to je kao da se server dinamički podešava prema klijentskim konvencijama za karaktersko kodiranje i prelom linija. Zato, koriste se uobičajene klase i metodi poput PrintWriter i readline() koje bi u opštem slučaju bile previše nepouzdane 28
primer 6, objašnjenja novi Socket objekat, thesocket, kreira se na portu 7 InputStream soketa vraća se metodom getinputstream() i olančava na InputStreamReader, a ovaj na BufferedReader nazvan networkin koji čita odgovore servera Pošto ovaj klijent takođe treba da čita ulaz korisnika, on kreira drugi BufferedReader, koji se zove userin i čita iz System.in Dalje, EchoClient poziva thesocket.getoutputstream() da dobije izlazni tok soketa thesocket, koji se koristi za konstruisanje novog PrintWriter objekta out. Podaci se čitaju iz userin i pišu na out. Nakon što se podaci pošalju echo serveru, networkin čeka odgovor. Kada odgovor stigne, on se štampa na System.out. Teoretski, klijent bi mogao da čeka na odovor koji nikada ne stiže. Međutim, to nije verovatno ako se može napraviti konekcija, pošto TCP protokol proverava loše pakete i automatski traži od servera zamene. Za implementiranje UDP echo klijenta (glava 13) potreban je drugačiji pristup jer UDP ne vrši kontrolu grešaka. 29
primer 6, objašnjenja primer je linijski-orijentisan on čita liniju iz konzole, šalje je serveru, i čeka da pročita liniju koju mu ovaj vrati međutim, echo protokol to ne zahteva on vraća svaki bajt pošto ga primi nije mu stalo da ti bajtovi predstavljaju karaktere u istom kodiranju ili da budu podeljeni u linije Java ne dopušta da se konzola prebaci u neobrađeni mod, gde se svaki karakter čita čim se ukuca umesto čekanja da korisnik pritisne Enter. Za razliku od mnogih protokola, echo ne zahteva da klijent pošalje zahtev, a onda čeka na pun odgovor servera pre nego što pošalje još podataka. Najjednostavniji način za rukovanje takvim protokolom u Javi je smestiti mrežni ulaz i izlaz u odvojene niti. 30
Zatvaranje soketa ovo je skoro sve što je potrebno znati o soketima kada se piše klijentska aplikacija, skoro sav posao je rukovanje tokovima i interpretiranje podataka sa samim soketima se radi jednostavno (svi teški delovi skriveni su od programera) 31
public void close() throws IOException primeri do sada podrazumevali su da se soketi sami zatvaraju i nisu radili ništa da počiste za sobom tačno je da se soket automatski zatvara kada se zatvori jedan od njegova dva stream-a, kada se završi program, ili kada ga počisti garbage collector. Međutim, loša je praksa pretpostavljati da će sistem zatvarati naše sokete, posebno za programe koji se mogu izvršavati neograničeno dugo u programima koji intenzivno koriste sokete, poput web browser-a, sistem može dostići max broj otvorenih soketa pre nego što ih pokupi garbage collector. primeri 1 i 2 su naročito loši u tom pogledu, pošto može proteći puno vremena dok program prođe sve portove 32
kada završite sa soketom, treba pozvati njigov close() metod za diskonektovanje idealno, on se stavlja u finally blok tako da se soket zatvara bez obzira da li je izbačen izuzetak ili ne sintaksa je pravolinijska 33
Socket connection = null; try{ connection = new Socket( www.oreilly.com, 13); // interact with the socket } // end try catch(unknownhostexception ex){ System.err.println(ex); } catch(ioexception ex){ System.err.println(ex); } finally{ if(connection!= null) connection.close(); } 34
nakon što je soket zatvoren, njegov InetAddress, broj porta, lokalna adresa i lokalni broj porta su još uvek dostupni preko odgovarajućih get*() metoda međutim, iako je moguće zvati getinputstream() ili getoutputstream(), pokušaj čitanja ili pisanja podataka dovodi do izbacivanja IOException primer 7, revizija PortScanner programa koja zatvara svaki soket kada završi sa njim. Ne zatvara sokete koji nisu uspeli da se konektuju. Pošto oni nisu nikada otvoreni, ne moraju se zatvoriti. Zapravo, kada konstruktor ne uspe, connection ima vrednost null. 35
public boolean isclosed() vraća true ako je soket zatvoren, false inače ako niste sigurni kakvo je stanje soketa, možete proveriti ovim metodom, radije nego da reskirate IOException if(socket.isclosed()) // do something... else // do something else međutim, ovo nije savršen test. ako soket nikada nije bio konektovan, isclosed() vraća false, čak i kada soket nije otvoren 36
public boolean isconnected() ime može da zavara ovaj metod ne kaže da li je soket trenutno konektovan na udaljeni host, već da li je soket ikada bio konektovan na udaljeni host. ako je soket bio u mogućnosti da se konektuje na udaljeni host ikada, metod vraća true, čak i ako je soket zatvoren za proveru da li je soket trenutno otvoren, mora se proveriti da isconnected() vraća true i isclosed() vraća false. boolean connected = socket.isconnected() &&!socket.isclosed(); 37
public boolean isbound() odnosi se na lokalni kraj soketa metod kaže da li je soket uspešno povezan na izlazni port lokalnog sistema. To u praksi nije vrlo važno. Postaće važnije kod serverskih soketa 38
poluzatvoreni soketi close() metod zatvara oba input i output soketa povremeno, želimo da zatvorimo samo pola konekcije, bilo izlaz bilo ulaz public void shutdowninput() throws IOException public void shutdownoutput() throws IOException Ovo ne zatvara soket. Ali podešava tok povezan na njega da misli da je kraj toka. Dalje čitanje iz ulaznog toka vraća -1. Dalje pisanje u izlazni tok izbacuje IOException. Mnogi protokoli, poput finger, whois, HTTP počinju tako što klijent šalje zahtev serveru, a zatim čita odgovor. Moguće je zatvoriti izlaz nakon što klijent pošalje zahtev. 39
primer Sledeći fragment koda šalje zahtev HTTP serveru i onda zatvara izlaz, pošto neće više ništa pisati serveru preko tog soketa Socket connection = null; try{ connection = new Socket( www.oreilly.com, 80); Writer out = new OutputStreamWriter(connection.getOutputStream, 8859_1 ); out.write( GET / HTTP 1.0\r\n\r\n ); out.flush(); connection.shutdownoutput(); // read the response } catch(ioexception ex){} finally{ try{ if(connection!=null) connection.close(); } catch(ioexception ex){} } 40
primetite da iako zatvorite pola, ili obe polovine konekcije, još uvek treba da zatvorite soket kada završite sa njim. shutdown metodi prosto utiču na tokove soketa. Oni ne oslobađaju resurse pridružene soketu poput porta koji on zauzima public boolean isinputshutdown() public boolean isoutputshutdown() ovi metodi se mogu koristiti (radije nego isconnected() i isclosed()) za određeniju proveru da li se može pisati ili čitati iz soketa 41
page 30 of 65 (3. izdanje) 9.3.4 Setting Socket Options (3.izdanje) 9.6 Examples page 39 of 65 42