0% au considerat acest document util (0 voturi)
3 vizualizări58 pagini

Programare CWS

Lucrarea de licență detaliază aplicația CWS, care permite interacțiunea utilizatorilor prin schimburi de mesaje și imagini, fiind implementată în Java pentru a funcționa pe multiple platforme. Documentul descrie modul de utilizare, arhitectura client-server, instrumentele necesare pentru realizarea interfeței grafice și comunicarea prin rețea, inclusiv protocoalele TCP și UDP. De asemenea, se discută despre instalarea serverului, conectarea clienților și tipurile de mesaje transmise între aceștia.

Încărcat de

ilescudeea
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca DOC, PDF, TXT sau citiți online pe Scribd
0% au considerat acest document util (0 voturi)
3 vizualizări58 pagini

Programare CWS

Lucrarea de licență detaliază aplicația CWS, care permite interacțiunea utilizatorilor prin schimburi de mesaje și imagini, fiind implementată în Java pentru a funcționa pe multiple platforme. Documentul descrie modul de utilizare, arhitectura client-server, instrumentele necesare pentru realizarea interfeței grafice și comunicarea prin rețea, inclusiv protocoalele TCP și UDP. De asemenea, se discută despre instalarea serverului, conectarea clienților și tipurile de mesaje transmise între aceștia.

Încărcat de

ilescudeea
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca DOC, PDF, TXT sau citiți online pe Scribd

Universitatea

Facultatea

Lucrare de licenta

Programare CWS

1
Cuprins:

1. O privire de ansamblu asupra aplicatiei.


2. Modul de utilizare.
3. Instrumente necesare realizarii interfetei (GUI) pe partea de client,mod
de realizare.
4. Instrumente necesare comunicatii prin retea (Sockets, Serializarea
obiectelor).
5. Fire de executie. Controlul firelor de executie.
6. Arhitectura server.
6.1 Privire de ansamblu
6.2 Retea
6.3 Baza de date cu canale si clienti
7. Arhitectura client.
7.1 Privire de ansamblu
7.2 Rretea
7.3 Baza de date cu canale si clienti
9. Tipuri de pachete transmise intre client si server
10. Comparatii cu alte produse soft existente pe piata in momenul de fata.
11. Concluzii
12. Bibliografie.

2
[Link] de ansamblu asupra aplicatiei

CWS face posibil ca mai multii utilizatori sa interactioneze prin


intermediul internetului. Acestia pot face schimburi de mesaje de tip text si
un numar finit de imagini.
Aplicatiea CWS are un mare ajantaj, este implementata in JAVA, ceia
ce, face posibil ca aceasta sa ruleze pe mai multe platforme: Windows,
Solaris, Linux, MacOS. Totodata are si un dezavantaj: ruleaza mai incet
datorita acestei independente de platforma..
Aplicatia CWS este formata din 2 parti: server si client. Server-ul va
rula pe o masina la distanta pe cand partea de client va rula pe masina
utilizatorului. CWS, partea de client, are o interfata simpla usor de folosit,
chiar si pentru un utilizator care are cunostinte minime in lucrul cu
calculatorul, partea de server nu are nici un tip de interfata, acesta lucrand in
background pe masina de la distanta.
Pentru a putea rula, aplicatia CWS, are nevoie de o conexiune la
Internet s-au poate fi instalata pe o retea locala de tip LAN si nu necesita o
latime de banda mare.

Fig 1. Client

Fig. 2 Server

3
2. Modul de utilizare

2.1. Modul in care se face instalarea server-lui si lansarea in executie

Trebuie instalat pe o masina care sa aiba o legatura la o retea locala


(clientii vor putea fi numai din cadru retelei locale) s-au Internet (clientii pot
fi din orice tip de retea). Masina pe care ruleaza server-ul aplicatiei CWS
necesita ca portul 1979 sa fie liber, in caz contrar aplicatia nu va functiona.
Lansarea server-ul se face prin comanda: java Server.

Daca fereastra de mai sus exista, in urma executarii comenzii java


Server, inseamna ca server-ul este activ si clientii pot sa se conecteze la el.
Acesta fereastra apare in Windows.

2.2 Modul de utilizarea al clientului.

Ca un anumit client sa se poata conecta la server si intra in contact cu


alti clientii trebuie sa urmeze o serie de pasii:
1. conectare
2. setarea nickname-ului, nick-ul va trebuia sa fie unic
3. logare

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]

Dublu click pe butonul din figura de mai sus va duce la conectare cu


server-ul, daca server-ul acepta sau nu conexiunea clientul este anuntat print-
un mesaj.

2.2.2. Setarea Nickname-ului

In textfield –ul din figura de mai sus se introduce nickname-ul cu care


un client va putea fi identificat pe chat.

2.2.3. Logare

Daca buton-ul de sus este apasat clientul trasmite un mesaj server-ului


prin care il anunta ca vrea sa se conecteze, server-ul primeste deasmenea si

5
numele de logare, server-ul verifica unicitatea nickname-ul si trasmite inpoi
la client daca logarea a avut loc cu succes sau nu.

2.2.4. Join la un canal

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.

2.2.5. Alegerea unei imagini

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.

2.2.6. Schimbul de mesaje

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:

Daca am ales un canal putem trimite mesaj pe tot canalul, selectand


din lista clientilor item-ul All sau unui anumit client de pe acel canal facand
o selectie din lista de clienti, ca in figura 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.

Mesaje trimise intre clienti vor putea fi inotite de o imagine, care


poate reprezenta starea unui anumit client.

3. Instrumente necesare realizarii interfetei (GUI) pe partea de


client,modul de realizare

Aplicatie CWS este implementa in Java un limbaj de programare care


contine o serie de clase prin care se poate crea o interfata cu utilizatorul
corespunzatoare standardelor actuale. Clasele raspunzatoare de GUI fac
parte de packet-ul Swing. In Java mai exista un packet de clase, numit AWT
dar care nu au aceiasi putere ca Swing-ul. Swing-ul, parte componenta a
JDK incepand cu versiunea 1.2, poate crea urmatoare tipuri de controale:
 Dialog
 Frame
 Scroll Panel
 Split Panel
 Tool Bar
 Internal Frame
 Tabbed Pane
 Buttons
 Combo Box
 Slider
 TextFields
 Label
 Progress Bar
 Tool Tip
 Color chooser
 File Chooser
 Table
 Text

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 Java putem crea aplicatii independenta sau miniaplicatii care ruleaza in


