Programare CWS
Programare CWS
Facultatea
Lucrare de licenta
Programare CWS
1
Cuprins:
2
[Link] de ansamblu asupra aplicatiei
Fig 1. Client
Fig. 2 Server
3
2. Modul de utilizare
4
4. join la un anumit canal
5. alegerea unei imagini care sa insoteasca mesajul transmis
6. schimbul propriu-zis de mesaje
6.1mesajele pot fi trimise unui anumit grup de utilizatori (unui
anumit canal)
6.2mesajele pot fi trimise unui anumit client care face parte dintr-
un anumit grup de clienti
7. aceste mesaje vor fi insotite de o imagine care va putea fi setata de
fiecare client in parte.
Detalii
[Link]
2.2.3. Logare
5
numele de logare, server-ul verifica unicitatea nickname-ul si trasmite inpoi
la client daca logarea a avut loc cu succes sau nu.
Daca butonul de mai sus este apasat atunci clientul trimite un mesaj la
server prin care ii spune ca doreste sa se alature unui anumit grup de clienti.
Daca join-ul a avut loc cu succes atunci server-ul trimite lista de clienti de pe
canal la care s-a alaturat clientul nostru.
Daca clientul doreste sa isi faca propriul lui canal atunci poate da
comanda /join numeCanal. In urma acestei comenzi clientul anunta server-
ul sa faca canalul cu numele de numeCanal.
Aceasta comanda este data din textfied-ul din imaginea de mai jos.
6
Mesaje care vor fi schimbate intre clienti vor putea fi insotite de o
imagine, aceste imagini vor putea fi selectate din tab-ul Setings ca in figura
de mai sus, efectuindu-se un click pe imaginea dorita.
Mesaje pot fi trimise unui anumit canal sau unui client anume de pe
aflat pe un anumit canal. Pentru a afla lista de canale se foloseste comanda
/lcl, care duce la obtinerea, la client, a listei de canale disponibile pe server.
Alegerea unui canal se face din combo-box-ul de mai jos:
Se poate observa, din figura de mai sus, ca s-a facut o selectie aupra
clientului Stefan.
7
2.2.7. Mesaje insotite de o imagine.
8
Tree
Interfata grafica sau, mai bine zis, interfata grafica cu utilizatorul (GUI),
este un termen cu înteles larg care se refera la toate tipurile de comunicare
vizuala între un program si utilizatorii sai. Aceasta este o particularizare a
interfetei cu utilizatorul (UI), prin care vom întelege conceptul generic de
interactiune între un program si utilizatorii sai. Asadar, UI se refera nu
numai la ceea ce utilizatorul vede pe ecran ci la toate mecanismele de
comunicare între acesta si program.
Limbajul Java pune la dispozitie numeroase clase pentru implementarea
diverselor functionalitati UI, însa ne vom ocupa în continuare de acelea care
permit realizarea unei intefete grafice cu utilizatorul (GUI).
Cu suport pentru bare de meniu, appleturle Swing pot avea o bara de meniu.
Layering-ul componentelor este in esenta plasarea de componente in layere
multiple, unele peste altele. Asta permite componentelor sa fie pozitionate
pentru a crea senzatia de adincime. Appleturile Swing contin o componenta
speciala numita "layered pane", speciala pentru a obtine acest efect.
9
// Obtine un handle la panelul de continut al appletului
Container contentPane = [Link]( );
/*
* <Applet code=TJApplet width=200 height=100>
* </Applet>;
*/
import [Link].*;
import [Link].*;
10
// 3. Atribuie o bordura prin folosirea unui icon si specificarea inseturilor
[Link]([Link](
10, // top inset
10, // left inset
10, // bottom inset
10, // right inset
new ImageIcon("[Link]"))); // icon border
Detalii la cod
[Link]
11
informatie care trebuie trimisa prin retea trebuie serializata astfel încât sa
poata fi transmisa secvential, octet cu octet, catre destinatie, era nevoie de
stabilirea unor conventii (protocoale) care sa fie folosite atât de calculatorul
care trimite datele cât si de cel care le primeste.
Cele mai utilizate protocoale sunt TCP si UDP.
Definitii
TCP (Transport Control Protocol) este un protocol ce furnizeaza un
flux sigur de date între doua calculatoare. Acest protocol asigura stabilirea
unei conexiuni permanente între cele doua calculatoare pe parcursul
comunicatiei.
12
informatiile respective. Identificarea proceselor se realizeaza prin intermdiul
porturilor.
Un port este un numar de 16 biti care identifica în mod unic procesle
care ruleaza pe o anumita masina. Orice aplicatie care realizeaza o
conexiune în retea va trebui sa ataseze un numar de port acelei conexiuni.
Valorile pe care le poate lua un numar de port sunt cuprinse între 0 si 65535
(deoarece sunt numere reprezentate pe 16 biti), numerele cuprinse între 0 si
1023 fiind însa rezervate unor servicii sistem si, din acest motiv, nu trebuie
folosite în aplicatii.
Definitie
Serializarea este o metoda ce permite transformarea unui obiect într-o
secventa de octeti din care sa poata fi refacut ulterior obiectul original. Cu
alte cuvinte, serializarea permite salvarea într-o maniera unitara a datelor
împreuna cu signatura unui obiect pe un mediu de stocare a informatiei
extern programului. Procesul invers de citirea a unui obiect serializat pentru
a-i reface starea originala se numeste deserializare. Intr-un cadru mai larg,
prin serializare se întelege procesul de scriere/citire a obiectelor.
Utilitatea serializarii obiectelor consta in urmatoarele aspecte:
Compensarea diferentelor între sisteme de operare, adica putem crea
un obiect pe o masina Windows, îl serializam, apoi îl trimitem prin
retea catre o masina UNIX unde va fi corect reconstruit. In acest fel
comunicarea între sisteme diferite se realizeaza unitar, independent de
reprezentarea datelor, ordinea octetilor sau alte detalii specifice
sistemelor repective.
Permite persistenta obiectelor, ceea ce înseamna ca durata de viata a
unui obiect nu este determinata de executia unui program în care
acesta este definit - obiectul poate exista si între apelurile programelor
care îl folosesc. Acest lucru se realizeaza prin serializarea obiectului si
13
scrierea lui pe disc înainte de terminarea unui program, apoi, la
relansarea programului, obiectul va fi citit de pe disc si starea lui
refacuta. Acest tip de persistenta a obiectelor se numeste persistenta
usoara, întrucât ea trebuie efectuata explicit de catre programator si
nu este realizeazata automat de catre sistem.
RMI (Remote Method Invocation) - comunicarea obiectelor prin
socket-uri: este o modalitate prin care obiectele de pe o alta masina se
comporta ca si când ar exista pe masina pe care ruleaza programul
nostru. Atunci când este trimis un mesaj catre un obiect "remote" (de
pe alta masina), serializarea este necesara pentru transportul
argumentelor prin retea si pentru returnarea valorilor.
Java Beans - sunt componente grafice definite de utilizator si care pot
fi folosite la fel ca si componentele grafice standard. Orice
componenta Bean are o stare initiala a informatiilor sale, stare care
este specificata la definirea sa. Atunci când ea este folosita într-un
program aceasta stare trebuie încarcata de undeva, ceea ce înseamna
ca aceste componente trebuie serializate si salvate pe disc.
COD1: (salvare)
import [Link].*;
import [Link];
public class SaveDate
{
public static void main(String argv[]) throws Exception
14
{
FileOutputStream fos = new FileOutputStream("[Link]");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Date date = new Date();
[Link](date);
[Link]();
[Link]();
[Link]();
}
}
Am creat un obiect de tip Date si l-am salvat in fluxul de iesire fos
care este un fisier de pe HDD calculatorului.
COD 2: (restaurare)
import [Link].*;
import [Link];
public class ReadDate
{
public static void main(String argv[]) throws Exception
{
FileInputStream fis = new FileInputStream("[Link]");
ObjectInputStream ois = new ObjectInputStream(fis);
Date date =(Date)[Link]();
[Link]("The date is:"+date);
[Link]();
[Link]();
}
}
15
Unealta serialver vine cu JDK, mai sus am testat daca clasa Date este
serializabila. Clasa Date este o clasa serializabila dar de retinut ca nu toate
clasele din Java sunt serializabile exemplu clasa Socket nu este o clasa
serializabila. Hai sa vedem daca putem scrie clase custom serializabile.
Avem urmatoare clasa:
COD 3
import [Link].*;
import [Link].*;
public class UserInfo implements Serializable
{
String name = null;
public UserInfo(String name)
{
[Link] = name;
}
public void printInfo()
{
[Link]("The name is: "+name);
}
}
import [Link].*;
import [Link];
public class SaveInfo
{
public static void main(String argv[]) throws Exception
{
FileOutputStream fos = new
FileOutputStream("[Link]"); ObjectOutputStream oos
= new ObjectOutputStream(fos);
// create two objects
UserInfo user1 = new UserInfo("Java Duke");
UserInfo user2 = new UserInfo("Java Blue");
// write the objects to the output stream
16
[Link](user1);
[Link](user2);
[Link]();
[Link]();
[Link]();
}
}
In final cream o clasa care restaureaza obiectele serializate in fluxul
[Link]. De retinut : putem folosii metoda readObject() de cate ori dorim.
COD 5
import [Link].*;
import [Link];
public class ReadInfo
{
public static void main(String argv[]) throws Exception
{
FileInputStream fis = new FileInputStream("[Link]");
ObjectInputStream ois = new ObjectInputStream(fis);
// read the objects from the input stream (the file [Link])
UserInfo user1 = (UserInfo) [Link]();
UserInfo user2 = (UserInfo) [Link]();
// invoke a method on the constructed object
[Link]();
[Link]();
[Link]();
[Link]();
}
}
[Link] serializarii
Exista cazuri cand dorim ca unele variabile membre sau sub-obiecte
ale unui obiect sa nu fie salvate automat în procesul de serializare. Acestea
sunt cazuri comune atunci cand respectivele campuri reprezinta informatii
confidentiale, cum ar fi parole, sau variabile auxiliare pe care nu are rost sa
le salvam. Chiar declarate ca private în cadrul clasei aceste campuri participa
17
la serializare. O modalitate de a controla serializare este implementarea
interfetei Externalizable, asa cum am vazut anterior. Aceasta metoda este
însa incomoda atunci când clasele sunt greu de serializat iar multimea
campurilor care nu trebuie salvate este redusa.
Pentru ca un camp sa nu fie salvat în procesul de serializare atunci el trebuie
declarat cu modificatorul transient si trebuie sa fie ne-static. De exemplu,
declararea unei parole ar trebui facuta astfel:
Definitie
Un socket (soclu) este o abstractie software folosita pentru a
reprezenta fiecare din cele doua "capete" ale unei conexiuni între doua
procese ce ruleaza într-o retea. Fiecare socket este atasat unui port astfel
încât sa poata identifica unic programul caruia îi sunt destinate datele.
Socket-urile sunt de doua tipuri:
TCP, implementate de clasele Socket si ServerSocket
UDP, implementate de clasa DatagramSocket
O aplicatie de retea ce foloseste socket-uri se încadreaza în modelul
client/server de concepere a unei aplicatii. In acest model aplicatia este
formata din doua categorii distincte de programe numite servere, respectiv
clienti.
Programele de tip server sunt cele care ofera diverse servicii
eventualilor clienti, fiind în stare de asteptare atâta vreme cât nici un client
nu le solicita serviciile.
Programele de tip client sunt cele care initiaza conversatia cu un
server, solicitând un anumit serviciu. Uzual, un server trebuie sa fie capabil
sa trateze mai multi clienti simultan si, din acest motiv, fiecare cerere
adresata serverului va fi tratata într-un fir de executie separat.
Acum cand stim ce este un socket si cum putem scrie/citii obiecte din
fluxuri de date hai sa vedem daca putem transporta obiecte prin socketi.
Prima data vom incerca sa trasportam o instanta a clasei Date si apoi vom
implementa o clasa custom si vom incerca sa trasportam prin socketi o
instanta a acestei clase create de noi.
18
Dezvoltam o aplicatie client/server, unde socketii sunt de tip TCP.
Cum va rula aplicatia de mai jos: un client se conecteaza la server, odata
stabilita aceasta conexiune server-ul ii trimite clientului o instanta a clasei
Date. Server-ul va putea accepta conexiuni multiple fiind capabil sa trateze
mai multi clienti simultan.
COD 6 ()
import [Link].*;
import [Link].*;
import [Link].*;
19
private Socket client = null;
private ObjectInputStream ois = null;
private ObjectOutputStream oos = null;
public Connect() {}
COD 7
20
import [Link].*;
import [Link].*;
import [Link].*;
21
COD 8.
import [Link].*;
import [Link].*;
public SerializedObject() {
}
COD 9.
import [Link].*;
import [Link].*;
public class ArrayClient {
public static void main(String argv[]) {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
//folositi aceasta adresa doar in cazul in care atat server
//cat si clientul ruleaza pe aceiasi masina
String adresaServer="[Link]";
// doi vectori
int dataset1[] = {3, 3, 3, 3, 3, 3, 3};
int dataset2[] = {5, 5, 5, 5, 5, 5, 5};
22
try {
// deschid o conexiune
Socket socket = new Socket(adresaServer, 4000);
// fac fluxurile
oos = new ObjectOutputStream([Link]());
ois = new ObjectInputStream([Link]());
// 2 instante SerializedObject
SerializedObject so1 = new SerializedObject();
SerializedObject so2 = new SerializedObject();
SerializedObject result = null;
int outArray[] = new int[7];
[Link](dataset1);
[Link](dataset2);
// scriu obiectele la fluxul de iesire
[Link](so1);
[Link](so2);
[Link]();
// citesc un obiect de la server
result = (SerializedObject) [Link]();
outArray = [Link]();
[Link]("The new array is: ");
for(int i=0;i<[Link];i++) {
[Link](outArray[i] + " ");
}
[Link]();
[Link]();
} catch(Exception e) {
[Link]([Link]());
}
}
}
COD 10.
import [Link].*;
import [Link].*;
23
public class ArrayMultiplier extends Thread {
public Connect() {}
24
} catch(Exception e1) {
try {
[Link]();
}catch(Exception e) {
[Link]([Link]());
}
return;
}
[Link]();
}
25
Important: atat clientul cat si server-ul trebuie sa aiba o copie a clasei
SerializedObject daca nu va exista acesta copie se arunca o exceptie. O
solutie la aceasta problema este sa scriem o interfata care sa extinda
Serializable, clasa SerializedObject va implementa aceasta clasa. Utilizand
aceasta tehnica este necesar sa furnizam clientului si server-ului numai o
copie a acestei interfete.
5. Fire de executie
26
intreprocesoare. Java contine citeva caracteristici care-l fac un limbaj ideal
pentru SMS. Este un limbaj orientat obiect foarte simplu si cel mai
important,este conceput sa suporte programare multiprocesor. In aceasta
prezentare voi modul in care Java ajuta la crearea de aplicatii paralele.
Un singur thread nu ofera nimic nou. Orice program scris pina acum avea
cel putin un thread in el. Noutatea apare atunci cind vrem sa folosim mai
multe thread-uri, ceea ce inseamna ca aplicatia noastra poate sa faca mai
multe lucruri in acelasi timp. Fiecare thread poate sa faca altceva in acelasi
timp: unul sa incarce o pagina Web in timp ce altul animeaza o icoana sau
toate pot colabora la acelasi job (generarea unei imagini 3D). Cind se
foloseste corespunzator multithreading-ul cresc mult performantele
appletului sau aplicatiei unde este folosit. Multithreading-ul poate simplifica
fazele de proiectare si planificare a tuturor aplicatiilor greu de realizat intr-
un program secvential. Un exemplu bun de thread-uri este un miniprocesor
de calcule matematice care in timp ce se afiseaza sa zicem graficul unei
functii, pregateste cateva date speciale rezultate din calcule matematice
(minim, maxim, continuitate etc).
Alt exemplu este acela a unui browser Web care permite defilarea unui text
al unei pagini pe care tocmai ai incarcat-o in timp ce browser-ul se ocupa cu
aducerea imaginilor. In cazul in care cautati ceva anume, ar fi neplacut sa
astepti incarcarea paginii in intregime si apoi sa realizezi ca nu e ceea ce
cauti. Thread-urile pot realiza de asemenea calculul mai rapid. Prin
segmentarea unui task in subtask-uri si apoi avind cite un thread pentru
fiecare subtask se poate creste mult viteza de executie. Aceasta este general
27
valabil pentru un SMS.
28
Fiecare thread are propriile variabile si puncte de executie, dar variabilele
globale sunt impartite de toate thread-urile. Deoarece ele impart acelasi
spatiu (variabilele globale si alte resurse) toate acestea pot accesa la un
moment dat aceeasi data. Este important de observat ca memoria comuna
prezinta o degradare a performantelor cind lucreaza cu un cluster de
computere conectateintr-o retea, asemenea cazului unui sistem distribuit de
tip clustere de [Link] care sunt comune trebuie sa fie transmise pe
retea de atitea ori de cit este nevoie. Programul poate avea de suferit in
asemenea cazuri si executia lui eficienta depinde mai degraba de parametrii
retelei.
class Test {
public static void main (String Args[]) {
new Thread(new PrintThread("A")).start();
new Thread(new PrintThread("B")).start();
}
}
29
Iesirea programului de mai sus poate arata cam cum urmeaza :
AAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBB
BBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBB
BBBBBBBBBBBBBBBBB
BBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAABBB
BBBBBBBBBBBBBBB...
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
Acest lucru este datorat faptului ca pe Solaris (si nu numai) thread-urile nu
sunt preemptive. Un thread trebuie sa aiba "o comportare echitabila" si sa
renunte la timpul sau procesor in asa fel incit sa permita si altor thread-uri
sa se executa. Renuntarea la propriul timp CPU in mod voluntar se
realizeaza prin invocarea metodei proprii yield(). In continuare iata o
versiune revizuita a clasei PrintThread care elibereaza CPU-ul dupa fiecare
litera afisata:
30
[Link]().yield();
}
}
ABABABABABABABABABABABABABABABABABABABABABABA
BABABABABABABABABAB
ABABABABABABABABABABABABABABABABABABABABABABA
BABABABABABABABABAB
ABABABABABABABABABABABABABABABAB
ABABABABABABABABABABABABABABABABABABABABABABA
BAB
ABABABABABABABABABABABABABABABABABABABABABABA
BAB
...
5.5.1. Metoda run() nu este in executie, timpul procesor nu este inca alocat.
Pentru a starta un thread trebuie apelata functia start(). In aceasta stare se
poate apela de asemenea metoda stop(), care va distruge thread-ul.
31
lucru. Atita timp cit exista un alt thread cu o prioritate mai mare, thread-ul
nu va trece in executie.
32
sleep(100);
} catch (InterruptedException e) {
[Link](0);
}
}
}
}
class Spawner {
public static void main( String args[] ) {
LowPriority l = new LowPriority();
HighPriority h = new HighPriority();
[Link]("Starting threads...");
[Link]();
[Link]();
[Link]("MAIN is done");
}
}
Starting threads...
The LOW priority thread is running
The LOW priority thread is running
The HIGH priority thread is running
The HIGH priority thread is running
The HIGH priority thread is running
The HIGH priority thread is running
MAIN is done
The LOW priority thread is running
...
The LOW priority thread is running
The HIGH priority thread is running
The HIGH priority thread is running
The HIGH priority thread is running
The HIGH priority thread is running
The LOW priority thread is running
...
The LOW priority thread is running
33
The HIGH priority thread is running
^C
[Link] de thread-uri
34
nostru grup de thread-uri, trebuie mai intii sa creem un obiect ThreadGroup.
Se poate folosi unul din acesti constructori :
ThreadGroup(String) - creaza un nou ThreadGroup cu numele specificat.
ThreadGroup(threadGroup, String) - creaza un nou ThreadGroup cu numele
specificat si apartinind la un anumit grup de thread-uri.
Dupa cum arata si al doilea constructor, un grup de thread-uri poate fi creat
in interiorul altui grup de thread-uri. Cel mai nou grup de thread-uri creat
devine membru la cel mai vechi realizindu-se astfel o ierarhie de grupuri.
Pentru a crea un thread in interiorul unui grup de thread-uri, altul decit
grupul main trebuie doar mentionat numele grupului atunci cind se apeleaza
constructorul thread-ului. In momentul in care avem mai multe thread-uri
organizate intr-un grup de thread-uri putem apela la operatii comune pentru
toti membrii acestui grup. Aceste operatii sunt in principal stop(), supend()
and resume() care au aceeasi semnificatie ca atunci cind se foloseste un
singur thread. Pe langa aceste operatii mai exista si alte operatii specifice
grupului de thread-uri.
5.6.1. Sincronizare
class MyData {
private int Data;
public void store(int Data) {
[Link]=Data;
}
public int load() {
return [Link];
}
}
35
Acum sa presupunem ca avem doua thread-uri: unul care incearca sa
depoziteze o valoare si unul care incearca sa scoata o valoare. In continuare
se prezinta codul care creaza cele doua thread-uri. Pentru a simula
procesarea in timp real, vom cere thread-urilor sa "adoarma" dupa fiecare
extragere de data :
36
}
public void run() {
for (;;) {
[Link] ("Consumer: "+[Link]());
try {
[Link] ((int) ([Link]()*500));
} catch (InterruptedException e) { }
}
}
}
Producer: 0
Consumer: 0
Producer: 1
Consumer: 1
Consumer: 1
Producer: 2
Producer: 3
Consumer: 3
Producer: 4
Producer: 5
Consumer: 5
Producer: 6
Consumer: 6
Producer: 7
Consumer: 7
Consumer: 7
Producer: 8
Producer: 9
Producer: 10
Consumer: 10
37
Dupa cum se observa, numerele 2, 4, 8 si 9 sunt produse dar nu sunt
niciodata consumate. Pe de alta parte, numerele 1 si 7 sunt produse o singura
data, dar consumate de cite doua ori. Acest lucru este datorat ordinei gresite
de executie. In primul rind ca producatorul nu are cum sa stie daca
consumatorul a consumat data si in consecinta suprascrie noua data. In al
doilea rind, consumatorul nu are cum sa stie daca citeste noua valoare sau pe
cea veche. Este deci nevoie de o comunicare intre cele doua thread-uri.
5.6.2. Solutie
Pentru a rezolva aceasta problema se pot folosi variabile binare pentru a
controla accesul la data. Flag-ul Ready va semnifica faptul ca noua data a
fost produsa si este gata de consum si flag-ul Taken va semnifica faptul ca
aceasta data a fost consumata si este gata de suprascriere.
class MyData {
private int Data;
private boolean Ready;
private boolean Taken;
public MyData() {
Ready=false;
Taken=true;
}
public void store(int Data) {
while (!Taken);
[Link]=Data;
Taken=false;
Ready=true;
}
public int load() {
int Data;
while (!Ready);
Data = [Link]; //Salvare, deoarece dupa ce
//Taken devine true, Data se poate schimba oricand
Ready=false;
Taken=true;
return Data;
}
}
Utilizind codul de mai sus vom obtine rezultatul asteptat : fiecare numar
38
este consumat o singura data si toate numerele sunt consumate. Oricum
aceasta solutieare un dezavantaj major : metodele store() si load() folosesc
bucle, thread-urile testeaza in mod constant flag-urile pentru a vedea daca
valorile lor s-au schimbat. Utilizarea buclelor de test ar putea determina ca
acest program sa nu functioneze pe platforme nepreemptive deoarece thread-
urile nu elibereaza procesorul si astfel thread-ul care trebuie sa schimbe o
valoare, poate sa nu fie planificat pentru executie (aceasta problema ar putea
fi rezolvata folosind apelurile metodei yield() in aceste bucle). O alta
problema in aceste conditii poate aparea atunci cind se foloseste acelasi cod
dar cu mai multi consumatori. Rulind mai mult de un thread de tip
Consumer vom avea mai multe thread-uri care consuma din aceeasi sursa
ceea ce ar putea conduce la situatii cind aceeasi valoare este consumata de
mai multe ori. Iata in continuare un exemplu de concurenta intre
Consumatori:
Dupa cum se poate observa cei doi consumatori consuma aceeasi valoare.
Aceasta se poate intimpla usor cind thread-urile impart acelasi procesor prin
preemptare, sau procesoare multiple care ruleaza thread-urile in acelasi timp.
O solutie la aceasta ultima problema este utilizarea mecanismului de
sincronizare din Java, mecanism care are la baza monitorul.
5.6.3. Monitoare
39
data. Cand un thread retine un monitor pentru a accesa o data, celelalte
thread-uri sunt blocate si nu pot avea acces la acea data. Un thread poate
prelua un monitor numai atunci cind celelalte thread-uri nu l-au preluat si il
poate elibera cind doreste. Poate exista un monitor pentru fiecare instanta a
unei clase care are o metoda declarata ca synchronized. Declararea unei
metode synchronized indica faptul ca numai acest thread care contine
monitorul poate executa aceasta metoda. Daca nici un thread nu a preluat
monitorul, apelarea unei astfel de metode are ca efect preluarea acestui
monitor. Achizitionarea monitorului este o operatie formata dintr-o singura
instructiune care garanteaza faptul ca un singur thread va prelua acel
monitor.
class MyData {
private int Data;
private boolean Ready;
private boolean Taken;
public MyData() {
Ready=false;
Taken=true;
}
40
}
class MyData {
private int Data;
private boolean Ready;
private boolean Taken;
public MyData() {
Ready=false;
Taken=true;
}
41
}
}
class MyData {
private int Data;
private boolean Ready;
public MyData() {
Ready=false;
}
public synchronized void store(int Data) {
while (Ready)
try {
wait();
} catch (InterruptedException e) { }
[Link]=Data;
42
Ready=true;
notify();
}
public synchronized int load() {
while (!Ready)
try {
wait();
} catch (InterruptedException e) { }
Ready=false;
notify();
return [Link];
}
}
In acest moment avem rezultatele dorite: fara a avea bucle wait si fara
probleme de sincronizare. De observat faptul ca metodele wait() si notify()
pot fi apelate numai din metode synchronized. A fi reentrant inseamna faptul
ca codul clasei este protejat impotriva accesului multiplu. Toate clasele
construite in Java sunt reentrante ceea ce inseamna ca ele pot fi folosite in
programarea multithreading fara probleme. Din moment ce Java este pentru
reutilizarea obiectelor si din moment ce nu stim cind sa utilizam o clasa din
nou, este necesar sa o proiectam reentranta inca de la inceput. Ultima
implementare a clasei MyData este o implementare reentranta. Sa vedem ce
se intimpla cind mai multe thread-uri incearca sa acceseze aceeasi metoda
simultan:
43
numar.
Acum situatia este viceversa: acum sunt mai multi producatori si un singur
consumator. Acum producatorii vor astepta (wait()) in timp ce consumatorul
va "trezi" pe fiecare dintre ei la un anumit timp permitindu-le sa genereze
noi date. Deoarece Ready este false dupa ce consumatorul "consuma" fiecare
valoare, un singur thread producator va produce o singura valoare. Deoarece
producatorul va schimba starea flag-ului Ready ceilalti producatori nu vor
suprascrie o noua valoare (chiar daca acestia sunt "treji") pina ce aceasta
valoare nu este consumata.
5.7. Bariere
import [Link].*;
44
} catch (InterruptedException e) { }
} else {
// Acesta a fost ultimul thread activ
notifyAll();
WaitingAtBarrier=0; // le elibereaza pe toate
}
}
}
45
Class MainThread extends Thread {
public void run() {
SecondaryThread s = new SecondaryThread();
[Link]();
// do my job...
// wait for the secondary thread to die
if ([Link]()) [Link]();
// We are done !
}
}
class Semaphore {
protected int value;
Semaphore( int initialValue ) {
value = initialValue;
}
Semaphore() {
value = 0;
}
public synchronized void Get() {
while (value<1) wait();
value--;
}
public synchronized void Put() {
value++;
notify();
}
}
5.10 Starvation
46
Termenul de starvation (flaminzirea) caracterizeaza situatiile in care un
thread este privat de resurse (accesul la un monitor). Spre deosebire de
interblocare, in situatia de starvation calculele pot continua in sistem, doar ca
thread-ul flaminzit nu mai poate continua. Starvation se poate produce atunci
cind un thread de prioritate mai mare isi incepe executia si nu mai elibereaza
procesorul. De altfel toate thread-urile de prioritate mai mica sunt flaminzite.
47
}
}
class Semaphore {
protected int value;
Semaphore( int initialValue ) {
value = initialValue;
}
Semaphore() {
value = 0;
}
public synchronized void Get() {
while (value<1) wait();
value--;
}
public synchronized void Put() {
value++;
notify();
}
}
5.14. Starvation
48
5.15 Interblocarea (Deadlock)
6. Arhitectura server.
Clasa Packet:
class Packet implements IPacket
{
int id=0;
Vector lada=new Vector();
//constructorul
Packet(int id)
{
[Link]=id;
}
49
}
Se observa ca are o variabila membru: int id – indentificatorul packet-
ului si data membru: lada de tip Vector, in care se poate pune absolut orice
obiect dorim.
Desriere semnificatii packete:
ID Client Server
packet
0 Lista canale disponibile Trebuie sa faca un packet cu lista de
canale
1 Cofirmare logare Un client se logeaza la server.
2 Clientul primeste lista de Un client face join la un anumit
clienti de pe un anumit canal server-ul trebuie sa-l plaseze
canal la care a facut join pe client pe acel client si sa-i
transmita lista de clienti de pe acel
canal
3 Anunt: un client pe Chat. Trebuie sa trasmita un mesaj unui
anumit client de pe un anumit canal
4 Un client pleaca de pe chat Transmit mesaj unui anumit client
de pe un anumit canal
10 Mesaj de la server. Mesaj broadcast la toate canalele si
clientilor aflati pe aceste canale
6.2 Retea
while (true) {
accept a connection ;
create a thread to deal with the client ;
end while
50
Server-ul va crea pentru fiecare client in parte cate in fir de executie,
un fir de executie moare in momentul in care, respectivul client, incheie
sesiunea cu server-ul sau conexiunea dintre un client si server se intrerupe.
jos:
Baza de date apare sub forma unei liste, lista principala se numeste
canale, care are 2 date membru: numeCanal si o referinta la o alta lista
numita clienti. Lista clienti are si ea date membru: numele clientului,
referinta la oos (pe unde se trimit date la client). Declararea listei canale din
cadrul clasei Canale are modificatorul static.
51
/**
* ADAUGA UN CANAL
*/
public void addCanal(String numeCanal)
{
clienti=new RCL();
[Link]=numeCanal;
synchronized(this)
{
[Link](clienti);
}
52
}
/**
* Returneaza lista de canale.
*/
public Vector listaCanale()
{
Vector listaCanale=new Vector();
//initializare canale
for (int i=0;i<[Link]();i++)
{
RCL canal=(RCL)[Link](i);
[Link]([Link]);
}//for
return listaCanale;
}//listaCanale
Metoda de mai sus extrage lista de canale si o returneaza sub forma
unei liste de tip Vector.
/**
* Lista clientilor de pe un anumit canal.
* @param numeCanal - numele canalului
*/
public Vector listaClientiDePeUnCanal(String numeCanal)
{
//[Link] calului
for(int i=0;i<[Link]();i++)
53
{
RCL canal=(RCL)[Link](i);
if ([Link](numeCanal))
{
return [Link]();
}//if
}//for
return new Vector();
}//listaClietiDePeUnCanal
Metoda de mai sus extrage lista de clienti de pe un anumit canal si o
returneaza sub forma unui liste, la fel ca si la listaCanale(), sub forma unui
liste de tip Vector.
7. Arhitectura client
7.2. Retea
Clientul TCP are o arhitectura mult mai simpla fata de server-ul TCP.
Clientul isi face un socket si apoi incearca sa se conecteze la server, daca
conexiunea a fost stabilita clientul primeste un mesaj prin care este informat
de starea conexiunii in caz contrar se arunca o exceptie Conection Reset by
Peer sau Conection Refuzed.
54
// open a socket connection
socket = new Socket([Link],1979);
// open I/O streams for objects
oos = new ObjectOutputStream([Link]());
ois = new ObjectInputStream([Link]());
//conectare cu succes
-oos si ois sunt stream-uri de date care sunt create in urma conexiunii.
55
{
RLCC canal=(RLCC)[Link](i);
if ([Link](numeCanal))
{
listaClienti=[Link]();
return listaClienti;
}//if
}//for
return listaClienti;
}//listaClientiDePeUnCanal
ID Client Server
packet
0 Lista canale de disponibile Trebuie sa faca un packet cu lista de
canale
1 Cofirmare logare Un client se logeaza la server.
2 Client primeste lista de Un client face join la un anumit
clienti de pe un anumit canal server-ul trebuie sa-l plaseze
canal la care a facut join pe client pe acel client si sa-i
transmita lista de clienti de pe acel
canal
3 Anunt: un client pe Chat. Trebuie sa trasmita un mesaj unui
anumit client de pe un anumit canal
4 Un client pleaca de pe chat Transmit mesaj unui anumit client
de pe un anumit canal
10 Mesaj de la server. Mesaj broadcast la toate canalele si
clientilor aflati pe canale
56
Pe piata de soft sunt nenumarate programele de chat si
videoconferinta in timp real. Una dintre cele mai populare astfel de aplicatii
este mIrc-ul, care are prin toata lumea servere si un site de unde client-ul de
mIrc poate fi descarcat contra unei anumite sume.
11. Concluzii
12. Bibliografie
57
[Link]
[Link]
[Link]
[Link]/~stefan
[Link]/~acf
58