In aceasta lucrare de laborator vor fi acoperite urmatoarele probleme:
- Utilizarea mediului de dezvoltare integrat
(IDE) BlueJ (vezi si Tutorial
BlueJ in limba romana)
- Crearea obiectelor si invocarea metodelor Java cu
BlueJ
- Tipuri de date Java si starea unui obiect
- Comportamentul unui
obiect si interactiunea
obiectelor
- Codul sursa Java, editarea si compilarea lui in
BlueJ
- Fire
de executie (threads)
- Programe de lucru cu fire de executie
- Socket-uri flux (TCP) in
Java:
- Incapsularea adreselor IP in Java. Utilizarea clasei java.net.InetAddress
- Socket-uri pentru fluxuri (conexiuni) TCP. Utilizarea clasei java.net.Socket
- Socket-uri pentru servere TCP. Utilizarea clasei java.net.ServerSocket
- Programe de lucru cu socket-uri flux
(TCP) – clienti si servere
Programele de calcul sunt secvente de instructiuni care prin executia
lor pe sisteme (masini) de calcul rezolva
probleme aparute in diferite domenii ale lumii reale. Programele sunt
solutii ale acestor probleme.
Un program scris
intr-un limbaj orientat spre obiecte
(OO) reprezinta un model
al unei parti din lumea reala.
Elementele care compun modelul (numite obiecte software) sunt construite prin analogie cu entitati care apar in lumea reala (obiecte reale, concepte).
Obiectele software obtinute prin modelare
(analogie cu lumea reala) trebuie reprezentate
in limbajul de programare.
Ca si in cazul obiectelor si conceptelor din lumea
reala, obiectele software pot fi categorisite.
O constructie software (structura complexa) numita clasa descrie intr-o forma abstracta toate
obiectele de un tip particular.
La fel ca in lumea reala, in care obiectele si conceptele sunt clasificate pe
baza atributelor esentiale pe care le au acestea, clasele reprezinta obiecte software care au
atribute similare (atributele
fiind elemente de date, variabile interne, proprietati care caracterizeaza
obiectele).
De exemplu, la intrarea intr-un laborator noi clasificam
obiectele individuale: banci, studenti, si interactionam cu ele pe baza
categoriei lor fara a fi necesar sa le cunoastem toate detaliile (atributele).
Clasa defineste elementele
comune, numite in Java campuri
(atribute in teoria orientarii spre
obiecte) si metode (operatii in teoria orientarii spre
obiecte), ale unei categorii de obiecte. Clasa reprezinta astfel tipul de date al obiectelor.
De exemplu, toate obiectele clasificate ca banci au latime,
inaltime, pozitie in sala, etc. Clasa Banca poate fi definita prin campurile ei ca:
class Banca latime inaltime pozitie |
Obiectul este un exemplu specific al unei clase,
numit instanta a clasei, in
care fiecare camp are o anumita valoare, clasa
fiind tiparul dupa care sunt
construite obiectele.
De exemplu, o sala poate avea 30 de obiecte clasificate ca
banci, fiecare banca avand propriile valori ale atributelor latime, inaltime,
etc. Doua obiecte banca, banca1 si banca2, sunt instante (exemple) diferite ale aceleiasi
clase Banca, au in comun atributele, dar pot avea
diferite valori ale lor:
banca1 |
|
banca2 |
latime 80 cm inaltime 70 cm pozitie rand 2, a 3-a |
|
latime 120 cm inaltime 70 cm pozitie rand 4, a 6-a |
In laborator: 1. Numiti
2 clase de obiecte din imediata voastra vecinatate in acest moment. 2. Scrieti numele fiecarei
clase si apoi numele a cate trei atribute evidente ale fiecarei
clase. 3. Pentru cate un obiect din fiecare clasa definiti valori ale
fiecaruia dintre cele trei campuri. |
Pentru a crea noi instante ale unor banci reale trebuie
folosite profile de lemn, metal, etc. Atunci cand modelam bancile intr-un
program de calcul putem sa cream
doua banci (in limbajul Java) folosind urmatoarele portiuni de cod:
new Banca() |
new Banca() |
Pentru a putea trata (accesa) distinct cele doua obiecte, este necesara utilizarea a doua nume
diferite pentru cele doua obiecte,
ceea ce ar corespunde codului Java:
Banca banca1 = new Banca() |
Banca banca2 = new Banca() |
In laborator: 1. Dati nume cate unui obiect Java din
fiecare dintre cele doua clase anterior numite. 2. Scrieti codul Java pentru crearea celor doua obiecte. |
In laborator: 1. Lansati in executie mediul de dezvoltare BlueJ. 2. Deschideti proiectul numit shapes. I. Click pe
meniul Project, apoi selectati Open Project … (sau direct Ctrl+O) II. Selectati
succesiv C:\,
BlueJ,
examples,
shapes,
(sau scrieti C:\BlueJ\examples\shapes) |
In laborator: 1. Click-dreapta (meniul pop-up)
pe 2. Creati un alt cerc, acceptand din nou valoarea
implicita oferita de BlueJ. 3. Creati un patrat (Square) in
aceleasi conditii. |
In laborator: 1. Click-dreapta (meniul pop-up) pe primul obiect de tip cerc 2. Repetati operatia pentru
al doilea cerc. Apoi comparati valorile atributelor (campurilor – fields). |
Metoda Java
(operatia in teoria OO), atunci cand este executata, realizeaza o secventa de actiuni (reprezentate in programe
prin instructiuni) asupra obiectului
caruia ii apartine.
Actiunile realizate
de executia metodelor au in general
efect asupra valorilor campurilor (atributelor) obiectului. Efectele acestor actiuni pot fi
combinatii intre:
- modificarea
valorilor campurilor obiectului, ca in cazul metodelor de tip setCamp(),
- obtinerea
valorilor campurilor, ca in cazul metodelor de tip getCamp(),
- realizarea
altor sarcini utilizand aceste valori.
Regruparea mai multor elemente de date (campuri/atribute) si/sau de
comportament (metode/operatii) asociate se numeste incapsulare.
Incapsularea OO (orientata spre obiecte) inseamna in plus
ascunderea
detaliilor interne de tip:
- informatii
(setul de campuri/atribute),
- si implementare (setul de coduri interne ale metodelor/operatiilor),
in spatele unei interfete publice (setul de declaratii/semnaturi ale metodelor/operatiilor).
In laborator: 1. Click-dreapta pe obiectul circle1 si selectati void makeVisible(). 2. Click pe 3. Click-dreapta pe obiectul
circle1 si selectati moveUp(). Urmariti
efectul grafic. 4. Repetati apelul moveUp(), urmarind efectul
grafic. |
Parametrii specifica valorile de intrare necesare metodelor
pentru a fi executate.
Declaratiile (semnaturile) metodelor pot include liste de declaratii de parametri. Acesti
parametri sunt variabile care au
ca scop intregul corp al metodei si se
numesc parametri formali sau simplu parametri. Parametrii formali sunt
declarati ca orice variabila, folosind formatul tipVariabila numeVariabila.
Apelurile metodelor pot include liste de valori date parametrilor, valori care trebuie sa corespunda ca
tip celor declarate. Valorile pasatele metodelor in momentul apelurilor se numesc parametri actuali sau simplu argumente.
De exemplu, apelul circle1.changeSize(50) specifica valoarea 50 ca argument, utilizat de metoda changeSize() pentru a da
valoarea 50 diametrului cercului.
In laborator: 1. Click-dreapta pe obiectul circle1 si selectati void makeVisible(). 2. Click pe 3. Click-dreapta pe circle1
si selectati void changeSize(int newDiameter). 4. Stabiliti valoarea diametrului
la 150
in fereastra care apare pe ecran. Urmariti efectul grafic. 5. Apelati metoda void slowMoveVertical(int distance)
pasandu-i 50.
Urmariti efectul grafic. 6. Apelati de mai multe ori metoda void
moveUp(). Urmariti
efectul grafic. Comparati efectele. 7. Apelati metoda void slowMoveHorizontal(int distance)
pasandu-i 50.
Urmariti efectul grafic. |
Descrierea problemelor reale sub forma de modele reprezentate ca programe de calcul
necesita definirea datelor problemei.
Urmatoarele campuri descriu
obiectul circle1 de tip Circle:
circle1 |
int diameter 30 int xPosition
20 int yPosition
60 String color "blue" boolean isVisible false |
Tipul de date este o descriere
abstracta a unui grup de entitati asemanatoare.
Tipul de date defineste structura variabilelor si domeniul
de definitie al valorilor. Mai exact, tipul de date specifica:
- spatiul
de memorie alocat pentru stocarea valorii campului/parametrului/variabilei
(de ex., 4B = 32b pentru tipul int, 1b pentru tipul boolean,
etc.),
- gama/multimea
valorilor posibile (-231…231-1
pentru int, valorile true si false pentru boolean),
- formatul
valorilor literale/de tip imediat (de ex., 100000 sau -2000 pentru
tipul int, true sau false pentru
tipul boolean, etc.),
- regulile
privind conversiile catre alte tipuri (de ex., tipul int se poate converti direct, implicit, la tipurile long, float si double, si
poate fi convertit explicit, prin cast – conversie prin trunchiere, la
tipurile byte si short, pe cand tipul boolean nu poate fi convertit la nici
un alt tip, etc.),
- valorile
implicite (doar in cazul
campurilor!, 0 pentru
tipul int, false pentru tipul boolean,
etc.),
- operatorii
asociati (permisi) – care tin de
partea de prelucrare asupra datelor.
Tipurile de date primitive Java:
Categorie |
Tip |
Valoare implicita |
Spatiu memorie |
Gama valori |
Conversii explicite (cast, trunchiere) |
Conversii implicite (extindere) |
Valori intregi cu semn |
byte |
0 |
8 biti (1B) |
-128 … 127 |
La char |
La short, int, long, float, double |
short |
0 |
16 biti (2B) |
-32768 … 32767 |
La byte, char |
La int, long, float, double |
|
int |
0 |
32 biti (4B) |
-2147483648 … 2147483647 |
La byte, short, char |
La long, float, double |
|
long |
0l |
64 biti (8B) |
-9223372036854775808 …9223372036854775807 |
La byte, short, int, char |
La float, double |
|
Valori
in virgula
mobile cu semn |
float |
0.0f |
32 biti (4B) |
+/-1.4E-45 … +/-3.4028235E+38, |
La byte, short, int, long, char |
La double |
double |
0.0 |
64 biti (8B) |
+/-4.9E-324 … +/-1.8+308, |
La byte, short, int, long, float, char |
Nu
exista (nu sunt necesare) |
|
Caractere codificate UNICODE |
char |
\u0000 (null) |
16 biti (2B) |
\u0000 … \uFFFF |
La byte, short |
La int, long, float, double |
Valori logice |
boolean |
false |
1 bit folosit din 32 biti |
true, false |
Nu exista (nu sunt posibile) |
Nu exista (nu sunt posibile) |
In Java, pe langa tipurile de date
primitive, exista si tipuri de date
complexe numite tipuri
referinta, tablourile si clasele.
In laborator: 1. Click-dreapta pe obiectul circle1 si selectati void makeVisible(). 2. Click pe 3. Apelati metoda void changeColor(String newColor)
pasandu-i "red". Urmariti efectul grafic. 4. Apelati metoda void changeColor(String newColor)
pasandu-i "rosu". Ce observati? 5. Apelati metoda void changeColor(String newColor)
pasandu-i red. Ce observati? |
Ansamblul valorilor campurilor (atributelor) unui obiect la un moment dat reprezinta starea obiectului.
Starea unui obiect poate diferi in timp, ca urmare
a comportamentului. Starea a doua
obiecte de acelasi tip poate fi diferita
la un moment dat.
Optional, in laborator: 1. Inspectati starea obiectului circle1
cu double-click pe 2. Schimbati culoarea obiectului circle1si inspectati-i din nou starea
(valorile campurilor). 3. Creati doua obiecte 4. Au toate campurile aceleasi nume? Sunt toate valorile aceleasi? 5. Apelati metode care schimba pozitia celor doua obiecte. Inspectati-le
starea. Ce s-a schimbat? 6. Creati doua obiecte din clase diferite. Inspectati-le starea. Ce campuri au aceleasi nume? |
O metoda realizeaza o
actiune asupra valorilor
campurilor obiectului caruia ii
apartine, putand folosi valorile acelor campuri, si astfel efectueaza o
sarcina pentru codul (context) care a apelat-o.
Metoda este un atom de comportament al
obiectului.
Comportamentul global al obiectului este obtinut prin inlantuirea
apelurilor de metode.
Toate obiectele
din aceeasi clasa au aceleasi metode
disponibile.
Clasa Circle
are metodele:
<--proprii ^ mostenite
In laborator: 1. Creati o imagine care sa schiteze o casa
si un soare similare celor din imaginea de mai sus. 2. Notati-va sarcinile pe care le-ati
indeplinit pentru a obtine acest efect. De exemplu: I. Circle circle1 = new Circle() //
altfel spus, e creat un cerc II. circle1 makeVisible() // apoi e facut vizibil
cercul III. circle1 moveHorizontal(200) //
e deplasat orizontal 200 pixeli IV. circle1 changeSize(50)
// e redimensionat la 50
pixeli V. circle1 changeColor("yellow") //
si e colorat in galben VI. ... 3. Ar fi putut fi apelate metodele in alta ordine pentru a obtine
acelasi efect? |
Observatie: Pentru a obtine automat pasii in forma electronica se deschide Terminal
Window (cu View->Show
Terminal sau cu Ctrl+T) si se seteaza in acea fereastra Options -> Record method calls. In acest
fel in Terminal Window vor fi scrisi
automat pasii parcursi, ca in exemplul care urmeaza. |
Sarcinile realizate manual in exercitiul anterior sunt in mod normal scrise sub forma de instructiuni Java intr-un
fisier, pentru a putea fi executate din
nou. Primii 5 pasi ar fi scrisi in Java:
Circle circle1 = new Circle(); circle1.makeVisible(); circle1.moveHorizontal(200); circle1.changeSize(50); circle1.changeColor("yellow"); |
BlueJ ofera un exemplu de program (proiectul picture) care contine pe
langa clasele Canvas, Circle, Square si Triangle si
codul unei clase Picture care creaza
obiectele necesare si le apeleaza
metodele, astfel incat ele sa fie pozitionate,
dimensionate si colorate ca in desenul anterior.
Obiectul de tip Picture interactioneaza (colaboreaza, comunica prin mesaje = apeluri metode) cu obiectele de tip Circle,
Square si Triangle pentru a realiza sarcina globala.
In laborator: 1. Deschideti proiectul numit picture
(Ctrl-O,
apoi pe C:\BlueJ\examples
selectati picture) 2. Creati un obiect
Picture. 3. Apelati metoda void draw(). 4. Click pe |
Sarcinile pentru
crearea obiectelor si apelul metodelor pot fi scrise sub forma de instructiuni Java, salvate intr-un fisier,
utilizate si reutilizate (executate) cand este nevoie de ele.
Listele instructiunilor Java (grupate in metode, iar acestea
impreuna cu campurile) definesc o clasa Java. Textul
scris al instructiunilor formeaza codul
sursa al clasei. Pentru a fi executate, instructiunile
trebuie mai intai compilate (translatate) cu compilatorul javac la cod de octeti Java. Apoi codul de octeti este executat
de interpretorul java.
In laborator: 1. Deschideti proiectul numit picture. 2. Vizualizati codul sursa al
clasei Picture, fie double-click pe 3. Care este numele clasei? Gasiti instructiunea
care defineste numele clasei. 4. Gasiti campurile pentru soare si partile componente ale casei. Observati cum sunt declarate. |
1. Gasiti codul metodei a carei
declaratie (semnatura) este public void draw(). 2. Care sunt sarcinile elementare
(instructiunile de tip apel) indeplinite
pentru a crea zidul? 3. Conteaza ordinea in care sunt efectuate aceste sarcini? |
In laborator: 1. Vizualizati codul sursa al
clasei Square.
Gasiti
semnaturile metodelor invocate in metoda draw()
a clasei Picture. Care sunt sarcinile indeplinite de codurile acestor metode? |
Programele de calcul simple sunt secventiale, fiecare avand un inceput, o secventa de executii si un sfarsit. In orice moment pe durata executiei unui astfel de program exista un singur punct de executie.
Un fir de executie (thread), sau mai simplu, un fir, este similar acestor programe secventiale, in sensul ca are un inceput, o secventa de executii si un sfarsit, si in orice moment pe durata executiei firului exista un singur punct de executie.
Totusi, un fir nu este el insusi un program, deoarece nu poate fi executat de sine statator. In schimb, firul este executat (ruleaza) intr-un program. Posibilitatea utilizarii mai multor fire de executie intr-un singur program, ruland (fiind executate) in acelasi timp si realizand diferite sarcini (nu in mod necesar diferite), este numita multithreading.
Pentru a crea un nou fir de executie exista doua modalitati.
1. Se poate declara o
clasa ca subclasa a clasei Thread, subclasa care trebuie sa rescrie
codul (override) metodei run() a clasei Thread (care nu contine nici un cod),
noul fir de executie fiind creat prin alocarea si lansarea unei instante a
subclasei.
Formatul pentru crearea unei subclase care extinde clasa Thread si ii reimplementeaza metoda run() este urmatorul:
class FirT extends Thread { public void run() { // codul firului de executie } } |
Formatul pentru crearea unei instante a subclasei este urmatorul:
FirT fir = new FirT(); |
2. Se poate declara o clasa care implementeaza interfata Runnable, interfata care contine doar declaratia
unei metode run() (declaratie identical celei din clasa Thread, deoarece clasa Thread implementeaza interfata Runnable), noul fir de executie fiind creat
prin alocarea unei instante a noii clase, pasarea acestei instante ca parametru
al constructorului, la crearea unei instante a clasei Thread, si lansarea acelei instante a clasei Thread.
Formatul pentru crearea unei clase care implementeaza interfata Runnable si ii implementeaza metoda run() este urmatorul:
class FirR implements Runnable { public void run() { // codul firului de executie } } |
Formatul pentru crearea unei instante a noii clase si apoi a unei instante a clasei Thread este urmatorul:
FirR r = new FirR(); Thread fir = new Thread(r); |
In ambele cazuri formatul pentru lansarea noului fir de
executie, este urmatorul:
fir.start(); |
Variante compacte pentru crearea si lansarea noilor fire de executie:
new FirT().start(); // nu exista variabila de tip FirT // care sa refere explicit firul |
FirR r = new FirR(); // nu exista variabila de tip Thread new Thread(r).start(); // care sa refere explicit firul |
Thread fir = new Thread(new FirR()); // nu exista variabila de tip FirR fir.start(); // care sa refere explicit firul |
new Thread(new FirR()).start(); // nu exista variabila de tip Thread // si nici variabila de tip FirR // care sa refere explicit firul |
Urmatorul program va fi folosit pentru a
ilustra lucrul cu fire de executie. Clasa FirSimplu extinde clasa Thread, iar metoda principala lanseaza metoda run() ca nou fir de
executie.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public
class FirSimplu extends Thread { // obiectele
din clasa curenta sunt thread-uri public FirSimplu(String str) { super(str); // invocarea constructorului
Thread(String) } // al
superclasei Thread public
void run() { // “metoda principala” a
thread-ului curent for
(int i = 0; i < 5; i++) {
System.out.println(i + " " + getName()); // obtinerea numelui thread-ului
try {
sleep((long)(Math.random()
* 1000)); // thread-ul “doarme” 0...1 secunda
} catch (InterruptedException e) {} }
System.out.println("Gata!
" + getName()); // obtinerea numelui thread-ului } public
static void main (String[]
args) { new FirSimplu("Unu").start(); // “lansarea”
thread-ului (apeleaza run()) } } |
Rezultatul executiei programului:
Clasa DemoDouaFire lanseaza doua fire de
executie de tip FirSimplu
executate concurent.
1 2 3 4 5 6 |
public
class DemoDouaFire { public
static void main (String[]
args) { new FirSimplu("Unu").start(); // “lansarea”
primului thread new FirSimplu("Doi").start(); // “lansarea”
celui de-al doilea thread } } |
Firele au evolutii diferite, in functie de durata intarzierii introdusa in
linia de cod:
sleep((long)(Math.random()
* 1000));
a programului FirSimplu.
In laborator: 1. Lansati mediul BlueJ. Inchideti
proiectele anterioare (Ctrl+W). Creati
un nou proiect fire (Project
-> New Project…, selectati D:/, apoi SwTc2007, apoi numarul
grupei, si scrieti fire). 2. Creati o noua clasa, numita FirSimplu, folosind codul dat mai sus. 3. Tot in proiectul fire
creati clasa DemoDouaFire folosind codul de mai sus. Compilati
clasele. 4. Right-click pe clasa,
selectati si executati main().
Urmariti efectul in Terminal Window. |
Rezultatele a doua executii succesive:
Doua variante (aplicatie de sine statatoare si applet) de program "ceas" bazat pe threads:
import java.util.*; import java.text.DateFormat; import java.io.*; public class CeasConsola
implements Runnable { private Thread firCeas = null; private static int contor = 0; public void start() { // (re)pornire fir if (firCeas == null) { firCeas = new Thread(this); firCeas.start();
// apeleaza run() } System.out.println("Ceas pornit"); } public void run() { // executie fir Thread firulMeu = Thread.currentThread(); contor++; while (firCeas == firulMeu) { Calendar cal =
Calendar.getInstance(); Date date =
cal.getTime(); DateFormat df
= DateFormat.getTimeInstance(); System.out.println("["
+ contor + "] " +
df.format(date)); try { Thread.sleep(1000); } catch
(InterruptedException e){ } } } public void stop() { // oprire fir
System.out.println("Ceas
oprit"); firCeas = null;
// firul dispare } public static void
main(String[] args)
throws IOException { CeasConsola ceas; BufferedReader
in = new BufferedReader (new InputStreamReader(System.in)); while (true) { ceas = new CeasConsola(); ceas.start(); //
(re)pornire fir la citirea in.readLine(); //
unui caracter de la consola ceas.stop(); //
oprire fir la citirea in.readLine(); //
unui caracter de la consola } } } |
import java.applet.Applet; import java.awt.*; import java.util.*; import java.text.DateFormat; public class CeasApplet
extends Applet
implements Runnable { private Thread firCeas = null; private static int contor = 0; public void init() { setBackground(Color.white);
} public void start() { // (re)pornire fir if (firCeas == null) { firCeas = new Thread(this,
"Ceas"); firCeas.start(); // apeleaza run() } } public void run() { // executie fir Thread firulMeu = Thread.currentThread(); contor++; while (firCeas == firulMeu) { repaint(); //
apeleaza paint() (redeseneaza) try { Thread.sleep(1000); } catch
(InterruptedException e){ } } } public void paint(Graphics g) { //
deseneaza Calendar cal =
Calendar.getInstance(); Date date =
cal.getTime(); DateFormat df =
DateFormat.getTimeInstance(); g.drawString("["
+ contor + "] " +
df.format(date), 5, 10); } public void stop(){ // oprire fir firCeas = null;
// firul dispare } } // Applet-ul nu are metoda principala.
// Metodele start(), stop(), run(), etc. sunt invocate // de Browserul Web
in care este “executat”. /* Exemplu de includere
intr-o pagina Web: <HTML> <TITLE> Applet Ceas </TITLE> <BODY> <APPLET CODE=CeasApplet.class WIDTH=200 HEIGHT=50> </APPLET> </BODY> </HTML> */ |
Varianta applet (miniaplicatie) Java CeasApplet:
In laborator: Testati applet-ul CeasApplet folosind link-ul de mai sus. |
Clasa InetAddress incapsuleaza
o adresa IP intr-un obiect care poate
intoarce informatia utila. Aceasta informatie utila se obtine invocand
metodele unui obiect al acestei clase. De exemplu, equals() intoarce adevarat daca doua obiecte
reprezinta aceeasi adresa IP.
Clasa InetAddress nu are constructor public. De aceea, pentru a crea obiecte ale acestei clase trebuie invocata una dintre metodele de clasa getByAddress() si getByName().
O
adresa IP speciala este adresa IP loopback (tot ce este trimis catre aceasta adresa IP se
intoarce si devine intrare IP pentru gazda locala), cu ajutorul careia pot fi testate local programe care utilizeaza socket-uri. Pentru a identifica adresa
IP loopback sunt folosite numele "localhost" si valoarea numerica "127.0.0.1".
Pentru a obtine InetAddress care incapsuleaza adresa IP loopback pot fi folosite apelurile:
|
InetAddress.getByName(null) InetAddress.getByName("localhost") InetAddress.getByName("127.0.0.1") |
Java ofera, in pachetul java.net, mai multe clase pentru lucrul cu socket-uri flux (TCP). Urmatoarele clase Java sunt implicate in realizarea conexiunilor TCP obisnuite: ServerSocket, Socket.
Clasa ServerSocket reprezinta socket-ul (aflat eventual pe un server bazat pe TCP) care asteapta si accepta cereri de conexiune (eventual de la un client bazat pe TCP).
Clasa Socket reprezinta punctul terminal al unei conexiuni TCP intre
doua masini (eventual un client si un server).
Clientul (sau, mai
general, masina conector) creeaza un punct terminal Socket in momentul in care
cererea sa de conexiune este lansata si acceptata.
Serverul (sau, mai
general, masina acceptor) creeaza un Socket in momentul in care
primeste si accepta o cerere de conexiune, si
continua sa asculte si sa astepte alte cereri pe ServerSocket.
Secventa tipica a mesajelor schimbate intre client si server este urmatoarea:
Odata conexiunea stabilita, metodele getInputStream() si getOutputSteam() ale clasei Socket trebuie utilizate pentru a obtine fluxuri de octeti, de intrare respectiv iesire, pentru comunicatia intre aplicatii.
Secventa tipica pentru crearea socket-ului unei aplicatii conector (client):
1 2 3 4 5 6 7 8 |
// Stabilirea adresei
serverului String adresaServer = "localhost"; // Stabilirea portului serverului int portServer = 2000; // Crearea socketului (implicit este
realizata conexiunea cu serverul) Socket socketTCPClient = new Socket(adresaServer,
portServer); |
Dupa utilizare, socket-ul este inchis. Secventa tipica pentru inchiderea socket-ului:
1 2 |
// Inchiderea socketului (implicit a
fluxurilor TCP) socketTCPClient.close(); |
Crearea unui socket pentru aplicatii conector/client si afisarea
informatiilor privind socketul:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import java.net.*; // pachetul claselor de biblioteca pentru socketuri import java.io.*; //
pachetul claselor de biblioteca pentru intrare-iesire public class InfoSocketTCP { // Afiseaza info. privind
conexiunea TCP creata public static void main (String args[]) {
BufferedReader inConsola = new BufferedReader(new
InputStreamReader(System.in)); String adresaIP = null; int numarPort = 0; Socket conexiuneTCP; try {
System.out.print("Introduceti
adresa IP dorita: "); adresaIP = inConsola.readLine(); System.out.print("Introduceti numarul de port dorit:
"); numarPort = Integer.parseInt(inConsola.readLine()); conexiuneTCP = new Socket(adresaIP,
numarPort); // creare socket afisareInfoSocketTCP(conexiuneTCP); // afisare info socket conexiuneTCP.close(); // inchidere
socket } catch
(NumberFormatException ex) { // eroare numar port
System.err.println("Numarul
de port nu are format intreg"); } catch
(UnknownHostException ex) { // eroare DNS
System.err.println("Nu
poate fi gasita adresa " + adresaIP); } catch
(SocketException ex) { // eroare conectare System.err.println("Nu se conecteaza la " + adresaIP
+ ":" + numarPort); } catch
(IOException ex) {
System.err.println(ex); } } // Afiseaza
informatii privind socketul TCP primit ca parametru public static void afisareInfoSocketTCP (Socket socketTCP) {
System.out.println("\nConexiune
de la adresa "+socketTCP.getLocalAddress()
+ " de pe portul " + socketTCP.getLocalPort() + " catre adresa " + socketTCP.getInetAddress() + " pe portul " + socketTCP.getPort() +
"\n"); } } |
In laborator: 1. Lansati mediul BlueJ.
Inchideti proiectele anterioare
(Ctrl+W). Creati un
nou proiect socket (Project->New Project…, selectati D:/, SwTc2007, numarul
grupei, si scrieti socket). 2. Creati o noua clasa, numita InfoSocketTCP, folosind codul
dat mai sus. Compilati codul. 3. Right-click pe clasa,
selectati si executati main().
4. Folositi adresa localhost si numarul de port 3000. Urmariti efectul in Terminal Window.
5. Folositi apoi adresa www.google.com si numarul de port 7. Folositi si alte adrese. |
Observatie: Cat timp bara de stare a executiei este activa ( Pentru a opri executia, folositi right
click pe |
Secventa tipica pentru crearea socket-ului server al unei aplicatii
acceptor (server):
1 2 3 4 5 |
// Stabilirea portului serverului int portServer
= 2000; // Crearea socketului server (care accepta
conexiunile) ServerSocket
serverTCP = new ServerSocket(portServer);
|
Secventa tipica pentru crearea socket-ului
pentru tratarea conexiunii TCP cu un client:
1 2 3 4 |
// Blocare in asteptarea cererii de
conexiune - in momentul acceptarii // cererii se creaza socketul care
serveste conexiunea Socket conexiuneTCP = serverTCP.accept();
|
Un caz special este acela in care se creaza un socket server fara a se preciza portul pe care asculta serverul. In acest caz, este alocat un numar de port aleator (unul dintre cele neocupate in acel moment). Acest lucru se realizeaza prin pasarea valorii 0 constructorului ServerSocket().
Program pentru crearea unui socket
server cu numar de port alocat aleator.
1 2 3 4 5 6 7 8 9 10 11 |
import java.net.*; import java.io.*; public class InfoPortTCPAleator
{ public static void main (String args[]) { try { ServerSocket serverTCP = new
ServerSocket(0);
System.out.println("\nServer
pe portul " + serverTCP.getLocalPort()); serverTCP.close(); } catch (IOException ex) { System.err.println(ex); } } } |
Secventa tipica pentru crearea fluxurilor de octeti asociate socket-ului:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//
Obtinerea fluxului de intrare octeti TCP
InputStream inTCP = socketTCPClient.getInputStream(); //
Obtinerea fluxului de intrare caractere dinspre retea
InputStreamReader inTCPCaractere
= new InputStreamReader(inTCP); // Adaugarea facilitatilor de stocare
temporara BufferedReader inRetea = new BufferedReader(inTCPCaractere); // Obtinerea fluxului de iesire octeti TCP OutputStream outTCP = socketTCPClient.getOutputStream(); // Obtinerea fluxului de iesire spre
retea, // cu facilitate de afisare (similare
consolei standard de iesire) PrintStream outRetea = new PrintStream(outTCP); |
Secventa tipica pentru trimiterea de date:
1 2 3 4 5 6 7 8 |
//
Crearea unui mesaj String
mesajDeTrimis = "Continut mesaj"; //
Scrierea catre retea (trimiterea mesajului) outRetea.println(mesajDeTrimis); //
Fortarea trimiterii outRetea.flush(); |
Secventa tipica pentru primirea de date:
1 2 3 4 5 |
//
Citirea dinspre retea (receptia unui mesaj) String
mesajPrimit = inRetea.readLine(); //
Afisarea mesajului primit
System.out.println(mesajPrimit); |
Program server ecou TCP care
raspunde mai multor mesaje trimise succesiv, mesajul format dintr-un punct (".") semnaland serverului
terminarea mesajelor de trimis (clientul
urmand sa isi termine executia):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import java.net.*; import java.io.*; import
javax.swing.JOptionPane; public class ServerEcouFluxRepetiv { //
Server ecou flux public static void main (String args[]) throws IOException { int portServer
= Integer.parseInt(JOptionPane.showInputDialog( "Introduceti numarul de port dorit:
")); // Crearea socketului server (care accepta
conexiunile)
ServerSocket serverTCP =
new ServerSocket(portServer);
System.out.println("Server in asteptare pe portul "+portServer+"..."); //
Blocare in asteptarea cererii de conexiune - in momentul acceptarii //
cererii se creaza socketul care serveste conexiunea Socket conexiuneTCP = serverTCP.accept();
System.out.println("Conexiune TCP pe portul " + portServer + "..."); // Crearea fluxurilor de caractere conectate la fluxurile de octeti //
obtinute de la socketul TCP PrintStream outRetea = new PrintStream(conexiuneTCP.getOutputStream());
BufferedReader inRetea = new BufferedReader(
new InputStreamReader(conexiuneTCP.getInputStream())); while (true) { // Citirea
unei linii din fluxul de intrare TCP String mesajPrimit = inRetea.readLine();
// Afisarea
liniei citite la consola de iesire System.out.println("Mesaj primit:
" + mesajPrimit); // Scrierea
liniei in fluxul de iesire TCP, cu fortarea trimiterii outRetea.println(mesajPrimit);
outRetea.flush(); // Testarea conditiei de
oprire a servirii if (mesajPrimit.equals("."))
break; } // Inchiderea socketului (si implicit a
fluxurilor) conexiuneTCP.close();
System.out.println("Bye!"); } } |
In laborator: 1. In acelasi proiect (socket) creati o clasa numita ServerEcouFluxRepetiv folosind codul dat. 2. Compilati codul, apoi right-click pe clasa, selectati si
executati main(). 3. Folositi numarul de port 4000. Urmariti
efectul in Terminal Window. |
Nu uitati: Pentru a opri executia, right click pe
|
Cu ajutorul unui client potrivit putem testa serverul ecou TCP.
Program client pentru serverul
ecou TCP anterior (care raspunde mai multor mesaje trimise
succesiv):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import java.net.*; import java.io.*; import
javax.swing.JOptionPane; public class ClientEcouFluxRepetitiv { // Client pentru server ecou flux public static void main (String args[]) throws IOException { String
adresaServer = JOptionPane.showInputDialog( "Introduceti adresa IP a serverului: "); int portServer = Integer.parseInt(JOptionPane.showInputDialog( "Introduceti numarul de port al serverului:
")); Socket conexiuneTCP = new Socket(adresaServer, portServer); // Creare socket
System.out.println("Conexiune
TCP cu serverul " + adresaServer + ":"
+ portServer + "..."); System.out.println("Pentru oprire introduceti '.' si
<Enter>"); PrintStream outRetea
= new
PrintStream(conexiuneTCP.getOutputStream()); // Creare fluxuri
BufferedReader inRetea =
new BufferedReader(
new InputStreamReader(conexiuneTCP.getInputStream())); while (true) { // Cat timp conditia de oprire nu este indeplinita String
mesajTrimis = JOptionPane.showInputDialog("Se trimite: "); //
Mesaj outRetea.println(mesajTrimis);
// Scrierea in fluxul de iesire TCP outRetea.flush(); String
mesajPrimit = inRetea.readLine(); //
Citirea din fluxul de intrare TCP
JOptionPane.showMessageDialog(null, "S-a primit: " +
mesajPrimit); // Afisare
System.out.println("S-a primit: " + mesajPrimit); // Afisarea la consola if (mesajPrimit.equals(".")) break; //
Testarea conditiei de oprire } conexiuneTCP.close(); // Inchiderea socketului (si implicit a
fluxurilor) System.out.println("Bye!"); } } |
In laborator: 1. In proiectul socket creati o clasa ClientEcouFluxRepetitiv cu codul dat.
Compilati codul. 2. Right-click pe clasa, executati main(). Folositi adresa localhost si portul 4000. Ce observati? |
In laborator: 1. La unul dintre calculatoare right-click pe clasa ServerEcouFluxRepetiv. 2. Selectati si executati main().
Folositi numarul de port 4000. 3. La un alt calculator
right-click pe clasa ClientEcouFluxRepetitiv, selectati si executati main(). 4. Folositi adresa primului calculator
(cel pe care se executa ServerEcouFluxRepetiv) si numarul de port 4000. Urmariti efectul in Terminal Window pe cele
doua calculatoare. |
In laborator (codul complet
este parte din tema de casa!): 1. Pornind de la codul ServerEcouFluxRepetiv sa se creeze o clasa ServerRepetiv care sa afiseze mesajele
primite de la client si sa astepte un utilizator uman sa furnizeze mesajul de
raspuns catre client. Sa se compileze
si testeze codul folosind un calculator pentru server si unul pentru client. |
Serverul ecou TCP poate fi transformat astfel incat sa
poata trata mai multi clienti (de exemplu, ClientEcouFluxRepetitiv) succesiv.
Program server ecou TCP care poate trata mai multi clienti (ClientEcouFluxRepetitiv) succesiv.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
import java.net.*; import java.io.*; import
javax.swing.JOptionPane; public class ServerEcouFluxRepetivSecvential
{ public
static void main (String args[])
throws IOException { int portServer
= Integer.parseInt(JOptionPane.showInputDialog( "Introduceti numarul de port al serverului: ")); // Crearea socketului server (care accepta conexiunile) ServerSocket
serverTCP = new ServerSocket(portServer); // Servirea mai multor clienti succesivi (in mod secvential) while (true) {
System.out.println("Server in asteptare pe port
"+portServer+"..."); // Blocare in
asteptarea cererii de conexiune - in momentul // acceptarii cererii se creaza
socketul care serveste conexiunea Socket conexiuneTCP =
serverTCP.accept(); System.out.println("Conexiune TCP
pe portul " + portServer + "..."); // Crearea fluxurilor
de caractere conectate la fluxurile de octeti // obtinute de la socketul TCP
PrintStream outRetea = new
PrintStream(conexiuneTCP.getOutputStream());
BufferedReader inRetea = new BufferedReader( new
InputStreamReader(conexiuneTCP.getInputStream())); // Servirea clientului curent while
(true) { // Citirea unei linii din fluxul de
intrare TCP String mesajPrimit =
inRetea.readLine(); //
Afisarea liniei citite la consola de iesire System.out.println("Mesaj
primit: " + mesajPrimit); // Scrierea liniei in fluxul de
iesire TCP, cu fortarea trimiterii outRetea.println(mesajPrimit);
outRetea.flush(); // Testarea conditiei de
oprire a servirii if
(mesajPrimit.equals(".")) break; } // Inchiderea socketului (si implicit a
fluxurilor) conexiuneTCP.close(); System.out.println("Bye!"); } } } |
In laborator: 1. In acelasi proiect (socket) creati o clasa ServerEcouFluxRepetivSecvential cu codul
dat. 2. Compilati codul. |
In laborator: 1. La unul dintre calculatoare right-click pe clasa ServerEcouFluxRepetivSecvential. 2. Selectati si executati main().
Folositi numarul de port 4000. 3. La un alt calculator
right-click pe clasa ClientEcouFluxRepetitiv, selectati si executati main(). 4. Folositi adresa primului calculator
(cel pe care se executa ServerEcouFluxRepetivSecvential) si numarul de port 4000. Urmariti efectul in Terminal Window pe cele
doua calculatoare. 5. Pastrand serverul deschis repetati
operatia cu clienti de pe diverse calculatoare. |
Serverul ecou TCP poate fi transformat astfel incat sa
poata trata mai multi clienti in acelasi timp (concurent).
In cazul serverelor care pot trata mai multi clienti in mod concurent (in
paralel), codul necesar pentru stabilirea conexiunilor cu clientii ce
urmeaza a fi tratati de
obiecte fir de executie (a caror clasa este numita generic ClasaFiruluiDeTratare) este urmatorul:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// initializare portServer //
Crearea socketului server (care accepta conexiunile)
ServerSocket serverTCP =
new ServerSocket(portServer); // Servirea mai multor clienti in paralel (in mod concurent) while (true)
{ // Blocare in asteptarea cererii de
conexiune Socket conexiuneTCP = serverTCP.accept();
// Crearea si lansarea firului de
executie pentru tratarea noului client ClasaFiruluiDeTratare
firExecutie = new ClasaFiruluiDeTratare(conexiuneTCP); firExecutie.start(); }
//
Gata pentru a accepta urmatorul client |
Codul necesar pentru comunicatia cu un client (care se va afla in clasa din care se vor crea obiecte fir de executie, numita generic ClasaFiruluiDeTratare) este urmatorul:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Socket socketTCP; // Atribut public ClasaFiruluiDeTratare(Socket
s) { // Constructor socketTCP = s; // Initializarea atributului } public void run() { // Implementarea firului de executie //
Crearea fluxurilor conectate la fluxurile obtinute de la socketul TCP
PrintStream out = new PrintStream(conexiuneTCP.getOutputStream());
BufferedReader in = new BufferedReader(
new InputStreamReader(conexiuneTCP.getInputStream())); // Tratarea
clientului curent while (true) { // Citiri din fluxul de intrare TCP
cu in.readLine();
// Scrieri in fluxul de iesire TCP cu
out.println();
out.flush(); }
// Incheierea tratarii clientului // Inchiderea socketului conexiuneTCP.close();
} |
O varianta mai simpla poate fi utilizarea unei singure
clase, care sa extinda clasa Thread, si:
- in metoda main() sa implementeze stabilirea conexiunilor cu
clientii
- in metoda run() sa implementeze codul necesar pentru
comunicatia cu un client.
Exemplu de server ecou care poate trata mesaje sosite de la mai multi
clienti in paralel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
import java.net.*; import java.io.*; import
javax.swing.JOptionPane; public class ServerEcouFluxRepetivConcurent
extends Thread { private Socket conexiuneTCP; public ServerEcouFluxRepetivConcurent(Socket
socketTCP) { conexiuneTCP
= socketTCP; } public static void main(String args[]) throws IOException { int portServer
= Integer.parseInt(JOptionPane.showInputDialog( "Introduceti numarul de port al serverului: ")); // Crearea socketului server (care accepta
conexiunile) ServerSocket serverTCP = new ServerSocket(portServer); System.out.println("Server in asteptare pe portul "+portServer+"..."); // Servirea mai multor clienti in acelasi
timp (in mod concurent) while (true)
{ // Blocare in asteptarea cererii de
conexiune - in momentul // acceptarii cererii se creaza
socketul care serveste conexiunea Socket
socketTCP = serverTCP.accept(); System.out.println("Conexiune TCP
pe portul " + portServer +
"..."); new ServerEcouFluxRepetivConcurent(socketTCP).start(); // run() } } public void run() { //
Fir de servire client try { // Crearea fluxurilor de caractere
conectate la fluxurile de octeti //
obtinute de la socketul TCP PrintStream outRetea
= new
PrintStream(conexiuneTCP.getOutputStream()); BufferedReader inRetea = new BufferedReader(
new InputStreamReader(conexiuneTCP.getInputStream())); // Servirea clientului curent while
(true) { // Citirea unei linii din fluxul de
intrare TCP String mesajPrimit = inRetea.readLine(); //
Afisarea liniei citite la consola de iesire System.out.println("Mesaj
primit: " + mesajPrimit); // Scrierea liniei in fluxul de
iesire TCP, cu fortarea trimiterii outRetea.println(mesajPrimit); outRetea.flush(); // Testarea conditiei de
oprire a servirii if
(mesajPrimit.equals(".")) break; } // Inchiderea socketului (si implicit a
fluxurilor) conexiuneTCP.close();
System.out.println("Bye!"); } catch
(IOException ex) {
System.err.println(ex); } } } |
In laborator: 1. In acelasi proiect (socket) creati o clasa ServerEcouFluxRepetivConcurent cu codul
dat. 2. Compilati codul. |
In laborator: 1. La unul dintre calculatoare right-click pe clasa ServerEcouFluxRepetivConcurent. 2. Selectati si executati main().
Folositi numarul de port 5000. 3. La un alt calculator
right-click pe clasa ClientEcouFluxRepetitiv, selectati si executati main(). 4. Folositi adresa primului calculator (cel pe care se
executa ServerEcouFluxRepetivConcurent) si numarul de port 5000. Urmariti efectul in Terminal Window pe cele
doua calculatoare. 5. Pastrand serverul deschis lansati
si utilizati si alti clienti de pe diverse calculatoare. |
I. Codul final al clasei ServerRepetiv (cerut in paragraful 2.5.2).
II. O varianta a clasei ServerRepetiv, numita ServerRepetivClientiSuccesivi, care permite tratarea mai multor clienti unul dupa altul.
Codurile vor fi predate sub forma de listing sau scrise de mana!