interiorul paginilor Web, sa vedem in continuare cum se poate crea o astfel
de aplicatie. Setul Swing introduce versiunea sa de applet, al carei parent
este appletul AWT. Appleturile Swing sunt facute mai sofisticat asa incat
suporta bare de meniu si layering de componente in dimensiunea adincimii,
si permite pictarea deasupra pe componentele deja introduse in applet.

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.

Alta facilitate care permite pictarea peste componente in interiorul appletului


poate fi realizata folosind o componenta numita "glass pane" (panel de
sticla). Este o componenta transparenta care permite componetelor din
fundal sa fie vizibile. Dar normal ca se poate picta peste acest panel ca sa
faci vizibil orice element din cadru.

Appleturile Swing sunt reprezentate de clasa JApplet, care este stocata in


pachetul [Link]. Se poate crea un applet Swing prin crearea unei clase
care extinde JApplet. In timp ce adaugi membrii clasei, trebuie sa ii adaugi
de fapt unui panel numit panel de continut, care primeste practic membrii
appletului. Biblioteca Swing introduce un container intermediar care
lucreaza cu complexitatea impusa de combinarea de componente in applet.
Urmatoarea sursa de cod arata cum o componenta poate fi adaugata intr-un
panel de continut intr-un applet Swing :

9
// Obtine un handle la panelul de continut al appletului
Container contentPane = [Link]( );

// Poate vrei sa atribui un nou layout


[Link](someLayoutObject);

// Adauga componenta la applet


[Link](someComponent);

Se pot obtine de asemenea referinte la panelul layered sau glass, si la bara de


meniu prin apelarea urmatoarelor metode asupra unei insatnte a appletului:

public void getLayeredPane( )


public void getGlassPane( )
public void getJMenuBar( )

Program exemplu cu JApplet

[Link], prezinta un applet care afiseaza o eticheta simpla cu


margine.

/*
* <Applet code=TJApplet width=200 height=100>
* </Applet>;
*/

import [Link].*;
import [Link].*;

public class TJApplet extends JApplet {

public void init( ) {


// 1. Obtine un handle la panelul de continut al appletului
Container contentPane = getContentPane( );
// 2. Creaza o eticheta cu un icon si text
JLabel label = new JLabel("This is a Swing applet", // text
new ImageIcon("[Link]"), // icon
[Link]); // pozitia orizontala

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

// 4. Adauga eticheta la panel.


[Link](label);
}
}

Detalii la cod

TJApplet extendinde clasa Swing JApplet si astfel se numeste applet Swing.


In interiorul metodei init(), se primeste un handle la panelul de continut al
appletului. Aceasta referinta se foloseste la adaugarea de componenente la
applet. Apoi se creaza o eticheta Swing cu un text specificat si un icon. Apoi
se atribuie o bordura si se specifica finetea bordurii. La urma se adauga
eticheta la panel.

4. Instrumente necesare comunicatii prin retea (Sockets, Serializarea


obiectelor), modul

Programarea în retea implica trimiterea de mesaje si date între


aplicatii ce ruleaza pe calculatoare aflate într-o retea locala sau conectate la
Internet. Pachetul care ofera suport pentru scrierea aplicatiilor de retea este
[Link]. Clasele din acest pachet ofera o modalitate facila de programare în
retea, fara a fi nevoie de cunostine prealabile referitoare la comunicarea
efectiva între calculatoare. Cu toate acestea sunt necesare câteva notiuni
fundamentale referitoare la retele, cum ar fi protocol, adresa IP, port, socket.

[Link]

Un protocol reprezinta o conventie de reprezentare a datelor folosita


în comunicarea între doua calculatoare. Având în vedere faptul ca orice

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.

UDP (User Datagram Protocol) este un protocol ce trimite pachete


independente de date, numite datagrame, de la un calculator catre altul fara
a garanta în vreun fel ajungerea acestora la destinatie. Acest protocol nu
stabileste o conexiune permanta între cele doua calculatoare.

[Link] este identificat un calculator în retea ?

Orice calculator gazda conectat la Internet este identificat în mod unic


de adresa sa IP (IP este acronimul de la Internet Protocol). Aceasta
reprezinta un numar reprezentat pe 32 de biti, uzual sub forma a 4 octeti,
cum ar fi de exemplu: [Link] si este numit adresa IP numerica.
Corespunzatoare unei adrese numerice exista si o adresa IP simbolica, cum
ar fi [Link].
De asemenea fiecare calculator aflat într-o retea locala are un nume
unic ce poat fi folosit la identificarea locala a acestuia.
Clasa Java care reprezinta notiunea de adresa IP este InetAddress.

[Link] este un port ?

Un calculator are în general o singura legatura fizica la retea. Orice


informatie destinata unei anumite masini trebuie deci sa specifice obligatoriu
adresa IP a acelei masini. Insa pe un calculator pot exista concurent mai
multe procese care au stabilite conexiuni în retea, asteptând diverse
informatii. Prin urmare datele trimise catre o destinatie trebuie sa specifice
pe lânga adresa IP a calculatorului si procesul catre care se îndreapta

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.

Serializarea obiectelor si socketii ...

[Link] este serializarea ?

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.

In limbajul de programare Java putem salva starea anumitor obiecte


prin serializare si deasemena putem restaura aceste obiecte prin procesul
invers serilizarii sau putem trimite obiecte pe retea prin socketi. Serializarea
unei clase se poate realiza foarte usor prin implementarea interfetei
[Link]. Aceasta interfata, Serializable, nu are nici o metoda care
trebuie implementata ci doar informeaza masina virtuala ca instantele acelei
clase (obiectele) vor putea fi serializate.
Exista 2 clase principale care pot scrie si citii obiecte: ObjectInputStream si
ObjectOutputStream. Clasa ObjectOutputStream are o metoda
writeObject(obj) care scrie un obiect intr-un flux de date, clasa
ObjectInputStream are o metoda readObject() care citeste un obiect dintr-un
flux de date.
Doua exemple de scriere respectiv citire a unui obiecte:

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]();
}
}

In exemplu de sus am restaurat obiectul serializat in COD 1. In


exemple de mai sus am lucrat cu instante ale clasei Date care este o clasa
serilizabila. Exista o unealta care ne permite sa vedem daca o clasa este sau
nu serializabila. Aceasta unealta o putem utiliza in urmatorul mod, la
promterul sistemului de operare tastam comanda:

