Sunteți pe pagina 1din 43

Capitolul V

Pachetul java.util

5.1. Dictionary

Clasa abstractă Dictionary este un depozit sau o colecţie de chei/valori (key/paire).


Memorarea în dicţionar se face cu put(key,value) iar regăsirea lor cu get(key). Cheile sunt
întoarse ca Enumeration, utilizând metodele key() şi elements(). Metoda size() dă numărul
perechilor cheie/valoare memorate în dicţionar iar isEmpty() este true când dicţionarul este
vid. Stergerea elementelor pereche se face cu remove(key).

5.2. Hashtable

Hashtable poate fi gândit ca un nivel înalt de asociere eterogenă a tablourilor de obiecte. In


engleză înseamnă o colecţie de chei/valori (key/paire), unde cheile (nu neapărat indici de
tablou) nu sunt numere şi valorile tabloului nu sunt neapărat obiecte din aceeaşi clasă.
Inserarea chei/valori într-o Hashtable se face utilizând h.put(key,value) iar regăsirea lor cu
h.get(key). Modificarea este permisă prin h.remove(key). [java_8]
Exemplu;
import java.util.Dictionary;
import java.util.Hashtable;
class HTDemo {
public static void main (String args [ ] ) {
Hashtable ht=new Hashtable();
ht.put("nume","Popescu Ion");
ht.put("specializare","economist");
ht.put("vechime","24");
show(ht);
}
static void show(Dictionary d ) {
System.out.println("numele"+d.get("nume"));
System.out.println("Specialist in "+d.get("specializare"));
System.out.println("vechime"+d.get("vechime"));
}
}

5.3. Properties

Clasa Properties este subclasă din Hashtable, furnizând mapare eficientă a cheii String către
valoarea String. Pentru a adăuga perechi cheie/valoare a unor proprietăţi obiectului myProp
se apelează metoda myProp.put(key,value) care este moştenită din java.util.Hashtable. Se
poate utiliza această clasă a obiectului pentru cheie şi valoare, deşi când se doreşte execuţia
metodei de regăsire getProperty(key) se întoarce un şir ca reprezentare a valorii. Obiectele
properties sunt utile când se doreşte tipărirea reprezentării perechilor de cheie/valoare.
Exemplu:
import java.util.Properties;
class PropDemo {

97
static Properties prop=new Properties();
public static void main (String args [ ] ) {
prop.put("nume","introduceti numele");
prop.put("specializare"," specializare ");
prop.put("vechime","neprecizata");
Properties loc=new Properties(prop);
loc.put("nume","Popescu Ion");
loc.put("specializare","economist");
loc.put("vechime","24");
System.out.println("numele"+loc.getProperty("nume"));
System.out.println("Specialist in "+ loc.getProperty ("specializare"));
System.out.println("vechime"+ loc.getProperty ("vechime"),"???");
}
}

5.4. Enumeration

Interfaţa Enumeration poate fi implementată de orice clasă reprezentând o colecţie de


obiecte şi este folosită pentru a parcurge elemente dintr-o colecţie. Această interfaţă are două
metode hasMoreElements() şi getNextElement() dar nu garantează ordinea în care sunt
returnate obiectele, deşi se poate implementa cu certitudine pentru a face asemenea garanţii.
Familiarizaţi cu proprietăţile OOP recunoaştem că interfaţa Enumeration înglobează
conceptul familiar de iterator abstract. Peste o colecţie de obiecte. In general nu se instanţiază
un obiect din Enumeration ci se solicită un obiect reprezentând o colecţie (Hashtable, object
Properties sau Vector)pentru crearea unei Enumeration proprii şi returnarea acesteia. Apoi se
poate folosi obiectul Enumeration returnat pentru a traversa elementele din colecţie. Nu există
variantă de deplasare înapoi într-o enumeraţie; ea se consideră consumată când se trece peste
ea. Dacă se doreşte deplasarea la un obiect Enumeration anterior, trebuie ţinută referinţă sau
făcută o cerere colecţiei de obicet, pentru a crea altă enumeraţie şi a o parcurge de la început.
Precizăm că este bine să se evite confuzia interfeţei Enumeration cu conceptul tip enumeraţie
(utilizând cuvântul cheie enum găsit în C şî C++). Java nu are tip enumeraţie, de aceea
variabilele static final sunt utilizate pentru a furniza simulări rezonabile ale funcţionalităţii
oferite de tip enum.
Exemplu:
public Object NextElement(){
count++;
if (count >4) {
return new Integer(count);
}
}
class Enumeration Demo {
public static void main (String args [ ] ) {
Enumeration enum=new Enumeration() ;
while(enum.hasMoreElements()) {
System.out.println(enum.next Element());
}
}
}

98
5.5. StringTokenitzer

Această clasă implementează interfaţa Enumeration. Constructorul pentru StringTokenitzer


ia trei parametri:
 String str1 - ce urmează a fi secompus în token (simboluri);
 String str2 - al doilea şir, comprimând caracterele ce se doresc a fi delimitatori
simbolici (token);
 Boolean b valoare booleană îndicată când delimitatorii nu se include ca şi ultim
caracter al fiecărui simbol.
Având construit StringTokenitzer, se pot parcurge elementele utilizând nextToken() şi
fiecare element va fi următorul simbol extras din şir.
Spre exemplu, dacă se parcurge o propoziţie în cuvinte, utlizând spaţii şi delimitatori (, !) :
Exemplu:
String oProp ="Am incercat un exemplu de StringTokenitzer.";
StringTokenitzer cateCuv= StringTokenitzer(oProp, ".,!",false);

Acest exemplu ilustrează utilizarea lui properties şi StringTokenitzer. Clasa java.lang.System


menţine obiectul properties, ce înmagazinează informaţii despre sistemul local. Pentru a
vedea aceste proprietăţi se rulează programul următor. Deoarece appleturile nu permit acces la
toate proprietăţile, se pot observa ce diferenţe rezultă, depinzând de modul de ruare al
programului (applet sau stand-alone). La apelul metodei java.lang.System.getProperties()
se întoarce obiectul String conţiând o listă de nume şi perechi de valoare, separate prin
virgulă, şi înconjurate de { }. Ieşirea este mai lizibilă dacă se renunţă la { } şi se separă pe o
linie fiecare element din listă.

Exemplu:
import java.util. StringTokenitzer;
public class PrintProp () extends java.applet.Applet {
String propString;
public PrintProp (){
java.util.Properties p = System.getProperties();
propString=p.toString();
}

public static void main (String args [ ] ) {


PrintProp app=new PrintProp ();
StringTokenitzer st= new StringTokenitzer(app.propString, "{,}", false);
while (st.hasMoreTokens()){
System.out.println (st.nextToken());
}
}

public void paint (java.awt.Graphics g) {


StringTokenitzer st= new StringTokenitzer(app.propString, "{,}", false);
int y=10;
while (st.hasMoreTokens()){
g.drawString(st. nextToken());
}
}
}

99
Deoarece StringTokenitzer este de fapt o Enumeration, putem doar citi valorile în st o
singură dată. Acesta este motivul pentru care de fiecare dată appletul este redesenat prin
paint(), creăm apoi un nou StringTokenitzer şi extragem valorile dorite pentru a putea desena
pe ecran.

5.6. Vectorul

Un Vector este tabloul de dimensiune ajustabilă care poate ţine obiecte (nu date) de tip
primitiv. Totuşi se pot acoperi tipuri de date primitive în clase furnizate de java.lang. Pentru
optimizarea managementului memoriei se poate uttiliza Vector adăugând şi ştergând dinamic
obiecte dintr-un vector. Un exemplu de utilizare este java.util.Stack (stiva ) iar un al exemplu
este dat de GUI.

5.7. Stiva

Stiva (Stack) este o listă de obiecte cu operaţii clare, bine definite. Se poate "împinge" un
obiect în vârful stivei, sau s epoate trage obiectul din listă ştergâdu-l dar întorcând o referinţă
către el, pentru a o putea folosi. Adăugarea/ştergerea are loc din vârful listei (metoda FIFO-
First In First Out). Stiva este utilă pentru implementarea controlului incuibat sau recursiv şi a
astructurilor de date. Fiecare sistem run-time modern foloseşte stiva pentru a ţine urma
variabilelor locale.
Stack este subclasă a lui Vector, furnizând metodele:
 push() adaugă element în stivă;
 pop() şterge element în stivă;
 empty() indică prin valoare booleană stiva plină (false) sau nu (true);
 peek() se întoarce a vârful elementelor din stivă fără a le şterge
 search() verifică stiva pentru a afla dacă există un obiect şi întoarce numărul de pop
solicitaţi pentru a ajunge în vârful stivei sau -1 la stivă goală.

