1.1 Elementele de baza ale limbajului Java
Constante
In limbajul Java
exista constante : numerice, booleene,
caracter si sir de caractere.
Constantele numerice sunt intregi si reale.
Constantele intregi se pot reprezenta in bazele :
zece : 5, 3159, -76,
opt :
0235, -0741, 0614 (incep cu cifra 0),
saisprezece : 0x123, 0x2b5, 0xf123 (incep cu prefixul 0x).
Implicit, tipul
constantelor intregi este int. Daca constanta este urmata de litera l sau L
tipul va fi long.
Constantele reale : 1.2, 1.2f, 1.2d, 2., 1.1e5,
2.3e-6 . Sunt valori de tip double daca sunt urmate de litera d sau D si de tip
float daca sunt urmate de litera f sau F. Daca nu se specifica explicit tipul,
atunci se considera ca valoarea este de tip double.
Constantele booleene : reprezinta
valorile logice de adevarat si fals : true, false.
Constantele caracter : sunt caractere intre
ghilimele simple, ex. ‘a’, ‘tn’ sau ca o notatie ‘tuxxx’, ex. ‘tu13f2’.
Constantele sir de caractere sunt siruri de
caractere scrise intre doua duble ghilimele : : “acesta este un sir de
caractere”, “tnsir de caractere precedat de ENTER”.
Variabile
Limbajul Java are trei tipuri de variabile : variabile instanta sau variabile c`mp si variabile locale. Primul
tip va fi prezentat odata cu notiunile de clasa si obiect (Sectiunea 1.2.1).
Variabilele locale sunt declarate in interiorul blocurilor de instructiuni (mai
multe instructiuni Java cuprinse intre parantezele {}). O variabila locala este vizibila doar in interiorul blocului in care
a fost declarata. Declaratia unei variabile are sintaxa :
<tip> <nume_variabila>a=valoare_initialas;
double pi;
sau
double pi=3.1415;
In declaratia unei variabile pot fi utilizati si modificatorii de acces, Acestia vor fi
prezentati in Sectiunea 1.2.3. Totusi, facem precizarea ca, in cazul
variabilelor locale, este permis doar modificatorul final.
1.1.2 Operatori
Operatorii aritmetici
: +, -, *, /, % (modulo, doar pentru tipurile intregi) sunt binari. Operatorii
+ si - pot fi si operatori semn. Operatorul + este folosit si pentru
concatenarea a doua siruri de caractere.
Operatori de incrementare ++ si decrementare
-- sunt unari.
int x=1;
x++ Ű x=x+1 Ţ x=2; x-- Ű x=x-1 Ţ x=0
Operatori logici
: && (si), || (sau) care sunt binari si operatorul ! (negatie) care
este unar. Au operanzi booleeni.
Operatori pe biti
: & (si), | (sau), ^ (sau exclusiv) care sunt binari si ~ (negatie) care este unar. Alti operatori
pe biti sunt si >> (deplasare dreapata), << (deplasare
st`nga), >>> (deplasare dreapta fara extensie de semn). Au
operanzi intregi.
Operatorii relationali
: >, >=, <, <=, == (egalitate), != (diferit). Rezultatul unei
expresii relationale este de tip booleean.
Operatori de atribuire
combinati : +=, -=, *=, /=, &=, |=, ^=, %=, <<=, >>=,
>>>=.
float x,a;
int i;
x+=a Ű
x=x+a; i<<=2 Ű
i=i<<2
Operatorul de decizie
(?:) este ternar. O expresie cu acest operator are forma :
expresie1
? expresie2: expresie3.
Expresie1
este de tip booleean. Daca aceasta este adevarata atunci intreaga expresie
capata valoarea expresie2 altfel ia valoarea lui expresie3.
int
min, a,b;
min=(a<b)
? a:b;
Operatorul instanceof, care este binar, intoarce true
daca obiectul din st`nga lui este o instanta a clasei sau interfetei specificate in dreapta si false
in caz contrar.
1.1.3 Tipuri de
date
In limbajul Java exista doua categorii de tipuri de date
: tipuri primitive si tipul referinta (cu adresa).
Tipurile primitive
sunt :
·
aritmetice :
·
intregi : byte (1 octet), short (2 octeti), int (4 octeti), long (8 octeti)
sunt intregi cu semn,
·
reale : float (4 octeti), double (8 octeti),
·
caracter : char (2 octeti) asimilat cu intreg fara semn cu valori intre ‘tu000’
si ‘tufff’,
·
logic : boolean (1 octet) : true si false.
Limbajul Java poseda pentru fiecare tip primitiv si c`te
o clasa ale carei instante reprezinta obiecte similare cu valorile din tipul
primitiv respectiv. Aceste clase sunt : Byte, Short, Integer, Long, Float,
Double, Character, Boolean.
Observatie : In limbajul Java
caracterele sunt codificate pe 16 biti in codul Unicode. Setul Unicode
este un superset al setului ASCII,
fiecare caracter (octet) ASCII fiind
completat, in acest caz, cu un octet semnificativ cu valoarea 0 (neinterpretat
de masini care vad doar ASCII).
Aceasta codificare se rasfr`nge, evident, si asupra sirurilor de caractere.
Pentru sirurile de caractere (care sunt
privite ca tablouri de caractere reprezentate in Unicode) exista clasa String in care sunt descrise toate operatiile
posibile cu siruri de caractere.
Intre toate tipurile aritmetice se pot face conversii cu
extensia zonei de memorie folosita pentru reprezentare sau micsorarea acesteia
(caz in care pot apre truncheri ale valorilor), dupa caz. Tipul char poate fi
convertit la unul dintre tipurile intregi sau reale. Limbajul admite si
conversii de tip casting intre tipuri compatibile. Prin casting se intelege atribuirea valorii unei expresii de un
anumit tip catre o variabila de alt tip. Sintaxa este
tip_data identificator=(tip_data)expresie;
Iata c`teva exemple.
int
i,j, a=10;
float
x=5.77,y=90.3;
……
(double)(i+j)
are tipul double,
(int)(x-y) are tipul int.
a=(int)y Ţ
a=90 ; x=a Ţ
x=10.0
Modul de executie al operatiilor aritmetice este cel
obisnuit. Se impune o observatie asupra operatorului de impartire. Rezultatul
unei impartiri este intreg sau real dupa tipul operanzilor.
int j;
float x;
j=1/2 Ţ
j=0; j=6/3 Ţ
j=2; j=9/4 Ţ
j=2; x=9./2 Ţ
x=4.5
Impartirea
cu zero, in cazul operanzilor de tip
intreg, provoaca o exceptie de tip ArithmeticException. In cazul tipurilor
reale impartirea cu zero nu provoaca o exceptie deoarece pentru aceste tipuri
de date exista constantele : POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN (Not a
Number) pe care le pot lua valorile expresiilor in astfel de cazuri.
La evaluarea expresiilor cu operatori aritmetici binari
se utilizeaza promovarea la tipul imediat superior in cazul operanzilor de
tipuri numerice diferite. Astfel,
·
daca unul din operanzi este de tip double atunci celalalt este convertit la
tipul double,
·
altfel daca unul din operanzi este de tip float celalalt este convertit la
tipul float,
·
altfel daca unul dintre operanzi este de tip long celalalt este convertit la
tipul long,
·
altfel ambii operanzi sunt convertiti la tipul int.
Tipurile de date referinta
sunt obiectele (instantele claselor) si tablourile. In Java, tipul referinta
este singura modalitate de accesare a obiectelor. In cazul acestor tipuri,
compilatorul pune adresa unui obiect sau tablou intr-o variabila. Astfel este
posibil ca doua variabile diferite sa refere acelasi obiect. Spre deosebire de
tipurile referinta, tipurile primitive sunt manevrate prin valoare. Pentru
variabilele de tip referinta alocarea spatiului de memorie se face dinamic prin
ordinul new. Dupa ce variabilele referinta nu mai sunt utilizate spatiul alocat
lor este eliberat prin mecanismul “garbage collection”.
Button a,b,c=new Button();
int i,j;
a=new Button();
b=a;
c.setLabel(“Start”);
a.setLabel(“Gata”);
String s1=a.getLabel(),s2=b.getLabel();
String s3=c.getLabel();
i=7; j=i; i=3;
String-urile s1 si s2 au valoarea “Gata” pe c`nd s3 are
valoarea “Start”. Variabilele i si j au valorile i=3, j=7.
1.1.3 Tablouri
Principiile enuntate mai sus sunt
valabile la tipul tablou deoarece acesta este un tip referinta. Sintaxa de
declarare a unui tablou este
tip_date nume_tablouas = new
tip_dateanumar_elementes;
Crearea
unui tablou se face in doua feluri :
1. prin
specificarea unei dimensiuni maxime :
byte
bas=new bytea10s;
int
aasas=new inta2sa3s;
float
xasas=new floata3sas;
String
myStringas=Stringa5s;
2. prin
initializare statica :
int
tablouas={1,2,3,4,5,6};
Dupa
cum se vede se pot declara si tablouri a caror elemente sun instante ale unor
clase (cazul tabloului myString). Numarul elementelor unui tablou este precizat
de c`mpul component length. Spre exemplu avem : b.length=10, aa1s.length=3.
1.1.4 Instructiuni
Marea majoritate a instructiunilor limbajului Java sunt
preluate din limbajul C respect`nd sintaxa si semantica acestora. Deosebirea
majora se face la instructiunile care necesita expresii conditionale. In Java
tipul acestora este booleean spre deosebire de C unde orice valoare numerica
diferita de zero este asociata cu true iar valoarea zero cu false. Vom
exemplifica c`teva dintre principale instructiuni.
Instructiunea de decizie if…else.
if (test==true)
Instructiune
1;
// daca
expresia test este adevarata
else{
Instructiune
2;
……
Instructiune
k;
// daca
test este falsa
}
Urmatorul cod exemplifica instructiunea de selectie
switch. Foloseste intregul count pentru a realiza testul si implicit selectia.
switch (count){
case 1 :
Instructiune 1;
break;
case 2 :
Instructiune 2;
break;
case 3 :
Instructiune 3;
Instructiune 4;
Instructiune 5;
break;
default
:Instructiune 6;
}
Exista trei instructiuni de ciclare. Instructiunea for
este structurata astfel
for(init; test; post_test)
Instructiune;
Expresia init este una de initializare. Poate fi si o
expresie complexa cu mai multe expresii despartite prin virgula (desi
operatorul virgula nu exista in Java). Expresia test are tipul boolean; c`t
timp acesta este adevarata se executa Instructiune (care poate fi si compusa).
Evaluarea lui test se face inaintea executiei lui Instructiune. Expresia post_test este cea in care se fac
modificari asupra variabilelor ce dau valoarea expresiei test. Se fac, spre
exemplu, incrementari sau/si decrementari.
for(int k=0; k<10; k++)
System.out.println(“Valoarea lui k este ”+k);
………
int aas=new inta20s;
int t;
for(int i=0,j=19; i<j; i++,j--){
t=aais;aais=aajs;aajs=t;
}
Instructiunea de ciclare while
poseda doar partea de test din for.
while (test)
Instructiune;
test se evalueaza inainte de executia lui Instructiune.
Spre exemplu, o parte din codul de mai sus se poate rescrie cu while astfel
int aas=new inta20s;
int
i=0,j=19,t;
while(i<j){
t=aais;aais=aajs;aajs=t;
i++;j--;
}
Instructiunea do…while executa testul la sf`rsit.
do{
Instructiune;
}while (test);
Un exemplu de folosire este urmatorul
int aas=new inta20s;
int r,t;
do{
r=0;
for(int i=0;
i<19; i++)
if
(aais>aai+1s){
t=aais;aais=aai+1s;aai+1s=t;
r++;
}
}while (r!=0);
Au fost extinse fata de limbajul C instructiunile break
si continue. Ele permit parasire, respectiv reiterarea celui mai interior ciclu
dar pot avea etichete. Este deci posibila o secventa de forma
test : if (…){
for…
……
break test;
……
}
In
plus fata de limbajul C s-au introdus c`teva instructiuni. Astfel,
sincronized (expresie) Instructiune;
Ea asteapta accesul exclusiv la obiectul sau tabloul
specificat prin expresie. In momentul c`nd a obtinutaccesul exclusiv, executa
Instructiune (care poate fi si compusa). Cu acesta instructiune se executa
sectiunile critice din contextele multithreading.
Instructiunile package si import vor fi tratate mai
t`rziu.
1.1.5 Exceptii si
manipularea lor
O exceptie este
un semnal prin care se indica faptul ca a aparut o situatie exceptionala : o
eroare de intrare/iesire, o depasire de indice etc. Exceptiile sunt de doua
feluri : exceptii propriu-zise si erori. Din prima categorie amintim :
depasirile de indice, tentativa de citire dupa int`lnirea sf`rsitului de
fisier, adresa host necunoscuta etc. Din categoria erorilor amintim : adresarea
in afara memoriei, depasirea de stiva etc.
Java defineste pentru erori si exceptii clasa
java.lang.Throwable cu doua subclase java.lang.Exception si java.lang.Error.
Pentru fiecare se instantiaza un obiect al uneia dintre cele doua subclase.
Fiecare obiect contine, printre altele, un string ce are ca valoare mesajul
explicativ al exceptiei respective.
Manevrarea exceptiilor se face cu ajutorul
instructiunilor try, catch, finally.
Informeaza
ca in acest bloc este posibila aparitia unor situatii neobisnuite.
}
catch (Exceptie_1 E1){
Trateaza
aparitia Exceptie_1. Obiectul E1 este generat de catre aceasta exceptie si este
utilizat pentru tratarea situatiei.
}
………
catch (Exceptie_n En){
Trateaza
Exceptie_n.
}
finally{
Acest
bloc se executa intotdeauna, indiferent de modul de parasire a blocului try.
Parasirea se poate face :
-
normal, la atingerea sf`rsitului de bloc try,
-
datorita unei instructiuni break, continue sau return
aparuta in blocul try,
-
datorita unei exceptii tratata prin catch,
-
datorita unei exceptii netratate prin catch.
}
Java cere ca orice metoda care ar putea provoca aparitia
unei exceptii fie sa trateze aceasta exceptie prin try si catch fie sa o anunte
printr-o declaratie throws in prototip ca in exemplul de mai jos.
public void metoda1() throws IOException{
………
}
public void metoda2() throws Exceptie_1, Exceptie_2 {
………
}
1.2
Orientarea
pe obiecte a limbajului Java
1.2.1 Clase si
obiecte
Un program Java este format din una sau mai multe clase.
O clasa este sablonul care descrie obiecte
de un anumit tip, preciz`nd structura si functionalitatea acestora. Declararea
unei clase este similara cu declararea unui nou tip de date. Obiectele se mai
numesc si instante ale unei clase
deoarece sunt variabile referinta declarate de tipul unei clase. Practic, un
obiect apartin`nd unei clase este o materializare a entitatii descrisa de
clasa.
O clasa consta din variabile
membre si functii membre sau metode. Sintaxa de definire a unei clase
este
class nume_clasa
aextendsnume_supraclasas aimplements nume_interfatas{
adeclaratii_variabile;s
adeclaratii_metode;s
}
Iata
un prim exemplu,
import java.io.*;
public class Ex1
{
public
int val=0;
public
void setVal(int a){
val=a;
}
public
int getVal(){
return val;
}
public
int getHalf(){
int h=val/2;
return h;
}
public
static void main (Stringas args)
{
Ex1 e=new Ex1();
System.out.println("Valoarea
variabilei este "+e.val);
e.setVal(15);
System.out.println("Valoarea
variabilei este "+e.getVal());
System.out.println("Semivaloarea
variabilei este "+e.getHalf());
}
}
Acest exemplu (program) consta dintr-o singura clasa iar
aceasta consta dintr-o singura variabila, val si trei metode setVal(),getVal(),getHalf().
Sa observam prezenta a inca unei metode, anume metoda main() al carei rol il
vom evidentia mai jos. Variabila val este o variabila instanta sau variabila
c`mp (definita in interiorul clasei). Ea corespunde unei instante a clasei, in
cazul de fata obiectul e. Capata loc in memorie si dispare odata cu obiectul.
Variabila h din interiorul metodei getHalf() este o variabila locala.
Metodele clasei se definesc dupa sintaxa
<tip_returnat>
nume_functie(a<lista_parametri>s){
adeclaratii_locale;s
lista_instructiuni;
}
Sa notam ca in linia de declaratie a metodei se poate
folosi si unul dintre modificatorii de acces (Sectiunea 1.2.3) pozitionat
inaintea tipului returnat. Daca metoda nu returneaza nicio valoare (cazul
procedurilor din alte limbaje) se foloseste cuv`ntul cheie void. Tipul metodei
precum si tipul, numarul si ordinea parametrilor poarta numele de “semnatura
metodei” (sau prototipul ei). Metodele clasei sunt apelate doar prin
intermediul unui obiect, aici obiectul e.
Exista si o exceptie de la aceasta regula, excepte care va fi prezentata ulterior.
Instantele
claselor se declara ca variabile referinta; spatiul de memorie necesar unei
instante de clasa se rezerva printr-o declaratie cu cuv`ntul cheie new. O
declaratie simpla
Ex1 e1;
creaza o variabila care
poate contine o referinta la obiecte de tip Ex1, fara sa creeze un obiect
instanta pentru clasa Ex1.
Punctul de intrare in orice aplicatie Java este metoda
main() care trebuie sa se gaseasca in una (si numai una) dintre clasele
aplicatiei. Compilatorul Java cauta aceasta metoda si executa instructiunile
din cadrul acesteia. Prototipul (amprenta) functiei main() este tot timpul
acelasi, anume cel precizat in exemplul de mai sus.
1.2.2 Constructori
In Exemplul 1 se
observa ca variabila membra val a obiectului e are initial valoarea 0,
corespunzatoare declaratiei din cadrul clasei. Valoarea acestei variabile poate
fi modificata prin metoda setVal(). Limbajul Java permite o alta modalitate de
initializare a variabilelor membre ale unui obiect inca din momentul rezervarii
spatiului in memorie. Aceasta se realizeaza prin intermediul constructorilor. Acestia sunt metode
speciale ale unei clase care sunt apelate implicit in momentul instantierii
unui obiect al clasei. Constructorii au sintaxa obisnuita a unei metode cu
deosebirea ca nu au tip iar numele lor este acelasi cu cel al clasei.
import java.io.*;
public class Persoana
{
private
String nume;
private
int virsta;
public
Persoana(){
nume=”fara nume”;
virsta=0;
}
public
Persoana(String nume, int virsta){
this.nume=nume;
this.virsta=virsta;
}
public
String getNume(){
return nume;
}
public
int getVirsta(){
return virsta;
}
public
void list(){
System.out.println(this.getNume()+” are virsta
“+this.getVirsta());
}
public
static void main (Stringas args)
{
Persoana pers1,pers2;
pers1=new Persoana();
pers2=new Persoana(“Ionescu”,35);
pers1.list();
System.out.print("A doua
persoana este "+pers2.getNume());
System.out.println(“ si are virsta
”+pers2.getVirsta());
}
}
Datorita facilitatilor de suprascriere a metodelor
(Sectiunea 1.2.4), in cadrul aceleiasi clase se pot scrie mai multi
constructori care se deosebesc prin semnatura. In exemplul de mai sus, la
instantierea obiectului pers1 se apeleaza primul constructor astfel ca prima
persoana nu va avea nume iar v`rsata va fi 0. La instantierea lui pers2 se
apeleaza al doilea constructor deci, a doua persoana va avea numele “Ionescu ”
si v`rsta 35.
In cadrul celui de-al doilea constructor se observa
utilizarea identificatorului this. Acesta specifica obiectul curent. La
instantierea obiectului pers2, this il va indica pe acesta, deci c`mpurile
this.nume si this.virsta vor fi ale lui pers2. Prin intermediul lui this se
poate face distinctia intre parametrii metodei si c`mpurile variabile ale
obiectului, in cazul c`nd au acelasi nume.
Prin intermediul lui this se poat apela metodele claseu,
inclusiv constructorii. Astfel, primul constructor al clasei Persoana poate fi
rescris utiliz`nd pe cel de-al doilea.
public Persoana(){
this(“fara nume”,0);
}
In cazul lipsei constructorilor intr-o clasa (vezi
Exemplul 1) se apeleaza constructorul supraclasei (Sectiunea 1.2.5).
1.2.3 Modificatori
de acces
Java foloseste modificatorii
de acces pentru a specifica nivelul de vizibilitate al variabilelor si
metodelor in raport cu alte clase. Java are patru nivele de acces : public,
private, protected si package. Ultimul necesita o prezentare separata.
Modificatorul public indica faptul ca variabila sau
metoda este vizibila din interiorul oricarei alte clase sau metode. Este cazul
variabilei c`mp val din clasa Ex1 (Exemplul 1). Variabila val a obiectului e
din functia main() poate fi accesata sub forma e.val. Daca am adauga aplicatiei din Exemplul 1 inca o clasa,
public class Clasa2{
………
public void metoda(){
Ex1 e1=new Ex1();
e1.val=50;
}
………
}
am putea accesa c`mpul val
al obiectului e1 instantiat intr-o metoda a acestei noi clase.
Modificatorul public trebuie asociat oricarei metode care
se doreste a fi accesata in orice punct al aplicatiei. Evident, constructorii
trebuie declarati cu acest modificator (vezi Exemplul 2).
Modificatorul private indica faptul ca variabila sau
metoda pentru care este folosit nu poate fi accesata din interiorul oricarei
alte clase cu exceptia clasei in care este declarata acea variabila sau metoda.
Este cazul variabilelor c`mp nume si virsta din clasa Persoana. Ele nu pot fi
vazute din exteriorul acestei clase. Daca am aduga aplicatiei din Exemplul 2 clasa,
public class Clasa2{
………
public void metoda(){
Persoana p=new Persoana();
p.nume=”Popescu”;
}
………
}
am constata ca instructiunea
p.nume=”Popescu” furnizeaza eroare de compilare deoarece variabila nume este
inaccesibila din exteriorul clasei Persoana.
Modificatorul protected restrictioneaza accesul la
subclasele clasei in care acest modificator insoteste declaratii de variabile
sau metode (a se vedea sectiunea despre mostenire). Mai precis, acele variabile
sau metode declarate cu protected pot fi accesate direct doar in subclasele
clasei in care sunt membre.
Ultimul modificator, package, nu este practic un
modificator de acces. El specifica o
biblioteca (pachet) de clase. Daca nu se specifica niciun modificator pentru o
variabila sau metoda atunci aceasta este vizibila in toate clasele din aceeasi
biblioteca.
1.2.4
Suprascrierea metodelor
In Sectiunea 1.2.1 am vazut ca fiecare metoda este
caracterizata de semnatura ei. Semnatura impreuna cu numele identifica unic o
metoda. Acest aspect este important deoarece Java poseda un mecanism prin
intermediul caruia se pot scrie intr-o clasa mai multe metode cu acelasi nume
dar cu semnaturi diferite. Acest mecanism poarta numele de suprascrierea metodelor si este foarte des int`lnit in procesul de
mostenire.
import java.io.*;
public class Dreptunghi
{
private
float latime=1;
private
float inaltime=1;
public
void setDim(float a){
latime=a;
}
public
void setDim(float a,float b)
latime=a;inaltime=b;
}
public
float getPerimetru(){
return 2*(latime+inaltime);
}
public
float getArie(){
return latime*inaltime;
}
public
void list(){
System.out.print(“dreptunghi cu latimea
“+latime+” si inaltimea “+inaltime);
}
public
static void main (Stringas args)
{
Dreptunghi d1=new Dreptunghi();
d1.setDim(25);
d1.list();
System.out.println(“are perimetrul
“+d1.getPerimetru()+” si aria “+d1.getArie());
Dreptunghi d2=new Dreptunghi();
d2.setDim(25,37);
d2.list();
System.out.println(“are perimetrul
“+d2.getPerimetru()+” si aria “+d2.getArie());
}
}
In clasa Dreptunghi exista doua versiuni ale metodei
setDim() cu unul si respectiv doi parametri.
1.2.5 Extinderi de
clase, clase componente, mostenire
Java permite doua mecanisme de folosire a facilitatilor
unei clase in interiorul alteia : clasa
componenta si clasa care extinde
(mosteneste) alta clasa.
O clasa componenta apare atunci c`nd in interiorul unei
clase se foloseste o variabila de tipul altei clase.
import java.io.*;
public class Salariat
{
private
Persoana pers;
private
int sectie;
private
float salariu;
public
Salariat(){
pers=new Persoana();
sectie=0;
salariu=0;
}
public
Persoana(String nume, int virsta, int sectie, float salariu){
pers=new Persoana(nume,virsta);
this.sectie=sectie;
this.salariu=salariu;
}
public
String getNume(){
return pers.getNume();
}
public
int getVirsta(){
return pers.getVirsta();
}
public
int getSectie(){
return sectie;
}
public float getSalariu(){
return salariu;
}
public
void list(){
System.out.print(pers.getNume()+” are virsta
“+pers.getVirsta());
System.out.println(“lucreaza la sectia
“+sectie+” si are salariul “+salariu);
}
public
static void main (Stringas args)
{
Salariat salariat1,salariat2;
salariat1=new Salariat();
pers2=new
Salariat(“Ionescu”,35,1,2500000);
salariat1.list();
salariat2.list();
}
}
Clasa Persoana este clasa componenta in clasa Salariat.
Desi este int`lnita in programe, facilitatea clasei componente are unele
inconveniente. Metodele getNume() si getVirsta() folosesc serviciile cu acelasi
nume din clasa Persoana prin intermediul instantei pers. Aceast aspect pare cam
fortat desi nu este un inconvenient. Situatia se schimba atunci c`nd observam
cazul primului salariat care se instantiaza prin intermediul constructorului
fara parametri. In interiorul acestuia se instantiaza variabila pers prin
constructorul fara parametri al clasei Persoana. Datorita limitarilor
functionale din clasa Persoana nu vom putea seta numele si v`rsta acestui
salariat cu alte valori. Vor ram`ne cele implicit stabilite in clasa Persoana.
In cazul celor doua tipuri de date Persoana si Salariat
solutia eleganta este oferita de mecanismul de extindere de clasa. Folosind
cuv`ntul cheie extends vom defini clasa Salariat ca extensie a clasei Persoana.
Spunem ca Salariat este subclasa a clasei Persoana sau ca aceasta este
supraclasa a clasei Salariat. Noua clasa va mosteni toti membrii clasei
Persoana. Pentru a avea acces nemijlocit in clasa Salariat asupra variabilelor
mostenite nume si virsta este necesar sa schimbam in clasa Persoana
modificatorul de acces al acestora din private in protected. Cu aceasta
precizare, clasa Salariat arata astfel,
import java.io.*;
public class Salariat
{
private
int sectie;
private
float salariu;
public
Salariat(){
nume=”fara nume”;
virsta=0;
sectie=0;
salariu=0;
}
public
Persoana(String nume, int virsta, int sectie, float salariu){
super(nume,virsta);
this.sectie=sectie;
this.salariu=salariu;
}
public
String getNume(){
return super.getNume();
}
public
int getVirsta(){
return super.getVirsta();
}
public
int getSectie(){
return sectie;
}
public float getSalariu(){
return salariu;
}
public void setNume(String
nume){
this.nume=nume;
}
public void setVirsta(int
virsta){
this.virsta=virsta;
}
public void setSectie(int
sectie){
this.sectie=sectie;
}
public void setSalariu(float
salariu){
this.salariu=salariu;
}
public
void list(){
super.list();
System.out.println(“lucreaza la sectia
“+sectie+” si are salariul “+salariu);
}
public
static void main (Stringas args)
{
Salariat salariat1,salariat2;
salariat1=new Salariat();
salariat2=new
Salariat(“Ionescu”,35,1,2500000);
salariat1.list();
salariat1.setNume(“Popescu”);
salariat1.setVirsta(23);
salariat1.setSectie(2);
salariat1.setSalariu(1300000);
salariat1.list();
salariat2.list();
}
}
Se observa ca o parte dintre instructiunile primului
constructor Salariat() coincid cu instructiunile primului constructor
Persoana(). Pentru a evita astfel de duplicari, Java permite apelul
constructorului supraclasei in cadrul constructorului subclasei folosind
cuv`ntul cheie super, dupa cum se vede in al doilea constructor Salariat().
Singura restrictie este ca super sa fie
primul apel in cadrul noului constructor.
Metodele getNume() si getVirsta() suprascriu pe cele din clasa Persoana prin
simplul apel al acestora folosind super.
Metoda list() o suprascrie pe cea din clasa Persoana dar adauga si o
noua instructiune.
In Java este permisa mostenirea multipla, spre exemplu
clasa C mosteneste (extinde) clasa B care mosteneste clasa A. Fie x un nume de
variabila int`lnit in fiecare clasa. Atunci, in clasa C sunt posibile
referirile,
this.x refera x din C
super.x refera x
din B
((B)this).x refera x din B
((A)this).x refera x din A
Daca o clasa este declarata cu atributul final atunci ea
nu mai poate fi extinsa. Opusul acestui atribut este abstract.
1.2.6 Variabile si metode statice
P`na acum, am vazut ca fiecare instanta a unei clase contine
c`te un exemplar din fiecare variabila precizata in definitia clasei. Limbajul
Java permite existenta unor variabile globale unei clase, adica variabile a
caror valoare este comuna tuturor instantelor clasei si poate fi referita de
catre acestea. Aceste variabile se numesc variabile
statice si se declara cu modificatorul static. O variabila statica este
locala clasei si se creaza la incarcarea clasei. }i metodele se pot declara cu
atmodificatorul static. O metoda statica nu poate folosi dec`t variabile
statice si nu poate apela dec`t alte metode statice dar poate fi apelata din
orice alta metoda a clasei. Variabilele si metodele statice pot fi referite
folosind numele clasei deoarece ele sunt asociate clasei si nu instantelor
clasei.
import java.io.*;
public class Floare
{
private int nrPetale;
private String nume;
private boolean miros;
static int numarObiecteFloare=0;
static void incNrOb(){
numarObiecteFloare++;
}
static int getNrOb(){
return
numarObiecteFloare;
}
public Floare(String nume, int nrPetale, boolean miros){
this.nume=nume;
this.nrPetale=nrPetale;
this.miros=miros;
incNrOb();
}
public void list(){
System.out.print(nume+"
are "+nrPetale+" petale si ");
if
(!miros) System.out.print(" nu ");
System.out.println("are
miros");
}
public static void main (Stringas args)
{
System.out.println("Numar
de flori "+Floare.getNrOb());
Floare
f1=new Floare("Ghiocel", 4,false);
Floare
f2=new Floare("Trandafir",20,true);
Floare
f3=new Floare("Crin", 10,true);
f1.list();
f2.list();
f3.list();
System.out.println("Sunt
"+Floare.getNrOb()+" flori");
}
}
1.2.7 Clase abstracte si interfete
In limbajul Java se pot preciza modele de functionare
care urmeaza sa fie implementate. Acestea se introduc prin intermediul claselor abstracte si a interfetelor.
O
clasa abstracta este o supraclasa pentru o ierarhie de clase. Ea contine
variabile si metode obisnuite carora li se specifica semnatura. Unele metode
pot fi chiar implementate in clasa abstracta. Acestea definesc functionarea
comuna a claselor care formeaza ierarhia. Alte metode, precizate doar ca
prototip, urmeaza sa fie obligatoriu implementate in subclase. Ele sunt
insotite de atributul abstract. O clasa
este considerata abstracta daca contine macar o metoda abstracta. De aceea nu
este neaparat obligatoriu ca ea sa fie declarata cu abstract. In clasele care
extind clasa abstracta se pot defini sI noi membrii (variabile sau metode) care
nu au fost precizati in clasa abstracta.
In
exemplul care urmeaza definim clasa abstracta Poligon. In cadrul ei se regasesc
cele amintite mai sus.
abstract class Poligon{
protected int nrLaturi=0;
protected float latas;
protected void setNrLaturi(int n){
nrLaturi=n;
lat=new
floatanrLaturis;
}
protected void setLaturi(float aas){
for(int
i=0;i<nrLaturi;i++){
latais=0;
try{
latais=aais;
}
catch(Exception
e){}
}
}
abstract void list();
abstract float perimetru();
}
public class Patrat extends Poligon
{
public Patrat(float
aas){
setNrLaturi(1);
setLaturi(a);
}
public void list(){
System.out.println("Este un
patrat cu latura "+lata0s);
}
public float
perimetru(){
return 4*lata0s;
}
}
public class Dreptunghi extends Poligon
{
public Dreptunghi(float
aas){
setNrLaturi(2);
setLaturi(a);
}
public void list(){
System.out.println("Este un
dreptunghi cu laturile "+lata0s+" "+lata1s);
}
public float
perimetru(){
return 2*(lata0s+lata1s);
}
}
}i acum sa folosim
ierarhia definita.
public class Prelucrare
{
public static void main (Stringas args)
{
Poligon
plg;
float
aas={1.2f,3.4f};
Dreptunghi
d=new Dreptunghi(a);
d.list();
plg=d;
System.out.println("Perimetrul
este "+plg.perimetru());
float
bas={5.f};
Patrat
p=new Patrat(b);
p.list();
plg=p;
System.out.println("Perimetrul
este "+plg.perimetru());
}
}
Clasa
abstracta Poligon nu poate fi direct instantiata. O variabila de tipul ei poate
prelua valoarea unei instante a uneia dintre subclase.
O
interfata se precizeaza cu cuv`ntul cheie interface si defineste un tip. Ea
este un model pe care orice clasa care il implementeaza trebuie sa-l respecte
intocmai. Metodele care apar intr-o interfata sunt implicit publice iar
variabilele (daca sunt declarate) sunt constante chiar daca nu se specifica
aceasta in mod explicit. Se pot crea ierarhii de interfete la fel ca in cazul
claselor.
Ca
si in cazul claselor abstracte se pot declara variabile de tipul unei
interfete. Valoarea unei astfel de variabile poate fi orice referinta la un
obiect care apartine unei clase ce implementeaza interfata. Variabilele de tip interfata abstractizeaza
notiunea de obiect deoarece sunt independente de clasele care fac
implementarea. Reluam exemplul anterior.
import java.io.*;
interface Poligon{
void setLaturi(float aas);
void list();
float perimetru();
}
class Patrat implements
Poligon
{
private float Lat;
public void setLaturi(float aas){
try{
Lat=aa0s;
}
catch(Exception
e){Lat=0.0f;}
}
public Patrat(float aas){
setLaturi(a);
}
public void list(){
System.out.println("Este
un patrat cu latura "+Lat);
}
public float perimetru(){
return
4*Lat;
}
}
class Dreptunghi implements
Poligon
{
private float Latas;
public void setLaturi(float aas){
for(int
i=0;i<2;i++)
try{
Latais=aais;
}
catch(Exception
e){Latais=0.0f;}
}
public Dreptunghi(float aas){
Lat=new
floata2s;
setLaturi(a);
}
public void list(){
System.out.println("Este
un dreptunghi cu laturile "+Lata0s+" si "+Lata1s);
}
public float perimetru(){
return
2*(Lata0s+Lata1s);
}
}
public class Prelucrare
{
public static void main (Stringas args)
{
Poligon
plg;
float
aas={1.45f};
Patrat p=new Patrat(a);
p.list();
plg=p;
System.out.println("
cu perimetrul "+plg.perimetru());
float
bas={2.3f, 7.8f};
Dreptunghi
d=new Dreptunghi(b);
d.list();
plg=d;
System.out.println("
cu perimetrul "+plg.perimetru());
}
}
1.2.8 Tratarea
exceptiilor
In limbajul Java, o exceptie
este un eveniment care nu permite continuarea normala a executiei programului.
Tratarea erorilor de executie prin exceptii este o caracteristica a limbajului
Java. Utilizarea exceptiilor este mecanismul de a renunta la executia secventei
de cod care a provocat eroarea, semnalarea acesteia si eventual, executia unei
secvente de tratare corespunzatoare.
Tratarea unei erori are doua componente : o componenta,
throws, care semnaleaza exceptia si alta care o trateaza efectiv. Secventa de
tratare poate fi in aceeasi metoda in care s-a semnalat exceptia, in metoda
care a apelat metoda respectiva sau intr-o metoda dintr-un sir mai lung de
apeluri.
Practic, o exceptie este un obiect care reprezinta o
instantiere a unei clase obtinuta prin extinderea clasei Throwable sau a unei
subclase a acesteia. Din Throwable sunt dezvoltate doua ierarhii Error si
Exception. Prima reprezinta exceptii generate de erori de functionare a
mediului Java. Aceste obiecte nu pot fi instantiate in program. Clasa Exception
sta la baza a doua ierarhii. Una, RunTimeException se refera la greseli de
programare (ex. depasirea limitelor unui tablou) si o alta care poate fi creata
de catre programator.
Obiectul generat in cazul unei exceptii este utilizat
pentru a transmite informatii de la secventa de cod care a produs eroarea la
secventa de cod care o trateaza.
Semnalarea exceptiei se face cu instructiunea
Throw identif_obiect;
fara sa se faca si tratarea
ei. Argumentul instructiunii este un obiect din ierarhia Exception. Tratarea
exceptiilor se face cu instructiunea try catch. Ea indica secventa de cod in
care poate sa apara o exceptie, tipul acesteia si modul in care se face
tratarea ei. Cuv`ntul cheie throws poate fi utilizat si in definitia unei
metode in corpul careia poate sa apara o exceptie fara ca aceasta sa fie
tratata in metoda.
public class TestException
extends Exception
{
TestException(){super();}
TestException(String s){super(s);}
}
import java.io.*;
public class TestThrow
{
static int thrower(String s) throws TestException {
try{
if
(s.equals("divide")){int i=0;return i/i;}
if
(s.equals("null")){s=null;return s.length ();}
if (s.equals("test")) throw new
TestException("Mesaj Test");
return
0;
}finally{
System.out.println("athrower("+s+")
genereazas");
}
}
public static void main (Stringas args)
{
for(int
i=0;i<args.length;i++)
try{
thrower(argsais);
System.out.println("Test
"+argsais+" fara exceptie");
}
catch(Exception
e){
System.out.println("Test
"+argsais+" exceptie de clasa "+e.getClass()+" cu mesajul :
"+e.getMessage());
}
}
}
Exemplul declara o clasa TestException care extinde clasa
Exception. In metoda main a clasei Test se genereaza spre testare trei exceptii
prevazute in limbajul Java si exceptia TestException.
1.3
Biblioteci
(packages) in Java
Un program Java este o colectie de
declaratii de clase care sunt organizate in biblioteci (packages). Membrii unei
biblioteci sunt deci, clase sau interfete declarate intr-o unitate de compilare
comuna. Termenul de package desemneaza nu numai o biblioteca, el reprezinta si
directorul care contine toate fisierele utilizate pentru declararea claselor ce
formeaza biblioteca.
Declararea unei biblioteci se face prin
ordinul
package
nume;
unde nume reprezinta calea completa a directorului unde se
afla biblioteca. Spre exemplu
package
dir1.aaa.my_package;
declara biblioteca my_package aflata in directorul
dir1/aaa/my_package. Declaratiile de import sunt sub doua forme
import
nume_complet_biblioteca;
import
nume_biblioteca.*;
spre exemplu
import
java.util.Vector;
import
java.util.*;
In
concluzie, o unitate de compilare este formata din : declaratii package,
declaratii de import si declaratii de clase. O unitate de compilare poate
contine doar o singura clasa declarata public. Daca aceasta exista, atunci
numele fisierului care o contine trebuie sa fie format din numele clasei si
extensia “java”. In urma compilarii rezulta c`te un fisier cu extensia class
pentru fiecare clasa din pachet si numele acelasi cu numele clasei
corespunzatoare.
Limbajul
Java se bazeaza pe o serie de biblioteci cu ajutorul carora se realizeaza
aplicatiile. Principalele biblioteci sunt :
-
java.lang
contine clasele de cea mai larga utilizare in limbaj;
-
java.util
contine clase care implementeaza, in general, structuri de date utile;
-
java.io
contine clasele ce implementeaza fuctii de I/O cu fisiere;
-
java.applet
contine clase ce implementeaza functii pentru applet-uri;
-
java.net
contine clase destinate lucrului in retea.
1.3.1 Pachetul java.lang
O parte
dintre clasele acestui pachet sunt : Error, Exception; o serie de clase care
implementeaza exceptii precum ArithmeticException,
ArrayIndexOutOfBoundsException, ArrayStoreException; clase corespunzatoare
tipurilor de date de baza : Boolean, Byte, Character, Short, Integer, Long,
Float, Double; clase de lucru cu siruri de caractere : String, StringBuffer;
clasa Math care contine functiile matematice; clasele System, Thread,
Throwable. Toate clasele extind clasa Object membra tot a pachetului java.lang.
Toate
clasele care implementeaza tipurile de date de baza poseda functii de conversie
cu sirurile de caractere (String-uri) in ambele directii. Astfel, poseda
functii valueOf (clasele Float, Double, Long) sau Byte.parseByte(),
Integer.parseInt(), Long.parseLong() care transforma un sir de caractere in valoarea
numerica corespunzatoare. Toate aceste clase poseda o metoda toString care
transforma o valoare din tipurile precizate in sir de caractere.
Urmatorul
program exemplifica conversii cu c`teva dintre tipurile amintite.
import
java.io.*;
public
class ShowConvers
{
public static void main (Stringas
args)
{
String
S=new String();
BufferedReader
br=new BufferedReader(new InputStreamReader(System.in));
long
l;
int
i;
Double
d;
try{
System.out.println("Introduceti
un intreg : ");
S=br.readLine();
i=Integer.parseInt(S);
System.out.println("Ati
introdus numarul "+i);
System.out.println("Introduceti
un intreg lung : ");
S=br.readLine();
l=Integer.parseInt(S);
System.out.println("Ati
introdus numarul "+l);
System.out.println("Introduceti
un numar real : ");
S=br.readLine();
d=Double.valueOf(S);
System.out.println("Ati
introdus numarul "+d.floatValue());
System.in.read();
}catch(IOException
e){}
}
}
Clasa
System are atributul final (nu poate fi extinsa). Ea defineste o interfata de
apeluri sistem, independenta de platforma. Toate metodele sunt statice. Contine
variabilele statice in, out si err care desemneaza fisierele standard de
intrare, iesire si de erori. Contine o functie arraycopy care copiaza un tablou
sau o portiune de tablou intr-un alt tablou. Elementele tablourilor pot fi de
tip Object sau derivate din acesta.
Manevrarea
sirurilor de caractere se face prin clasele String si StringBuffer care sunt
finale. Un obiect de tip String se poate crea direct,
String
sir=”exemplu”; sau
String
sir=new String(“exemplu”);
Clasa
StringBuffer este utilizata in cazul in care se folosesc sirurui de caractere
care se modifica. Urmatorul program exemplifica una dintre posibilitatile de
lucru cu cele doua tipuri de siruri de caractere. Tipul sir de caractere a
aparut si va apare foarte des in programe.
import
java.io.*;
public
class ShowString
{
public static String invers1(String
S){
int
len=S.length();
StringBuffer
SB=new StringBuffer(len);
for(int
i=S.length()-1;i>=0;i--)
SB.append(S.charAt(i));
return
SB.toString();
}
public static String invers2(String
S){
StringBuffer
SB1=new StringBuffer(S);
StringBuffer
SB2=new StringBuffer();
SB2=SB1.reverse();
return
SB2.toString();
}
public static void main (Stringas
args)
{
String
S=new String("abcdefghijklmn");
System.out.println("Sirul
initial este "+S.toString());
System.out.println("Raspuns
1 :"+invers1(S));
System.out.println("Raspuns
2 :"+invers2(S));
}
}
Programul
declara si foloseste doua functii de inversare a unui sir de caractere. Ambele
primesc ca argument un String si folosesc ca variabila intermediara o instanta
a clasei StringBuffer.
1.3.2 Pachetul java.util
Dintre
clasele acestui pachet amintim StringTokenizer, Vector si Stack. Clasa
StringTokenizer este un analizor pe siruri de caractere. Se refera la elemente
precum numere, nume, operatori, delimitatori. Delimitatorii impliciti sunt :
tt, tr tn dar se pot specifica si delimitatori noi. Programul urmator foloseste
caracterul ‘;’ pentru a delimita instructunile din cadrul unui sir de
caractere.
import
java.io.*;
import
java.util.StringTokenizer ;
public
class ShowToken
{
public static void main (Stringas
args)
{
String
SirAnalizat=new String("int a=23;float b=5.5;float x=a+b;b=7-x;");
StringTokenizer
Analizor=new StringTokenizer(SirAnalizat,";");
System.out.println("Sirul
analizat este "+SirAnalizat);
System.out.println("Sunt
"+Analizor.countTokens()+" instructiuni");
try{
while (Analizor.hasMoreTokens())
System.out.println(Analizor.nextToken());
}catch(Exception
e){System.out.println(e);}
try{
System.in.read();
}catch(IOException
e){}
}
}
Urmatorul
program exemplifica utilizarea instantelor de tip Vector. Aceasta clasa se
foloseste in cazul in care este nevoie de vectori care isi modifica dimensiunea
(altfel tablourile obisnuite sunt suficiente). In definitia acestei clase,
elemetele sunt de tip Object si pot fi inlocuite cu orice tip derivat din
acesta. Clasa poseda metodele capacity() si size() care intorc capacitatea si
respectiv dimensiunea actuala a vectorului. Clasa poseda diferite metode care
permit adaugarea sau inserarea de elemente. Daca dimensiunea vectorului este
egala cu capacitatea sa si se adauga un nou element atunci capacitatea
vectorului se va mari implicit, fara actiunea programatorului. In constructor
se poate preciza incrementul de crestere a capacitatii. Declaratia
Vector
vec=new Vector(10,3);
precizeaza un vector care are 10 elemente initial si
posibilitatea de a-si incrementa la nevoie capacitatea cu 3 elemete.
Programul
urmator exemplifica utilizarea tipului Vector. Vom utiliza un vector de
persoane.
class Persoana
{
private String nume;
private int virsta;
public void SetNume(String S){
nume=S;
}
public void SetVirsta(int n){
virsta=n;
}
public Persoana(String S,int v){
nume=new
String();
SetNume(S);
SetVirsta(v);
}
public String GetNume(){
return
nume;
}
public int GetVirsta(){
return
virsta;
}
public String toString(){
String
S=new String("Persoana "+nume+" are virsta "+virsta);
return
S;
}
}
import
java.io.*;
import
java.util.*;
public
class ShowVector
{
private Vector vector;
BufferedReader br=new
BufferedReader(new InputStreamReader(System.in));
public ShowVector(int capacitate,
int pas){
vector=new
Vector(capacitate,pas);
}
public void Introducere(){
String
nume=new String();
String
virsta=new String();
int
v;
do{
System.out.println("Nume
Persoana (Enter=Sfirsit) : ");
try{
nume="";
nume=br.readLine();
if
(nume.equals("")) break;
System.out.println("Virsta
Persoana : ");
virsta=br.readLine();
vector.addElement(new
Persoana(nume,Integer.parseInt(virsta)));
}
catch(IOException
e){System.err.println("Eroare de I/O "+e);}
}while(true);
}
public void Listare(){
System.out.println("tnListare
elemente din vector :tn");
for(int
i=0;i<vector.size();i++)
System.out.println(((Persoana)vector.elementAt(i)).toString());
}
public static void main (Stringas
args)
{
ShowVector
show=new ShowVector(10,5);
show.Introducere();
show.Listare();
show.Introducere();
show.Listare();
try{
System.in.read();
}
catch(IOException
e){System.err.println("Eroare de I/O "+e);}
}
}
Metoda
addElement() adauga un element pe ultima pozitie din vector. Pentru a memora
sau modifica un element pe pozitia i se foloseste metoda setElementAt(E,i).
Inserarea pe pozitia i se face cu metoda insertElementAt(E,i). Metoda
removeElementAt(i) sterge elementul de pe pozitia i. Metoda contains(E)
verifica exiastenta unui anumit element pritre elementele vectorului.
1.4 Tratarea
firelor de execuţie
Aplicaţiile
moderne trebuie să permită în anumite situaţii execuţia
simultană a mai multor prelucrări diferite (afişare imagini,
preluare fişiere din reţea, etc). Limbajul Java posedă un
mecanism care permite programarea unor activităţi simultane sub forma
firelor de execuţie (thread-uri). Toate firele de execuţie dintr-un program
(proces) împart acelaşi spaţiu de adrese (aceleaşi variabile).
Acest aspect uşurează comunicarea între firele de execuţie prin
intermediul unora dintre variabile. Utilizarea firelor de execuţie nu
micşorează timpul de execuţie al aplicaţiilor ci le fac mai
uşor de scris deoarece se poate scrie câte un program pentru fiecare activitate
ce trebuie controlată de către aplicaţie.
Mecanismul
de fir de execuţie este descris de calasa Thread din biblioteca java.lang.
Clasa Thread conţine metoda run() care, aici, nu este implementată.
Acest lucru trebuie făcut într-o clasă
care extinde clasa Thread. Firele de execuţie ale aplicaţiei vor fi
instanţe ale acestei noi clase. Codul care va fi executat de către
firul de execuţie este conţinut în metoda run().
Class NewThread1 extends Thread{
public
void run(){
…..
}
……
}
Aceasta este o primă
metodă de definire a firelor de execuţie. O a doua metodă
constă în “adăugarea” la o
clasă a facilităţilor din interfaţa Runnable care are
prevăzută, deasemenea, metoda run().
Class NewThread2 implements Runnable{
public void run(){
….
}
…..
}
În primul caz, instanţierea
unui fir de execuţie se face sub forma
NewTread1 fir = new NewThread1();
iar în al doilea caz
NewThread2 f =
new NewThread2();
Thread fir
= new Thread(f);
În cazul
firelor de execuţie instanţiate după a doua metodă nu se
pot apela în metoda run() metode specifice obiectelor Thread (sleep(),
setPriority(), getPriority()). Deşi firul de execuţie este
instanţiat, execuţia sa nu poate începe decât după invocarea
metodei start().
fir.start();
Ca urmare a execuţiei acestei metode firul de execuţie
este pregătit pentru execuţie dar aceasta nu începe imediat. În cazul
în care firul este creat după prima metodă şi se doreşte
pornirea lui imediat după instanţiere atunci constructorul clasei
care extinde pe Thread trebuie să conţină apelul lui start().
public
NewThread1 (<lista parametri>){
Super(<lista parametri>);
Start();
}
Dacă
există mai multe fire de execuţie care se execută pe un singur
procesor, se pune problema alegerii firului care se execută la un moment
dat. Aceasta se face în funcţie de prioritatea firelor de execuţie.
Orice cod Java se execută într-un
fir de execuţie. La creare, un fir de execuţie are un nivel de
prioritate egal cu cel al firului din care a fost creat. Prioritatea i se poate
modifica prin metoda setPriority() (se pot da valori între Thread.Min_Priority
= 1 şi Thread.Max_Priority = 10). Execuţia a două fire cu
aceeaşi prioritate se face în mod diferit funcţie de sistemul de
operare pe care se execută.
Dacă sistemul de operare admite
divizarea timpului atunci fiecărui proces i se alocă o cuantă de
timp după care este întrerupt şi execuţia continuă cu alt
fir. În sistemele fără divizare a timpului, un fir de execuţie
cedează controlul procesorului prin apelul explicit al metodei yield() sau
prin apelul unei metode care conduce la autoblocare sleep(), wait().
Un fir de execuţie poate fi
într-una din
situaţiile : creat, gata de execuţie, suspendat, terminat.
1.4.1
Sincronizarea firelor de execuţie
În cazul când două procese se
execută simultan, probelma sincronizării apare dacă cele
două procese concură la utilizarea aceloraşi resurse sau
dacă cooperează între ele.
Dacă procesele, în cazul nostru
firele de execuţie, sunt în concurenţă atunci este necesar ca
gestionarea resurselor partajate să se facă consistent. Aceasta
înseamnă că la un moment dat un singur fir are acces la resursele
comune. Rolul sincronizării este
acela de a asigura accesul exclusiv la resursele comune. Secvenţa de cod
care asigură accesul exclusiv se numeşte regiune critică.
Dacă procesele cooperează
atunci ele trebuie să schimbe informaţii. În acest caz sincronizarea
presupune ca procesele să se aştepte unul pe altul până când
îşi pot transmite informaţiile necesare. Regiunea critică
conţine acel cod program care asigură schimbul de informaţie.
Să considerăm cazul
firelor de execuţie concurente. Primul exemplu este cel a două
instanţe ale unei clase, FirNeSyncro, care extinde clasa Thread.
Partajarea aceleiaşi resurse, anume o instanţă a clasei
AccesComun va genera rezultate eronate. Pentru a se observa mai bine acest
aspect am introdus blocajul firului curent prin apelul metiodei sleep().
public class AccesComun
{
int a,b;
public
AccesComun(){
a=0;b=0;
}
void
metoda(int numar){
Thread t=Thread.currentThread();
System.out.println("Din firul
"+numar+": a="+a+" b="+b);
a++;
try{
t.sleep((int)Math.random()*1000);
}catch (InterruptedException e){}
b++;
}
}
class FirNeSyncro extends Thread
{
private
AccesComun A;
private
int numar;
public
FirNeSyncro(AccesComun A, int numar){
this.A=A;this.numar=numar;
}
public
void run(){
for(int i=0;i<3;i++)
A.metoda(numar);
System.out.println("Firul
"+numar+" Gata !");
}
}
public class NeSynchro
{
public
static void main (Stringas args)
{
AccesComun A=new AccesComun();
new FirNeSincro(A,1).start();
new FirNeSincro(A,2).start();
System.out.println("Gata
main!");
}
}
Rezultatul rulării este :
Din firul 1 : a = 0, b = 0
Din firul 2 : a = 0, b = 0
Din firul 1 : a = 2, b = 1
Din firul 2 : a = 3, b = 2
Din firul 1 : a = 4, b = 3
Din firul 2 : a = 5, b = 4
şi arată
că cele două variabile a şi b nu sunt actualizate corect.
Soluţia corectă presupune
sincronizarea accesului la obiectul partajat şi se realizează cu
ajutorul secvenţelor sau metodelor ce se execută în excludere
mutuală. Implementarea foloseşte un mecanism de tip zăvor. Acesta presupune declararea
unor metode sau secvenţe de cod sincronizate (atributul synchronized).
Pentru a executa metoda sau secvenţa declarată synchronized firul de
execuţie închide zăvorul. Cât timp zăvorul este închis nici un
alt fir nu poate executa metoda sau secvenţa respectivă. Un fir de
execuţie care încearcă execuţia unei metode sau secvenţe
“închise” este blocat şi trecut într-o coadă
de aşteptare. Când se termină execuţia unei metode cu atributul
synchronized zăvorul este deschis şi se preia din coda de
aşteptare unul dintre firele blocate care trece în starea gata de execuţie.
class FirSyncro extends Thread
{
static
int a=0,b=0;
static
Object o=new Object();
public
FirSyncro(String nume){
this.setName(nume);
}
void
metoda(){
System.out.println(this.getName()+"
: a="+a+" b="+b);
a++;
try{
sleep((int)Math.random()*1000);
}catch (InterruptedException e){}
b++;
}
public
void run(){
for(int i=0;i<3;i++)
synchronized(o){
metoda();
}
System.out.println(this.getName()+"
Gata !");
}
}
public class Synchro2
{
public
static void main (Stringas args)
{
new FirSyncro("Primul
fir").start();
new FirSyncro("Al doi-lea
fir").start();
}
}
Obiectul o are doar rolul de a
furniza zăvorul. Zăvorul poate fi asociat unei clase
synchronized(getClass()){
metoda();
}
sau chiar funcţiei metoda(),
synchronized void metoda(){
….
}
Dacă metoda ar fi statică atunci zăvorul
ar fi asociat chiar clasei din care face parte.
Execuţia programului va actualiza concomitent, deci
corect, variabilele a şi b.
Următorul
exemplu consideră cazul proceselor care colaborează. Instanţele
claselor Profesor şi Elev sunt fire de execuţie care comunică
prin intermediul unei instanţe a clasei ZonaTampon. Această
clasă conţine date pentru coeficienţii coeficienţii unei
ecuaţii şi valoarea rădăcinii. Profesorul propune o
ecuaţie iar elevul o rezolvă. Elevul nu poate rezolva ecuaţia
până când nu primeşte datele necesare în instanţa ZonaTampon.
Profesorul nu propune o altă ecuaţie până când nu primeşte
rezultatul la problema pusă. Cheia programului se află în clasa
ZonaTampon care conţine două metode sincronizate.
public class Ecuatie
{
public
static void main (Stringas args)
{
ZonaTampon Tampon_1=new ZonaTampon();
Profesor profesor_1=new
Profesor(Tampon_1,1);
Elev elev_1=new Elev(Tampon_1,1);
profesor_1.start();
elev_1.start();
System.out.println("======================"+"tn");
ZonaTampon Tampon_2=new
ZonaTampon();
Profesor profesor_2=new Profesor(Tampon_2,2);
Elev elev_2=new Elev(Tampon_2,2);
elev_2.start();
profesor_2.start();
}
}
public
class ZonaTampon
{ private
float a,b,valoare;
private
boolean disponibil;
public
ZonaTampon(){
a=0;b=0;valoare=100000;disponibil=false;
}
public
synchronized float a_Calculat(int numar){
if (!disponibil){
try{
wait();
}catch
(InterruptedException e){}
}
valoare=-b/a;
System.out.println(" iar elevul
"+numar+" a calculat "+valoare+"tn");
disponibil=false;
notify();
return valoare;
}
public
synchronized void a_Propus(float a,float b,int numar){
if (disponibil){
try{
wait();
}catch
(InterruptedException e){}
}
this.a=a;this.b=b;valoare=100000;
System.out.print("Profesorul
"+numar+" a propus "+a+"x+"+b+"=0");
disponibil=true;
notify();
}
}
class
Elev extends Thread
{
private
ZonaTampon Tampon;
private
int numar;
public
Elev(ZonaTampon T,int n){
Tampon=T;numar=n;
}
int
getNumar(){
return numar;
}
public
void run(){
float valoare;
for(int i=1;i<=3;i++){
valoare=Tampon.a_Calculat(getNumar());
}
}
}
class
Profesor extends Thread
{
private
ZonaTampon Tampon;
private
int numar;
public
Profesor(ZonaTampon T, int n){
Tampon=T;numar=n;
}
int
getNumar(){
return numar;
}
public
void run(){
for(int i=1;i<=3;i++){
Tampon.a_Propus(i,i+1,getNumar());
try{
//System.out.print("Profesorul
"+numar+" a propus "+i+"x+"+(i+1)+"=0");
sleep(1000);
}catch
(InterruptedException e){}
}
}
}
1.5 Intrări/Ieşiri în Java
În limbajul
Java operaţiile de intrare/ieşire
utilizează fluxuri (stream-uri). În general, un flux este un
obiect care poate să producă sau să primească octeţi.
Clasele care descriu operaţiile de intrare/ieşire se află în biblioteca java.io. Ele formează
ierarhii determinate de specificul fluxului de intrare sau ieşire, fluxuri
de octeţi sau de caractere. Spre exemplu, clasele InputStream şi
OutputStream sunt specializate în manevrarea fluxurilor de octeţi iar
clasele Reader şi Writer lucrează la nivel de caracter.
Cu o
singură excepţie, toate clasele implementează doar operaţii
de scriere sau operaţii de citire. Excepţia o constitue clasa
RandomAccessFile care are atât metode de scriere cât şi metode de citire.
1.5.1 Intrări/Ieşiri la nivel de octet
Clasele
InputStream şi OutputStream sunt abstracte. Ele se extind pentru a
obţine câteva tipuri utile de fluxuri. În aceste clase nu se face referire
la tipul fluxului. Astfel se pot face citiri dintr-un fişier sau vector, din
reţea. Modul diferit în care se specifică sursa de octeţi
determină diferitele clase care extind clasa InputStream. Acelaşi
lucru se poate spune despre clasa OutputStream şi ierarhia dezvoltată
din aceasta.
Clasele
FileInputStream şi FileOutputStream se referă la lucrul cu
fişiere. Clasele ByteArrayInputStream şi ByteArrayOutputStream citesc/scriu în/dintr-un vector. Alte
două perechi de clase sun BufferedInputStream şi BufferedOutputStream
care folosesc fiecare câte o zonă tampon care optimizează
operaţiile de intrări/ieşiri. Dacă se doreşte
să se citească sau să se scrie date din tipurile primitive ex.
byte, int, float, double, se vor folosi clasele DataInputStream şi
DataOutputStream.
Toate
metodele din clasele InputStream şi OutputStream sunt declarate cu throws
IOException (deci generează excepţia generală de intrare/ieşire). Această
declaraţie este prezentă şi în clasele derivate.
Următorul
program face citirea şi scrierea la nivel de octet, din/în fişiere text.
import java.io.*;
public class FileCopy
{
/*
Copiere fisiere. Citirea/Scrierea se face la
nivel de octet.
*/
public
static void main (Stringas args)
{
if (args.length==2){
try{
FileInputStream in=new
FileInputStream(argsa0s);
FileOutputStream out=new
FileOutputStream(argsa1s);
int b;
while ((b=in.read())!=-1)
out.write(b);
out.close();
}catch
(IOException e){System.out.println("Eroare de IO !");}
}
}
}
Funcţiile read() şi
write() lucrează cu date de tip int ştiindu-se faptul că, caracterele dintr-un fişier text sunt în codul
Unicode (pe doi octeţi).
Următorul program
exemplifică operaţii de scriere şi citire cu fluxuri ce
utilizează vectori (in cazul de faţă vectori cu elemente de tip
byte).
import java.io.*;
public class Copy
{
public
static void copy(InputStream inp1,InputStream inp2, OutputStream out){
byte b;
try{
while((b=(byte)inp1.read())!=-1)
out.write(b);
while((b=(byte)inp2.read())!=-1)
out.write(b);
}catch(IOException e){}
}
public
static void main (Stringas args)
{
byte
sursa1as={(byte)'a',(byte)'b',(byte)'c',(byte)'d'};
byte
sursa2as={(byte)'e',(byte)'f',(byte)'g',(byte)'h'};
ByteArrayInputStream fluxIn1=new
ByteArrayInputStream(sursa1);
ByteArrayInputStream fluxIn2=new
ByteArrayInputStream(sursa2);
ByteArrayOutputStream fluxOut=new
ByteArrayOutputStream();
copy(fluxIn1,fluxIn2,fluxOut);
System.out.println("Flux-ul
iesire este este : "+fluxOut);
}
}
În cazul când se lucrează cu
fluxuri la nivel de octet, deobicei nu se foloseşte un singur flux ci mai
multe fluxuri suprapuse,
BufferedInputStream br = new
BufferedInputStream(new System.in);
Programul următor
exemplifică acest aspect. Concatenează mai multe fişiere.
Intrarea este o secvenţă de fluxuri (fişiere), cu zonă
tampon, precizate în linia de comandă. Ieşirea este tot fişier
cu zonă tampon.
import java.io.*;
import java.util.*;
public class ConcFis
{
public
static void main (Stringas args)
{
if (args.length<3){
System.out.print("Utilizare
: ConcFis <Fis. iesire>");
System.out.println("
<Fis. intrare 1> <Fis. intrare 2>...");
System.exit(1);
}
try{
/*
Se pregateste secventa de fluxuri (fisiere) de intrare */
FileInputStream
VectorFluxIntrareas=new FileInputStreamaargs.length-1s;
for(int
i=0;i<args.length-1;i++)
VectorFluxIntrareais=new
FileInputStream(argsai+1s);
Vector
fisiereIntrare=new Vector(args.length-1);
for(int
i=0;i<args.length-1;i++)
fisiereIntrare.addElement(new
BufferedInputStream(VectorFluxIntrareais));
SequenceInputStream
intrare=new SequenceInputStream(fisiereIntrare.elements());
/*
Iesirea este un fisier cu zona tampon */
BufferedOutputStream
rez=new BufferedOutputStream(new FileOutputStream(argsa0s),1024);
int
cit;
/*
Citire/Scriere la nivel de octet */
while
((cit=intrare.read())!=-1)
rez.write(cit);
/*
Citire/Scriere cu buffer */
byte
bas=new bytea512s;
while
((cit=intrare.read(b))>0){
rez.write(b);
rez.flush();
}
rez.close();
intrare.close();
}catch(IOException
e){System.err.println("Eroare de I/O"+e);}
}
}
1.5.2 Intrări/Ieşiri la nivel de caracter
Clasele
Reader şi Writer sunt şi ele abstracte şi stau la baza unei ierarhii
de clase pentru intrări/ieşiri
la nivel de caracter. Pe lângă aceasta, cele două clase permit
sincronizarea operaţiilor asupra fluxurilor derivate. Aceasta prin
intermediul unei instanţe Object membră în aceste două clase.
Clasele InputStreamReader şi OutputStreamWriter realizează o punte
între fluxurile de caractere şi cele de octeţi. Ele citesc/scriu
caractere traducându-le în
octeţi.
Programul
următor realizează copia unui fişier. Citirile-scrierile se fac
la nivel de caracter sau linie. Se folosesc fluxuri suprapuse care în final
posedă zonă tampon.
import java.io.*;
public class CopyFile2
{
public
static void Copyfile(String NameFileIn,String NameFileOut){
try{
BufferedReader br=new
BufferedReader(new FileReader(NameFileIn));
BufferedWriter bw=new
BufferedWriter(new FileWriter(NameFileOut));
String linie;
while ((linie=br.readLine())!=null){
bw.write(linie);
bw.newLine();
linie="";
}
bw.close();
}catch (IOException e){System.out.println("Eroare
I/O");};
}
public
static void main (Stringas args)
{
Copyfile("Fis1.txt","Fis2.txt");
}
}
1.5.2 Intrări/Ieşiri la nivel de caracter
Clasele
DataInputStream şi DataOutputStream oferă posibilitatea să se
transfere prin fluxuri date primare precum boolean, char, int, float, etc.
Utilizând metodele acestor clase, informaţia este transferată într-un format independent de sistem.
Totuşi, pentru ca datele să fie citite cu DataInputStream trebuie
să fi fost scrise cu DataOutputStream.
Programul
următor exemplifică citirea/scrierea
datelor primitive de tipuri diferite.
/**
Fluxuri de date
si fisiere. Fisierul (fluxul) de intrare poate fi
si tastatura. Se citesc si se scriu in fisierul (fluxul)
de iesire
nr_intregi,nr_flotanti si intregii, respectiv flotantii
corespunzatori.
*/
import java.io.*;
public class FluxDate
{
public
static void main (Stringas args)
{
if (args.length<1){
System.out.print("Utilizare
: FluxDate <fis. iesire>");
System.out.println("
<Fis. intrare> (optional)");
System.exit(1);
}
try{
OutputStream
fluxIesire=new FileOutputStream(argsa0s);
DataOutput
fluxIesireDate=new DataOutputStream(fluxIesire);
InputStream
fluxIntrare;
DataInput
fluxIntrareDate;
if
(args.length==2){
fluxIntrare=new
FileInputStream(argsa1s);
}else{
fluxIntrare=System.in;
}
fluxIntrareDate=new
DataInputStream(fluxIntrare);
int
nrInt=fluxIntrareDate.readInt(),nrFloat=fluxIntrareDate.readInt();
fluxIesireDate.writeInt(nrInt);
fluxIesireDate.writeInt(nrFloat);
int
i,cit;
for(i=0;i<nrInt;i++){
cit=fluxIntrareDate.readInt();
fluxIesireDate.writeInt(cit);
}
float
a;
for(i=0;i<nrFloat;i++){
a=fluxIntrareDate.readFloat();
fluxIesireDate.writeFloat(a);
}
fluxIntrare.close();
fluxIesire.close();
}catch (IOException
e){System.err.println("Eroare de I/O");}
}
}
4. Aplicaţii
de reţea în Java
4.1 Noţiuni
generale despre reţelele de calculatoare şi protocoale de
comunicaţie
O reţea de calculatoare este un sistem de comunicaţie care
conectează mai multe noduri (hosts).
Un nod poate fi un calculator sau un alt dispozitiv care poate recepţiona
date (ex. un telefon mobil, imprimantă). Pentru a fi conectate în
reţea, nodurile au nevoie de un dispozitiv numit interfaţă sau controller.
Unele noduri au o singură interfaţă (noduri monopunct), altele dispun de mai multe astfel de dispozitive
(noduri multipunct). Nodurile se
deosebesc între ele, în primul rând, prin rolul pe care îl joacă în
reţea. Astfel, unele noduri sunt specializate, spre exemplu servere de
fişiere sau servere Web, iar altele sunt de uz general accesibile pentru
diverse sevicii pentru unul sau mai mulţi utilizatori interactivi.
Din punct de vedere al acoperirii geografice exdistă
două tipuri de reţele.
O reţea
locală (Local Area Network – LAN) conectează calculatoare
apropiate geografic : în aceeaşi sală sau clădire, în
aceeaşi instituţie (eventual cu mai multe clădiri).
Calculatoarele pot fi situate în acest tip de reţea şi la câţiva
kilometri distanţă. Legătura se realizează de regulă
prin cablu şi beneficiază de viteze mari de transfer : 10 Mbps-100
Mbps sau 1 Gbps.
O reţea răspândită
geografic (Wide Area Network - WAN) conectează calculatoare aflate la
mari distanţe geografice, chiar în ţări şi continente
diferite. Legăture se realizează prin linii telefonice, canale radio
sau satelit. Vitezele de transfer sunt mai mici decât în cazul reţelelor
locale : zeci de Kbps sau sute de Mbps (cea mai utilizată valoare este 155
Mbps).
În prezent se foloseşte termenul de intranet pentru a desemna o reţea
locală la nivelul unei instituţii. Prin internet se înţelege reţeaua WAN întinsă în prezent
la nivel mondial. Ea este realizată prin interconectarea tuturor
reţelelor de pe glob pe baza familiei de protocoale TCP/IP. Prin interconecatare înţelegem faptul că există un nod
mulipunct care face parte din mai multe reţele fiind conectat în fiecare
reţea prin unul dintre punctele sale.
4.1.1 Modele de
protocoale
Comunicarea între calculatoarele dintr-o reţea are
nevoie de existenţa unor protocoale
de comunicare. În general, prin protocol înţelegem un set de reguli
şi convenţii stabilite între participanţii la o activitate
comună, în cazul de faţă transferul de informaţii între
calculatoare.
Fiind deosebit de complexe, protocoalele utilizate în
reţelele de calculatoare sunt proiectate pe niveluri sau straturi (layers). Fiecare nivel defineşte o
serie de servicii şi protocoalele corespunzătoare. Există astfel
mai multe modele de protocoale, cele
mai cunoscute sunt OSI şi TCP/IP.
Modelul OSI
(Open System Interconection) este mai mult un ghid decât o specificare. Este
structurat pe 7 niveluri. Enumerăm nivelurile de la cel mai de jos la cel
mai înalt.
Nivelul fizic
realizează transferul biţilor printr-un canal de comunicare. Apar
probleme precum : reprezentarea fizică a unui bit, dacă se poate face
transmisie simultană în ambele sensuri, stabilirea şi terminarea unei
conexiuni.
Nivelul
legăturii de date transmite cadre
de date (sute sau mii de biţi) de la sursă la destinaţie
şi cadre de confirmare de la destinaţie la sursă. Pentru
delimitare se ataşează secvenţe speciale de biţi la
începutul şi sfârşitul unui cadru.
Nivelul reţea
transferă pachete de date.
Fiecare pachet conţine atât datele care se transmit cât şi adresele
de identificare a sursei şi destinaţiei. Sunt cazuri când pachetele
traversează mai multe reţele pentru a ajunge de la sursă la
destinaţie.
Nivelul transport
acceptă date la nivelul sesiune. Se crează câte o conexiune de
reţea distinctă pentru fiecare conexiune de transport solicitată
de nivelul sesiune. La nivelul transport comunicarea se face între vecini
imediaţi în reţea. Această comunicare este capăt la capăt adică un program din calculatorul
sursă conversează cu un program din calculatorul destinaţie.
Nivelul sesiune
furnizează servicii suplimentare faţă de cel transport. Un
serviciu este controlul dialogului care stabileşte dacă se poate
realiza trafic simultan în ambele sensuri. Un alt serviciu este cel care
permite ca în caz de eşec, o
operaţie să poată fi reluată din punctul de întrerupere şi
nu de la început.
Nivelul prezentare priveşte sintaxa
şi semantica informaţiilor
transmise de la o aplicaţie la alta. Se stabileşte o
codificare standard a informaţiei transportate şi o standardizare a
structurilor de date.
Nivelul
aplicaţie conţine cea mai mare diversitate de protocoale deoarece
fiecare apliacţie poate defini propriul protocol.
Modelul TCP/IP este, de fapt, primul
apărut. A fost definit în legătură cu prima reţea de
calculatoare realizată în jurul anului 1970 la Departamentul
Apărării al SUA. Numele provine de la cele două protocoale
principale ale sale. Comaparând cu modelul OSI se poate observa că nu sunt
definite niveluri distincte pentru sesiune şi prezentare iar nivelurile
fizic şi legătură de date sunt cuprinse într-unul singur numit nod – reţea. Apare nivelul internet destinat
comunicării de pachete. Nodurile emit pachete de date care circulă
independent până la destinaţie. Ele pot ajunge la destinaţie în
altă ordine decât cea în care au fost trimise. Rearanjarea lor va fi
sarcina nivelurilor superioare.
Nivelul transport
corespunde celui de la modelul OSI. Sunt definite la acest nivel două
protocoale mai importante anume TCP
(Transmission Control Protocol) care realizează transmisii cu conexiune
şi UDP (User Datagram Protocol)
care corespunde transmisiilor fără conexiune de tip cap la cap.
Nivelul aplicaţie
apare imediat deasupra celui transport. La acest nivel cele mai cunoscute
protocoale sunt : TELNET pentru transmisii virtuale (sesiune la
distanţă), FTP pentru transfer de fişiere, SMTP pentru
poştă electronică, HTTP pentru hipertext (pagini Web).
Există o serie de probleme comune tuturor familiilor
de protocoale. Dintre acestea o vom menţiona pe aceeea care priveşte
serviciile pe care un nivel le oferă nivelurilor superioare. Ne vom referi
doar la serviciile pe care nivelul transport îl oferă nivelului
aplicaţie (in cazul modelului TCP/IP).
Aceste servicii sunt :
-
cu conexiune sau fără conexiune
-
secvenţiere
-
controlul erorilor
-
controlul fluxului
-
flux de octeţi sau mesaje
-
full-duplex sau half-duplex
Un serviciu cu conexiune presupune că
cele două programe de aplicaţie stabilesc o conexiune logică
înainte de a începe transferul de date. Se efectuează trei operaţii :
stabilirea conexiunii, transferul de mesaje (date) şi terminarea
conexiunii.
Serviciul fără
conexiune sau serviciu
de datagrame transmite independent fiecare mesaj. Mesajele trebuie să
conţină toate informaţiile necesare unui transfer efectuat în
condiţii optime.
Secvenţializarea presupune că receptorul primeşte pachetele de datele în ordinea
în care au fost trimise. Aceasta, în ciuda faptului că este posibil ca
două pachete consecutive să urmeze rute diferite şi pachetul
care a plecat al doilea să ajungă primul.
Controlul erorilor asigură faptul că programele aplicaţie primesc date
corecte, fără erori.
Controlul fluxului nu permite
emiţătorului să trimită date mai rapid decât poate
recepţiona destinatarul.
Serviciul de flux de
octeţi nu conţine informaţii care să
indice limitele mesajelor. Protocoalele cu conexiune lucrează de obicei în
acest mod.
Conexiunea
full-duplex permite transfer simultan în ambele direcţii. În
cazul half-duplex se permite la un
moment dat transfer numai într-o direcţie.
4.1.2
Familia de protocoale utilizate în Internet
Protocoalele utilizate în prezent în
Internet permit interconectarea oricăror tipuri de reţele inclusiv reţelele LAN şi WAN. Protocoalele
Internet reprezintă o suită de protocoale dintre care cele mai
cunoscute sunt TCP şi IP. Aceste două protocoale sunt de nivel
inferior. Suita de protocoale Internet conţine şi protocoale de nivel
superior care specifică aplicaţii comune precum poşta
electronică, transferul de fişiere, accesul la fişiere hipertext
(pagini Web), etc. Le vom enumera în ordinea crescătoare a plasării
lor pe niveluri în comparaţie cu medelul OSI.
Protocoalele ARP (Address Resolution Protocol)
şi RARP (Reverse Address Resolution) se pot plasa între nivelurile 2 şi
3 din OSI. ARP realizează corespondenţa între o adresă Internet
şi o adresă hardware iar RARP realizează corespondenţa
inversă.
La nivelul 3 OSI sunt plasate
protocoalele IP (Internet Protocol) şi ICMP
(Internet Control Message Protocol).
Protocolul IP pune la dispoziţia protocoalelor de nivel superior un
serviciu de transfer de pachete. ICMP este utilizat pentru transmiterea
informaţiilor de comandă şi de eroare între componentele
reţelei.
Protocoalele TCP şi UDP corespund
nivelului 4 din OSI. Protocolul TCP este orientat pe conexiune şi
transmite fluxuri de octeţi, full-duplex. Pe acest protocol se
bazează cele mai multe programe de aplicaţii din Internet. UDP este
un protocol fără conexiune destinat transmiterii de datagrame. Este
utilizat în aplicaţiile mai simple din Internet în care fiabilitatea
datelor nu este o cerinţă majoră.
Ultimelor trei niveluri din OSI (Sesiune, Prezentare,
Aplicaţie) le corespund protocoalele FTP,
Telnet , SMTP (Simple Mail Transfer
Protocol), SNMP (Simple Network
Management Protocol). Protocolul FTP este dedicat transferării de
fişiere. Telnet este protocolul care serveşte la realizarea
sesiunilor de lucru la distanţă. SMTP oferă servicii de
poştă electronică. SNMP semnalează apariţia
condiţiilor anormale din reţea şi setează anumiţi
parametri de lucru ai reţelei.
4.1.2.1
Nivelul IP
Protocolul IP transmite datagrame de la o adresă
sursă la o adresă destinaţie. Pe lângă datele care se
transmit, o datagramă conţine adresa sursei şi cea a
destinaţiei. În acest fel livrarea datagramelor se poate face independent.
Pentru a identifica reţelele şi nodurile,
protocolul IP foloseşte adrese Internet reprezentate pe 32 de biţi.
Aceste adrese sunt alocate în mod unic la nivel global de către o
autoritate centrală şi mai multe autorităţi regionale.
Orice nod conectat într-o reţea TCP/IP trebuie
să aibă o adresă IP. Adresele IP sunt divizate în 5 clase de
adrese, A, B, C, D, E, în funcţie de modul cum sunt utilizaţi cel 4
octeţi ai adresei. Comună este scrierea, fiecare octet fiind notat
prin numărul zecimal corespunzător iar cele 4 numere sunt separate
prin punct, spre exemplu 172.16.122.204. În general, prima parte a adresei
identifică reţeaua iar ultima parte identifică nodul (hostul).
Adresele de clasă A se remarcă prin faptul că primul număr poate fi
maxim 127. Se pot adresa maxim 126 de reţele. Pentru desemnarea nodurilor
se folosesc 24 de biţi deci se pot adresa 16 milioane de noduri. Astfel de
adrese caracterizează organizaţii mondiale sau companii
transnaţionale.
Adresele de clasă B folosesc 16 biţi pentru a identifica o reţea şi
restu de 16 pentru nodurile din cadrul reţelei. Primii doi biţi din
zona destinată reţelei sunt 10 astfel că primul octet este între
128 şi 191. Numărul de reţele ce se pot adresa sunt cca 2e14,
fiecare reţea putând avea peste 65.000 de noduri. Astfel de adrese au fost
date marilor companii sau universităţi.
Adresele de clasă C folosesc 24 de biţi pentru a desemna reţeaua şi 8
pentru nod. Primul octet este cu valori între 192 şi 223. Se pot
identifica cca. 2 milioane de reţele, fiecare reţea având maxim 254
noduri. Sunt cele mai alocate adrese în prezent.
Adresele de clasă D au primul octet cu valori între 224 şi 239. Se numesc adrese
multicast şi se folosesc în aplicaţii în care se emit mesaje ce ajung
simultan la mai mulţi receptori.
Adresele de clasă E sunt rezervate pentru viitor. Ele au valori peste 240.0.0.0.
4.1.2.2
Nivelul TCP şi UDP
Există posibilitatea ca într-un nod mai multe
procese utilizator să lucreze simultan cu protocolul TCP sau UDP fiind
necesar să se identifice aceste procese. Identificarea se face prin
interemediul porturilor care se reprezintă fizic pe 16 biţi. Fiecare
din cele două protocoale pot adresa 65536 porturi diferite. Numerele de
port apar în antetele TCP şi UDP şi împreună cu adresele IP
identifică unic destinţia mesajelor.
Prin convenţie numerele de port mai mici decât 1024
se atribuie unor servicii cunoscute. Programele server îşi
înregistrează numerele de port pentru a fi găsite la locaţii
fixe. Programele client capătă numere de port efemere toate mai mari
decît 1024.
4.1.2.3 Nivelul
aplicaţie
Protocoalele de nivel aplicaţie sunt deja multe
şi sunt în continuă creştere. Amintim câteva dintre acestea.
Serviciul DNS
al numelor de serviciu se referă la denumirea simbolică a unui nod
şi la corespondenţa dintre aceste nume şi adresele Internet.
Există domenii de nume de nivel superior. Fiecare
din acestea se împarte ăn subdomeni care la rândul lor se împart în
subdomeni…
La nivelul superior (nivelul 1) sunt două categorii
de domenii : generice şi nume de ţări. Domenii generice sunt : com (comercial), edu (educaţional), gov
(guvernamental), org
(organizaţii non-profit), etc. Domeniile nume de ţări constau din
2 litere ex. ro (Romania), uk (Marea Britanie), fr (Franţa). Organizarea de la
nivelul 2 depinde de administrarea de la nivelul superior.
Numele de domenii sunt separate de punct. Nu se face
distincţie între literele mari şi mici. Lungimea unei componente nu
depăşeşte 64 caractere iar întregul nume nu
depăşeşte 255.
Serviciul de poştă electronică (e-mail)
foloseşte protocolul SMTP care este dedicat transmiterii de
mesaje cu conţinut ASCII. Un client este identificat printr-un nume de
login şi un nume de domeniu : numelogin@numedomeniu. Serviciul
presupune transmiterea şi recepţia mesajelor, editarea mesajelor,
accesul la mesajul recepţionat în cutia poştală (mailbox),
salvarea mesajelor recepţionate.
Portul rezervat este 25.
World Wide Web (WWW) este
cea mai cunoscută aplicaţie Internet. Permite transmiterea de
documente, desene, fotografii. În WWW se face distincţie între partea de
client şi partea de server a aplicaţiei. Un server WWW
gestionează documente şi le trimite clienţilor la cererea
acestora. Pentru identificarea documentelor s-a propus conceptul de locator universal de resurse (Universal
Resource Locator – URL) care se specifică prin
protocol : //calculator:port/nume_de
_cale#eticheta
unde :
-
protocol specifică cum se
comunică cu documentul solicitat ; se folosesc
http
pentru documente hipertext, ftp
pentru transfer de fişiere sau file
pentru acces la fişier local.
-
calculator este numele nodului pe care se află documentul
-
port precizează portul la care rulează serverul pentru documentul
specificat
-
nume_de_cale este numele de cale
al unui fişier
-
etichetă este un element obţional care poate indica o
locaţie din cadrul documentului.
4.2 Programarea cu sockets în Java
Java este un limbaj dedicat programării pentru
reţele. Prin intermediul bibliotecii java.net se
implementează o interfaţă mai veche de programare, aceea
bazată pe socluri (sockets).
Soclurile sunt un nivel de abstractizare care permite o
programare uşoară a reţelelor de calculatoare. Se poate face o
analogie între operaţiile cu fişiere într-un sistem de calcul şi
comunicarea în reţea. Noţiunii de fişier îi corespunde soclul
dar spre deosebire de fişier, soclul există doar în memorie. Câteva
particularităţi ale lucrului în reţea sunt :
-
relaţia client-server este tipică
aplicaţiilor de reţea; ea este
asimetrică, deci un program trebuie să cunoască rolul pe care îl
joacă în reţea;
-
comunicarea în reţea se face cu conexiune, fiind
asemănătoare cu transferurile din cazul fişierelor sau
fără conexiune caz în care fircare operaţie poate implica un alt
proces , eventual pe un alt calculator;
-
se
specifică mai mulţi parametri decât în cazul fişierelor;
Interfaţa de programare cu socluri din Java permite comunicarea cu programe
scrise în alte limaje care folosesc interfeţe de tip soclu. Teoretic
această interfaţă este independentă de protocoalele de
comunicaţie folosite dar practic, este strâns legată de protocoalele
TCP/IP.
4.2.1
Utilizarea adreselor Internet şi a URL-urilor
Pentru lucrul cu identificatorii simbolici (nume) şi
cei numerici (adrese numerice) ai calculatoarelor din reţeaua Internet,
limbajul Java definşte clasa InetAddress care nu are
constructori expliciţi. Menţionăm câteva metode ale acestei
clase :
-
public static InetAddress getByName(String Host) throws
UnknownHostException determină adresa IP a unui calculator căruia i
se cunoaşte numele (forma simbolica ex. www.ucv.ro). Se poate
preciza adresa Internet (numerică) sub forma unui şir de caractere, ex.
“183.224.7.202”. Deoarece
întoarce un obiect de tip InetAddress, metoda poate juca rolul de constructor;
-
public static InetAddress getLocalHost() throws UnknownHostException
determină adresa IP a calculatorului local. Poate juca rol de constructor;
-
public static InetAddress[] getAllByName(String
Host) throws UnknownHostException întoarce într-un tablou toate adresele IP ale calculatorului specificat prin nume;
-
public String getHostName() întoarce numele calculatorului căruia îi corespunde
obiectul InetAddress curent. Dacă returnează null atunci obiectul InetAddress care a
apelat metoda se referă la una dintre adresele de reţea ale
calculatorului local;
-
public String getHostAddress() întoarce adresa IP a obiectului InetAddress curent sub
forma %d.%d.%d.%d.
Următorul program determină numele simbolic al
calculatorului local şi afişează toate eventualele nume
simbolice şi adrese IP ale calculatorului local dacă acesta este
multi-punct (nod în mai multe reţele).
import java.net.*;
import java.io.*;
public class TestInet
{
public
static void main (String[] args)
{
try{
InetAddress
adresaLocala=InetAddress.getLocalHost();
System.out.println("Numele
gazdei : "+adresaLocala.getHostName());
InetAddress
vectorAdrese[] =
InetAddress.getAllByName(adresaLocala.getHostName());
System.out.println("Adresele
IP ale statiei sunt :\n");
for(int
i=0;i<vectorAdrese.length;i++)
System.out.println(vectorAdrese[i].getHostName()+"
+ "+vectorAdrese[i].getHostAddress());
System.in.read();
}
catch (IOException
e){System.err.println("Exceptie : "+e);}
}
}
Clasa URL
permite lucrul cu locatori universali de resurse, URL. În clasa URL sunt 4 constructori :
-
public URL(String
protocol, String host, int port, String file) throws MalformedURLException. Valoarea port = -1 precizează
portul standard utilizat de protocolul specificat.
-
public URL(String
protocol, String host, String file) throws MalformedURLException. Noul obiect URL foloseşte portul
standard pentru protocolul specificat.
-
public URL(URL
context,String spec) throws MalformedURLException. Se crează un obiect URL prin interpretarea
string-ului spec în contextul specificat.
-
public URL(String
spec) throws MalformedURLException crează
un obiect URL plecând de la reprezentarea precizată.
Alte metode :
- public int getPort() întoarce numărul de port al
obiectului URL curent sau –1 dacă
numărul de port nu este specificat în respectivul URL;
-
public String
getHost()întoarce numele de
host al URL-ului curent;
-
public String
getFile()întoarce numele de
fişier al URL-ului curent;
-
public String
getProtocol()întoarce
protocolul utilizat de URL-ul curent;
-
public boolean
sameFile(URL other)compară
URL-ul curent cu un altul;
-
public String
toExternalForm()construieşte
o reprezentare sub formă de String a URL-ului curent;
-
public URLConnection
openConnection()throes IOException returnează
un obiect URLConnection care este o conexiune la obiectul URL
curent;
-
public final
InputStream openStream()throws IOException
deschide o conexiune spre URL-ul curent şi întoarce un flux pentru a citi
din această conexiune.
Prin
intermediul clasei URL se pot crea legături de comunicare între o
aplicaţie şi o resursă putându-se prelua documente de pe orice
server Web. Următorul program realizează o conexiune cu URL-ul
specificat în linia de comandă. Se copiază fişierul specificat
în declaraţia de URL într-un fişier local al cărui nume este dat
tot în linia de comandă. Instanţei URL i se asociază un flux de
intrare deschis prin apelul metodei openStream.
import java.net.*;
import java.io.*;
public class TestUrl
{
public
static void main (String[] args)
{
try{
URL
url=new URL(args[0]);
BufferedReader
br = new BufferedReader(new InputStreamReader(url.openStream()));
BufferedWriter
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(args[1])));
String
line=new String();
while
((line=br.readLine ())!=null){
bw.write(line);
bw.newLine();
}
bw.close();
}catch(Exception
e){System.err.println("Exceptie : "+e);}
}
}
Clasa URLConnection este cea
mai utilizată pentru preluarea documentelor din Internet. Spre deosebire
de instanţele URL, instanţele clasei URLConnection pot citi şi scrie la aceeaşi
resursă. Astfel, clasa URLConnection permite transmiterea unei cereri
către o aplicaţie server şi preluarea răspunsului de la
aceasta. Cele două programe care urmează trimit către un servlet
o cerere de adunare a două numere întregi. Cererea este furnizată sub
forma şirului de caractere
“numar=123&numar=456”
iar răspunsul servlet-ului Sumator
va fi “suma este : 579”.
Primul program transmite o cerere
GET incorporată în cadrul adresei URL, spre exemplu http://nume_host:8080/servlet/Sumator?numar=123&numar=456. Adresa
poate fi dată în linia de comandă a programului sau precizată
direct ca şir de caractere constant (instrucţiunea trecută în
comentariu). Apelul metodei openConnection a clasei URL crează un
obiect URLConnection şi
deschide conexiunea cu resursa specificată. Prin intermediul acestei
instanţe se deschide un flux de intrare asociat fluxului de ieşire al
servlet-ului.
import java.io.*;
import java.net.*;
public class UrlWrite1{
public
static void main(String[] args) throws Exception {
try{
String
adrURL=new String(args[0]);
URL
url=new URL(adrURL);
//URL
url = new URL("http://name:8080/servlet/Sumator?numar="+"456"+"&numar="+"123");
URLConnection
connection = url.openConnection();
InputStream
in = connection.getInputStream();
BufferedReader
input = new BufferedReader(new InputStreamReader(in));
String
line = input.readLine();
System.out.println(line);
input.close();
System.in.read();
}catch(IOException e)
{
System.err.println("Eroare
I/O "+e);
}
}
}
Al doilea program transmite o cerere POST. El funcţionează
după schema clasică :
1. Crează
un URL.
2. Deschide o
conexiune URLConnection.
3. Setează
facilităţile de scriere la URLConnection (metoda setDoOutput).
4. Deschide un
flux de ieşire la conexiunea stabilită (metoda getOutputStream). Acesta
este conectat la intrarea standard a servlet-ului.
5. Scrie la
fluxul de ieşire şi apoi îl închide.
6. Deschide un
flux de intrare la conexiunea stabilită (metoda getInputStream). Acesta
este conectat la ieşirea standard a servlet-ului.
7. Citeşte
de la fluxul de intrare şi apoi îl închide.
import java.io.*;
import java.net.*;
public class UrlWrite2
{
public
static void main(String[] args) throws Exception {
try{
URL
url = new URL("http://name:8080/servlet/Sumator");
URLConnection
connection = url.openConnection();
connection.setDoOutput(true);
PrintWriter
out = new PrintWriter(connection.getOutputStream());
out.println("numar=5&numar=7");
out.close();
InputStream
in = connection.getInputStream();
BufferedReader
input = new BufferedReader(new InputStreamReader(in));
String
line = input.readLine();
System.out.println(line);
input.close();
System.in.read();
}catch(IOException e)
{
System.err.println("Eroare
I/O "+e);
}
}
}
4.2.2 Comunicarea orientată pe conexiune : clasele
Socket şi ServerSocket
În cele mai multe cazuri
aplicaţiile din Internet folosesc o comunicaţie sigură, un canal
de comunicaţie care oferă certitudinea că mesajele care se
transmit sunt recepţionate în aceeaşi ordine în care au fost trimise.
Aceasta este comunicarea prin conexiune. După stabilirea conexiunii
aplicaţiile folosesc canalul de comunicaţie prin intermediul
fluxurilor de date. Un canal are nevoie de două fluxuri
unidirecţionale (unul de citire, altul de scriere) folosite pentru comunicarea
într-un singur sens. La capetele canalului se află câte un soclu (socket)
care asigură transmiterea şi recepţia datelor. Cele două
socluri identifică unic conexiunea care s-a stabilit şi permit
accesul programatorului la conexiune.
Majoritatea aplicaţiilor din Internet sunt de tip
client-server. Ele se împart în două grupuri distincte aplicaţiile client şi cele server. Programele server oferă
servicii clienţilor acceptând cererile de comunicare ale acestora.
Clientul este programul care iniţiază comunicarea şi a
cărui funcţionare se bazează pe serviciile oferite de server.
Programul client crează un soclu specificându-i adresa programului server
şi trimite cererea de conexiune spre acesta. Programul server
aşteaptă astfel de cereri iar
după acceptarea unei cereri crează soclul pereche. Conexiunea este
stabilită şi transferul de date poate începe.
Folosind clasele Socket şi ServerSocket în limbajul
Java se pot scrie aplicaţii
client-server bazate pe protocolul TCP/IP. Din
existenţa celor două clase deducem faptul că în Java se face distincţie între
socketul client şi cel al serverului.
Clasa Socket se referă la
socket-urile client. Constructorii acestei clase sunt :
-
public Socket(String host, int port) throws UnknownHostException,
IOException crează un socket şi-l conectează la
calculatorul şi portul specificat;
-
public Socket(InetAddress address, int port) throws IOException; identitatea calculatorului la distanţă se
precizează printr-un obiect InetAddress;
-
public Socket(String host, int port, InetAddress localaddress,int localport
) throws IOException crează un socket şi-l conectează la
distanţă la calculatorul şi portul specificat. Socketul este
conectat şi local la adresa IP şi portul specificate;
-
public Socket(InetAddress address, int port, InetAddress localaddress,int
localport ) throws IOException. Calculatorul la distanţă este precizat prin
adresa IP şi portul specificate. Socketul este conectat şi local la
adresa IP şi portul specificate.
Principalele
metode sunt :
-
public InputStream getInputStream() throws IOException returnează un
flux de intrare ataşat socket-ului curent. Acest flux este utilizat pentru
a citi de la socket-ul respectiv;
-
public OutputStream getOutputStream() throws IOException
returnează un flux de ieşire ataşat socket-ului curent. Acest
flux este utilizat pentru a scrie la socket-ul respectiv;
-
public synchronized void close() throws IOException închide socket-ul
curent;
-
public InetAddress getInetAddress() returnează adresa la
distanţă la care este conectat socket-ul curent;
-
public InetAddress getInetAddress() returnează adresa locală la
care este conectat socket-ul curent;
-
public int getPort() returnează portul la distanţă la care
este conectat socket-ul curent;
-
public int getLocalPort() returnează portul local la care este conectat
socket-ul curent.
Clasa ServerSocket are trei constructori
dintre care cel mai utilizat este
public ServerSocket(int port) throws IOException
care crează un soclu server la portul specificat. Cea
mai importantă metodă a clasei este metoda
public Socket accept()
care permite soclului server să aştepte cereri de
conexiune de la clienţi şi apoi să le accepte. Soclul
(Socket-ul) returnat de metodă este soclul asociat clientului care a
făcut cererea; prin intermediul acestuia se va desfăşura
comunicarea.
Următoarele
două programe, client şi server, sunt o primă exemplificare a
celor spuse mai sus.
Programul
server aşteaptă o cerere de conexiune din partea unui client.
Instanţa ServerSocket este folosită pentru aceasta. După ce
acceptă conexiunea (desemnând aceasta prin crearea unui soclu pereche
pentru cel al clientului) îi asociază un flux de date de tip ieşiere,
DataOutputStream, prin intermediul căruia va transmite date către
client. Aceste date sunt preluate dintr-un fişier specificat ca argument
în linia de comandă la lansarea în execuţie a severului.
import
java.net.*;
import
java.io.*;
public
class Server1
{
public static void main (String[] args)
{
try{
ServerSocket ss=new ServerSocket(15812);
Socket client=ss.accept();
DataOutputStream out = new DataOutputStream(client.getOutputStream());
BufferedReader br = new BufferedReader(new
FileReader(args[0]));
String line="";
while ((line=br.readLine())!=null){
System.out.println(line);
out.writeUTF(line);
}
out.writeUTF("Serverul a terminat
!");
client.close();
}catch
(Exception e){System.out.println("Eroare : "+e);}
}
}
Programul
client îşi declară un soclu şi îl asociază serverului aflat
pe calculatorul args[0] şi la portul 15812 (acelaşi cu cel declarat în
programul server). Implicit sistemul lansează cererea de conexiune
către acest calculator. Soclului i se asociază un flux de intrare,
DataInputStream, prin care vor fi preluate date de la server. Datele preluate
vor fi scrise de către client într-un fişier al cărui nume este
specificat în args[1].
import
java.net.*;
import
java.io.*;
public
class Client1
{
public static void main (String[] args)
{
try{
Socket s=new Socket(args[0],15812);
DataInputStream intrare = new
DataInputStream(s.getInputStream());
BufferedWriter bw=new BufferedWriter(new
FileWriter(args[1]));
boolean gata=false;
String line="";
while (!gata){
line=intrare.readUTF();
System.out.println(line);
bw.write(line);
bw.newLine();
if ((line.startsWith("Serverul a
terminat !"))) gata=true;
}
s.close();
bw.close();
}catch(Exception
e){System.out.println("Eroare clienr: "+e);}
}
}
Programul
serever pe care îl prezentăm în continuare este de tip multifir. El ia în
consideraţie faptul că la server pot sosi cereri de transfer date de
la mai mulţi clienţi în acelaşi timp.
import
java.io.*;
import
java.net.*;
import
java.util.*;
public
class Server2
{
protected final int portServer=15000;
protected ServerSocket socketAscultare;
public Server2(){
try{
socketAscultare=new
ServerSocket(portServer);
}catch(BindException
e1){
System.err.println("Exista un alt
server pe portul "+portServer);
System.exit(1);
}catch(IOException
e2){
System.err.println("Eroare la crearea
soclului de ascultare ");
System.exit(1);
}
}
public Socket asculta(){
Socket
socket=null;
try{
socket=socketAscultare.accept();
}
catch(IOException
e){
System.err.println("Eroare la
receptia cererii de conexiune");
System.exit(1);
}
System.out.println("Client
acceptat !");
return
socket;
}
public static void main (String[] args)
{
Server2
server=new Server2();
try{
while (true){
Socket socketConexiune=server.asculta();
new Thread(new
Conexiune(socketConexiune));
}
}
finally{
try{
server.socketAscultare.close();
}
catch(IOException e){
System.err.println("Eroare la inchiderea
soclului de ascultare");
}
}
}
}
class
Conexiune extends Thread
{
protected Socket socketConexiune;
protected String numefisServer;
public Conexiune(Socket socketConexiune){
this.socketConexiune=socketConexiune;
this.start();
}
public void run(){
try{
BufferedReader fluxDeLaClient=
new BufferedReader(new
InputStreamReader(socketConexiune.getInputStream()));
numefisServer=fluxDeLaClient.readLine();
Vector vector=new Vector(100,10);
synchronized(numefisServer){
File file=new File(numefisServer);
if (file.isDirectory()){
vector.addElement(
new
String(numefisServer+" este un director cu continutul
:"+"\n"));
String[]
rezumat=file.list();
for(int
i=0;i<rezumat.length;i++)
vector.addElement(rezumat[i]);
}
else if (file.isFile()){
vector.addElement(
new
String(numefisServer+" este un fisier cu continutul
:"+"\n"));
String
linie="";
BufferedReader
fisInput=
new
BufferedReader(new FileReader(file));
while((linie=fisInput.readLine())!=null)
vector.addElement(linie);
fisInput.close();
}
else vector.addElement(
new
String(numefisServer+" nu este nume de director sau fisier"));
}
DataOutputStream fluxSpreClient=
new DataOutputStream(socketConexiune.getOutputStream());
for(int i=0;i<vector.size();i++)
fluxSpreClient.writeUTF((String)vector.elementAt(i));
fluxSpreClient.writeUTF("Gata");
fluxSpreClient.close();
fluxDeLaClient.close();
System.out.println("Gata
client!");
}
catch(IOException
e){
System.err.println("Eroare de
comunicare ");
}
finally{
try{
socketConexiune.close();
}catch(IOException e){
System.err.println("Eroare la
inchidere conexiune ");
}
}
}
}
În constructorul
clasei Server2 se instanţiază soclul socketAscultare unde aplicaţia “aşteaptă” cereri de
conexiune. În ciclul infinit din metoda main aplicaţia
aşteaptă cereri de transfer date de la clienţi. Acceptarea unei
astfel de cereri este urmată de instanţierea unui nou fir de execuţie
al clasei Conexiune. Această clasă conţine metodele care se
ocupă de transferul de date cu clientul. Se poate transmite către
client conţinutul unui fişier sau al unui director.
4.2.3 Comunicarea fără conexiune : clasele
DatagramSocket şi DatagramPacket
În pachetul java.net sunt prevăzute două clase care permit
comunicarea bazată pe protocolul UDP. Acestea
sunt DatagramSocket şi DatagramPacket.
Instanţele clasei DatagramPacket sunt pachetele datagramă care se
transmit între server şi client. Clasa are doi constructori.
public DatagramPacket(byte buf[],int
length) instanţiază
un pachet datagramă destinat recepţionării pachetelor de lungime
length în zona buf. Este necesar ca length să fie
cel puţin egal cu buf.length.
public DatagramPacket(byte buf[], int length,
InetAddress adr, int port) instanţiază
un pachet datagramă destinat trimiterii pachetelor de lungime length la calculatorul şi portul specificate.
Clasa conţine metode care
permit preluarea, din pachetul datagramă recepţionat, a unor
informaţii precum : adresa IP (metoda getAddress()) şi portul (metoda getPort()) corespunzătoare calculatorului de la care s-a făcut recepţia cât şi
partea de date a datagramei (metoda getData()). Metodele setAddress(InetAddress adr), setPort(int port) şi setData(byte buf[]) permit setarea elementelor de mai sus în pachetul
datagramă ce urmează să fie trimis.
Obiectele clasei DatagramSocket sunt
soclurile prin intermediul cărora se face transmisia în ambele sensuri.
Clasa are doi constructori.
public DatagramSocket(int port) throws SocketException - care crează un soclu
datagramă şi îl asociază portului specificat de pe calculatorul
local.
public DatagramSocket(int port, InetAddress adr) throws
SocketException - care deschide un soclu datagramă şi îl
asociază portului şi calculatorului local specificate.
Trei metode ale clasei sunt mai
importante :
public void send(DatagramPocket dgp) throws IOEception - transmite
de la soclul curent un pachet datagramă. Pachetul trebuie pregătit în
prealabil astfel încât să conţină datele transmise, adresa IP şi portul calculatorului unde trebuie să
ajungă datagrama.
public synchronized void receive(DatagramPacket dgp)
throws IOException - care recepţionează un pachet datagramă
la soclul curent. Recepţia se face în pachetu specificat. Dacă
datagrama recepţionată are lungimea mai mare decât lungimea
tamponului destinat recepţiei atunci datagrama va fi trunchiată.
Metoda blochează firul de execuţie curent până când se recepţionează
o datagramă.
public void close() - închide soclul datagramă
curent.
Aplicaţia client-server care
urmează exemplifică comunicaţia fără conexiune.
Într-un ciclu infinit, sereverul
aşteaptă cereri de transmisie date de la posibili clienţi
(apelul metodei receive()). Pentru fiecare nou client se instanţiază
un fir de execuţie al clasei DatagramServer
precizându-se adresa şi portul acestuia (preluate din pachetul
datagramă recepţionat). Clasa DatagramServer
implementează transmisia datelor (conţinutul unui fişier text)
prin protocolul UDP. În metoda run()se
instanţiază câte un pachet datagramă pentru fiecare linie din
fişier specificându-se în declaraţie adresa şi portul clientului
pentru care se face transmisia.
import
java.io.*;
import
java.net.*;
public
class DatagramServer implements Runnable
{
private DatagramSocket dgSocket;
private InetAddress inetaddressClient;
private int portClient;
private int dimbuf;
private String filename;
public DatagramServer(int
dimbuf,DatagramSocket dgSocket,DatagramPacket dgp){
this.dimbuf=dimbuf;
this.inetaddressClient=dgp.getAddress();
this.portClient=dgp.getPort();
this.dgSocket=dgSocket;
this.filename=new
String(dgp.getData());
}
public void run(){
byte[]
buff=new byte[dimbuf];
try{
synchronized(filename){
FileInputStream input=new
FileInputStream(filename);
while (input.read(buff)!=-1){
DatagramPacket
dgpSend=new DatagramPacket(buff,buff.length,inetaddressClient,portClient);
dgSocket.send(dgpSend);
}
buff=(new
String("GATA")).getBytes();
DatagramPacket dgpSend=new
DatagramPacket(buff,4,inetaddressClient,portClient);
dgSocket.send(dgpSend);
dgSocket.close();
input.close();
}
}
catch(IOException
e){e.printStackTrace();}
}
public static void main (String[] args)
{
if
(args.length != 1){
System.out.println("Utilizare :
DatagramServer <numar port server>");
return;
}
int
dimbuf=256;
byte[]
buff=new byte[dimbuf];
DatagramPacket
dgpReceive=new DatagramPacket(buff,dimbuf);
try{
DatagramSocket dgSocket=new DatagramSocket(Integer.parseInt(args[0]));
while (true){
dgSocket.receive(dgpReceive);
(new Thread(new
DatagramServer(dimbuf,dgSocket,dgpReceive))).start();
}
}catch(IOException
e1){e1.printStackTrace();}
}
}
Partea de client este implementată
de clasa DatagramClient. În constructorul
clasei se instanţiază un soclu datagramă care va fi folosit
pentru transmisia şi recepţia datagramelor.
Metoda conexiune() iniţiază transferul de date cu serverul
trimiţându-i acestuia un singur pachet datagramă în care este trecut
numele fişierului de pe server al cărui conţinut se doreşte
a fi recepţionat. În metoda receptie(), prin
intermediul aceluiaşi pachet datagramă, se recepţionează
toate liniile acestui fişier. De fiecare dată, din datagramă
este preluată linia curentă din fişier şi scrisă
într-un fişier local al clientului.
import
java.io.*;
import
java.net.*;
//
Program client care preia de la server un fisier text.
//
Se foloseste protocolul UDP.
public
class DatagramClient
{
protected int portServer, dimbuff;
protected InetAddress inetaddressServer;
protected DatagramSocket dgSocket=null;
protected String numefisServer;
public DatagramClient(String numeServer,
int portServer, int dimbuff, String numefis){
this.portServer=portServer;
this.dimbuff=dimbuff;
this.numefisServer=new
String(numefis);
try{
inetaddressServer=InetAddress.getByName(numeServer);
dgSocket = new DatagramSocket();
}
catch(IOException
e){e.printStackTrace();}
}
public void conexiune(){
byte[]
buff=new byte[dimbuff];
buff=(new
String(numefisServer)).getBytes();
DatagramPacket
dgpSend = new DatagramPacket(buff,buff.length,inetaddressServer,portServer);
try{
dgSocket.send(dgpSend);
}catch(IOException
e){e.printStackTrace();}
}
public void receptie(){
byte[]
buff=new byte[dimbuff];
DatagramPacket
dgpReceive=new DatagramPacket(buff,dimbuff);
boolean
ok=true;
try{
BufferedWriter output=
new
BufferedWriter(new FileWriter("myfis.txt"));
while (ok){
dgSocket.receive(dgpReceive);
String linie=new
String(dgpReceive.getData());
if (!linie.startsWith("GATA")){
output.write(linie);
output.newLine();
System.out.println(linie);
}
else ok=false;
}
dgSocket.close();
output.close();
}catch(IOException
e){e.printStackTrace();}
}
public static void main (String[] args)
{
if(args.length
!= 3){
System.out.println("DatagramClient
nume_Server port_Server nume_fisier_Server");
return;
}
DatagramClient
client=new DatagramClient(args[0],Integer.parseInt(args[1]),256,args[2]);
client.conexiune();
client.receptie();
}
}