> serialver [Link]

Rezultat : [Link]: static final long serialVersionUID =


7523967970034938905L;

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);
}
}

Scriem o clasa care face 2 obiecte de tip UserInfo si le pune intr-un


flux de date care se numeste [Link]. De retinut este ca putem apela
metoda writeObject de cate ori dorim.
COD 4

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]();
}
}

Daca compilam si rula aceste fisiere sursa obtinem ca rezultat:


The name is: Java Duke
The name is: Java Blue

[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:

transient private String parola; //ignorat la serializare


Atentie : Modificatorul static anuleaza efectul modificatorului transient;
static transient private String parola; //participa la serializare

[Link] obiectelor prin socketi

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].*;

public class DateServer extends Thread {

private ServerSocket dateServer;

public static void main(String argv[]) throws Exception {


new DateServer();
}

public DateServer() throws Exception {


dateServer = new ServerSocket(3000);
[Link]("Server listening on port 3000.");
[Link]();
}

public void run() {


while(true) {
try {
[Link]("Waiting for connections.");
Socket client = [Link]();
[Link]("Accepted a connection from: "+
[Link]());
Connect c = new Connect(client);
} catch(Exception e) {}
}
}
}

class Connect extends Thread {

19
private Socket client = null;
private ObjectInputStream ois = null;
private ObjectOutputStream oos = null;

public Connect() {}

public Connect(Socket clientSocket) {


client = clientSocket;
try {
ois = new ObjectInputStream([Link]());
oos = new ObjectOutputStream([Link]());
} catch(Exception e1) {
try {
[Link]();
}catch(Exception e) {
[Link]([Link]());
}
return;
}
[Link]();
}

public void run() {


try {
[Link](new Date());
[Link]();
// close streams and connections
[Link]();
[Link]();
[Link]();
} catch(Exception e) {}
}
}

Clientul nostru nu are nimic de transmis ci doar primeste un obiecte


de tip Date si apoi afiseaza data.

COD 7

20
import [Link].*;
import [Link].*;
import [Link].*;

public class DateClient {


public static void main(String argv[]) {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
Socket socket = null;
String adresaServer="[Link]";
Date date = null;
try {
// open a socket connection
socket = new Socket(adresaServer, 3000);
// open I/O streams for objects
oos = new ObjectOutputStream([Link]());
ois = new ObjectInputStream([Link]());
// read an object from the server
date = (Date) [Link]();
[Link]("The date is: " + date);
[Link]();
[Link]();
} catch(Exception e) {
[Link]([Link]());
}
}
}

Pentru a putea rula aceasta aplicatie client/sever, trebuie sa modificati


in client adresa server-ului, asta numai in cazul in care server-ul ruleaza pe o
masina si clientul pe alta masina. Daca atat clientul cat si serverul ruleaza pe
aceiasi masina atunci totul e OK.

In exemplul precedent, am lucrat cu obiecte existente. Ce se intampla


daca vrei sa transporti instante ale unor clase scrise de noi. E procesul
diferit ?

Primul lucru pe care il facem este sa implementam clasa ale care


instante vor fi transportate prin socketi.

21
COD 8.

import [Link].*;
import [Link].*;

public class SerializedObject implements Serializable {


private int array[] = null;

public SerializedObject() {
}

public void setArray(int array[]) {


[Link] = array;
}

public int[] getArray() {


return array;
}
}

Clasa din COD 8 trebuie sa fie serializabila de aceia trebuie sa


implementeze neaparat interfata Serializable. Urmatorul pas este de a
implementa clientul, clientul creaza doua instante de tip SeralizedObject si le
scrie apoi la fluxul de iesire (la server) asa cum ve-ti vedea in codul sursa
COD 9.

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]());
}
}
}

Trebuie sa mai implementam si serverul ca aplicatia noastra sa


mearga. Server-ul din COD 10 este similar cu cel de la COD 6 diferenta
consta doar prin felul in care se proceseaza datele.

COD 10.
import [Link].*;
import [Link].*;

23
public class ArrayMultiplier extends Thread {

private ServerSocket arrayServer;

public static void main(String argv[]) throws Exception {


new ArrayMultiplier();
}

public ArrayMultiplier() throws Exception {


arrayServer = new ServerSocket(4000);
[Link]("Server listening on port 4000.");
[Link]();
}

public void run() {


while(true) {
try {
[Link]("Waiting for connections.");
Socket client = [Link]();
[Link]("Accepted a connection from: "+
[Link]());
Connect c = new Connect(client);
} catch(Exception e) {}
}
}
}

class Connect extends Thread {


private Socket client = null;
private ObjectInputStream ois = null;
private ObjectOutputStream oos = null;

public Connect() {}

public Connect(Socket clientSocket) {


client = clientSocket;
try {
ois = new ObjectInputStream([Link]());
oos = new ObjectOutputStream([Link]());

24
} catch(Exception e1) {
try {
[Link]();
}catch(Exception e) {
[Link]([Link]());
}
return;
}
[Link]();
}

public void run() {


SerializedObject x = null;
SerializedObject y = null;
int dataset1[] = new int[7];
int dataset2[] = new int[7];
int result[] = new int[7];
try {
x = (SerializedObject) [Link]();
y = (SerializedObject) [Link]();
dataset1 = [Link]();
dataset2 = [Link]();
// create an array by multiplying two arrays
for(int i=0;i<[Link];i++) {
result[i] = dataset1[i] * dataset2[i];
}
// ship the object to the client
SerializedObject output = new SerializedObject();
[Link](result);
[Link](output);
[Link]();
// close connections
[Link]();
[Link]();
[Link]();
} catch(Exception e) {}
}
}

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.

Daca rulati aplicatia de mai sus obtineti ca rezultat :

The new array is: 15 15 15 15 15 15 15

RMI vs. Socketi si Seralizare

RMI - Remote Method Invocation


RMI poate fi exprimat dupa urmatoarea formula :
RMI = Sockets + Object Serialization + Some Utilities

Avantaje ale RMI:


-simplitate
-nu trebuie sa crezi un protocol intre client si server
-trateaza toate obiectele ca si cum ar fi pe aceiasi masina

Toate aceste avantaje, in schimb, duc la o viteza mica de transmisie de


date "utile" intre client si server. Daca dorim sa realizam un proiect trebuie
sa ne gandim cu mare atentie ce sistem sa folosim pentru o obtine rezultate
cat mai optime.