Exemplu;
import java.util.Stack;
import java.util.EmptyStackException;
class StackDemo {
static void shopush(Stack St, int a ) {
St.push(new Integer (a));
System.out.println("push ("+a+");
System.out.println("stiva"+St);

static void shopop(Stack St) {


System.out.println("pop");
Integer a = (Integer)St.pop();
System.out.println(a);
System.out.println("stiva"+St);
}

public static void main (String args [ ] ) {


Stack St=new Stack();

100
System.out.println("stiva"+St);
showpush(St,42);
showpush(St,11);
showpush(St,55);
showpush(St,5);
showpop(St);
showpop(St);
showpop(St);
showpop(St);
try{
showpop(St);
}catch (EmptyStackexception e){
System.out.println("stiva goala");
}
}
}
Capitolul VI
Pachetul java.io

6.1. Concepte fundamentale ale arhitecturii client-server

Pachetul java.io conţine clasele şi interfeţele necesare accesului secvenţial şi direct la fişierele
de intrare-ieşire (I/O), cuprinzând o varietate de tipuri de clase ale fluxului intrărilor şi-
ieşirilor care facilitează toate modurile de I/O specifice aplicaţiei inclusiv cele de intrare-ieşire
ale consolei şi firele de comunicaţie via "pipes".Din motive de securitate appleturile nu au
permisiunea citirii, scrierii, creării sau verificării accesibilităţii sau existenţei fişierelor sau
directoarelor. Acest tip de funcţionalitate este destinat aplicaţiilor stand-alone.

Socket este o abstractizare utilă pentru manipularea reţea a intrărilor şi ieşirilor. I/O ale reţelei
sunt similare cu fişierele I/O, fiind tratate ca fişiere la care atât clientul cât serverul au acces.
[Dada_99], [Rons_00]
Client-server este arhitectura de comunicaţii unde o entitate (clientul) solicită servicii
(întoarce nişte informaţii, execută un program, etc) din altă entitate (serverul) care prelucrează
serviciile solicitate. In programarea client-server, clientul şi serverul sunt 2 aplicaţii separate,
comunicând cu altele prin reţea.
Programul server stă în inactivitate, rulând într-o buclă în care "ascultă" portul, aşteptând să
primească o cerere pentru servicii. Serverul nu iniţiază comunicaţia ci aşteaptă clientul pentru
a-I cere un serviciu şi la acel punct prelucrează serviciile cerute, întorcând informaţiile către
client. Programul client pe de altă parte, iniţiază comunicaţia, trimiţând cereri către server.
Clientul trebuie să cunoscă numărul portului şi adresa de reţea a serverului pentru a putea
trimite cererea.
Portul este un număr ce permite distincţia între mesajele trimise de un program faţă de cele
ale altui program, dat fiind faptul că pe o maşină rulează mai multe tipuri de client şi server.
Programul server primeşte mesaje succesive la care sunt ataşate numărul de port cu care
programul client este legat. Un program poate utiliza un număr de port.
In stiva de protocul TCP/IP este fixat numărul de porturi ce pot fi utilizate pentru
comunicaţie. Numerele de port 0-1024 sunt rezervate pentru servicii binecunoscute
(telnet=23, HTTP=80,etc). Numerele de port între 1024 şi 65535 sunt disponibile
programatorului, dacă nu sunt utilizate de alte programe.[Muhr_98]

101
Protocolul este un set specific de reguli de comunicaţie. Portul, sockets şi modelul client-
server oferă fundaţia cu care poate fi construită comunicaţia între programe. Protocolul
stabileşte cine poate iniţia comunicaţia, ce tipuri de mesaje pot fi trimise, ce conţinut pot avea
şi care parte răspunde de acest conţinut. Dacă datele trimise violează protocolul, oprirea este
semnificativă, deoarece nu poate fi interpretată mai departe. Protocolul poate specifica ce
parte violează protocolul dacă primeşte informaţiile necesare. Crearea aplicaţiilor client-
server implică mai degrabă urmarea unor protocoale binecunoscute (HTTP,FTP, telnet) sau
crearea unui protocol cu care atât clientul cât şi serverul sunt familiarizaţi.[Rado_96]

6.2. HTTP şi modelul client-server

HTTP formează bazele WWW şi este un exemplu preferat pentru aplicaţiile client-server. Un
program Web server este el însuşi disponibil pe host, la portul 80 şi rulează ca daemon
(program background) ascultând cererile făcute de client. Web browserul este program client.
Pentru a regăsi o pagină pe host, se transmite browsaerului (via URL) adesa network şi
calculatorul unde serverul rulează (implicit portul 80). URL conţine informaţii adiţionale (de
obicei calea relativă) utilizată pentru a forma cererea specificată. Web browserul trimite
cererea serverului la adresa reţea şi al portul lui.[Raco_00]. Cererea se regăseşte pe maşina
host sub forma unui mesaj ataşat la portul serverului. Mesajul conţine adresa reţea şi numărul
de port al programului client astfel încât serverul să poată replica. Serverul, văzând cererea
venită pe port, procesează şi apoi trimite răspuns în pagina Web a programului client, la
adresa reţea a browserului şi la portul lui.Chiar dacă se utilizează un număr de port >1024
trebuie să se asigure că alt program nu a ales deja acest port.pentru un socket se trimite email
către everyone, ce adresează serverul Web, sau se utilizează fără precauţii,d epinstând dacă
apar erori.

6.3. InputStream şi OutputStream

Stream (flux) este o cale imaginară ce transportă date între două puncte: sursa şi destinaţia
-cele două puncte finale ale comunicaţiei. Acestea pot fi entităţi ce dau programului intrări
sau primesc ieşiri (fişier, consolă, tastatură, pachet de reţea, etc.). La cel mai înalt nivel sunt
două tipuri de flux: de intrare, prin care programul poate citi date şi de ieşire, prin care
programul poate scrie date şi sunt reprezentate de clasele abstracte java.io.InputStream
respectiv java.io.OutputStream.
try {
//... cod ce proiecteaza exception
}
catch (Exception ex) {
System.err.println(ex);
}

System.out System.err sunt print streams adicǎ instanţe de java.io.PrintStream1. Cea mai
mare parte a pachetului java.io constă în 10 clase definite ca subclase ale java.io.InputStream
respectiv 7 clase definite ca subclase ale java.io.OutputStream. Comportamentul particular al
claselor Stream permite conectarea acestora, ceea ce dă funcţionalitate combinată, furnizată
de câteva clase diferite de flux, ce procesează acelaşi flux de date.
Aproape orice operaţie care implică intrări sau ieşiri are o clasă de nesiguranţă, pentru că
depinde de hardware şi de condiţiile sistemului, ambele departe de controlul programatorului.
1
Elliotte Rusty Harold Java™ I/O, 2nd Edition, O'Reilly&Soons, 2006

102
Ca rezultat, metodele virtuale definite în orice clasă din java.io sunt declarate cu clauze throw
IOException. [Chan_95]. IOException nu este o subclasă a lui RuntimeException.La apelul
unei metode ce poate proiecta o excepţie (alta decât RuntimeException) se poate prinde sau
ignora această excepţie.
java.io.InputStream şi java.io.OutputStream sunt clase de bazǎ, abstracte pentru diferite
subclase care posedǎ abilitǎţi specializate. Aceste subclases includ:
BufferedInputStream
BufferedOutputStream
ByteArrayInputStream
ByteArrayOutputStream
DataInputStream
DataOutputStream
FileInputStream
FileOutputStream
FilterInputStream
FilterOutputStream
ObjectInputStream
ObjectOutputStream
PipedInputStream
PipedOutputStream
PrintStream
PushbackInputStream
SequenceInputStream
Pachetul java.util.zip conţine clase ce citesc în input stream data în format comprimat şi le
returneazǎ în format decomprimat iar pentru clase output stream citesc data în format
decomprimat şi scriu în format comprimat.
CheckedInputStream
CheckedOutputStream
DeflaterOutputStream
GZIPInputStream
GZIPOutputStream
InflaterInputStream
ZipInputStream
ZipOutputStream
Pachetul java.util.jar include douǎ clase stream pentru citirea fişierelor din arhive JAR:
JarInputStream şi JarOutputStream
Pachetul java.security conţine cupluri de clase stream utilizate la calcularea mesajelor
digest: DigestInputStream şi DigestOutputStream.
Java Cryptography Extension (JCE) adaugǎ douǎ clase pentru încriptare şi decriptare:
CipherInputStream şi CipherOutputStream
Câteva clase stream random sunt ascunse (hiding) în interiorul pachetului,
sun.net.TelnetInputStream şi sun.net.TelnetOutputStream, în mod deliberat deoarece sunt
prezente ca instanţe de java.io.InputStream sau java.io.OutputStream.
Clasele java.io.Reader şi java.io.Writer sunt superclase abstracte pentru clasele ce citesc şi
scriu date character-based, cu rol data notabil de manipulare a conversiei între diferite seturi
de caractere. Nucleul Java API include 9 clase de citire si 8 se scriere, în pachetul java.io:
BufferedReader
BufferedWriter
CharArrayReader
CharArrayWriter

103
FileReader
FileWriter
FilterReader
FilterWriter
InputStreamReader
LineNumberReader
OutputStreamWriter
PipedReader
PipedWriter
PrintWriter
PushbackReader
StringReader
StringWriter

Clasa java.io.OutputStream declarǎ 3 metode write( ):


public abstract void write(int i) throws IOException
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException

Din acest motiv, clasa java.io.Writer declarǎ tot 3 metode write( ) :


public void write(int i) throws IOException
public void write(char[] data) throws IOException
public abstract void write(char[] data, int offset, int length) throws IOException

Semnǎtura le deosebeşte, în timp ce int trimis de metoda write( ) OutputStream este redusǎ la
modulo 256 înainte de ieşire, int trimis de write( ) din Writer este redusǎ modulo 65,536.
java.io.Writer are în plus 2 metode write( ) ce iau date din şir:
public void write(String s) throws IOException
public void write(String s, int offset, int length) throws IOException

Deoarece nu este precizatǎ legǎtura cu date character-based nu au metode corespondente în


clasa java.io.OutputStream.

IOExceptions
Cum operaţiile de intrare ieşire proiecteazǎ erori, ele sunt manipulate de IOException.
IOException gestioneazǎ excepţiile în blocuri try/catch block, cu excepţia claselor
PrintStream şi PrintWriter. Inconvenientul de a avea bloc try/catch la apel System.out.println(
), a determinat Sun sǎ construiascǎ PrintStream (apoi PrintWriter) pentru a prinde excepţii în
metode print( ) sau println( ). Verificarea excepţiilor în metode print( ) sau println( ) se poate
face cu apel la metoda checkError( ): public boolean checkError( )

IOException are multe subclasses (15 în java.io înafara metodelor ce dau excepţii specifice
ale subclaselor IOException (EOFException sau UnsupportedEncodingException când se
citeşte un text cu caracter necunoscut). Constructorii:
public IOException( )
public IOException(String message)

Primul creazǎ IOException cu mesaj vid al doilea dǎ detailii despree eroare, IOException
având metode uzuale moştenite de la clasele exception ca: toString( ) şi printStackTrace( ).

104
System.out este prima instanţǎ de clasǎ OutputStream şi dǎ un câmp static din clasa
java.lang.System class. Este instanţǎ de java.io.PrintStream, subclasǎ a java.io.OutputStream.
"Hello World!" devine:
byte[] hello = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10,
13};
System.out.write(hello);

System.err este versiune Java de stderr, instanţǎ de java.io.PrintStream, subclasǎ a


java.io.OutputStream, utilizatǎ în clauze de TRy/catch, utile la depanare.
System.in (la Unix sau C stdin) dǎ and can be redirected from a shell in the same fashion.
System.in un câmp static din clasa java.lang.System class. Este instanţǎ de
java.io.InputStream, în realitate java.io.BufferedInputStream.

Redirectarea System.out, System.in, şi System.errpoate fi realizatǎ cu 3 metode statice din


clasa java.lang.System:
public static void setIn(InputStream in)
public static void setOut(PrintStream out)
public static void setErr(PrintStream err)

Clasa Console
Java 6 adaugǎ java.lang.Console2 un singleton ce are o instanţǎ doar şi întotdeauna aplicǎ
acelaşi shell la System.in, System.out, şi System.err point to. Regǎsirea singurei instanţe a
clasei se face cu metoda staticǎ System.console( ):
Console theConsole = System.console( );

Metoda întoarce null dacǎ ruleazǎ în alt mediu (telefon mobil sau web browser care nu are
console. Metoda simplǎ readLine( ) întoarce un singur şir de la consolǎ, fǎrǎ line-break:
public String readLine( ) throws IOError

Opţional se poate da prompt la citire:


public String readLine(String prompt, Object... formatting)

Sirul prompt este interpretat ca şi printf( ) şi nu are echivalent scanf( ). Valoarea returnatǎ este
similarǎ cu readLine( ). Console are 2 metode readPassword( ):
public char[] readPassword( )
public char[] readPassword(String prompt, Object... formatting)

Pentru ieşire Console are 2 metode sinonime, printf( ) şi format( ):


public Console format(String format, Object... arguments)
public Console printf(String format, Object... arguments)

Console console = System.console( );


for (double degrees = 0.0; degrees < 360.0; degrees++) {
double radians = Math.PI * degrees / 180.0;
double grads = 400 * degrees / 360;
console.printf("%5.1f %5.1f %5.1f\n", degrees, radians, grads);
}

Ieşirea:
2
Motivaţia este datǎ de controversele cu comunitǎţile Python şi Ruby

105
0.0 0.0 0.0
1.0 0.0 1.1
2.0 0.0 2.2
3.0 0.1 3.3
...

Se poate forţa data scrisǎ înainte de line break invocând metoda flush( ):
formatter.flush( );
formatter.close( );

Consola este asociatǎ cu PrintWriter şi Reader:


public PrintWriter writer( )
public Reader reader( )

Exemplu:
import java.io.*;
class Homework {
public static void main(String[] args) {
Console console = System.console( );
String input = console.readLine(
"Please enter a number between 1 and 10: ");
int max = Integer.parseInt(input);
for (int i = 1; i < max; i++) {
console.printf("%d\n", i*i);
}
}
}

C:\>java Homework
Please enter a number between 1 and 10: 4
1
4
9

Output Stream
Clasa java.io.OutputStream declarǎ metode întorc void() şi tratează IOException în caz de
eroare.
 write(int b) -scrie un singur bit pe fluxul de ieşire b şi ermite scrierea expresiei fără
a o transforma înapoi la byte;
 write (byte b[ ] ) - scrie un tablou complet de bit pe fluxul de ieşire b;
 write(byte b[ ] int off, int len) - scrie subrang de len biţi din b, începând de la
b[off];
 skip(long n ) - încearcă să sară peste n biţi din b şi întoarce întreg reprezentând
numărul de biţi săriţi cu succes;
 flush() - finalizează starea ieşirii şi şterge bufferul;
 close() - închide fluxul de ieşire caz în care viitorul write(0 generează IOException.
es the three basic methods you need to write bytes of data onto a stream. It also has
methods for closing and flushing streams:
Syntaxǎ:
public abstract void write(int b) throws IOException

106
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public void flush( ) throws IOException
public void close( ) throws IOException

Clasa FileOutputStream suprascrie cele 5 metode cu metode ce apeleazǎ cod native ce scrie
fişiere, OutputStream este abstractǎ, subclasele specifice OutputStream sunt ascunse. De
examplu, metoda getOutputStream( ) din java.net.URLConnection are semnǎtura:
public OutputStream getOutputStream( ) throws IOException

Depinzând de tipul de URL asociat obiectului URLConnection, clasa actualǎ returnatǎ pentru
output stream poate fi sun.net.TelnetOutputStream, sun.net.smtp.SmtpPrintStream,
sun.net.www.http.KeepAliveStream, sau altceva complet. Clasa java.io.DataOutputStream
class nu declarǎ metoda close( ), dar apeleazǎ metoda moştenitǎ din superclasa ei
public abstract void write(int b) throws IOException
import java.io.*;
public class AsciiChart {
public static void main(String[] args) {
for (int i = 32; i < 127; i++) {
System.out.write(i);
// break line after every eight characters.
if (i % 8 == 7) System.out.write('\n');
else System.out.write('\t');
}
System.out.write('\n');
}
}
Scrierea tablourilor de biţi
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException

Fişierele au blocuri de dimensiune 1024, 2048, or 4096 bytes, reţeaua oferǎ buffer de 128 sau
256 bytes şi se recomandǎ 128 bytes la conexiune reţea şi 1024 bytes la fişiere.

Exemplu: AsciiArray program


import java.io.*;
public class AsciiArray {
public static void main(String[] args) {
byte[] b = new byte[(127-31)*2];
int index = 0;
for (int i = 32; i < 127; i++) {
b[index++] = (byte) i;
// Break line after every eight characters.
if (i % 8 == 7) b[index++] = (byte) '\n';
else b[index++] = (byte) '\t';
}
b[index++] = (byte) '\n';
try {
System.out.write(b);
}

107
catch (IOException ex) {
System.err.println(ex);
}
}
}
Inchiderea Output Streams
public void close( ) throws IOException
La apel out.close( ) din OutputStream, se închide fluxul dar se şi elibereazǎ resursele associate
cu acesta. System.out este excepţie parţialǎ a PrintStream.
Fişierele sunt închise prin:
try {
OutputStream out = new FileOutputStream("numbers.dat");
// Write to the stream...
out.close( );
}
catch (IOException ex) {
System.err.println(ex);
}
Initialize this to null to keep the compiler from complaining
// about uninitialized variables
OutputStream out == null;
try {
out = new FileOutputStream("numbers.dat");
// Write to the stream...
}
finally {
if (out != null) out.close( );
}

Closeable Interface
Java 5 oferǎ Closeable interface pe care o implementeazǎ OutputStream:
package java.io;
public interface Closeable {
void close( ) throws IOException;
}
InputStream, Channel, Formatter, şi alte fluxuri ce pot fi închise implementeazǎ aceastǎ
interfaţǎ.

Golirea Output Streams


Metoda flush( ) forţeazǎ scrierea datelor chiar dacǎ bufferul nu e plin:
public void flush( ) throws IOException

Metoda sync( ) din clasa FileDescriptor goleşte uneori bufferele.

Java 5 adaugǎ Flushable interface în clasa OutputStream class:


package java.io;
public interface Flushable {
void flush( ) throws IOException;
}

108
Subclasarea OutputStream
OutputStream este abstractǎ iar clasele specifice o extend. FileOutputStream utilizeazǎ cod
native la scrierea fişierelor, ByteArrayOutputStream utilizaezǎ Java pur la scriere şi
expandare byte array. Suprascrierea variantelor de metodǎ write( ) din OutputStream, dǎ o
metodǎ abstractǎ, şi 2 concrete:
public abstract void write(int b) throws IOException
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException

Subclasele trebuie sǎ implementeze metoda abstractǎ write(int b).

public void write(byte[] data, int offset, int length) throws IOException {
for (int i = offset; i < offset+length; i++) write(data[i]);
}

Exemplu The NullOutputStream class


package com.elharo.io;
import java.io.*;
public class NullOutputStream extends OutputStream {
private boolean closed = false;
public void write(int b) throws IOException {
if (closed) throw new IOException("Write to closed stream");
}
public void write(byte[] data, int offset, int length)
throws IOException {
if (data == null) throw new NullPointerException("data is null");
if (closed) throw new IOException("Write to closed stream");
}
public void close( ) {
closed = true;
}
}

Redirectând System.out şi System.err cǎtre null output stream se dezactiveazǎ mesajele de


depanare, astfel:
OutputStream out = new NullOutputStream( );
PrintStream ps = new PrintStream(out);
System.setOut(ps);
System.setErr(ps);
.
Interfaţǎ graficǎ pentru Output Stream
Subclasa din javax.swing.JTextArea poate fi conectatǎ la flux de ieşire Actala ieşire este
conţinutǎ în interiorul clasei JStreamedTextArea. Fiecare component JStreamedTextArea
conţine obiect TextAreaOutputStream în Output field. Clienţii acceseazǎ acest obicet via
metoda getOutputStream( ). Clasa JStreamedTextArea are 4 constructori ce imitatǎ pe cei 4
din clasa javax.swing.JTextArea, luând diverse combinaţii de text, rânduri, coloane. Primii 3
constructori paseazǎ argumente cǎtre cel mai general constructor folosind this( ). Constructor
apeleazǎ cel mai general constructor al superclasei, apoi setEditable(false) pentru a fi sigur cǎ
nu s-a schimabt textul.
Examplu The JStreamedTextArea component

109
package com.elharo.io.ui;
import javax.swing.*;
import java.io.*;
public class JStreamedTextArea extends JTextArea {
private OutputStream theOutput = new TextAreaOutputStream( );
public JStreamedTextArea( ) {
this("", 0, 0);
}
public JStreamedTextArea(String text) {
this(text, 0, 0);
}
public JStreamedTextArea(int rows, int columns) {
this("", rows, columns);
}
public JStreamedTextArea(String text, int rows, int columns) {
super(text, rows, columns);
setEditable(false);
}
public OutputStream getOutputStream( ) {
return theOutput;
}
private class TextAreaOutputStream extends OutputStream {
private boolean closed = false;
public void write(int b) throws IOException {
checkOpen( );
// recall that the int should really just be a byte
b &= 0x000000FF;
// must convert byte to a char in order to append it
char c = (char) b;
append(String.valueOf(c));
}
private void checkOpen( ) throws IOException {
if (closed) throw new IOException("Write to closed stream");
}
public void write(byte[] data, int offset, int length)
throws IOException {
checkOpen( );
append(new String(data, offset, length));
}
public void close( ) {
this.closed = true;
}
}
}

TextAreaOutputStream extinde OutputStream si trebuie sa implementeze metoda abstract


write( ). Suprascrierea metodei write( ) furnizeazǎ implementare mai eficientǎ iar
suprascrierea close( ) se asigurǎ cǎ apelul write nu mai are loc dupǎ ce fluxul este închis.
To use this class, simply add an instance of it to a container such as an applet or a window,
much as you'd add a regular text area. Next, invoke its getOutputStream( ) method to get a

110
reference to the output stream for the area, then use the usual write( ) methods to write into
the text area. Often, these steps will take place at different times in different methods.

 read() - întoarce întreg reprezentând numărul de biţi ai intrării;


 read(byte b[ ] ) - încearcă să citească b.length biţi din b şi întoarce întreg
reprezentând numărul de biţi citiţi cu succes;
 read(byte b[ ] int off, int len) - încearcă să citească len biţi din b, începând de la
b[off] şi întoarce întreg reprezentând numărul de biţi citiţi cu succes;
 skip(long n ) - încearcă să sară peste n biţi din b şi întoarce întreg reprezentând
numărul de biţi săriţi cu succes;
 available() - întoarce întreg reprezentând numărul de biţi ai intrării curente,
disponibili la citire;
 close() - închide sursa de intrare caz în care viitorul read(0 generează IOException;
mark(int limita) -plasează marker la punctul curent al fluxului de intrare, rămânând
valabil până când numărul de biţi ai intrării (limita) sunt citiţi;
 reset () -se întoarce pointerul d eintrare la marker-ul anterior;
 markSupported() întoarce true dacă mark() sau reset() sunt suportate de fluxul de
intrare

6.3.2. Metode OutputStream

Spre deosebire de metodele InputStream, toate aceste


6.4. FileStream

Pentru citirea respectiv scrierea fişierelor sunt definite clasele FileInputStream şi


FileOutputStream.

6.4.1. FileInputStream
Dacă fişierul nu este găsit constructorul FileInputStream() ca proiecta
FileNotFoundException. Dacă fişierul este găsit dar nu este citibil, se va proiecta
IOException. Când FileInputStream este construit dintr-un şir, parametrul constructorului
poate fi numele simplu al fişierului din directorul curent sau calea completă sau relativă către
fişier.
Exemplu:
FileInputStream fis=new FileInputStream("fis_intrare");
byte b[ ]=new byte[100];
// citirea a 100 biti in b
int numByteRead=fis.read(b);
System.out.println(new String(b,0,0, numByteRead));
System.out.println(numByteRead+"biti cititi");

6.4.2. FileOutputStream
Deschiderea unui fişier pentru scriere se realizează cu:
FileOutputStream f_ies=new FileOutputStream("fis_iesire");
Dacă fişierul nu este găsit când constructorul FileOutputStream este apelat, acesta se crează
iar dacă nu s-a precizat calea validă, constructorul proiectează FileNotFoundException. Dacă
fişierul este găsit dar nu este citibil, se va proiecta IOException. Calea sistemului este
dependentă de sistem. Dacă se deschide un fişier pe UNIX, cu cale stil Windows ca separator
(folosind \), se va proiecta IOException. Soluţia cea mai comodă este utilizarea lui / (slash)
ca separator deoarece Java garantează tratarea caracterului ca separator de fişier, translatând

111
dacă este necesar, pentru a menţine independenţa sitemului. Pentru construcţia
FileInputStream şi FileOutputStream nu este neapărat necesară utilizarea lui String deoarece
poate fi trecută opţional în FileDescriptor sau în obiectul File al constructorului. Odată
construit FileInputStream şi FileOutputStream (ca obiecte) se poate accesa fişierul utilizând
metodel implementate în clasă. Aceste două clase au funcţionalitate limitată deoarece nu se
pot scrie şiruri obiecte sau alte date de tip primitiv în şi din aceste clase de flux.
FileInputStream şi FileOutputStream manipulează doar biţi.
Exemplu:
import java.io.*;
class FileOutputStream S {
public static byte getInput()[ ] throws Exception {
byte buffer [ ]= new byte{12];
for (int i =0; i<12;i++){
buffer [i]= (byte)System.in.read();
}
return buffer;
}
public static void main (String args [ ] ) throws Exception {
byte buf[ ]=getInput();
OutputStream f0= new FileOutputStream("fis1.txt');
OutputStream f1= new FileOutputStream("fis2.txt');
OutputStream f2= new FileOutputStream("fis3.txt');
for (int i =0; i<12;i++){
f0.write(buf [i]));
}
f0.close();
f1.write(buf);
f1.close();
f1.write(buf,12/4,12/2);
f1.close();
}
}
FileOutputStream nu se folosesc în operaţii de adăugare.

6.4.3. ByteArrayInputStream

ByteArrayInputStream este implementarea lui InputStream ce foloseşte ca sursă tablou de biţi


şi are doi constructori.
Exemplu:
String tmp="sanmashds";
byte b[ ]=new byte [tmp.length()];
tmp.getBytes(0,tmp,length(),b,0);
ByteArrayInputStream input1= new ByteArrayInputStream (b);
ByteArrayInputStream input2= new ByteArrayInputStream (b,0,3);

Se implementează în plus o metodă reset pentru referirea de la începutul fluxului.


Exemplu
for (int i =0; i<2;i++){
while (( c=Input.read()) != -1) {
if (i == 0) {

112
System.out.println((char)c));
}else{
System.out.println(Character.toUppercase((char)c));
}
System.out.println();
}

6.4.4. ByteArrayOutputStream

ByteArrayOutputStream este implementarea lui OutputStream, ce foloseşte ca sursă tablou de


32 biţi cu argument număr de biţi, întoarce void şi tratează IOException, având doi
constructori:
OutputStream out1= new ByteArrayOutputStream ();
OutputStream out2= new ByteArrayOutputStream (1024);

Exemplu:
import java.io.*;
import java.util.*;
class ByteArrayOutputStream S {
public static void main (String args [ ] ) throws Exception {

int i;
ByteArrayOutputStream f0= new ByteArrayInputStream (12);
System.out.println("10 caractere si return");
while (f0.size() !=0) {
f0.write(System.in.read());
}
System.out.println("Buffer a sir");
System.out.println(f0.toString());
System.out.println("in tablou");

byte b[ ]=f0.toByteArray();
for (int i =0; i<b.lenght;i++){
System.out.println((char)b[i]));
}
System.out.println();
System.out.println("Intr-un OutputStream");
OutputStream f2= new File OutputStream{"test.txt");
f0.writeTo(f2);
System.out.println("facem reset");
while (f0.size() != 10) {
f0.write(System.in.read());
}
System.out.println("gata'");
}
}

StringBufferInputStream este identic cu ByteArrayIntputStream doar că bufferul de


intrare corespunde unui şir şi are un constructor:
StringBufferInputStream(String s);

113
FilteredStream extinde fluxurile de bază furnizând sincronizarea.

6.5. DataInputStream şi DataOutputStream

Pentru citirea respectiv scrierea datelor de nivel mai înalt sunt definite clasele
DataInputStream şi DataOutputStream. DataInputStream permite citirea şi DataOutputStream
permite scrierea sub forma reprezentării binare a tipurilor primitive. De aceea
DataInputStream are metode instanţiate ca şi readInt() care citeşte 32 biţi şi îî interpretează
ca Int, readDouble() care citeşte 64 biţi şi îî interpretează ca Double. In mod analog,
DataOutputStream furnizează metode instanţiate pentru writeInt() şi writeDouble().
In mare parte DataInputStream şi DataOutputStream urmează o proiectare paralelă şi totuşi,
DataInputStream furnizează metoda readLine() care nu face parte din DataInputStream şi
care citeşte o linie oprindu-se la sfârşitul ei (\n sau sfârşit de fişier). Această metodă devine o
modalitate convenabilă de citire a liniilor de text în fişiere ASCII ale intrărilor standard.
Metoda readLine() citeşte caractere 8-biţi ASCII nu 16-bit Unicode iar şirul returnat conţine
partea Unicode a caracterelor ASCII citite. La DataOutputStream se foloseşte metoda
PrintStream() care permite scrierea unui şir şi astfel DataInputStream este optimizat pentru
text introdus în timp ce DataOutputStream.nu. Fluxul de ieşire standard System.out este
declarat ca PrintScreen, care optimizează ieşirile text, în timp ce intrarea standard este
declarată InputStream, înţelegând că trebuie conectată DataInputStream pentru a facilita
intrările apelate de readLine().

6.5.1. PrintStream

Această clasă a fluxului de ieşire optimizează textul afişat şi nu vine în pereche, deoarece
PrintStream este prin natura sa flux de ieşire. PrintStream furnizează metode ca print() şi
println() ce vor afişa fluxul unui şir (String) ca reprezentare a obiectului sau a tipului
primitiv. Astfel se pot conecta FileOutputStream, DataOutputStream şi PrintStream pentru a
adăuga posibilităţi de utilizare pentru print() şi println(). Standard output stream (System.out)
şi standard error stream (System.err) sunt subclase ale lui PrintStream. Clasa are doi
constructori:
PrintStream(OutputSream out);
PrintStream(OutputSream out, boolean autoflush);
Unde autoflush controlează ieşirea de date de câte ori apare "Xn".
Caracterele puse în flux pentru print() şi println() sunt ieşiri care utilizează 8-biţi ASCII mai
degrabă decât 16-biţi Unicode. Primii 8 biţi ai fiecărui caracter sunt neafectaţi, fiind folosiţi
ultimii 8, dat fiind faptul că setul de caractere ASCII este un subset al setului Unicode. Pentru
caracterele din setul non-Latin este o problemă utilizarea metodelor print() şi println().
[Naugh_96]

6.6. BufferedInputStream şi BufferedOutputStream

Utilizarea eficientă a memoriei şi a timpului de lucru al procesorului este realizată cu buffere


I/O. Când se citeşte un element din BufferedInputStream bufferul de memorie adresează
un bloc mare de date. La un moment dat se preferă citirea unui element de pe disc, ceea ce
este relativ lent (foarte lent chiar în reţea) şi apoi citirea din buffer, mult mai rapidă. Implicit
dimensiunea bufferului este de 1024 biţi iar la alte dimensiuni solicitate de utilizator, se
specifică parametrul din constructor. Un alt avantaj este deplasarea înapoi şi poziţionarea
curentă în fluxul de date existent în buffer. BufferedInputStream are doi constructori:
BufferedInputStream(InputStream in) la 32 biţi

114
BufferedInputStream(InputStream in, int dimens)
unde dimens este independentă de sistemul de operare pe care se lucrează
BufferedInputStream oferă metoda mark(), ce permite poziţionarea curentă în flux şi reset()
pentru poziţionarea pointerului înapoi la poziţia marcată prin metoda mark().
PushbackInputStream este o implementare a lui InputStream şi permite citirea unui
caracter şi apoi aşezarea lui înapoi în fluxul de date de intrare. Incercarea de a pune mai multe
caractere determină IOException iar construtorul este:
PushbackInputStream ( InputStream in)
In plus oferă metoda unread(char c) care acceptă argument caracter, ce se va pune înapoi în
fluxul de intrare.
SequenceInputStream este o clasă a lui InputStream care suportă concatenarea mai multor
InputStream în unul singur.Construtorul ei este:
SequenceInputStream ( InputStream s0, InputStream s1);
SequenceInputStream ( Enumeration e);

6.7. PipedInputStream şi PipedOutputStream

Aceste două fluxuri sunt utile în comunicaţia fir-cu-fir iar ideea este împrumutată de la
utiliarul UNIX- pipe, unde ieşirile unui program sunt trimise ca intrări ale altui program.
Diferenţa este dată de faptul că UNIX pipe este destinat comunicaţiei între procese în timp ce
Piped sunt destinate comunicaţiei între fire (interthread). Cele două clase pot fi folosite
astfel:
PipedInputStream b Input= new PipedInputStream ();
PipedOutputStream ies Output= new PipedOutputStream (b);

După ce este setată relaţia se poate instanţia Thread ies ce trimite date altui Thread b iar când
ies scrie date în iesOutput, b poate citi date din bInput.
StreamTokenitzer este o clasă Enumeration (enumeraţie), virtual identică cu
StringTokenitzer, extrăgând un token (simboluri) dintr-un flux de intrare.[An_96]

6.8. Obiectul File

File este obiectul ce permite verificarea şi execuţia diverselor operaţii cu fişiere (diferite de
citire şi scriere) prin care se poate verifica accesibilitatea fişierului cu exists(), canRead(),
canWrite(), se poate crea un director cu metoda mkDir(), se poate redenumi un fişier
renameTO() sau se poate obţine lista fişierelor dintr-un director - list(). Precizăm că
directorul nu se poate şterge.

Exemplu:
import java.io.File;
class FileTest {
private p(Strings) {
System.out.println(s);
}
public static void main(String args{[ ]) {
File f1= new File("/java/Exemplu");
p("nume Fisier"+f1.getName());
p("cale Fisier"+f1.getPath());
p("cale absoluta Fisier"+f1.getAbsolutePath());
p("parinte"+f1.getParent());

115
p(f1.exists() ? "exista" : "nu exista";
p(f1.canRead() ? "pot citi" : "nu pot citi ";
p(f1.canWrite() ? "pot scrie" : "nu pot scrie ";

if (f1.isDirectory()) {
System.out.println("Director "+dirname);
String s[ ]=f1.list();
for (int i=0; i<s.length;i++){
File f= new File(dirname+"/"+s[i]);
if (f1.isDirectory()) {

System.out.println(s[i] +" este Director ");


}else{
System.out.println(s[i] +" este fisier ");
}
}
}else{
System.out.println("NU este director "+dirname);
}
}
}

6.9. Interfaţa FilenameFilter

Uneori este de dorit limitarea fişierelor transmise prin list la cele cu structura delimitată prin
pattern, caz în care se implementează interfaţa FilenameFilter cu o singură metodă accept ()
apelată odată pentru fiecare fişier din listă. Restricţiile se aplică fisierului cu sufix identic cu
extensia dată.[An-96]
Exemplu:
Modificăm exemplul anterior, astfel:
import java.io.*;
public class NumaiExt implements FilenameFilter {
String ext;
public NumaiExt (String ext) {
this.ext="."+ext;
}
public boolean accept( File dirname, String Nume) {
return name.endsWith(ext);
}
}

Când se apelează NumaiExt cu constructor 'html" apare:


FileNameFilter only= new NumaiExt("html");
String s[ ] f1.list(only);

RandomAccess este obiectul ce permite citire şi scriere simultană a unui fişier fără a deschide
fluxuri separate. Astfel s epoate specifica modul se acces al fişierului ("rw") şi clasa vine cu
metodele de citire a tipurilor primitive. De asemenea se poate seta pointerul de fişier cu
metoda seek().

116
FileDescriptor este identificator unic de fişiere şi în plus, identificator unic de intrare
standard, ieşire standard şi eroare standard.FileDescriptor este opac şi nu se poate face nimic
pentru a-l altera în schimb se poate folosi pentru a construi un obiect de tip File,
FileInputStream, FileOutputStream sau RandomAccessFile. Aceste clase furnizează metoda
getFD() prin care se obţine obiectul FileDescriptor ce identifică în mod unic fişierul File la
care este ataşat. In plus, se poate apela la FileDescriptor ca metodă valid() care întoarce o
valoare booleană ce indică FileDescriptor valid.
Clasa FileDescriptor defineşte 3 obiecte constante FileDescriptor.in, FileDescriptor.out şi
FileDescriptor.err iar comparând fluxurile FileDescriptor ne putem convinge că sunt
conectate a aceste puncte finale ale fluxului standard.

6.10. Imbinarea fluxurilor

Există mai multe metode de îmbinare a fluxurilor dar tipic se crează un flux proiectat pentru
a fi ataşat unui punct final cert (ca şi Socket sau FileInputStream) şi apoi se trece acest
parametru la constructorul unei alte clase a fluxului proiectat pentru filtrarea datelor într-un
anume mod.
Filtru este clasa de flux care alterează transmisia datelor (BufferedInputStream şi
BufferedInputStream) sau care furnizează metode ce permit vizualizarea într-un mod
convenabil a datelor (DataInputStream şi PrintStream).
Deseori se citeşte un fişier text utilizând DataInputStream sau se scrie cu PrintStream pentru a
folosi avantajele metodelor centrate pe text, furnizate de aceste clase. Deoarece fluxul nu
citeşte sau scrie în/din fişiere, este necesară crearea unui FileInputStream sau
FileOutputStream sau se pot chiar combina aceste fluxuri, tipic prin constructor încuibat
apelat, astfel:

Exemplu:
// cream FileInputStream şi îl înconjurăm cu DataInputStream
DataInputStream dis=new DataInputStream (new FileInputStream(
"FisierIn");

//cream FileOutputStream şi îl înconjurăm cu PrintStream


PrintStream ps=new PrintStream (new FileOutputStream(
"FisierOut");

Se preferă totuşi buffere I/O iar ideea este de a avea un flux ce are de a face cu fişierele, unul
ce este conectat la manipularea bufferului şi un flux conectat pentru a oferi abilităţi de
abordare a datelor de la cel mai înalt punct de vedere. Astfel utilizăm constructori imbricaţi:
Exemplu:
// cream FileInputStream şi îl înconjurăm cu BufferedInputStream
// şi apoi cu DataInputStream
DataInputStream dis=new DataInputStream (
new BufferedInputStream (
new FileInputStream("FisierIn")));

//cream FileOutputStream şi îl înconjurăm BufferedOutputStream


//şi apoi cu cu PrintStream
PrintStream ps=new PrintStream (
new BufferedInputStream(
new FileOutputStream("FisierOut")));

117
Aceşti constructori imbricaţi crează confuzii dacă nu se ţine cont de cele două aspecte
funcţionale:[Hoff_96]
1. Constructorul cel mai interior este invocat primul. Fluxul ce este cel mai direct în
legătură cu punctul final de transmisie a datelor va fi construit primul (punctul
final fiind fişierul). Când se deschide un flux de fişier, primul flux ce se va
construi este cel în legătură directă cu fişierul. Ultimul flux construit este cel
despre care se "vorbeşte" mai mult.
2. Când se cunoaşte funcţionalitatea fiecărui flux, nu este o problemă aşezarea lui în
transmisia datelor.
Exemplu:
import java.io.*;
class WordCount {
public static int words=0;
public static int lines=0;
public static int chars=0;
public static void Wco(InputStream f) throws Exception {
boolean lastNotWhite =false;
int c=0;
String whiteSpace=" \+\n\r";
while ((c=f.read()) != -1){
chars==;
if (c=="\n"){
words++;
}
lastNotWhite=false;
}else{
lastNotWhite=true;
}
}
}
public static void main(String args[ ] ){
FileInputStream f;
try{
if (args.length==0 ){
f= FileInputStream(System.in);
Wco(f);
//lucram cu lista de fişiere
} else {
for (int i=0;I<args.length;i++){
f=new FileInputStream(args[i] );
Wco(f);
}
}
}catch(IOException e){
return;
}
System.out.println(lines+" si cuvinte"+words+
"si caractere"+chars):
}

118
}

O altă implementare a metodei:

public static void Wco(InputStream f) throws Exception {


PushBack InputStream plaf=new PushBack InputStream f);
int c=0;
String whiteSpace=" \+\n\r";
while ((c=plaf.read()) != -1){
chars==;
if (c=="\n"){
words++;
}
if(whiteSpace.indexOf ( c) !=-1){
c=plaf.read();
if(whiteSpace.indexOf ( c) !=-1){
words++;
}
plaf.unread (c);
}
}
}
Altă variantă de implementare o dă StreamTokenitzer astfel:
public static void Wco(InputStream f) throws Exception {
StreamTokenitzer tok=new StreamTokenitzer (f);
tok.resetSyntax();
tok.wordchars(33,255);
tok.whitespaceChars(0,' ');
tok.eolISSignificant(true);
(33,255);
while (tok.type !=TT-EOF){
int tokenType=tok.nextToken();
switch(tokenType) {
case tok. TT-EOF:
chars==;
lines++;
break;
case tok.TT-WORD:
words++;
default:
chars+=tok.sval.length();
break;
}
}
}

119
Capitolul VII

Programarea în reţea
Pachetul .net

7.1.Caracteristicile pachetului .net

Introducere

Ca şi oricare alt limbaj, Java asigură o modalitate de exprimare a conceptelor. Toate limbajele
de programare folosesc abstractizarea iar complexitatea problemei pe care o rezolvăm este
direct proporţională cu modul şi gradul de abstractizare. Programatorul trebuie să realizeze
asocierea dintre modelul maşinii şi modelul problemei care se rezolvă. Alternativa la
modelarea maşinii este modelarea problemei. Abordarea programării orientate obiect acordă
utilizatorului instrumente ce asigură reprezentarea elementelor în spaţiul problemei. Această
reprezentare asigură programatorului libertatea de a nu fi constrâns într-o problemă
particulara. Ideea este că programul poate adăuga tipuri noi de obiecte de care are nevoie,
pentru a rezolva o anumită problema. Astfel atunci când citim codul sursă ce descrie soluţia
problemei, citim de fapt cuvinte care descriu problema.

Programarea în reţea a fost dificilă, complexa şi plina de erori. Programatorul trebuia să


cunoasca multe detalii privind reţeaua, sau chiar şi partea de hardware. Numeroasele layer-e
ale protocoalelor reţelei trebuiau intelese şi existau o serie de funcţii care variau de la o
librarie a reţelei la alta în ceea ce priveste conectarea, împachetarea, despachetarea blocurilor
de informaţii, îngreunând simţitor programarea în reţea.
Totuşi conceptul de reţea se rezumă la dorinţa de a obţine informaţii de la “celalalt” calculator
şi de a le transfera pe calculatorul propriu, sau invers. El este asemănător cu citirea sau
scrierea fişierelor, cu excepţia faptului că fişierele se află pe o maşina care ştie exact ce să
facă cu informaţia ceruta sau cu informaţia de care are nevoie.

Atuurile pe care le are Java la nivelul lucrului în reţea sunt diminuarea, eliminarea unora
dintre aceste probleme şi transferarea lor pe cât posibil, în modelele abstractizate şi preluate
de JVM . Modelul de programare utilizat este cel al unui fişier. Conceptul de multithreading ,
care este incorporat, este foarte util, mai ales atunci când avem de-a face cu o alta problemă
legată de reţea - conectarile multiple care au loc în acelaşi timp.

Identificarea unui calculator

Pentru a asigura comunicarea între calculatoare, şi pentru a avea siguranţa că suntem


conectaţi la calculatorul dorit, trebuie să existe o cale de a identifica în mod unic
calculatoarele dintr-o reţea. Datorita faptului că Java lucreaza în cadrul Internetului, este
necesară gasirea unei căi de identificare unică a unui calculator faţă de celelalte din întreaga
lume. Acest lucru este asigurat de adresele IP (Internet Protocol), care pot exista sub două
forme :
1. sub forma familiara de DNS (Domain Name System).
Astfel , daca numele meu de domeniu este bruceeckel.com, şi avem un calculator în acest
domeniu numit CalcS, atunci, numele de domeniu al calculatorului va fi

120
CalcS.bruceeckel.com . Genul de nume este similar cu cel care care-l folosim atunci când
trimitem un e-mail, şi care de obicei este incorporat într-o adresa www (World -Wide -Web).
2. sub forma de 4 numere despărţite prin punct, de exemplu, 123.243.23.121.
In ambele cazuri, adresa de IP este reprezentată pe 32 biti (nici unul din numere nu poate
depăşi 255).

Java suportă nume Internet prin InetAddress clasă ce este comprimată la 32 biţi ca host
identifier şi 32 de biţi ca selector de port. [java_1]
InetAddress nu are constructor vizibil şi este necesară utilizarea metodelor factoriale, statice
ca şi: getLocalHost, getByName, getAllByName pentru a crea instanţe la InetAddress. Dacă
getByName nu poate rezolva cererea apare excepţia UnknownHoseException.
InetAddress are câteva metode non-statice:
 getHostName() care dă numele de host asociat cu InetAddress;
 getAddress() dă 4 elemente de tablou byte ce reprezintă Inetwork byte order
 toString() - întoarce un şir ca listă de nume host şi adresă IP.
Java implementează Datagram în vârful protocolulul TCP/IP UDP utilizând 2 clase
DatagramPacket (care conţine date şi DatagramSocket (care reprezintă mecanismul de
transmisie şi recepţie a DatagramPacket.
DatagramPacket are 2 constructori:[java_8], [java_2]
DatagramPacket (byte ibuf[ ], int ilenght);
DatagramPacket (byte ibuf[ ], int ilenght, InetAddress iadr, int iport);
Pentru a accesa informaţiile se oferă câteva metode:
 getAddress() întoarce destinaţia InetAddress şi se foloseşte la trimitere
 getPort() întoarce întreg, portul InetAddress şi se foloseşte la trimitere
 getData() înroarce elemente de tablou byte ale datelor conţinute în Datagram
după ce au fost primite.
 getLenght() înroarcelungimea datei valide conţinută în tabloul byte, ce va fi
returnată de getData(). De obicei lungimea datelor diferă de tabloul byte.

Prin folosirea metodei static InetAddress.getByName() care se afla în pachetul java.net se


poate obţine un obiect Java special care să reprezinte acest număr (IP) în oricare din cele doua
forme (DNS sau IP). Rezultatul este un obiect de tipul InetAddress care poate fi folosit
pentru a construi un socket .

Pentru a evidenţia într-un exemplu simplu modul de folosire a metodei


InetAddress.getByName() , considerăm situaţia în care avem un provider de Internet (ISP)
care face legătura prin telefon. De fiecare dată când intram pe Internet vom obţine o adresa IP
temporară, dar care în timpul conectării are aceeaşi validitate ca orice alta adresa IP. Daca
cineva s-ar conecta la acest calculator folosind adresa IP , acea persoana se poate conecta la
un server web sau la un server FTP ce ruleaza pe acest calculator. Dar acest lucru este posibil
doar cunoscând aceasta adresa IP, şi cum această adresă se atribuie la fiecare conectare în
parte, se pune problemă modului în care putem afla aceasta adresa IP.

Codul programului foloseste InetAddress.getByName() pentru a afla aceasta adresa, dar


trebuie să cunoastem numele calculatorului .
Exemplu:
import java.net .* ;
public class WhoAmI {
Public static void main (String[] args)
throws Excepţion {

121
if (args.length != 1) {
System.err.println (
“Usage :WhoAmI MachineName”);
System.exit (1);
}
InetAddress a=
InetAddress.getByName (args[0]);
System.out.println(a) ;
}
}

Daca numele calculatorului este de exemplu “CalcS”, atunci după conectarea la ISP, execut
programul java WhoAmI CalcS şi vom obţine un raspuns de forma :
CalcS / 165.234.28.75 ( şi evident că adresa e alta de fiecare data).

Dacă se cunoaste această adresa se poate conecta la serverul web personal prin URL-ul :
http:// 165.234.28.75 doar atat cât sunt conectaţi la Internet.
Aceasta metodă este binevenită atunci când se doreste de exemplu, împarţirea unei informaţii
cu alţii sau pentru a testa configurarea propriului web site înainte de a-l pune pe un server
real.

Clienti şi servere

Ideea de baza într-o reţea este aceea de a permite conectarea, comunicarea dintre 2 sau mai
multe calculatoare, însă pentru a putea comunica între ele se pune problema cum se găsesc
calculatoarele între ele. Calculatorul căutat este serverul, iar cel care caută este clientul.
Aceasta delimitare este importantă doar până în momentul în care clientul încearca să se
conecteze la server. Odata realizată conectarea, apare un proces de comunicare bidirectională,
şi nu mai are importanţă faptul că unul din calculatoare joacă rolul de client iar celalalt de
server. Sarcina serverului este să “asculte” conectarea, lucru realizat de obiectul sever special
creat. Sarcina clientului este aceeea de a incerca stabilirea conectării la server, acest lucru
fiind realizat prin intermediul obiectului client special creat. După conectare, aceasta permite
realizarea de operaţii asemanatoare cu citirea sau scrierea într-un fişier.

Testarea programelor fără existenta unei reţele

Poate aparea situaţia în care nu avem la dispoziţie un calculator server, unul client şi o reţea,
necesare pentru testarea programului. Creatorii protocolului IP au luat în considerare aceasta
problemă şi au creat o adresa specială numită localhost ce asigură testarea programului fara
existenta unei reţele (loopback local al adresei ).
Modul generic de a produce această adresă în Java este :
InetAddress addr = InetAddress.getByName (null) ;

Prin atribuirea valorii null lui getByName(), se foloseste automat localhost.


InetAddress este folosit pentru a face referinţă la un anumit calculator. Singura modalitate de
a crea un InetAddress este prin intermediul uneia dintre urmatoarele metode : getByName() ,
getAllByName() , sau getLocalhost()
De asemenea, se poate face loopback local al adresei astfel :
InetAddress.getByName (“localhost”) ;
sau prin introducerea adresei sub forma de numere :

122
InetAddress.getByName (“127.0.0.1”).
Toate cele trei forme au acelaşi rezultat.

Portale

O adresă IP nu este suficientă pentru a identifica un server în mod unic, datorită faptului că
pot exista mai multe servere pe acelaşi calculator. Fiecare maşina IP conţine de asemenea şi
portale care trebuiesc alese pentru că atât clientul cât şi serverul să se poată conecta. Portalul
nu are o locaţie fizică în calculator ci este o abstractizare a software-ului. Programul client ştie
cum să se conecteze la calculator, prin adresa IP, dar se pune problema cum să se conecteze la
un anumit serviciu (unul din cele multe existente pe un calculator). Aici apar numerele
portalului, deoarece atunci când se cere un anumit portal, se cere de fapt conectarea la un
anumit serviciu asociat cu numărul portalului. In mod tipic, fiecare serviciu este asociat cu un
număr unic de portal pe un anumit calculator server. Aflarea orei, este un exemplu simplu de
serviciu. Este problema clientului să ştie ce număr de portal corespunde serviciului pe care
vrea sa-l solicite. Serviciile de sistem îşi rezerva folosirea portalurilor intre 1 şi 1024 .

Socket-uri

Socket-ul este o abstractizare a software-ului folosit pentru a reprezenta terminalele unei


conectări între două maşini. Pentru o anumită conectare, există un socket pe fiecare maşină
care poate fi imaginat sub forma unui cablu “virtual” care leagă cele două maşini, fiecare
capăt al cablului fiind introdus într-un socket. In Java, se crează un socket pentru a face
legătura cu un alt calculator, după care se obţine un InputSream şi un OutputStream de la
socket pentru a putea crea o legătura (conectare) sub forma unui Obiect Stream Input /
Output .

Există două tipuri de clase socket bazate pe stream-uri :


 ServerSocket folosit de server pentru a “asculta conectarea”;
 Socket folosit de client pentru a iniţia conectarea.
Odată ce clientul realizează o conectare de tip socket, ServerSocket returnează prin
intermediul metodei accept() Socket-ul corespunzător de pe partea de server, prin
intermediul cărora are loc comunicarea directă. Dupa acest proces, apare într-adevăr o
comunicare Socket la Socket , iar ambele capete sunt tratate la fel, deoarece acestea sunt
similare.

Pentru a obţine pentru fiecare Socket în parte obiectele InputStream şi OutputStream


corespunzătoare se folosesc metodele getInputStream() şi getOutputStream(). Termenul de
ServerSocket poate crea confuzii; s-ar putea spune că mai bine s-ar potrivi denumirea de
“ServerConnector” decât cea de ServerSocket. De asemenea s-ar putea crede că
ServerSocket şi Socket ar trebui să fie moştenite dintr-o clasă comună . Deşi cele doua clase
au cateva metode comune, nu sunt suficiente pentru a proveni din aceeaşi clasă. Sarcina lui
ServerSocket este de a aştepta până când o alta maşină se conectează, pentru ca apoi să
returneze Socket-ul actual.

Denumirea nu este tocmai potrivită, din moment ce nu este un socket ci genereaza un obiect
Socket atunci când cineva se conectează. ServerSocket creaza un “server” fizic sau un socket
ce ascultă pe calculatorul host. Acest socket asculta conectarea, şi returneaza un socket stabilit
prin intermediul metodei accept() . Partea derutantă este că ambele socket-uri, şi cel ce

123
asculta şi cel stabilit sunt asociate cu acelaşi server socket . Socketul ce asculta poate accepta
doar noi cereri de conectare, dar nu şi pachete de date.

Atunci când se crează un ServerSocket, se dă doar numărul portalului fără a fi necesară şi


adresa IP, deoarece se afla deja pe calculatorul pe care-l reprezinta. în schimb, atunci când
cream un Socket, este necesară specificarea adresei IP cat şi a adresei portalului unde
încercăm să ne conectăm. Pe de alta parte, Socket-ul ce se întoarce de la
ServerSocket.accept() conţine deja toate aceste informaţii .

In exempul următor, redăm folosirea cea mai simplistă a socketurilor într-o situaţie client –
server. Serverul aşteaptă conectarea, după care foloseşte Socket-ul produs de conectare
pentru a crea un InputStream şi un OutputStream. Apoi, tot ceea ce citeşte din
InputStream transmite sub forma de ecou în OutputStream până când citeşte linia END,
când închide conectarea. Clientul face conectarea la server, apoi crează un OutputStream .
Liniile textului sunt transmise prin OutputStream. Clientul crează de asemenea un
InputStream pentru a auzi ceea ce “spune“serverul, care în acest caz sunt doar cuvintele
retransmise. Atât clientul cat şi serverul folosesc acelaşi număr de portal, iar clientul foloseşte
adresa de loopback local pentru a se conecta la server, pe aceeaşi maşină, deci nu trebuie
testat pe o reţea particulară.

Exemplul pe partea de server :

// un server foarte simplu care reda doar ecoul a ceea ce transmite clientul
import java.io.* ;
import java.net.* ;
public class JabberServer {
// alegem un portal în afara intervalului 1- 1024
public static final int PORT = 8080 ;
public static void main (String [] args)
throws IOExcepţion {
ServerSocket s = new ServerSocket (PORT) ;
System.out.println(“Started: ” + s ) ;
try {
System.out.println(
“Connection accepted : ” + socket ) ;
BufferedReader în =
new BufferedReader (
new InputStreamReader(
socket.getInputStream() ) ) ;
PrintWriter out =
new PrintWriter (
new BufferedWriter (
new OutputStreamWriter(
socket.getOutputStream( ) ) ) , true ) ;
while (true) {
String str = in.readLine () ;
if (str.equals (“END”) ) break ;
System.out.println (“Echoing: ” + str) ;
out.println(str) ;
}

124
// intodeauna trebuiesc inchise cele 2 socket-uri
} finally {
System.out.println (“closing … ” ) ;
socket.close ();
}
} finally {
s.close() ;
}}}

Explicaţia codului:
Se observă că ServerSocket are nevoie doar de numărul portalului, nu şi de adresa IP din
moment ce rulează pe această maşina. Când se face apel la accept(), metoda se blochează
până când un client încearcă să se conecteze. In realitate este o aşteptare până la conectare,
dar celelalte procese pot rula. Când conectarea are loc, accept() returnează un obiect Socket
ce reprezintă conectarea. Responsabilitatea eliberării socket-urilor este împarţita cu multă
atentie . Astfel dacă constructorul de ServerSocket eşuază, programul iese pur şi simplu.
Trebuie să avem în vedere faptul că un constructor de ServerSocket nu lasă deschise nici un
socket de reţea în cazul în care eşuaza. In acest caz, avem throws IOExcepţion, deci, un bloc
de try nu este necesar. Daca constructorul ServerSocket îşi realizează sarcina, atunci
apelurile la celelalte metode trebuie trecute într-un bloc try–finally, pentru a se asigura că
indiferent cum e părăsit blocul, ServerSocket-ul este închis corect. Aceeasi logică se
foloseste şi pentru Socketul retunat de accept() . Daca accept () eşuaza atunci trebuie să ne
asiguram că Socketul nu mai există sau nu reţine resurse, adică nu e nevoie să fie curăţat.
Daca nu apare nici o problemă, atunci instrucţiunile ce urmează trebuiesc încorporate într-un
bloc try-finally pentru a ne asigura de faptul că dacă una dintre ele eşueaza, vom avea
Socketul curat. Sockeet-urile folosesc resurse din afara memoriei, şi datorită faptului că în
Java nu există destructori, acestea trebuie curatate iar operaţia nu intră în sarcina lui Garbage
Collector.
Atat ServerSocket cat şi Socket-ul produse de accept() sunt afişate în System.out. Aceasta
inseamna că metodele lor toString() sunt automat apelate. Astfel rezultă:
ServerSocket [addr = 0.0.0.0, PORT = 0 , localport = 8080]
Socket [addr = 127.0.0.1,PORT = 1077, localport = 8080]
Părtile urmatoare din program sunt asemanatoare cu deschiderea fişierelor pentru citire şi
scriere cu diferenţa că InputStream şi OutputStream sunt create din obiectul Socket. Atat
obiectele InputStream cat şi OutputStream sunt convertite în obiecte Reader şi Writer
folosind clasele specifice de convertire InputStreamReader şi OutputStreamWriter.

Ori de cate ori se face referire la out, bufferul sau trebuie umplut astfel încăt informaţia să
treca prin reţea. Umplerea este foarte importanta în acest caz deoarece atat serverul cat şi
clientul asteapta o linie de la cealaltă parte, înainte de a conţinua. Daca nu are loc “umplerea”,
informaţia nu va fi trimisa prin reţea până ce bufferul nu se umple, ceea ce duce la apariţia
unor probleme. Atunci când se scriu programe pentru reţea trebuie tratate cu mare atentie
umplerile automate. De fiecare dată când se umple bufferul, un pachet de date este creat şi
trimis.In acest caz, este tocmai ceea ce se doreşte, deoarece dacă pachetul ce conţine linia nu
este trimis, atunci reacţiile dintre server şi client nu mai au loc. Practic, sfârşitul unei linii este
sfârşitul mesajului. Dar în multe cazuri mesajele nu sunt delimitate de linii, de aceea este
avantajos să nu se folosească umplerea automată a bufferului, ci să se lase bufferul construit
să decida de la sine când să formeze şi să trimită pachetul. Asfel pot fi construite pachete mai
mari, iar procesul va fi mai rapid. Bucla while citeşte linii din BufferedReader şi scrie

125
informaţii la System.out şi PrintWriter. out. Când clientul trimite linia ce conţine “END”
programul iese din bucla şi închide Socket-ul.

Exemplul pe partea de server Client:


// Client simplu ce trimite doar linii serverului şi citeste liniile trimise de server
import java.net.* ;
import java.io.* ;
public class JabberClient {
public static void main (String [] args)
throws IOExcepţion {
// prin atribuirea de null to getByName() are loc loopback-ul local
InetAddress addr =
InetAddress.getByName(null) ;
// în mod alternativ se pot folosi adresa sau numele :
// InetAddress addr =
// InetAddress.getByName(“127.0.0.1”) ;
// InetAddress addr =
//InetAddress.getByName(“localhost”) ;
System.out.println(“addr = “ + addr ) ;
Socket socket =
new Socket (addr , JabberServer.PORT) ;
// includem totul într-un bloc try-finallypentru a ne asigura că socketul va fi inchis
try {
System.out.println (“socket = “ + socket);
BufferedReader în =
new BufferedReader {
new InputStreamReader {
socket.getInputSream () ) ) ;
// output este în mod automat umplut de catre PrintWriter
PrintWriter out =
new PrintWriter (
new BufferedWriter (
new OutputStreamWriter (
socket.getOutputStream ( ) )), true) ;
for (int i = 0 ; i < 10 ; i++ ) {
out.println (“howdy ” + i );
String str = in.readLine();
System.out.println (str);
}
out.println(“END”);
} finally {
System.out.println (“closing… ”);
socket.close ();
}
}
}

Explicaţia codului:
In main () se pot observa toate cele trei modalităţi de a produce InetAddress-ul adresei IP
prin loopback local: folosind null, localhost sau adresa explicita 127.0.0.1. Dacă se doreşte

126
conectarea la un calculator prin reţea, se înlocuieşte cu adresa IP a calculatorului respectiv.
Când InetAddress addr este afişata prin intermediul metodei toString() , se obţine: localhost
/ 127.0.0.1. Prin atribuirea lui getByName() a valorii null, se caută automat localhost-ul şi se
obţine adresa speciala 127.0.0.1 Se remarcă faptul că Socketul este creat atat de InetAddress
cat şi de numărul portalului. Pentru a intelege ce inseamna de fapt afisarea unuia dintre aceste
obiecte Socket, să reamintim că o conexiune Internet este determinata în mod unic de
urmatoarele 4 tipuri de date : clientHost, clientPortNumber, serverHost, şi
serverPortNumber.

Pentru că datele să poata migra de la un calculator la altul, de la server la client şi invers,


fiecare parte trebuie să stie unde să trimita datele. De aceea , în timpul conectarii la un server
“cunoscut”, clientul trimite o adresa “de returnare”, astfel incat serverul să stie unde să trimita
datele.
Acest lucru se vede în exemplul prezentat pe partea de server :
Socket [addr = 127.0.0.1, port = 1077, localport = 8080]

ceea ce inseamna că serverul a acceptat conectarea de la 127.0.0.1 pe portalul 1077 în timp ce


“asculta” pe propriul portal local (8080).
Pe partea de client avem :
Socket [addr = localhost / 127.0.0.1 , PORT = 8080 , localport = 1077 ],
ceea ce inseamna că clientul a facut o conectare la 127.0.0.1 pe portalul 8080 , folosind
portalul local 1077.

Odata ce obiectul Socket a fost creat, procesul de a-l transforma în BufferedReader şi


PrintWriter este acelasi că şi la partea de server. Clientul initiaza conversatia prin trimiterea
string-ului “howdy”, urmat de un număr . De remarcat faptul că buffer-ul trebuie să fie
umplut (ceea ce are loc în mod automat datorita celui de-al doilea argument al constructorului
PrintWriter). Daca bufferul nu este umplut, conversatia se va intrerupe , deoarece stringul
“howdy” nu va fi trimis niciodata. Fiecare linie trimisa inapoi de la server este scrisa în
System.out pentru a se verifica daca totul funcţioneaza bine. Pentru terminarea conversatiei
se trimite “END”. Daca clientul intrerupe pur şi simplu conectarea, serverul apeleaza
throwExcepţiopn. Se observă şi în acest caz masurile de siguranţă astfle că Socketul să fie
eliberat prin folosirea unui bloc try – finally. Socket-urile produc o conectare dedicata care
persista pana în momentul în care este intrerupta în mod expicit.

Clienti multipli

Serverul JabberServer funcţioneaza, dar poate servi doar un singur client pe moment. într-un
server tipic se doreste deservirea mai multor clienti în mod concurent. Raspunsul il reprezinta
multithreading-ul , iar în limbajele ce nu suporta multithreading apar o serie de complicatii.
Ideea de baza este de a crea un singur ServerSocket în partea de server şi de a apela accept()
să astepte pentru o noua conectare. când metoda accept() se intoarce, se ia socket-ul rezultat
şi se foloseşte pentru crearea unui nou thread a carui sarcina este de a servi clientul particular.
Apoi se apelează accept () din nou, pentru a astepta un nou client.

Codul pentru partea de server este similar cu codul pentru JabberServer (din exemplul
anterior), cu excepţia că toate operatiile de deservire a unui client au fost mutate în interiorul
unei clase de thread separate.

127
Exemplu:
// server ce foloseste multithreading pentru a face faţă cererilor oricator clienti
import java.io. * ;
import java.net. * ;
class ServeOneJabber extends Thread {
private Socket socket ;
private BufferedReader în ;
private PrintWriter out ;
public ServeOneKubber (Socket s )
throws IOExcepţion {
socket = s ;
in =
new BufferedReader (
new InputStreamReader (
socket.getInputStream () ) ) ;
// nu permitem umplerea automata
out =
new PrintWritwer (
new BufferedWriter (
new OutputStreamWriter (
socket.getOutputStream () ) ), true) ;
// daca oricare din apelarile anterioare produce excepţion
// apelantul este responsabil de inchiderea socket-ului ;
//altfel , thread-ul il va inchide el
start ( ) ; // apeleaza run ( )
}
public void run ( ){
try {
while (true) {
String str = in.readLine ( ) ;
if str.equals (“END”) ) break ;
System.out.println ( “Echoing : ” + str) ;
out.println (str) ;
}
System.out.println ( “Closing…: ” ) ;
} catch (IOExcepţion e) {
} finally {
socket.close ( ) ;
} catch ( IOExcepţion e) { }
}
}
}
public class MultiJabberServer {
static final int PORT = 8080 ;
public static void main (String [] args)
throws IOExcepţion {
ServerSocket s = new ServerSocket (PORT) ;
System.out.println ( “Server Started” ) ;
try {
while ( true ) {

128
// blocam pana la aparitia unei conectari
Socket socket = s.accept ( ) ;
try {
new ServeOneJubber (socket) ;
} catch (IOExcepţion e) {
// daca nu reuseste , inchide socket-ul, daca nu, i-l va inchide thread-ul
socket .clse () ;
}}
} finally {
s.close () ;
}
}
}

Thread-ul din ServeOneJubber ia obiectul Socket produs de accept () în main () de fiecare


data când un nou client se conecteaza. Apoi, creaza un BufferedReader şi un obiect
PrintWriter ce se “umple” singur folosind Socket-ul şi în final face apel la metoda speciala
thread start () ,care face operatie de initializare de thread apoi apeleaza run (). Această
metodă face acceasi operatii că şi în exemplul anterior : citeste ceva din socket şi apoi
transmite ecoul inapoi pana citeste “END”. Trebuie acordata aceeaşi atentie “curatirii” socket-
ului. în acest caz, socketul este creat în afara ServeOneJubber, deci responsabilitatea poate fi
impartita. Daca constructorul ServeOneJubber esueaza , se ajunge la situatia de excepţie , iar
apelantul va curata thread-ul însa, daca constructorul nu intampina probleme, atunci
ServeOneJubber va curata thread-ul în cadrul lui run ( ).

Se observă simplitatea lui MultiJubberServer: un ServerSocket este creat şi se face apel la


accept ( ) pentru o noua conectare. în acest caz valoarea returnata de accept () (un Socket)
este cedat constructorului pentru ServeOneJubber care creaza un nou thread pentru a face faţă
conectarii. Cand conectarea ia sfarsit, thread-ul dispare. Daca crearea lui ServerSocket nu
reuseste, apare iar iesirea de excepţie prin main () . Dar daca crearea reuseste, blocul try –
finally garanteaza curatirea. Try- catch se refera doar la cazul esuarii constructorului
ServeOneJubber , daca constructorul nu are probleme, atunci thread-ul din ServeOneJubber
va inchide socket-ul asociat. Pentru a testa daca serverul face faţă cerintelor mai multor
clienti, umatorul program creaza mai multi clienti (folosind thread-uri), care se conecteaza la
acelas server. Fiecare thread are un ciclu de viata limitat , iar când dispare lasa spatiu pentru
crearea altui thread. Numărul maxim de threaduri permise este determinat de final int
maxthreads. Aceasta valoare este mai mult critica , deoarece daca este prea mare , thread-
urile nu vor avea resurse , iar programul nu va funcţiona.
Exemplu:
// client ce testeaza MultiJubberServer
// prin crearea mai multor clienti
import java.net.* ;
import java.io.* ;
class JabberClientThread extends Thred {
private Socket socket ;
private BufferedReader în ;
private PrintWriter out ;
private static int counter = 0 ;
private int id = counter + + ;
private static int threadcount = 0 ;

129
private static int threadCount ( ) {
return threadcount ;
}
public JubberClientThread (InetAddress addr) {
System.out.println (“Making client ” + id) ;
threadcount + + ;
try {
socket =
new Socket (addr, MultiJubberServer. PORT) ;
} catch (IOExcepţion e) {
// daca crearea socketului esuaza , nu este nevoie de curatire
}
try {
in =
new BufferedReader (
new InputStremReader (
socket.getInputStream ( ) ) ) ;
// scoatem umplerea automata
out =
new PrintWriter (
new BufferedWriter (
new OutputStreamWriter (
socket.getOutputStream ( ) ) ) , true) ;
start ( ) ;
} catch (IOExcepţion e) {
// socketul trebuie inchis în cazul esuarilor, altele decat ale constructorului
try {
socket.close ( ) ;
} catch (IOExcepţion e2 ) {}
}
// altfel socketul va fi inchis de metoda run ( ) a thread-ului
}
public void run ( ) {
try {
for (int i = 0 ; i < 25 ; i++) {
out.println (“Client ” + id + “:” + i) ;
String str = in.readLine ( ) ;
System.out.prinln (str) ;
}
out.println (“”END) ;
} catch (IOExcepţion e ) {
} finally {
try {
socket.close () ;
} catch (IOExcepţion e ) { }
threadcount -- ;
}}
}
public class MultiJubberClient {
static final int MAX_THREADS = 40 ;

130
public static void main (String [] args)
throws IOExcepţion , InterruptedExcepţion {
InetAddress addr =
InetAddress.getByName (null) ;
while (true) {
if (JubberClientThread.threadCount ( ) < MAX_THREADS )
new JubberClientThread (addr) ;
Thread.currentThread ( ). Sleep (100);
}}
}

Constructorul JubberClientThread ia un InetAddress si-l foloseste pentru deschiderea


socket-ului. Din nou, start () initializeaza thread-ul şi apeleaza run (). Aici, mesajele sunt
trimise la server, iar informaţiile de la server apar că un ecou pe monitor. Threadcount (),
contorizeaza numărul obiectelor JubberClientThread existente. Este incrementat că parte a
constructorului, respectiv decrementat când apare run () . în MultiJubberClient numărul
thread-urilor este testat , iar daca sunt prea multe, nu se mai creaza.

BruceEckel- Thinking in Java,pp. 664 www.bruceEckel.com

Datagrams

Transmission Control Protocol (TCP, este cunoscut ca şi stream-based sockets), ce permite


retransmisii de date, furniznd cǎi multiple prin diferite rutere, byte-I fiind livraţi în ordinea
trimisǎ. Protocolul User Datagram Protocol (UDP), garanteazǎ cǎ pachetele vor fi livrate
darn u garenteazǎ ordinea acestora, fiind definit ca “unreliable protocol” (faţǎ de TCP care
este numit “reliable protocol”). In multe aplicaţii ordinea este vitalǎ nu timpul alte aplicatii
solicitǎ în schimb vitezǎ.
Suportul pentru datagram în Java este similar cu socket TCP,cu cateva diferenţe
semnificative. Cu datagrams, se vor crea DatagramSocket pe partea client şi pe partea de
server, dar nu se întlnesc analogii cu ServerSocket ce aşteaptǎ conexiunea, deoarece nu apare
o “conecxiune,” decât datagram. O altǎ diferenţǎ fundamentalǎ fatǎ de socket TCP,odatǎ
realizatǎ conexiunea, pachetele sunt trimise înainte şi înapoi cu fluxuri conventionale. Totuşi
cu datagram, datagram packet trebuie sǎ cunascǎ sursa şi destinaţia pentru fiecare pachet.
DatagramSocket trimite şi primeşte pachete şi DatagramPacket conţine informaţii. Când se
primeşte datagram, se furnizeazǎ un buffer unde sunt plasate datele iar informaţiile despre
adresa Internet şi numǎul de port vor fi automat initializate când pachtetul soseşte cu
DatagramSocket. Constructorul pentru DatagramPacket pentru a to primi datagrams:
DatagramPacket(buf, buf.length)
buf –tablou de byte.
Dimensiunea maximǎ este restricţionatǎ la dimensiunea pachetului, sub 64Kbytes. Când se
Trimit datagram, DatagramPacket trebuie sǎ conţinǎ nu numai date, dar şi adresa Internet şi
numǎul de port iar constructorul pentru DatagramPacket este :
DatagramPacket(buf, length, inetAddress, port)
buf –tablou de byte.
length lungimea buf, sau mai micǎ
inetAddress, port - adresa Internet şi numǎul de port
Astfel se apeleazǎ la 2 constructori ce creazǎ obiecte diferite: unul pentru a primi datagrams şi
altul pentru a trimite.

131
Exemplu:
Crearea de DatagramPacket pentru String
//: Dgram.java
import java.net.*;
public class Dgram {
public static DatagramPacket toDatagram(
String s, InetAddress destIA, int destPort) {
byte[] buf = new byte[s.length() + 1];
s.getBytes(0, s.length(), buf, 0);
return new DatagramPacket(buf, buf.length,
destIA, destPort);
}
public static String toString(DatagramPacket p){
return
new String(p.getData(), 0, p.getLength());
}
} ///:~

Porturile TCP şi UDP sunt considerate unice şi se poate lucra simultan cu TCP şi UDP

Prima metodǎ a Dgram ia String, şi InetAddress, portul şi construieşte DatagramPacket


copiind conţinutul din String în buffer byte şi trimiţând bufferul în constructorul
DatagramPacket. La alocarea bufferului “+1” este necesarǎ pentru a preveni trunchierea.
Metoda getBytes( ) din String este o operaţie ce copiazǎ char din String în buffer byte.
Metoda Dgram.toString( ) foloseşte constructor String.
//: ChatterServer.java
// Un server ce trimite datagrams ecou
import java.net.*;
import java.io.*;
import java.util.*;
public class ChatterServer {
static final int INPORT = 1711;
private byte[] buf = new byte[1000];
private DatagramPacket dp =
new DatagramPacket(buf, buf.length);
// Can listen & send on the same socket:
private DatagramSocket socket;
public ChatterServer() {
try {
socket = new DatagramSocket(INPORT);
System.out.println("Server started");
while(true) {
// Block pânǎ la datagram apare :
socket.receive(dp);
String rcvd = Dgram.toString(dp) +
", from address: " + dp.getAddress() +
", port: " + dp.getPort();
System.out.println(rcvd);
String echoString =
"Echoed: " + rcvd;

132
// Extrage address si port din datagram primit si trimis ca ecou:
DatagramPacket echo =
Dgram.toDatagram(echoString,
dp.getAddress(), dp.getPort());
socket.send(echo);
}
} catch(SocketException e) {
System.err.println("Can't open socket");
System.exit(1);
} catch(IOException e) {
System.err.println("Communication error");
e.printStackTrace();
}
}
public static void main(String[] args) {
new ChatterServer();
}
} ///:~
ChatterServer conţine un singur DatagramSocket pentru primirea mesajelor, în loc sǎ
creeze câte unul de fiecare datǎ când primeşte un nou mesaj. Acest DatagramSocket este
utilizat repetat, are numǎr de port deoarece acest server şi clientul trebuie sǎ aibǎ adresǎ
exactǎ când doresc sǎ trimitǎ datagram. Se dǎ numǎr de port şi nu adresǎ Internet deoarece el
rezidǎ me acea maşinǎ pentru care se cunoaşte adresa Internet (default localhost). In bucla
infinitǎ while loop, socket-ul anunţǎ receive( ), când apar blockurile pânǎ cǎnd datagram
ajunge la receptorul destinaţie DatagramPacket dp. Pachetul este convertit în String
împreunǎ cu along with informaţii despre adresa Internet şi socket de unde vine pachetul
(destinaţia acestuia). Aceste informaţii sunt afişate apoi un extra string este adǎugat pentru a
indicate faptul cǎ începe ecoul înapoi de la server. In aceastǎ demonstraţie datagram rpimite
şi trimise rezidǎ pe localhost, dar numǎrul de port pentru fiecare client este diferit . pentru a
trimite un mesaj înapoi la clientul original este necesar sǎ se cunoascǎ adresa Internet şi
numǎrul de port al clientului, informaţii conţinute în DatagramPacket care au fost trimise
odatǎ cu mesajul, astfel încât pot fi obţinute/extrase cu getAddress( ) and getPort( ), care
sunt utilizate pentru a construi DatagramPacket echo, trimise înapoi la acelaşi socket ce a
fǎcut recepţia. In plus, când socketul trimite datagram, adaugǎ automat adresa Internet şi
numǎrul de port al maşinii clientului, deci când se recepţioneazǎ mesajul, se poate folosi
getAddress( ) şi getPort( ) pentru a afla de unde vine datagram.
//: ChatterClient.java
// ChatterServer cu clienti multipli, fiecare cu datagram proprii.
import java.lang.Thread;
import java.net.*;
import java.io.*;
public class ChatterClient extends Thread {
// Can listen & send on the same socket:
private DatagramSocket s;
private InetAddress hostAddress;
private byte[] buf = new byte[1000];
private DatagramPacket dp =
new DatagramPacket(buf, buf.length);
private int id;
public ChatterClient(int identifier) {

133
id = identifier;
try {
// Auto-assign port number:
s = new DatagramSocket();
hostAddress =
InetAddress.getByName("localhost");
} catch(UnknownHostException e) {
System.err.println("Cannot find host");
System.exit(1);
} catch(SocketException e) {
System.err.println("Can't open socket");
e.printStackTrace();
System.exit(1);
}
System.out.println("ChatterClient starting");
}
public void run() {
try {
for(int i = 0; i < 25; i++) {
String outMessage = "Client #" +
id + ", message #" + i;
// Make and send a datagram:
s.send(Dgram.toDatagram(outMessage, hostAddress, ChatterServer.INPORT));
// Block until it echoes back:
s.receive(dp);
// Print out the echoed contents:
String rcvd = "Client #" + id +
", rcvd from " +
dp.getAddress() + ", " +
dp.getPort() + ": " +
Dgram.toString(dp);
System.out.println(rcvd);
}
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++)
new ChatterClient(i).start();
}
} ///:~
ChatterClient este creat ca Thread deci pot fi multipli clienţi pe server, DatagramPacket
primit aratǎ similar cu cel pentru ChatterServer. Constructortl DatagramSocket este creat
fǎrǎ argumente pentru cǎ nu dǎ un numǎr particular de port. Adresa Internet este “this
machine” (localhost) numǎr particular de port este automat atribuit ca output.
DatagramSocket, unic pentru server, va fi folosit la primirea /trimiterea datagram,
hostAddress este adresa Internet a maşinii host parte a DatagramPacket. Intotdeauna hostul
are cunoscutǎ adresa şi portul pentru clienţii ce iniţiazǎ conversaţii cu host, numǎr de

134
identificare unic (la fel ca şi portul, atribuit automatla firul ce furnizeazǎ identificatro unic). In
run( ), este creat mesaj String ce conţine numǎrul de identificare al firului şi numǎrul
mesajului de pe firul current. Acest String este folosit la crearea datagram trimisǎ la adresa
hostului; portul este luat direc ca şi constantǎ în ChatterServer. Odatǎ trimis mesajul este
receive( ) replicat pe server.

7.2. Exemplu de aplicaţie client-server

Aceste exemplu demonstrează modul de creare a unei aplicaţii client-server în Java, folosind
obiectul Socket furnizat de pachetul java.net . Pentru a realiza exemplul este necesar accesul
la site-ul şi serverul HTTP pe care rulează programul.
Rolul lui este de a ţine evidenţa numărului d epersoane ce utilizează Java enable browser
pentru a vizita pagina. Dezavantajul acestui program este faptul ca ţine numai urma traficului
disponibil java. Clientul java este un applet şi numai un browser enable Java îl poate executa.
Avantajul programului este că poate fi rulat chiar şi atunci când nu se pot rula programe CGI.
Pentru a rula programe CGI administratorul sistem trebuie să precizeze unul sau mai multe
directoare în care sunt rezidente programe CGI şi să garanteze accesul al aceste directoare. Alt
avantaj este faptul că dacă clientul reîncarcă pagina Web, counterul nu va fi incrementat.
Construit pe model client-server, sunt necesare 2 programe: unul client (appletul Java) şi unul
server (aplicaţie de sine stătătoare) [Naugh_96]
CounterServer.java

import java.io.RandomAccessFile;
import java.io.Print Stream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

//programul server
public class CounterServer {
Socket clientSock;
// fisier de memorat in contul curent
String counterFileName;
//numar de port
int ourPort;

public static void main (String argv[ ]){


try {
// se ia argument fisier si numar de port
String givenName=argv[0 ];
int givenPort=Integer.parseInt(argv[1]);
//instantiere CounterServer
CounterServer cs=new CounterServer(givenName,givenPort)
// se activează ca server
cs.takeRequest();

}catch (ArrayIndexOutOfBoundsExceptions e){


//nu sunt destule argumente
System.out.println("Utilizati Counter Server" + "counterFile

135
portNumber");

}catch (NumberFormatException e){


//numar de port #intreg
System.out.println(argv[1]+"invalid");

}catch (java.io.IOException e){


//eroare la server
e.PrintStackTrace();
}
}

//counstruirea unui nou CounterServer

public CounterServer(String givenName, int givenPort);


counterFile Name=givenname;
this.ourPort=givenPort;
}
//creare socket si asteptarea cererii
//start ascultarea

while (true) {
//blocaj pana s eprimeste cererea
client sock=serverSock.accept();

//incrementeaza counterul in fisierul counter


update Counter();
}
}

//metoda citeste contorul din fisier, il incrementeaza si il rescrie


private syncronized void update Counter() trows IOException {
IOException {
PrintStream ps;
int counter;

//deschide fisierul pt. Acces aleator ce permite citire/scriere


File(counterFile Name, "rw")

If (counterFile.length()==0){
//fisier inexistent
//scrie 1 in fisier
counterFile.writeBytes("1");

}else{
//citeste fisierul intr-un String
String line= counterFile.readLine();
try {
//incrementeaza counter
counter = 1+Integer.parseInt(line);

136
//repune pointer la inceput de fisier
counterFile.seek(0);

//rescrie valoarea anterioara cu alta noua


counterFile.writeBytes(String.valueOf(counter));
//Conectate InputStream al Socket cu printScreen ca sa se tipareasca
//datele in Socket

ps=new PrintStrem(client.Sock.getOutputStream());
// tipareste counterul in Socket (pentru a-l regasi clientul
ps.print(counter);
}catch(NumberFormatException e) {
//data in vcounter nu este sir intreg
System.out.println ("Sir necoresp."+counterFileName);
System.exit (-1);

}finally{
//se inchide socket
try {
client Sock.close();
}catch (IOException alreadyClosed) {
}
}
}
}
}
}

Explicaţia codului
Serverul lucrează prin memorarea unui counter în fişier, dar de fapt memorează întregul ca şir
de caractere(decimal digits) unde fiecare caracter este un bit in fişier. Motivul reprezentării
decimal digits este simplitatea faţă de reprezentarea binară a întregului. In acest mod, clientul
paote citi fişierul fără a scrie un program de citire. Când serverul ia cererea de la client, el
citeşte date în fişier, incrementează counter, memorează noua valoare înapoi în fişier şi
transmite valoarea la client. Apoi se reîntoarce pentru a asculta o nouă cerere.
Sunt 4 metode în clasă:
 main() care ia parametri din linia de comandă, indicând numele utilizat pentru
fişier şi portul ce va fia scultat. Apoi instanţiază obiectul din clasa CounterServer() şi
dispune să ia cererea takeRequest()
 CounterServer(0 constructor pentru clasă
 takeRequest() simpla aşteptare a cererii, care apelează updatecounter() când
funcţionează şi apoi sare înapoi pentru a aştepta noua cerere.
 updateCounter() este de fapt prelucrarea de servicii pentru serverul pentru care
este creat. El citeşte datele din fişier, actualizează counterul şi trimite informaţiile
înapoi la client.
Metoda main() are o structură caracteristică de manipulare a excepţiilor. In loc să plaseze
fiecare metodă ce poate cauza excepţii într-un bloc try.catch, utilizează un singur bloc
try.catch şi prinde toate excepţiile individuale la sfârşit.Aceasta face codul mai uşor de citit şi
se orientează spre excepţiile dorite de CounterServer(). Utilizăm un constructor pentru

137
obiectul din clasa CounterServer, fiind necesar pentru a aputea accesa variabilel de instanţă şi
pentru a apela metodele de instanţă.
Metoda takeRequest() este o metodă server clasică. Programul server trebuie să acceseze un
obiect din clasa java.net.Socket, legat rapid într-un port specificat. Acest bloc aşteaptă
conexiunea şi chiar dacă aparent este o buclă infinită nu se monopolizează procesorul datorită
metodei ServerSocket.accept() Când spunem că metoda blochează până la primirea
conexiunii, de fapt aşteaptă până este trimis un mesaj de client. Acest mesaj activează CPU
pentru a procesa, iar în realiteate takeRequest itereaza o dată pe proces.Când se prezintă o
cerere, ServerSocket.accept() instanţiază şi întoarce un nou socket client (din casa Socket) ce
va fi utilizat pentru comunicaţia ulterioară cu clientul.
Sunt 2 tipiuri de socketuri utilizate ServerSocket , pentru acceptarea conexiunii şi care nu are
asociate InputStream şi OutputStream, pentru că nu există conexiuni în acest loc. Aceasta are
loc după ce conexiunea este cerută de client, când obiectul Socket full-fledget (complet
maturizat) este construit şi comunicaţia este reală.
Metoda updateCounter() este declarată sincronizată din cauză că poate determina
inconsistenţă dacă mai mulţi clienţi cer actualizarea counterului în acelaşi timp.
Ca regulă generală se recomandă metodă/bloc syncronized de oricâte ori de încearcă
citirea/scrierea într-un fişier şi din aceste motive este şi private.[Orfa_00]
RandomAccesFile este un obiect ce manipulează fişiere I/O dat fiind faptul că se doreşte
abilitate la citire/scriere în acest fişier. Putem utiliza DataInputStream pentru citire şi
PrintStream pentru scrierea în fişier. Citirea unui şir din RandomAccesFile este similară cu
readLine(). Deoarece nu cunoaştem cu cât creşte dimensiunea şirului se preferă scrierea
întregului conţinut al fişierului peste vechea valoare semnificativă. Astfel se crează un nou
obiect PrintStrem cu grijă pentru a nu-l pierde, pasând socket-ului client OutputStream în
constructorul PrintStream.
Observăm că este util de conceput un socket ca fişier în care server-ul şi client-ul au acces iar
socket-ului client are un OutputStream cu care poate scrie, trimiţând date către client. Un
obiect din clasa PrintStream este de apt un Stream(flux) ce poate scrie cu uşurinţă text.
Legând PrintStream cu ieşirea socket-ului facilităm "tipărirea" textului în socket-ul clientului.
Construind un PrintStream pentru socket-ul OutputStrem, va trimite date prin reţea ca simplă
tipărire la consolă.Apoi creăm programul client care este standalone.Blocul finally din
updateCounter() se asigură că socket-ul este închis iar dacă deja operaţia este realizată, nu
execută nimic.
Exemplu:
CounterClient.java
import java.io.applet.Applet;
import java.io.DataInput Stream;
import java.io.IOException;
import java.net.Socket;

//programul client
public class CounterClient extends Applet {
int counter;

public void init() {


try {
// ia DNS la host-ului
String host=getCodeBase().getHost();

//porul specificat in parametru tag

138
int port=Integer.parseInt(getParameter("port"));

//deschide socket si il leaga de portul unde asculta serverul


Socket s= new Socket(host,port);

//ia intrare din socket si o leaga de fluxul de date de intrare


DataInputStream inStream =new DataInputStream (s.getInputStream));
Srting Line=inStream.headLine();
Counter=Integer.parseInt(line);
}catch (IOException e) {
showSatus("Nu se poate citi din socket");
e.printStackTrace();

}finally{
//inchide socket
try{
s.close();
}catch (IOException allreadyClosed) {
//deja inchis
}
}
}
public void paint (Graphics g){
//tipareste conteinerul
g.drawString(Integer.toString(counter),5,size(),height);
}
}

Explicaţia codului
Appletul client ia DNS din host şi îl utilizează pentru a crea socket cu care va comunica cu
serverul. Crearea unui socket dă serverului posibilitatea preluării serviciilor. Clientul primeşte
ieşirile serverului şi le afişează în fereastra appletului, după care închide socketul.Din motive
de securiatte nu poate deschide un socket al host-ului în acre appletul este încărcat, motiv
pentru care appletul nu trebuie aşezat în acelaşi host ca şi fişierul HTML care îl referă. Apelul
getCodeBase().getHost() dă numele host-ului în care este încărcat fişierul .class. Deoarece
deschidem un socket pe acest host, pentru a face conexiune proprie, aplicaţia server trebuie
pusă pe acelaşi host ca şi fişierul .class.

139