5. Fire de executie

Un sistem multiprocesor (SM) este un mecanism care permite unui


sistem de a
folosi mai mult de un procesor. Sistemul Mutiprocesor Simetric (SMS) este
o parte a Calculului Paralel unde toate procesoarele sunt identice. In SMS
procesoarele sunt dirijate in asa fel incit sarcinile sunt impartite de catre
sistemul de operare iar aplicatiile sunt executate pe mai multe procesoare
care impart acelasi spatiu de memorie. SMS garanteaza ca intreg numarul de
procesoare deserveste sistemul de operare. Fiecare sub-proces poate fi
executatpe orice procesor liber. Astfel se poate realiza echilibrarea incarcarii

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.

5.1 Ce sunt thread-urile?

Thread-ul reprezinta executia liniara a unei singure secvente de instructiuni


care ruleaza in interiorul programului nostru. Toti programatorii sunt
familiarizati cu scrierea programelor secventiale. Programele secventiale au
un punct de start, o secventa de executie si un punct terminal. Cel mai
important lucru la programul secvential este acela ca la orice moment o
singura instructiune este executata. Thread-ul este similar cu un program
secvential in sensul ca thread-ul are si el un punct de start, o secventa de
executie si un punct terminal. De asemenea intr-un thread se executa doar o
singura instructiune la un moment dat. Si totusi un thread nu este la fel ca un
program obisnuit. Spre deosebire de programe, thread-ul nu poate exista de
unul singur. Thread-urile coexista in interiorul unui program. Deci putem
avea mai multe thread-uri care se executa simultan.

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.

5.2 Concurenta thread-urilor.

Fara a intra intr-o discutie pe teme hardware, este bine de spus ca


procesoarele calculatoarelor pot executa doar o instructiune la un moment
dat. De ce spunem ca thread-uri diferite se executa in acelasi timp? Spunind
simultan nu inseamna numai lucruri in medii diferite. Pe omasina
multiprocesor, thread-urile pot exista pe procesoare diferite in acelasitimp
fizic, aceasta mentinindu-se valabil chiar daca procesoarele sunt pe
calculatoare diferite conectate intr-o retea. Dar si pe o masina cu un
singurprocesor, thread-urile pot imparti acelasi procesor, rulind intr-o
manieraintretesuta, competitia pentru timpii CPU creind iluzia ca ele se
executasimultan. Aceasta iluzie pare reala cind 30 de imagini distincte pe
secundacaptate de ochiul uman sunt percepute intr-un flux continuu de
imagine. Comutarea intre thread-uri are si ea un pret : consuma timp CPU
pentru ca acesta sa inghete starea unui thread si sa dezghete starea unui alt
thread (schimbare de context). Daca thread-urile concurente sunt executate
pe acelasi procesor si toate executa calcule atunci timpul total de executie nu
va lua mai mult decit timpul de executie al unui program secvential care
realizeaza acelasi lucru.
Din moment ce intr-un sistem monoprocesor thread-urile concura la timpul
procesor cum este posibil cresterea vitezei sistemului? Aceasta se realizeaza
prin intreteserea diferitelor faze ale diferitelor thread-uri. Multe task-uri pot
fi segmentate logic in tipuri de faze: faza de calcul si faza I/O. Faza de calcul
necesita atentia maxima din partea CPU-ului prin utilizarea diferitelor
metode de calcul. Faza de I/O (intrare/iesire) necesita atentie maxima din
partea perifericelor (imprimante, hard discuri, placi de retea, etc) si in aceste
situatii procesorul este in general liber, asteptind ca perifericul sa-si termine
sarcina. Cresterea vitezei este obtinuta prin intreteserea fazelor. In timp ce
un thread se afla intr-o faza de I/O asteptind ca o secventa de date sa fie
incarcata de pe hard disk, un thread cu o faza de calcul poate ocupa
procesorul si cind ajunge la o faza I/O, celalalt thread (care tocmai a
terminat faza I/O proprie) poateincepe sa utilizeze CPU-ul.

5.3 Contextul thread-urilor si memoria distribuita

Thread-urile ruleaza in contextul unui program, folosind resursele acestuia.

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.

5.4 Executia paralela a thread-urilor

Cu toate ca exemplele de pina acum nu au fost in masura sa puna in evidenta


vreun nivel de paralelism, ele de fapt au facut acest lucru. Odata ce s-a
terminat executia instructiunii [Link](), instanta clasei MyThread isi
incepe executia. Nu se poate spune cu siguranta ca mesajul de pe ecran a fost
tiparit dupa terminarea metodei main(), tiparirea pe ecran putind avea loc si
inaintea terminarii metodei main(). Totul depinde de felul in care au fost
alocati timpii procesor la thread-uri si de faptul daca exista unul sau mai
multe procesoare. Pentru a demonstra acest principiu, sa consideram
urmatorul program si rezultatul lui:

class PrintThread implements Runnable {


String str;
public PrintThread (String str) {
[Link] = str;
}
public void run() {
for (;;)
[Link] (str);
}
}

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...

Cu un numar egal de "A" si de "B". Acest exemplu este menit sa


demonstreze faptul ca aceste doua thread-uri lucreaza in paralel.
Multithreading preemptiv contra multithreading non-preemptiv.
Multithreading preemptiv inseamna faptul ca un thread poate fi preemptat
(suspendat) de catre alt thread in timp ce se executa. Nu toate sistemele care
suporta multithreading prezinta mecanism preemptiv. Iesirea aceluiasi
program pe un sistem SPARC/Solaris 2.5 ar arata in felul urmator:

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:

class WellBehavedPrintThread implements Runnable {


String str;
public PrintThread (String str) {
[Link] = str;
}
public void run() {
for (;;) {
[Link] (str);

30
[Link]().yield();
}
}

Instructiunea [Link]().yield() utilizeaza o metoda publica a


clasei Thread pentru a capata handler-ul catre thread-ul curent si apoi ii
spune sa elibereze procesorul. Iesirea acestui exemplu este:

ABABABABABABABABABABABABABABABABABABABABABABA
BABABABABABABABABAB
ABABABABABABABABABABABABABABABABABABABABABABA
BABABABABABABABABAB
ABABABABABABABABABABABABABABABAB
ABABABABABABABABABABABABABABABABABABABABABABA
BAB
ABABABABABABABABABABABABABABABABABABABABABABA
BAB
...

5.5 Starile thread-urilor

Creind o instanta a unui thread acesta nu este lansat. Aceasta sarcina de a


lansa thread-ul in executie este realizata de metoda start(). Un thread se
poate gasi in stari diferite in functie de evenimentele trecute. Putem fi in una
din situatiile:

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.

5.5.2. Thread in executie


Thread-ul a fost startat cu metoda start(). Este thread-ul pe care procesorul il
executa in acest moment.

5.5.3. Thread gata de executie (runnable)


Thread-ul a fost startat dar nu este in executie pe procesor in acest moment.
Motivul ar fi acela ca thread-ul a renuntat la procesor apelind metoda yeld(),
sau din cauza unui mecanism de programare a timpului procesor care a decis
sa distribuie timpul procesor al acestui thread altui thread. Thread-ul va
putea trece in executie cind mecanismul de programare va decide acest

31
lucru. Atita timp cit exista un alt thread cu o prioritate mai mare, thread-ul
nu va trece in executie.

5.5.4. Thread nepregatit pentru executie (non runable thread)


Thread-ul nu poate fi executat din anumite motive. Ar putea sa astepte o
operatie de tip I/O sa se termine. A trebuit sa apeleze metoda wait(), sleep()
sau suspend().

5.6 Prioritatile thread-urilor

Fiecarui thread ii este asignata o prioritate cuprinsa intre MIN_PRIORITY


(egala cu 1) si MAX_PRIORITY (egala cu 10). Un thread mosteneste
prioritatea de la thread-ul care l-a creat, dar aceasta prioritate se poate
schimba apelind metoda setPriority() si aceasta prioritate se poate afla
apelind getPriority().
Algoritmul de planificare intodeauna va lansa thread-ul cu prioritatea cea
mai mare sa se execute. Daca exista mai multe thread-uri cu aceeasi
prioritate maxima atunci procesorul le va executa intr-o maniera round-
robin. Astfel un thread cu prioritate mica se poate executa numai atunci cind
toate thread-urile de prioritate mai mare sun in starea non-runnable().

Prioritatea thread-ului maineste NORM_PRIORITY( egala cu 5). Iata un


exemplu de utilizare a prioritatilor:

class LowPriority extends Thread {


public void run(){
setPriority(Thread.MIN_PRIORITY);
for(;;){
[Link]("The LOW priority thread is running");
}
}
}
class HighPriority extends Thread {
public void run(){
setPriority(Thread.MAX_PRIORITY);
for(;;){
for (int i=0; i<5; i++)
[Link]("The HIGH priority thread is running");
try {

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");
}
}

Iesirea acestui program va arata ca mai jos:

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

Sa analizam putin iesirea programului:

1. Prima linie este afisata de thread-ul principal. Thread-ul principal


lanseaza thread-ul de prioritate minima si thread-ul de prioritate maxima.
2. Threadul de prioritate minima incepe sa se execute, acesta este facut
imediat non-runnable de catre un thread de prioritate mai mare.
3. Thread-ul de prioritate mai mare incepe sa se execute, isi seteaza
prioritatea maxima si astfel isi incepe executia. Dupa afisarea a 5 linii,
thread-ul este "adormit" si thread-ul main devine cel mai prioritar.
4. Thread-ul main afiseaza un mesaj dupa care se termina. In acest moment
singurul thread gata de executie (runnable) este thread-ul de prioritate mica.
5. De aici incolo iesirea programului va arata la fel.
6. Thread-ul de prioritate mica va afisa mesaje proprii pina cind thread-ul de
prioritate mai mare se va trezi.
7. Thread-ul de prioritate mare incepe sa se execute, afiseaza 5 linii si
"adoarme" din nou.
8. Thread-ul de prioritate minima poate rula din nou

Sistemul poate genera o exceptie de tipul IllegalThreadStateException cind


este apelata o metoda a unui thread cind starea acestuia nu permite acest
lucru. De exemplu, exceptia, IllegalThreadStateException este generata cind
se face apelul metodei suspend() unui thread care nu este runnable. Si un
ultim amanunt despre starile thread-urilor: clasa Thread cuprinde o metoda
numita isAlive() care returneaza true daca thread-ul a fost startat dar nu
stopat si false cind thread-ul este in starea new Thread sau Dead. Nu se poate
face distinctie intre un thread in stare new Thread si un thread in stare Dead
si nici intre unul Runnable si unul not Runnable.

[Link] de thread-uri

Fiecare thread apartine unui grup de thread-uri. Un grup de thread-uri este o


multime de thread-uri (si posibil grupuri de thread-uri) impreuna cu un
mecanism de realizare a operatiilor asupra tuturor membrilor multimii.
Grupul implicit de thread-uri este implicit numit main, si fiecare group de
thread-uri nou creat apartine acestui grup, mai putin acelea specificate in
constructorul sau. Pentru a afla pentru un thread la ce grup de thread-uri
apartine se poate folosi metoda getThreadGroup(). Pentru a crea propriul

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

Cind se utilizeaza mai multe thread-uri avem nevoie de o sincronizare a


activitatilor lor. Exista cazuri in care dorim sa prevenim accesul concurent
la structurile de date ale programului care reprezinta secvente comune
acestor thread-uri. Limbajul Java ne ajuta in acest sens cu ajutorul unui
mecanism de sincronizare si excludere mutuala (permite numai unui singur
thread sa opereze asupra unei sectiuni critice). Sincronizarea dintre thread-
uri in Java se realizeaza folosind metodele notify() si wait(). Ecluderea
mutuala se realizeaza prin folosirea monitoarelor.

Sa consideram urmatoarea clasa al carei obiectiv este de a stoca date:

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 :

class Main {// Clasa utilizata pentru a pune lucrurile in


miscare
public static void main(String argv[]) {
MyData data = new MyData();
new Thread(new Producer(data)).start();
new Thread(new Consumer(data)).start();
}
}

class Producer implements Runnable {//Thread-ul -


Producator
MyData data;
public Producer(MyData data) {
[Link]=data;
}
public void run() {
int i;
for (i=0;;i++) {
[Link](i);
[Link] ("Producer: "+i);
try {
// "adormire" de 0 pina la 0.5 sec
[Link] ((int) ([Link]()*500));
} catch (InterruptedException e) { }
}
}
}

class Consumer implements Runnable { //Thread-ul -


Consumator
MyData data;
public Consumer(MyData data) {
[Link]=data;

36
}
public void run() {
for (;;) {
[Link] ("Consumer: "+[Link]());
try {
[Link] ((int) ([Link]()*500));
} catch (InterruptedException e) { }
}
}
}

Acest program consta din doua thread-uri: Consumer (consumator) si


Producer (producator). Producatorul "produce" si stocheaza datele in
structura comuna celor doua thread-uri utilizind metoda store().
Consumatorul extrage acele date din structura MyData comuna. La prima
vedere codul pare a fi in regula, dar acesta nu este din moment ce iesirea
programului arata astfel:

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:

Consumer1 Consumer2 Producer


while(!Ready)
while(!Ready)
[Link] = Data;
Taken = false;
Ready = true
Data = [Link];
Data = [Link];
Ready = false;
Taken = true;
return Data;
Ready = false;
Taken = true;
return data;

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

Un monitor (pentru prima data introdus de Hoare in 1974) este asociat


intodeauna cu o data specifica si o functie care controleaza accesul la aceasta

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.

5.6.4 Sincronizarea utilizand monitoare

In urmatorul exemplu al clasei MyDatase foloseste metode de tip


synchronized:

class MyData {
private int Data;
private boolean Ready;
private boolean Taken;
public MyData() {
Ready=false;
Taken=true;
}

public synchronized void store(int Data) {


while (!Taken);
[Link]=Data;
Taken=false;
Ready=true;
}

public synchronized int load() {


while (!Ready);
Ready=false;
Taken=true;
return [Link];
}

40
}

Metodele "sincronizate" elimina nevoia de a stoca variabile de tip Data in


interiorul metodei load(), astfel ca metodele load() si store() nu vor putea sa
se execute in acelasi timp in thread-uri diferite. O problema apare, insa, cind
un thread este "surprins" intr-o bucla de test si detine inca monitorul.
Celalalt thread nu va mai fi niciodata capabil sa-si execute propriul cod
deoarece nu poate prelua monitorul. Doua thread-uri sunt in stare de
interblocare cind unul dintre ele asteapta ca o valoare sa se schimbe in timp
ce-l impiedica pe celalalt sa modifice acea valoare. Lucrul de care avem
nevoie este de a prelua monitorul dupa ce a asteptat ca un flag sa se
modifice. Solutia este de a folosi cuvintul cheie synchronized in asa fel incit
sa protejam numai segmentul de cod critic care are nevoie de excludere
mutuala:

class MyData {
private int Data;
private boolean Ready;
private boolean Taken;
public MyData() {
Ready=false;
Taken=true;
}

public void store(int Data) {


while (!Taken);
synchronized (this) {
[Link]=Data;
Taken=false;
Ready=true;
}
}
public int load() {
while (!Ready);
synchronized (this) {
Ready=false;
Taken=true;
return [Link];
}

41
}
}

De notat faptul ca atunci cind folosim cuvintul cheie synchronized pe un


segment de cod, trebuie sa declaram obiectul insusi ca parametru al lui
synchronized. Si totusi o problema ramine: bucla de test. Aceasta practica de
a folosi bucle de test este considerata a fi gresita pentru thread-uri:
consumatoare mare de timp procesor intr-o implementare preemptiva care,
dupa cum s-a vazut, cauzeaza interblocarea intr-o implementare
nonpreemptiva.

5.6.5. Asteptarea de evenimente

Exista totusi o cale de a evita buclele de test si in acelasi timp de a elimina


necesitatea folosirii unuia din flag-uri utilizate pentru sincronizare. Solutia
este de a folosi metodele wait() si notify() care sunt membre ale clasei
Object din care este derivata orice clasa (aceste metode exista pentru orice
obiect in Java). Metodele wait() si notify() sunt utilizate pentru
determinarea asteptarii unui eveniment si respectiv trimiterea lor la un
thread. Acest mecanism functioneaza dupa cum urmeaza: metoda wait() face
ca thread-ul sa elibereze monitorul si il comuta pe acesta din starea runnable
in starea non-runnable. Thread-ul va astepta in aceasta stare din urma pina
cind este "trezit" de un apel al metodei notify(). Cind un thread isi termina
"asteptat-ul" el va recapata monitorul. Metoda notify() alege in mod arbitrar
un thread din cele care sunt in asteptare si il elibereaza de aceasta stare.
Metodele wait() si notify() sunt utilizate in clasa MyData dupa cum
urmeaza:

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:

a. Mai multi consumatori - un singur producator.

Consideram urmatoarea problema: producatorul nu a produs inca nimic.


Primul consumator preia monitorul dar va intra in wait() si-l va elibera
permitind ca al doilea consumator sa urmeze aceeasi cale. Vom avea
eventual toti consumatorii in stare wait() si monitorul eliberat. In acest
moment producatorul intra in actiune: produce si apeleaza notify(). Aceasta
cauzeaza faptul ca unul din thread-urile in wait() incearca sa preia monitorul.
Din moment ce producatorul tocmai a eliberat monitorul iesind din
segmentul de cod synchronized aceasta nu va cauza nici o problema.
Consumatorul va "consuma" data si va apela notify().
Acest notify() va "trezi" alt thread care va gasi ca nu exista nimic de citit
pentru moment si astfel se va reintoarce in wait() eliberind din nou
monitorul. Producatorul va fi din nou capabil sa-l preia si sa emita un nou

43
numar.

b. Mai multi producatori - un singur consumator

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

In aplicatiile multithreading este necesar ca anumite thread-uri sa se


sincronizeze la un anumit punct. Un exemplu este calculul paralel in faza, in
care toate thread-urile trebuie sa-si termine faza de executie inainte de a
trece toate odata la faza urmatoare. O bariera este un mecanism folosit
pentru a sincroniza mai multe thread-uri. Un thread care intilneste o bariera
intra automat in wait(). Cind ultimul thread "ajunge" la bariera, semnaleaza
(notify()) si celorlalte thread-uri care sunt in asteptare rezultind o "trecere" in
grup a barierei. Iata un exemplu in acest sens:

import [Link].*;

class Barrier { // Clasa Barrier sincronizeaza toti


//participantii
private int ParticipatingThreads;
private int WaitingAtBarrier;
public Barrier(int num){ //Constructorul obiectului
ParticipatingThreads = num;
WaitingAtBarrier=0;
}
public synchronized void Reached() { //Metoda bariera
WaitingAtBarrier++;
if ( ParticipatingThreads != WaitingAtBarrier ) {
//Inseamna ca thread-ul nu este ultimul
try {
wait(); //Thread-ul este oprit pina ce //este eliberat

44
} catch (InterruptedException e) { }
} else {
// Acesta a fost ultimul thread activ
notifyAll();
WaitingAtBarrier=0; // le elibereaza pe toate
}
}
}

Ajungind la bariera toate thread-urile, mai putin ultimul, asteapta in


interiorul metodei synchronized ca ultimul thread ajuns sa le elibereze.
Odata ce s-au "trezit" ei intra in competitie pentru monitor si unul dintre ei il
cistiga, apoi iese imediat eliberind monitorul. Celelalte thread-uri continua
sa concureze pentru acest monitor si sa-l elibereze pina ce ultimul thread
face acest lucru si-n acest moment sunt cu totii "liberi". In acest exemplu
am presupus ca se cunoaste dinainte numarul de thread-uri participante.
Daca, insa, acest numar nu este cunoscut dinainte poate fi aplicat un
mecanism de inregistrare a participantilor. Orice thread care doreste sa
astepte la bariera, mai intii trebuie sa se "inregistreze" incrementind numarul
de thread-uri in asteptare. De unde stim ca toate thread-urile care asteapta la
bariera s-au inregistrat? Ce se intimpla daca toate thread-urile mai putin unul
si-au terminat de facut calculele si ultimul thread (care, probabil, nici nu a
fost planificat sa ruleze inca) nu s-a inregistrat inca. Toate celelalte thread-
uri vor trece bariera nestiind ca trebuie sa-l astepte si pe acesta din urma.

5.8. Asteptarea terminarii unui thread

Uneori este necesar a se astepta terminarea unui thread. De exemplu tread-


ul principal (main) poate crea un al doilea thread pentru a executa ceva in
interiorul lui. Java permite monitorizarea starii unui thread, altul decit cel in
care se face aceasta operatie si suspendarea executiei pina ce acesta se
termina. O metoda care poate fi folosita este metoda isAlive() care
returneaza true daca thread-ul invocat nu este dead. Pentru asteptarea
terminarii unui thread (fara a utiliza bucle) putem utiliza metoda join().
Aceasta metoda face ca un thread sa astepte pina cind un alt thread isi
termina executia urmind sa-si reia executia in momentul in care thread-ul
asteptat s-a terminat. Un thread poate fi intrerupt prin apelarea metodei
interrupt(), caz in care metoda join() va "arunca" exceptia
InterruptedException. Iata un exemplu de program in care un
thread asteapta un alt thread sa se termine:

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 !
}
}

5.9. Alte metode de sincronizare

In regula, veti spune, Java are aceasta sincronizare cu monitoare dar eu


vreau sa am vechile mele semafoare. Nici o problema. Utilizind monitoare
se poate implementa orice obiect sincronizat dorit inclusiv semafoare. Iata in
continuare un exemplu de semafor in Java:

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.

5.11 Interblocarea (Deadlock)

Interblocarea se produce cind unul sau mai multe thread-uri asteapta


schimbarea unei conditii in timp ce acea conditie este exclus sa se schimbe
deoarece toate thread-urile care ar putea face acest lucru sunt in asteptare.
Am vazut cum poate aparea un interblocaj atunci cind se folosesc bucle de
test in interiorul unui monitor, asteptind ca un alt thread sa schimbe o
conditie dar fara ai da posibilitatea de a obtine monitorul.

5.12. Asteptarea terminarii unui thread

Uneori este necesar a se astepta terminarea unui thread. De exemplu tread-


ul principal (main) poate crea un al doilea thread pentru a executa ceva in
interiorul lui. Java permite monitorizarea starii unui thread, altul decit cel in
care se face aceasta operatie si suspendarea executiei pina ce acesta se
termina. O metoda care poate fi folosita este metoda isAlive() care
returneaza true daca thread-ul invocat nu este dead. Pentru asteptarea
terminarii unui thread (fara a utiliza bucle) putem utiliza metoda join().
Aceasta metoda face ca un thread sa astepte pina cind un alt thread isi
termina executia urmind sa-si reia executia in momentul in care thread-ul
asteptat s-a terminat. Un thread poate fi intrerupt prin apelarea metodei
interrupt(), caz in care metoda join() va "arunca" exceptia
InterruptedException. Iata un exemplu de program in care un thread asteapta
un alt thread sa se termine:

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 !

47
}
}

5.13 Alte metode de sincronizare

In regula, veti spune, Java are aceasta sincronizare cu monitoare dar eu


vreau sa am vechile mele semafoare. Nici o problema. Utilizind monitoare
se poate implementa orice obiect sincronizat dorit inclusiv semafoare. Iata in
continuare un exemplu de semafor in Java:

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

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.

48
5.15 Interblocarea (Deadlock)

Interblocarea se produce cind unul sau mai multe thread-uri asteapta


schimbarea unei conditii in timp ce acea conditie este exclus sa se schimbe
deoarece toate thread-urile care ar putea face acest lucru sunt in asteptare.
Am vazut cum poate aparea un interblocaj atunci cind se folosesc bucle de
test in interiorul unui monitor, asteptind ca un alt thread sa schimbe o
conditie dar fara ai da posibilitatea de a obtine monitorul.

6. Arhitectura server.

6.1 Privire de ansamblu


Server –ul aplicatie CWS accepta conexiuni de la clienti cu sockets
TCP, folosind port-ul 1979, este capabil sa deserveasca mai multi clienti in
acelas timp, lansand pentru fiecare client in parte cate un fir de excecutie
care sa faca comunicatia cu un anumit client. Server-ul contine o bucla while
unde astepta conexiuni, daca un client face o cere de conectare se creaza un
fir de executie care va schimba mesaje cu acel client. Noul client este
inregistrat in baza de date si apoi se trimite un mesaj de tip broadcast care
anunta toti clienti de inregistrarea noului client pe Chat. Se foloseste
serializare obiectelor, descrisa in sectiunea 4, pentru trasmitrea de packete
intre client si server, se creaza astfel un protocol propriu intre client si
server, spre exemplu: daca clientul trasmite packet-ul 1 atunci server-ul va
trimite si e packet-ul 1 la client dar va avea cu totul alta semnificatie packet-
ul 1 de la server si packet-ul 1 pe partea de client.

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

Structura generala a unui server bazat pe conexiune:

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.

6.3. Baza de date cu canale si clienti


Baza de date cu canele si clientii de pe aceste canale arata ca in fig. de mai

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.

Pentru adaugarea unui nou canal folosesc metoda :

51
/**
* ADAUGA UN CANAL
*/
public void addCanal(String numeCanal)
{
clienti=new RCL();
[Link]=numeCanal;
synchronized(this)
{
[Link](clienti);
}

Pentru adaugarea unui client pe un anumit canal folosec metoda:


/**
* ADAUGARE DE CLIENT PE UN CANAL
*/
public void addClientPeCanal(String numeCanal,//numele canalului
String numeClient,//numele de
logare
ObjectOutputStream
oos)//teava //NET
{
//adauga un client pe canal
//1. identific canalul
for (int i=0;i<[Link]();i++)
{
RCL canal=(RCL)[Link](i);
if ([Link](numeCanal))
{
//adaugarea
synchronized(this)
{
[Link](numeClient,oos);
break;
}
}//if
}//for

52
}

Pentru a nu avea probleme cu transmiterea de date (Ex: doi clienti


trasmit mesaj unui alt client in acelas timp) codul care trasmite mesajul este
synchronized, adica bucata respectiva de cod este blocata de un fir de
executie care intra in acea sectiune critica. Un alt fir de executie va trebui sa
astepte pana cand respectivul fir paraseste acea sectiune critica.

Pentru extragerea de informatii din baza de date se folosesc metode


ca:

public Vector listaCanale();


public Vector listaClientiDePeUnCanal(String numeCanal);

/**
* 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.1 Privire de ansamblu


Clientul isi creaza un socket si incearca sa obtina o conexiune cu
server-ul, daca conexiunea a fost stabilita se creaza la client un fir de
executie care se va ocupa cu citirea mesajelor de la server. Clientul are si el
o baza de date pentru a micsorarea volum de informatii trasmis intre client si
server, este mult mai econom sa trasmiti cite un nume de canal, care a fost
creat de un client sau un nume de client, nou conectat, decat sa trasmit toata
lista de canale sau lista de clienti de pe un anumit canal.

Clasa Reciving este cea care se ocupa de primirea mesajelor de la


server, liste de clienti, listei de canale. Instanta acestei clase creaza un fir si
isi incheie viata in momentul in care se intrerupe conexiunea cu server-ul.
Metoda run() contine o bucla in care se citesc pachetele de server. Aceste
pachete sunt interpretate si in functie de tipul lor se produc anumite
evenimente.

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.

7.3. Baza de date


Este asemanatoare cu cea a serverului, a fost creata pentru restangerea
trasmiterii de date reduntante pe retea. Arata ca in figura de mai jos:

Baza de date contine metode specifice pentru extragerea listei de


clienti de pe un anumit canal sau a listei de canale. Deasemenea contine
metode pentru adaugarea de noi canale sau adaugare de noi clienti pe canale.

Lista de clienti de pe un anumit canal:


/**
*Lista clientilor de pe un anumit canal.
*/
public Vector listaClientiDePeUnCanal(String numeCanal)
{
Vector listaClienti=new Vector();
//1. identific canalul de pe care etrag lista de clienti
for(int i=0;i<[Link]();i++)

55
{
RLCC canal=(RLCC)[Link](i);
if ([Link](numeCanal))
{
listaClienti=[Link]();
return listaClienti;
}//if
}//for
return listaClienti;
}//listaClientiDePeUnCanal

9. Tipuri de pachete transmise intre client si server

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

10. Comparatii cu alte produse soft existente pe piata in momenul de


fata.

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.

Aplicatia mea, CWS, este implemntata in Java, ceia ce, ii confera un


mare ajantaj, este independenta de platforma, are avea acelas Luck and fell
pe orice platforma, o dimensiune forte mica, clientul are numai 54 Kocteti
pe cand clientul de mIrc are 1,5 Mb. Clientul CWS-ului poate sa ruleze si in
cadrul unei pagini Web, sub forma unui applet, si nu cere cunostinte vaste in
utilizarea calculatoarelor pentru a putea utiliza acest program.

Un alt mare avantaj ar fi siguranta datelor, se stie ca appleturile


lucreaza intr-un sandbox, care are multe restrictii, nu poate scrie fisiere pe
HDD calculatorului, nu poate citii nici un fel de informatii despre utilizator,
calculatorului, adresa de e-mail, etc. Alte aplicatii, Yahoo Messanger spre
exemplu, sunt foarte des supuse atacurilor hackerilor dotorita breselor de
securitate.

11. Concluzii

Folosirea limajului de programare Java ii confera aplicatie CWS mari


ajantaje fata de alte programe de acest gen. Poate fi folosit de creatorii de
pagini Web pentru inglobarea clientului de chat in paginile site-urilor care le
creaza, deasemena poate lucra si ca o aplicatie independenta.

Nouatatea cu care vine acesta aplicatie ar sta in felul in care este


proiectat interfata acestuia, clientul mIrc-ului zapaceste utilizatorul cand
schimba mesaje cu mai multi clienti prin numarul de ferestre deschise, exista
pericolul ca utilizatorul sa nu mai stie unde trimite mesaje pentru tot canalul
sau care e fereasta cu un client anume, toate aceste erori de proiectarea
interfetei au fost inlaturate in aplicatia CWS. Mesaje insotite de imagine pot
usura viata unui client deorece acesta poate identifica cu o usurinta mai mare
de la cine a primit un anumit mesaj.

12. Bibliografie

12.1 Referinte Internet

57
[Link]
[Link]
[Link]
[Link]/~stefan
[Link]/~acf

12.2. Referinte (carti):


[BeP99] - Bell D., Perr M.: Java for Students, Second Edition,
Prentice Hall, 1999
[Bud2000] - Budd, T.: Understanding Object-Oriented Programming with
JAVA. Addison-Wesley, 2000
[CGI2000] - Chan,M.C., Griffith,S.W., Iasi,A.F.: JAVA. 1001 secrete
pentru programatori, Teora, 2000
[GrK97]- Grand, M., Knudsen, J.: JAVA. Fundamental Classes Reference,
O'Reilly, 1997
[HSH1999] - Hughes,M., Shoffner,M., Hamner, D.: JAVA. Network
Programming, Manning, 1999
[LaO99] - Lambert,K. A., Osborne,M.: Java. A Framework for
Programming and Problem Solving, PWS Publishing, 1999

58

S-ar putea să vă placă și