Sunteți pe pagina 1din 67

Universitatea Tehnică “Gheorghe Asachi“ Iași

Facultatea de Inginerie Electrică, Energetică și Informatică aplicată

Lucrare de licență
Control de tip multi user pentru echipamente convenționale
folosind tehnici de tip remote

Coordonator științific: Absolvent:

conf.dr.ing.inf. Damian Cătălin Duhan Alin

DECLARAȚIE DE ASUMARE A AUTENTICITĂȚII


LUCRĂRII DE LICENȚĂ

Subsemnatul DUHAN Alin,

legitimat cu C.I. seria MZ nr. 324 115,

autorul lucrării
Control de tip multi user pentru echipamente convenționale folosind tehnici
de tip remote

elaborată în vederea susținerii examenului de finalizare a studiilor de licență


organizat de către Facultatea de Inginerie Electrică, Energetică și Informatică
Aplicată din cadrul Universității Tehnice „Gheorghe Asachi” din Iași, sesiunea din
Februarie a anului universitar 2019/2020, luând în considerare conținutul Art. 34
din Codul de etică universitară al Universității Tehnice „Gheorghe Asachi” din Iași
(Manualul Procedurilor, UTI.POM.02 – Funcționarea Comisiei de etică
universitară), declar pe proprie răspundere, că această lucrare este rezultatul
propriei activități intelectuale, nu conține porțiuni plagiate, iar sursele bibliografice
au fost folosite cu respectarea legislației române (legea 8/1996) și a convențiilor
internaționale privind drepturile de autor.

Data 5.02.2020 Semnătura


Cuprins

1. Introducere
1.1. Context
1.2. Prezentarea pe scurt a aplicației
1.3. Descrierea capitolelor

2. Resurse software
2.1. Limbajul de programare JAVA
2.1.1. Noțiuni generale
2.1.2. Programare în rețea
2.1.3. Socket-uri
2.1.4.Threads
2.1.5. Swing
2.2. Intellij IDEA

3. Arhitectura aplicației
3.1. Module componente
3.2. Mod de funcționare
3.3. Modalități de dezvoltare

4. Concluzii

5. Bibliografie

6. Anexe

1. Introducere

1.1. Context
La nivel mondial se poate observa tendința de a automatiza majoritatea proceselor ce implică un
cadru de muncă monoton și activități repetitive. Pentru a crește eficiența, pentru a evita apariția
defectelor produse de om, dar și pentru a diminua cheltuielile dintr-o companie, prin micșorarea
numărului de angajați, se construiesc diferiți algoritmi ce imită anumite tipare, cu ajutorul cărora
să se înlocuiască o parte dintre meserii. [1]

Acest lucru nu este întotdeauna posibil, de aceea regăsim și astăzi unele locuri de muncă ce nu
pot fi înlocuite în totalitate de mașini, în schimb, pentru a ușura munca operatorilor ce trebuie să
administreze aparatura, se folosesc diferite aplicații ce permit controlul de la distanță a acestora.

Când vine vorba de aplicații, putem observa cum programatorii crează interfețe cât mai inedite
cu ajutorul cărora un utilizator poate controla cu ușurință anumiți parametri, fără a depune efort
sau fără să îi fie necesare cunoștințe avansate în prealabil.

Mare parte dintre aceste aplicații folosesc tehnologia client/server, ce prezintă posibilitatea de a
controla de la distanță, utilizatorul trimițând către “server” solicitarea de a se folosi de anumiți
parametri, urmând ca acesta să aștepte să primească confirmarea.

Diferite sisteme implementează rețele întregi de echipamente ce comunică între ele și pot fi
setate dintr-un singur computer, pentru a eficientiza modul în care sunt utilizate resursele, cât
și pentru fi mai ușor de urmărit și de asigurat mentenanța acestora.

În aplicația pe care urmează să o prezint, vom simula un sistem specific dispozitivelor de tip
HVAC (Heating, ventilation, and air conditioning), mai exact, un utilizator poate să aleagă o
anumită temperatură, umiditate, sau de ce nu, luminozitate, pentru a fi sigur că are parte de
condițiile optime de muncă

Încălzirea, ventilarea și aerul condiționat (HVAC) reprezintă tehnologia confortului mediului


înconjurător și al vehiculelor. Scopul său este de a oferi confort termic și calitate acceptabilă a
aerului din interior. Proiectarea sistemului HVAC este o subdisciplină a ingineriei mecanice,
bazată pe principiile termodinamicii, mecanicii fluidelor și transferului de căldură.

HVAC este o parte importantă a structurilor rezidențiale, cum ar fi casele unifamiliale, clădirile
de apartamente, hotelurile, clădirile industriale și de birouri de dimensiuni mari până la zgârie-
nori și spitale, vehicule precum mașini, trenuri, avioane, nave și submarine și în medii marine,
unde condițiile de construcție sigure și sănătoase sunt reglementate în ceea ce privește
temperatura și umiditatea, folosind aer proaspăt din aer liber.

Ventilarea sau ventilația este procesul de schimb sau de înlocuire a aerului în orice spațiu pentru
a oferi o calitate ridicată a aerului interior, care implică controlul temperaturii, reumplerea
oxigenului și eliminarea umidității, mirosurilor, fumului, căldurii, prafului, bacteriilor aeriene,
dioxid de carbon și alte gaze. Ventilarea elimină mirosurile neplăcute și umiditatea excesivă,
introduce aer exterior, menține circulația aerului din interior și previne stagnarea acestuia.
Deși HVAC este executat în clădiri individuale sau în alte spații închise, echipamentul implicat
este, în unele cazuri, o extindere a unei rețele de dispozitive. În astfel de cazuri, aspectele de
operare și întreținere sunt simplificate, iar contorizarea devine necesară pentru a factura energia
consumată și, în unele cazuri, energia care este returnată sistemului mai mare. De exemplu, la un
moment dat, o clădire poate utiliza apă rece pentru aer condiționat, iar apa caldă pe care o
returnează poate fi utilizată într-o altă clădire pentru încălzire.

Utilizarea HVAC pe o rețea mai mare ajută la asigurarea unei economii de scară care nu este
adesea posibilă pentru clădirile individuale, pentru utilizarea surselor de energie regenerabilă,
cum ar fi căldura solară, frigul de iarnă, potențialul de răcire în unele locuri de lacuri sau apă de
mare pentru răcire gratuită și funcția de activare a stocării sezoniere a energiei termice. [2]

Am ales să vorbesc despre acest topic deoarece reprezintă un domeniu de interes, iar după cum
voi prezenta și în capitolele următoare, sunt de părere că rețelele de comunicare între
dispozitive vor deveni tot mai avansate în anii ce vor urma, iar cunoștințele în ceea ce privește
acest domeniu vor fi indispensabile unui inginer.

1.2. Prezentarea pe scurt a aplicației

Aplicația pe care o voi prezenta pe parcursul acestei lucrări descrie modul de funcționare al
unui sistem ce asigură un mediu plăcut într-o clădire, permițându-le utilizatorilor să seteze
anumiți parametri cum ar fi temperatura sau umiditatea, pentru a-și crea condiții optime de
lucru.

Pentru a dezvolta acest proiect, am folosit tehnologia client/server, ajutându-mă de limbajul de


programare JAVA.

Am folosit această tehnologie gândindu-mă că, într-o clădire precum sunt cele de birouri, există
un departament ce se ocupă de facilitățile de care au nevoie angajații. Având în vedere că spre
exemplu, într-un etaj pot să lucreze mai mulți oameni în funcție de departamentul în care
activează, sau, există anumite aparate ce au nevoie să fie întreținute în anumite condiții, vor
avea nevoie de altă temperatura, sau mai bine zis, de alți parametri care să le producă un
mediu plăcut.

Cum angajații trebuie să se concentreze pe activitatea pe care o desfășoară, le vine în datoria


celor ce se ocupă de facilități să se asigure că toate condițiile de muncă sunt îndeplinite.

Această aplicație simulează această activitate prin faptul că redă comunicarea în rețea a
dispozitivelor de aer condiționat, ce sunt conectate la un server ce se asigură că acestea sunt în
uz în momentul în care în clădire se lucrează.
Aplicația este formată din două module, ChatServer și ChatUser, fiecare fiind compusă din trei,
respectiv șase clase. ChatServer l-am denumit în cadrul proiectului “localhost” și comunică cu
ceilalți clienți prin portul 8818. ChatUser pentru a se conecta la server, are impusă condiția de a
se folosi de un nume de utilizator și o parolă, ambele fiind prestabilite.

Am creat o interfață cu ajutorul Java Swing, care să arate câte dispozitive sunt conectate la
server, deși, pentru a simula comunicarea cu un client, la început am folosit comanda “telnet”
în Command Prompt.

Un utilizator va trimite solicitarea către server, iar iar în același timp, ceilalți clienți conectați vor
fi capabili să vadă solicitarea nou creată.

Atât despre tehnologiile folosite, cât și despre componentele aplicației voi dezvolta în capitolele
ce urmează.

1.3. Descrierea capitolelor


Capitolul 1. Prezintă contextul în care a apărut ideea de realiza această lucrare, precum și
sistemele unde această tehnologie este deja utilizată. Am vorbit despre dispozitivele de tip
HVAC și cum acestea reprezintă un factor important în ceea ce privește condițiile optime de
muncă.

Capitolul 2. În acest capitol am prezentat resursele software ce mi-au fost necesare în realizarea
acestei lucrări. Am prezentat limbajul de programare JAVA și descris pe scurt conceptele de
care m-am folosit în realizarea aplicației . Am prezentat mediul de dezvoltare Intellij IDEA pe
care l-am folosit la compilare.

Capitolul 3. Aici am descris arhitectura lucrării și i-am prezentat funcționalitatea. Am vorbit


despre fiecare componentă și despre metodele utilizate. Am prezentat și posibilitățile de
scalare ale aplicației sau industriile unde este implementată această tehnologie.

Capitolul 4. Sunt prezentate concluziile cu care am rămas după întocmirea acestei lucrări. Am
prezentat pe scurt și modul în care voi continua să dezvolt aplicația.
2. Tehnologii folosite

2.1. Limbajul de programare JAVA

2.1.1. Noțiuni generale


Java este o tehnologie inovatoare lansată de compania Sun Microsystems în 1995, aceasta avand
un impact remarcabil asupra întregii comunități a dezvoltatorilor de software, impunându-se prin
calități deosebite cum ar fi simplitate, robustețe¸ și nu în ultimul rând portabilitate. Denumită
inițial OAK, tehnologia Java este formată dintr-un limbaj de programare de nivel înalt pe baza
căruia sunt construite o serie de platforme destinate implementării de aplicații pentru toate
segmentele industriei software.

Limbajul de programare Java a fost folosit la dezvoltarea unor tehnologii dedicate rezolvării
unor probleme din cele mai diverse domenii. Aceste tehnologii au fost grupate în așa numitele
platforme de lucru, ce reprezintă seturi de librării scrise în limbajul Java, precum și diverse
programe utilitare, folosite pentru dezvoltarea de aplicații sau componente destinate unei
anume categorii de utilizatori.

2.1.2. Programare în rețea


Programarea în rețea implică trimiterea de mesaje și date între aplicații ce rulează pe calculatoare
aflate într-o rețea locală sau conectate la Internet. Pachetul care oferă suport pentru scrierea
aplicațiilor de rețea este java.net.

Clasele din acest pachet oferă o modalitate facilă de programare în rețea, fără a fi nevoie de
cunoștințe prealabile referitoare la comunicarea efectivă între calculatoare. Cu toate acestea, sunt
necesare câteva noțiuni fundamentale referitoare la rețele cum ar fi: protocol, adresa IP, port,
socket.

Un protocol reprezintă o convenție de reprezentare a datelor folosită în comunicarea între două


calculatoare. Având în vedere faptul că orice informație care trebuie trimisă prin rețea trebuie
serializată astfel încât să poată fi transmisă secvențial, octet cu octet, către destinație, era nevoie
de stabilirea unor convenții (protocoale) care să fie folosite atât de calculatorul care trimite datele
cât și de cel care le primește, pentru a se ”înțelege” între ele. Două dintre cele mai utilizate
protocoale sunt TCP și UDP.

- TCP (Transport Control Protocol) este un protocol ce furnizează un flux sigur de date între
două calculatoare aflate în rețea. Acest protocol asigură stabilirea unei conexiuni permanente
între cele două calculatoare pe parcursul comunicației.
- UDP (User Datagram Protocol) este un protocol bazat pe pachete independente de date, numite
datagrame, trimise de la un calculator către altul fără a se garanta în vreun fel ajungerea acestora
la destinație sau ordinea în care acestea ajung. Acest protocol nu stabilește o conexiune
permanentă între cele două calculatoare.

2.1.3. Socket-uri
Un socket (soclu) este o abstracțiune software folosită pentru a reprezenta fiecare din cele două
”capete” ale unei conexiuni între două procese ce rulează într-o rețea. Fiecare socket este atașat
unui port astfel încât să poată identifica unic programul căruia îi sunt destinate datele.

Socket-urile sunt de două tipuri:

- TCP, implementate de clasele Socket și ServerSocket;

- UDP, implementate de clasa DatagramSocket.

O aplicație de rețea ce folosește socket-uri se încadrează în modelul client/server de concepere a


unei aplicații. În acest model aplicația este formată din două categorii distincte de programe
numite servere, respectiv clienți.

Programele de tip server sunt cele care oferă diverse servicii eventualilor clienți, fiind în stare de
așteptare atâta vreme cât niciun client nu le solicită serviciile. Programele de tip client sunt cele
care inițiază conversația cu un server, solicitând un anumit serviciu. Uzual, un server trebuie să
fie capabil să trateze mai mulți clienți simultan ¸și, din acest motiv, fiecare cerere adresată
serverului va fi tratată într-un fir de execuție separat.

Legătura între un client și un server se realizează prin intermediul a două obiecte de tip Socket,
câte unul pentru fiecare capăt al ”canalului” de comunicație dintre cei doi. La nivelul clientului
crearea socketului se realizează specificând adresa IP a serverului și portul la care rulează
acesta, constructorul uzual folosit fiind:

Socket(InetAddress address, int port)

La nivelul serverului, acesta trebuie să creeze întâi un obiect de tip ServerSocket. Acest tip de
socket nu asigură comunicarea efectivă cu clienții ci este responsabil cu ”ascultarea” rețelei și
crearea unor obiecte de tip Socket pentru fiecare cerere apărută, prin intermediul căruia va fi
realizată legătura cu clientul. Crearea unui obiect de tip ServerSocket se face specificând portul
la care rulează serverul, constructorul folosit fiind:

ServerSocket(int port)
Metoda clasei ServerSocket care așteaptă ”ascultă” rețeaua este accept. Această blochează
procesul părinte până la apariția unui cereri și returnează un nou obiect de tip Socket ce va
asigura comunicarea cu clientul. Blocarea poate să nu fie permanentă ci doar pentru o anumită
perioada de timp - aceasta va fi specificată prin metoda setSoTimeout, cu argumentul dat în
milisecunde.

Pentru fiecare din cele două socket-uri deschise pot fi create apoi două fluxuri pe octeți pentru
citirea, respectiv scrierea datelor. Acest lucru se realizează prin intermediul metodelor
getInputStream, respectiv getOutputStream. Fluxurile obținute vor fi folosite împreună cu
fluxuri de procesare care să asigure o comunicare facilă între cele două procese. În funcție de
specificul aplicației acestea pot fi perechile:

- BufferedReader, BufferedWriter și PrintWriter - pentru comunicare prin intermediul șirurilor


de caractere;

- DataInputStream, DataOutputStream - pentru comunicare prin date primitive;

- ObjectInputStream, ObjectOutputStream - pentru comunicare prin intermediul obiectelor.

2.1.4. Threads
Firele de execuție fac trecerea de la programarea secvențială la programarea concurentă. Un
program secvențial reprezintă modelul clasic de program: are un început, o secvență de
execuție a instrucțiunilor sale și un sfârșit. Cu alte cuvinte, la un moment dat programul are un
singur punct de execuție. Un program aflat în execuție se numește proces. Un sistem de
operare monotasking, cum ar fi fi MS-DOS, nu este capabil să execute decât un singur proces la
un moment dat, în timp ce un sistem de operare multitasking, cum ar fi UNIX sau Windows,
poate rula oricâte procese în același timp (concurent), folosind diverse strategii de alocare a
procesorului fiecăruia dintre acestea.

Un fir de execuție este similar unui proces secvențial, în sensul că are un început, o secvență de
execuție și un sfârșit. Diferența dintre un fir de execuție și un proces constă în faptul că un fir de
execuție nu poate rula independent ci trebuie să ruleze în cadrul unui proces.

Ca orice alt obiect Java, un fir de execuție este o instanță a unei clase. Firele de execuție definite
de o clasă vor avea același cod și, prin urmare, aceeași secvență de instrucțiuni. Crearea unei
clase care să definească fire de execuție poate fi făcută prin două modalități:

- prin extinderea clasei Thread

- prin implementarea interfeței Runnable


Cea mai simplă metodă de a crea un fir de execuție care să realizeze o anumită acțiune este prin
extinderea clasei Thread și supradefinirea metodei run a acesteia. Formatul general al unei
astfel de clase este:

public class FirExcecutie extends Thread {

public FirExcecutie(String nume) {

// Apelăm constructorul superclasei

super(nume);

public void run() {

// Codul firului de execuție

...

Prima metodă a clasei este constructorul, care primește ca argument un șir ce va reprezenta
numele firului de execuție. În cazul în care nu vrem să dăm nume firelor pe care le creăm,
atunci putem renunța la supradefinirea acestui constructor și să folosim constructorul implicit,
fără argumente, care creează un fir de execuție fără niciun nume. Ulterior, acesta poate primi
un nume cu metodă setName. Evident, se pot defini și alți constructori, aceștia fiind utili atunci
când vrem să trimitem diverși parametri de inițializare firului nostru. A doua metodă este
metoda run, ”inima” oricărui fir de execuție, în care scriem efectiv codul care trebuie să se
execute.

Un fir de execuție creat nu este automat pornit, lansarea să fiind realizată de metodă start,
definită în clasa Thread.

// Creăm firul de execuție

FirExecutie fir = new FirExecutie("simplu");

// Lansăm în execuție

fir.start();
2.1.5. Swing
Tehnologia Swing face parte dintr-un proiect mai amplu numit JFC (Java Foundation Classes)
care pune la dispoziție o serie întreagă de facilități pentru scrierea de aplicații cu o interfață
grafică mult îmbogățită funcțional și estetic față de vechiul model AWT.

Unul din principalele obiective ale tehnologiei Swing a fost să pună la dispoziție un set de
componente GUI extensibile care să permită dezvoltarea rapidă de aplicații Java cu interfață
grafică competitivă din punct de vedere comercial. Pentru a realiza acest lucru, API-ul oferit de
Swing este deosebit de complex având 17 pachete în care se găsesc sute de clase și interfețe,
cel mai important și care conține componentele de baza fiind javax.swing.[3]

2.2. IntelliJ IDEA


Este un mediu de dezvoltare integrat Java pentru dezvoltarea de software de calculator, dezvoltat
de JetBrains. Specificațiile necesare sunt:

- în ceea ce privește sistemul de operare, un computer cu licență de la Microsoft, trebuie să


aibă cel puțin versiunea de Windows 7;

- drept memorie RAM, sunt necesari minim 2 GB sau 8 GB pentru un developer Android;

- ocupă 1.5 GB spațiu de stocare pe hard disk și încă 1 GB pentru caches.

IntelliJ IDEA oferă anumite caracteristici cum ar fi completarea codului prin analizarea
contextului, navigarea codului, care permite saltul la o clasă, refactorizarea codului, alinarea și
opțiunile pentru a remedia inconsecvențele prin sugestii.

Suporta sisteme de control a versiunilor precum Git, Mercurial, Perforce și SVN. Baze de date
precum Microsoft SQL Server, Oracle, PostgreSQL, SQLite și MySQL pot fi accesate direct de la
IDE în ediția Ultimate.[4]
3. Arhitectura aplicației

3.1. Module componente


În acest capitol voi prezenta fiecare clasă din care este format acest proiect, voi descrie câteva
dintre cele mai importante metode folosite, atașând totodată și liniile de cod.

Proiectul este compus din două module, ChatServer, care descrie modul în care acționează
serverul, și ChatClient, cel care asigura comunicarea cu acesta.

Am atașat mai jos o captura unde sunt afișate cele două module împreună cu interfețele și
clasele din care sunt formate.
Fig. 1. Meniul ce conține cele două module și clasele asociate acestora

ChatServer este format din trei clase componente: ServerMain, ServerWorker și Server, pe când
ChatClient conține patru clase și două interfețe: ChatClient, UserListPane, LoginWindow,
MessagePane, UserStatusListener și MessageListener.

Clasa ServerMain conține metoda principală main(), în care am declarat portul, am creat un
constructor care să facă legătura cu celelalte clase din pachet și am pornit firul de execuție.
Pentru a nu o încărca, am adăugat în celelalte clase metodele prin care se realizează
comunicarea cu utilizatorii conectați la server. Am atașat mai jos codul acesteia.
package main.java.com.muc;

public class ServerMain {


public static void main(String[] args) {
int port = 8818;
Server server = new Server(port);
server.start();
}
}

Clasele ServerWorker și Server conțin cele mai importante metode deoarece acestea fac
legătura cu utilizatorii.

În ServerWorker avem metoda handleClientSocket pe care am atașat-o mai jos, ea reprezintă


“motorul” întregii legături pe care o are serverul cu un utilizator. Cuprinde metodele de “LogIn”
și “LogOff”, posibilitatea de a trimite informații sau solicitări pe un anumit topic, cum sunt
modificarea unor parametri (temperatura, umiditate sau luminozitate) sau posibilitatea de a
părăsi acel topic, în caz de un utilizator nu dorește să aibă acces la acele informații.

private void handleClientSocket() throws IOException, InterruptedException {


InputStream inputStream = clientSocket.getInputStream();
this.outputStream = clientSocket.getOutputStream();

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));


String line;
while ( (line = reader.readLine()) != null) {
String[] tokens = StringUtils.split(line);
if (tokens != null && tokens.length > 0) {
String cmd = tokens[0];
if ("logoff".equals(cmd) || "quit".equalsIgnoreCase(cmd)) {
handleLogoff();
break;
} else if ("login".equalsIgnoreCase(cmd)) {
handleLogin(outputStream, tokens);
} else if ("msg".equalsIgnoreCase(cmd)) {
String[] tokensMsg = StringUtils.split(line, null, 3);
handleMessage(tokensMsg);
} else if ("join".equalsIgnoreCase(cmd)) {
handleJoin(tokens);
} else if ("leave".equalsIgnoreCase(cmd)) {
handleLeave(tokens);
} else {
String msg = "unknown " + cmd + "\n";
outputStream.write(msg.getBytes());
}

}
}

clientSocket.close();
}

În realizarea aplicației am avut nevoie și de o librărie de la Apache, apache commons lang 3.9,
cu ajutorul căreia am putut folosi comanda StringUtils.split, ce împarte un String, în cazul nostru
“tokens”, într-o arie de alte Stringuri.[5]

Metoda handleLogin cuprinde cele două seturi de date de autentificare, totodată fiind atașate
și condițiile necesare ca un utilizator să poată vedea statusul unui utilizator care se conectează
sau se deconectează de la server.

private void handleLogin(OutputStream outputStream, String[] tokens) throws IOException {


if (tokens.length == 3) {
String login = tokens[1];
String password = tokens[2];

if ((login.equals("guest") && password.equals("guest")) || (login.equals("alind") &&


password.equals("alind"))) {
String msg = "ok login\n";
outputStream.write(msg.getBytes());
this.login = login;
System.out.println("User logged in successfully: " + login);

List<ServerWorker> workerList = server.getWorkerList();

// instiinteaza utilizatorul curent atunci cand se conecteaza alti utilizatori


for (ServerWorker worker : workerList) {
if (worker.getLogin() != null) {
if (!login.equals(worker.getLogin())) {
String msg2 = "online " + worker.getLogin() + "\n";
send(msg2);
}
}
}

//instiinteaza ceilalti utilizatori asupra statusului in care se afla utilizatorul curent


String onlineMsg = "online " + login + "\n";
for (ServerWorker worker : workerList) {
if (!login.equals(worker.getLogin())) {
worker.send(onlineMsg);
}
}
} else {
String msg = "error login\n";
outputStream.write(msg.getBytes());
System.err.println("Login failed for " + login);
}
}
}

Clasa Server conține metoda care face legătura cu ceilalți clienți și prin care serverul așteaptă să
primească un mesaj de la utilizatori, și anume “accept()”. Am atașat mai jos secvența ce
cuprinde această metodă.

ServerSocket serverSocket = new ServerSocket(serverPort);


while (true) {
System.out.println("About to accept client connection...");
Socket clientSocket = serverSocket.accept();
System.out.println("Accepted connection from " + clientSocket);
ServerWorker worker = new ServerWorker(this, clientSocket);
workerList.add(worker);
worker.start();
}
În ceea ce privește modulul ChatClient, acesta cuprinde două interfețe ce simplifică anumite
procese pe care urmează să le prezint, și alte patru clase din care trei dintre ele conțin metodă
principală main(). Fiecăreia i-am creat posibilitatea de a se conecta la server folosind setul de
date de autentificare “guest”. Dintre acestea, UserListPane și LoginWindow au și o interfață
realizată cu ajutorul Java Swing ce apare în momentul rulării.

Clasa ChatClient cuprinde o serie de metode ce facilitează comunicarea cu serverul. Cu ajutorul


acestei clase, utilizatorul primește notificări în momentul în care un alt client se conectează la
server, sau este anunțat în caz de un alt utilizator încearcă să comunice cu acesta.

Pentru a se conecta la server, am creat metoda “connect” pe care am prezentat-o mai jos.

public boolean connect() {


try {
this.socket = new Socket(serverName, serverPort);
System.out.println("Client port is " + socket.getLocalPort());
this.serverOut = socket.getOutputStream();
this.serverIn = socket.getInputStream();
this.bufferedIn = new BufferedReader(new InputStreamReader(serverIn));
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}

Prin această secvență, utilizatorul primește și trimite informații către server. În capitolul
următor voi prezenta și modul în care lucrează cu ceilalți utilizatori.

În clasa UserListPane am folosit pentru prima dată tehnologia Swing, pentru a crea o listă unde
să fie afișați toți utilizatorii conectați în acel moment la server. Sunt de părere că se mai poate
lucra la ea, și pentru a induce mai mult ideea de sistem cu dispozitive HVAC integrate, ar putea
să fie afișați toți utilizatorii ce trebuie conectați la server, împreună cu statusul în care se află,
fie online, fie offline.

Fereastra unde sunt afișați utilizatorii este prezentată prin programul afișat mai jos.

public static void main(String[] args) {


ChatClient client = new ChatClient("localhost", 8818);
UserListPane userListPane = new UserListPane(client);
JFrame frame = new JFrame("User List");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 600);

frame.getContentPane().add(userListPane, BorderLayout.CENTER);
frame.setVisible(true);

if (client.connect()) {
try {
client.login("guest", "guest");
} catch (IOException e) {
e.printStackTrace();
}
}
}

În clasa LoginWindow am adăugat o opțiune în plus, și anume i-am creat utilizatorului


posibilitatea de a se conecta introducând datele de autentificare într-o fereastră separată, în
celelalte clase acest lucru realizându-se în terminal, cu setul de date de autentificare “guest”.

Această fereastră înlocuiește interfața creată cu ajutorul comenzii “telnet” în Command


Prompt, în schimb, nu reușește să ilustreze comunicarea la fel de bine. Codul pentru fereastra
respectivă l-am atașat mai jos.

public LoginWindow() {
super("Login");

this.client = new ChatClient("localhost", 8818);


client.connect();

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel p = new JPanel();


p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
p.add(loginField);
p.add(passwordField);
p.add(loginButton);
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doLogin();
}
});

getContentPane().add(p, BorderLayout.CENTER);

pack();

setVisible(true);
}

UserStatusListener reprezintă interfața ce dă statusul pe care îl are un utilizator, în cazul în care


acesta este conectat la server. Am folosit-o pentru a afișa statusul în cadrul ferestrei create în
clasele UserListPane și LoginWindow. Atunci când un utilizator se conectează la server, acesta
va fi afișat în fereastră, iar atunci când se va deconecta, utilizatorul va dispărea.

package com.muc;

public interface UserStatusListener {


public void online(String login);
public void offline(String login);
}

Interfața MessageListener este folosită pentru a da posibilitatea de a se comunica între clienți.


A fost introdusă în clasa MessagePane și cu ajutorul acesteia, utilizatorii pot să-și transmită
informații între ei. Această mai are nevoie de modificări deoarece are momente în care nu
afișează mesajele dintre utilizatori.

package com.muc;

public interface MessageListener {


public void onMessage(String fromLogin, String msgBody);
}
3.2. Mod de lucru
În acest capitol voi prezenta modul de funcționare al aplicației, și voi detalia modul în care
acționează clasele ce au impact mai mare asupra proiectului.

Pentru a avea mai mult control, m-am gândit că ar fi indicat impun condiția că userii să se
conecteze printr-un set de date de autentificare prestabilite. Seturile de date introduse
momentan în aplicație sunt “guest” și “alind”, acestea fiind folosite atât pentru numele de
utilizator, cât și pentru parolă. Bineînțeles că acestea ar putea fi schimbate în “parter” și “etaj1”
în caz de vrem să facem referire la o clădire, sau de ce nu, să se impună o condiție cu ajutorul
căreia să se conecteze mai mulți utilizatori.

Pentru a afișa comunicarea dintre utilizatori, am folosit comanda “telnet” in Command Prompt.
Telnet este un protocol de rețea care oferă o interfață de linie de comandă pentru a comunica
cu un dispozitiv. Este utilizat cel mai adesea pentru gestionarea de la distanță, dar și uneori
pentru configurarea inițială pentru unele dispozitive, în special hardware-ul de rețea, cum ar fi
switch-urile și punctele de acces. Telnet este de asemenea utilizat pentru a gestiona fișierele de
pe un site web.
Fig. 2. Comanda în Command Prompt

Telnet a fost folosit inițial pe terminale. Aceste computere necesită doar o tastatură, deoarece
totul pe ecran se afișează ca text. Terminalul oferă o modalitate de a te conecta de la distanță la
un alt dispozitiv, la fel ca și cum ai fi așezat în fața lui și l-ai folosi ca orice alt computer.

În zilele noastre, Telnet poate fi utilizat de la un terminal virtual, sau un emulator de terminale,
care este în esență un computer modern care comunică cu același protocol Telnet. Un exemplu
în acest sens este comanda telnet, disponibilă de la promptul de comandă din Windows.
Comanda telnet utilizează protocolul Telnet pentru a comunica cu un dispozitiv sau sistem de la
distanță.

Comenzile Telnet pot fi executate și pe alte sisteme de operare, cum ar fi Linux, Mac și Unix, în
același mod în care sunt executate comenzi telnet în Windows.

Telnet nu este la fel ca alte protocoale TCP / IP, cum ar fi HTTP, care transferă fișierele către și
de pe un server. În schimb, protocolul Telnet vă permite să vă conectați la un server ca și cum
ați fi un utilizator efectiv, apoi vă acordă controlul direct și toate aceleași drepturi asupra
fișierelor și aplicațiilor ca și utilizatorul la care v-ați autentificat.[6]

După ce am folosit comanda “telnet”, practic am trimis serverul încercarea noastră de a ne


autentifica, moment în care ne va cere să ne logam. În caz de am introdus corect datele,
serverul va trimite un mesajul “ok login” prin care ne anunță că ne-am conectat cu succes, în
caz contrar, va transmite un mesaj de “error login” și vom putea încerca din nou.
Fig. 3. Conectare cu succes la server

Fig. 4. Încercare nereușită de conectare la server

Totodată, serverul ne va anunța și dacă mai sunt alți utilizatori conectați, sau în caz de nu sunt
în momentul respectiv, va afișa un mesaj “online” și numele utilizatorului (care poate purta
denumirea unei săli sau poate a unui etaj, spre exemplu) în momentul în care acesta se
conectează. Atunci când se deconectează de la server, vom primi un mesaj de genul “offline” și
numele utilizatorului, pentru a fi la curent cu statusul în care se află.
Fig. 5. Status utilizator

În ServerWorker am introdus o metodă prin care să se comunice pe un anumit topic folosind


tasta “#” în fața cuvântului ce introduce topicul de discuție. După cum se poate vedea mai jos,
am ilustrat pentru fiecare dintre parametrii reprezentanți ai dispozitivelor de tip HVAC,
temperatura și umiditatea, dar și luminozitate, deoarece reprezintă un factor de importantă
atunci când facem referire la facilitățile de care au nevoie angajații. Metodele cu ajutorul cărora
am realizat ideea de solicitarea de schimbare a unui parametru le-am atașat mai jos.

public boolean isMemberOfTopic(String topic) {


return topicSet.contains(topic);
}

private void handleJoin(String[] tokens) {


if (tokens.length > 1) {
String topic = tokens[1];
topicSet.add(topic);
}
}

private void handleLeave(String[] tokens) {


if (tokens.length > 1) {
String topic = tokens[1];
topicSet.remove(topic);
}
}

private void handleMessage(String[] tokens) throws IOException {


String sendTo = tokens[1];
String body = tokens[2];

boolean isTopic = sendTo.charAt(0) =='#';

List<ServerWorker> workerList = server.getWorkerList();


for (ServerWorker worker : workerList) {
if (isTopic) {
if (worker.isMemberOfTopic(sendTo)) {
String outMsg = "msg " + sendTo + ": " + login + ": " + body + "\n";
worker.send(outMsg);
}

} else {
if (sendTo.equalsIgnoreCase(worker.getLogin())) {
String outMsg = "msg " + login + " " + body + "\n";
worker.send(outMsg);
}
}
}
}

Pentru a folosi aceste metode am instantiat un HashSet, sub denumirea de topicSet. HashSet
implementează interfața Set, susținută de o instanță HashMap. Nu oferă garanții cu privire la
ordinea de iterație a setului; în special, nu garantează dacă o comandă va rămâne constantă în
timp. Această clasă permite elementul null.

Această clasă oferă performanțe de timp constante pentru operațiunile de bază (add, remove,
contain și size), presupunând că funcția de hash împrăștie elementele în mod corespunzător.
Iterarea acestui set necesită timp proporțional cu suma dimensiunii instanței HashSet (numărul
de elemente), plus "capacitatea" instanței HashMap de rezervă. [7]

În imaginea de dedesubt, am indus ideea de solicitare de ridicare a temperaturii cu două grade.


În primul rând, am început secvența folosind “msg” prin care anunțăm că vrem să trimitem un
mesaj. Am continuat cu topicul, după care am atașat și solicitarea, “Set T+2”.
Fig. 6. Controlul temperaturii

În același fel, putem trimite un mesaj prin care anunțăm că modificăm umiditatea, după cum
am afișat mai jos.

Fig. 7. Controlul umidității

După cum prezentat și mai devreme, am atașat o captură ce surprinde și modificarea


luminozității.
Fig. 8. Controlul luminozității

Pentru a realiza aceste lucruri, nu avem nevoie de ajutorul altei clase, deoarece “telnet” ne
pune la dispoziție această interfață ce simulează clientul.

Cu toate acestea, am vrut să construiesc o clasă ce va face același lucru, iar aici intervine
modulul ChatClient.

După cum am prezentat anterior, acest modul conține trei clase ce interacționează cu serverul,
iar în continuare, voi descrie ceea ce este afișat în terminal, sau, în cazul claselor UserListPane și
LoginWindow, voi atașa capturi cu ferestrele create cu ajutorul Java Swing.

Clasa ChatClient, a fost prima clasa creată cu scopul de a comunica cu serverul. După cum am
prezentat și anterior, aceasta se folosește de setul de date “guest” pentru a se conecta la
server. În momentul în care o rulăm, în terminal va fi afișată următoarea secvență:
Fig. 9. Clasa ChatClient în terminal

În cadrul acestei clase, utilizatorul poate să primească și mesaje după o anumită structură.
Pentru a fi realizabil acest lucru, am folosit interfața MessageListener pe care am creat-o și pe
care am prezentat-o anterior. Metoda de care m-am folosit, am atașat-o dedesubt.

client.addMessageListener(new MessageListener() {
@Override
public void onMessage(String fromLogin, String msgBody) {
System.out.println("You got a message from: " + fromLogin + " ==> " + msgBody);
}
});

După cum se poate vedea mai jos, în momentul în care am trimis un mesaj utilizatorului cu setul
de date “guest”, acesta a apărut și în terminal, împreună cu numele utilizatorului de la care
provine.

Fig. 10. Comunicarea între ChatClient și “telnet”


UserListPane este prima clasa în care m-am folosit de Java Swing pentru a crea o interfață. În
momentul rulării, în terminal va fi afișat următorul mesaj:

Fig. 11. Mesaj afișat în terminal

Totodată, un utilizator se conectează utilizând setul de date “guest”, iar dacă nu am mai rulat
nicio altă clasa și ceilalți utilizatori sunt deconectați, pe ecran va apărea fereastra de mai jos,
fără să conțină vreun nume afișat. Aici ar trebui să adaug anumite modificări, deoarece un
utilizator nu poate să se vadă pe el în acea listă, lucru care nu se întâmplă într-o rețea de
dispozitive HVAC.

Pe lângă asta, ar putea fi introduse toate dispozitivele ce sunt sau care ar trebui conectate la
server, împreună cu statusul în care se află, fie online, fie offline.
Fig. 12. Fereastra aparuta dupa rularea clasei UserListPane

Diferența cu care vine în plus clasa LoginWindow este că, atunci când o rulăm, va apărea
următoarea fereastră unde va trebui să introducem datele de autentificare:
Fig. 13. Interfața unde se pot introduce datele de logare

După ce utilizatorul introduce datele de autentificare, va apărea aceeași pagină ce ne apare și în


momentul rulării clasei UserListPane, unde sunt afișați ceilalți utilizatori conectați.

În captura de mai jos vedem un utilizator conectat deoarece am folosit în prealabil setul de date
“alind” prin telnet, iar acum, introducând setul de date “guest”, am regăsit celălalt utilizator
conectat la server.
Fig. 14. Interfața unde sunt afișați ceilalți utilizatori

Așadar, acum că am parcurs toate clasele ce fac parte din modulul ChatClient, putem spune că
am risipit toate metodele create, care să producă o legătură între server și utilizatori.

3.3. Modalități de dezvoltare


Momentan aplicația permite accesul doar pentru două seturi de nume de utilizatori și parole,
fiind necesară o condiție care să permită accesul cu ajutorul mai multor seturi de nume de
utilizatori și parole. Am încercat să mai adaug încă un utilizator fără să impun vreo condiție și
am început să am erori, așa că am preferat să păstrez codul simplu.

Aplicația în sine poate fi dezvoltată prin asocierea cu o baza de date care să salveze aceste
informații, cât și prin implementarea unor metode de a crea anumite rapoarte unde să se afle
temperatura medie într-o zi, poate să se facă o comparație între anotimpuri (spre exemplu:
comparația între vară și iarnă), astfel să se tragă anumite concluzii și să se caute metode prin
care să fie economisită energie.

Am putea primi informații cu privire la starea în care se află, dacă răspunde sau nu la comenzile
serverului, pentru a ști dacă un dispozitiv se află în stare de bună funcționare.

Pentru a îmbunătăți aplicația ar putea fi setate anumite condiții în cod cum ar fi, în caz că se
depășește o anumită valoare, severul să nu accepte (spre exemplu: să nu fie de acord să fie
setată o temperatura de peste 30 de grade, dacă în acea încăpere lucrează oameni), atunci
când userul se conectează la server, să fie întrebat căror parametri dorește să le schimbe
valoarea.

Acestea reprezintă doar câteva modalități prin care mă gândesc că aplicația ar putea fi
îmbunătățită, și totodată, prin aceste modificări ar putea induce și mai mult ideea de sistem ce
simulează comunicarea între un server și dispozitive de tip HVAC. Acesta e și scopul meu pe
viitor, să dezvolt și mai mult acest proiect pentru a-l face cât mai compact și totodată, să-mi
îmbogățesc cunoștințele pe care le-am dobândit în timp ce l-am realizat.

Această aplicație reprezintă doar o simulare, însă acest este deja dezvoltat cu succes cu ajutorul
unor tehnologii pe care le-am prezentat în mare mai jos. Cu ajutorul lor au fost create rețele de
dispozitive ce comunică și distribuie informații către server.

Câteva dintre dispozitivele/mecanismele utilizate în acest moment sunt:

JACE (Java Application Control Engine) – Pentru a integra sisteme diverse, este necesară o
conexiune fizică la rețeaua unui dispozitiv. Un JACE este un mecanism / dispozitiv care asigură
conectivitate la sisteme (HVAC, electric, chiar și securitate în unele cazuri) în cadrul unei mașini
cu ajutorul Niagara Framework, care permite driverelor să integreze sisteme de control pentru
o gama mare de producători. Prin conectarea protocoalelor de rețea comune, cum ar fi
LonWorks, BACnet și Modbus, împreună cu multe rețele proprii, apare un sistem compact. Un
JACE poate găzdui, de asemenea, interfața utilizatorului cu sistemul de control în multe cazuri.

Niagara Framework este o infrastructură software universală care permite organizațiilor să


construiască cu ușurință aplicații personalizate, web-based, pentru accesarea, automatizarea și
controlul dispozitivelor „inteligente” în timp real pe internet.[8]

BACnet (Building Automation and Control networks) – a fost realizat pentru a permite
comunicarea construcțiilor automate și a sistemelor de control pentru aplicații precum
încălzirea, ventilarea, și controlul aerului condiționat (HVAC), controlul iluminatului, controlul
accesului și în cazul sistemelor de detectare a incendiilor și echipamentele asociate acestora.
Protocolul BACnet oferă mecanisme pentru dispozitivele automatizate să facă schimb de
informații, indiferent de serviciul pe care îl oferă.[9]

Am atașat o schemă ce prezintă în mare modul în care funcționează un sistem ce are JACE
integrat:

Fig. 15. Schema sistemului cu JACE implementat


4. Concluzii

Proiectul acesta m-a ajutat să-mi îmbogățesc cunoștințele de Java și totodată, prin realizarea
acestei aplicații m-am familiarizat cu acest concept, aplicațiile de tip client/server. Suntem în
secolul vitezei, iar căile de comunicare se doresc a fi cât mai rapide și bine întreținute pentru a
putea distribui eficient informația. Comunicarea dintre un server și dispozitivele asociate
ușurează modul de operare al acestora, plus că ajută și la monitorizare, deoarece dacă un
dispozitiv nu apare în sistem, lipsa acestuia este sesizată, așa că este mai simplu de asigurat și
mentenanța acestora.

Cu toate că aplicația reușește să simuleze modul în care comunică dispozitivele de tip HVAC cu
un server, sunt de părere că poate fi mult îmbunătățită. După cum am prezentat mai sus, pe
lângă asocierea cu o baza de date unde să fie stocate anumite informații, sunt anumite condiții
ce ar putea fi adăugate în cod pentru a crea anumite limitări în ceea ce privește gama de
parametri ce urmează a fi setați. Aplicația are momentan destul de multe bug-uri ce pot fi
înlăturate, în schimb pentru a fi cât mai compactă, mai trebuie dezvoltată.

Acesta reprezintă și scopul meu în momentul de față, pentru a-mi finaliza lucrarea, sunt
conștient că are nevoie de mai multe îmbunătățiri și de aceea îmi propun ca pe viitor să
implementez schimbările pe care le-am menționat, și poate să mă folosesc și de anumite
metode mai eficiente, pentru a nu încarcă foarte mult proiectul. Îmi propun să creez și o
interfață ceva mai prietenoasă și care să-mi permită interacționez mai mult cu serverul.

După cum am prezentat și mai sus, tehnologia această este implementată în diverse aplicații, cu
ajutorul dispozitivelor de tip Java Application Control Engine (JACE), așa că scopul meu ar putea
fi să achizitionez un astfel de dispozitiv, pentru a mă familiariza mai mult cu acest domeniu.

Având în vedere tendința din ultimii ani, putem trage concluzia că o tehnologie similară va fi
implementată și pentru controlul automobilelor autonome/infrastructura necesară utilizării
unor astfel de mașini, deoarece tendința este ca pe viitor, un server să dețină informații cât și să
seteze viteza autoturismelor ce nu vor avea nevoie de șofer. [10]

Indiferent de cum vor evolua lucrurile, subiectul acestei lucrări, sau mai ales, tehnologia folosită
în conceperea acestei aplicații, client/server, va fi mereu de interes în societatea actuală.
5. Bibliografie

[1] Alex Ciutacu, “Robotii vor lasa oamenii fara locuri de munca” https://www.zf.ro/business-
international/robotii-vor-lasa-800-de-milioane-de-oameni-fara-loc-de-munca-pana-in-2030-iar-
problema-va-fi-mai-grava-in-cadrul-tarilor-bogate-care-sunt-primele-meserii-care-vor-fi-
afectate-16842078

[2] Încălzirea, ventilatia si aerul conditionat


https://en.wikipedia.org/wiki/Heating,_ventilation,_and_air_conditioning

[3] Cristian Frasinaru, “Curs practic de Java”


https://profs.info.uaic.ro/~acf/java/Cristian_Frasinaru-Curs_practic_de_Java.pdf

[4] IntelliJ IDEA, https://en.wikipedia.org/wiki/IntelliJ_IDEA

[5] Apache Commons Lang, https://commons.apache.org/proper/commons-lang/

[6] Cum sa folosesti Telnet Client pentru Windows https://www.lifewire.com/what-is-telnet-


2626026

[7] HashSet, https://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html

[8] Tridium Niagara Framework, https://en.wikipedia.org/wiki/Tridium

[9] BACnet, https://en.wikipedia.org/wiki/BACnet


[10] Radu Ivănuş, “Maşina autonomă. Cum va arăta maşina viitorului?”
https://www.todaysoftmag.ro/article/2869/masina-autonoma-cum-va-arata-masina-viitorului
6. Anexe
ChatServer:

ServerMain

package main.java.com.muc;

public class ServerMain {

public static void main(String[] args) {

int port = 8818;

Server server = new Server(port);

server.start();

ServerWorker

package main.java.com.muc;

import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.net.Socket;

import java.sql.SQLOutput;

import java.util.Date;

import java.util.HashSet;

import java.util.List;

public class ServerWorker extends Thread {

private final Socket clientSocket;

private final Server server;

private String login = null;

private OutputStream outputStream;

private HashSet<String> topicSet = new HashSet<>();

public ServerWorker(Server server, Socket clientSocket) {

this.server = server;

this.clientSocket = clientSocket;

@Override

public void run() {

try {
handleClientSocket();

} catch (IOException e) {

e.printStackTrace();

} catch (InterruptedException e) {

e.printStackTrace();

private void handleClientSocket() throws IOException, InterruptedException {

InputStream inputStream = clientSocket.getInputStream();

this.outputStream = clientSocket.getOutputStream();

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

String line;

while ( (line = reader.readLine()) != null) {

String[] tokens = StringUtils.split(line);

if (tokens != null && tokens.length > 0) {

String cmd = tokens[0];

if ("logoff".equals(cmd) || "quit".equalsIgnoreCase(cmd)) {

handleLogoff();

break;

} else if ("login".equalsIgnoreCase(cmd)) {

handleLogin(outputStream, tokens);
} else if ("msg".equalsIgnoreCase(cmd)) {

String[] tokensMsg = StringUtils.split(line, null, 3);

handleMessage(tokensMsg);

} else if ("join".equalsIgnoreCase(cmd)) {

handleJoin(tokens);

} else if ("leave".equalsIgnoreCase(cmd)) {

handleLeave(tokens);

} else {

String msg = "unknown " + cmd + "\n";

outputStream.write(msg.getBytes());

clientSocket.close();

private void handleLeave(String[] tokens) {

if (tokens.length > 1) {

String topic = tokens[1];


topicSet.remove(topic);

public boolean isMemberOfTopic(String topic) {

return topicSet.contains(topic);

private void handleJoin(String[] tokens) {

if (tokens.length > 1) {

String topic = tokens[1];

topicSet.add(topic);

// format: "msg" "login" body...

// format: "msg" "#topic" body...

private void handleMessage(String[] tokens) throws IOException {

String sendTo = tokens[1];

String body = tokens[2];

boolean isTopic = sendTo.charAt(0) =='#';


List<ServerWorker> workerList = server.getWorkerList();

for (ServerWorker worker : workerList) {

if (isTopic) {

if (worker.isMemberOfTopic(sendTo)) {

String outMsg = "msg " + sendTo + ": " + login + ": " + body + "\n";

worker.send(outMsg);

} else {

if (sendTo.equalsIgnoreCase(worker.getLogin())) {

String outMsg = "msg " + login + " " + body + "\n";

worker.send(outMsg);

private void handleLogoff() throws IOException {

server.removeWorker(this);

List<ServerWorker> workerList = server.getWorkerList();


//instiinteaza ceilalti utilizatori asupra statusului in care se afla utilizatorul curent

String onlineMsg = "offline " + login + "\n";

for (ServerWorker worker : workerList) {

if (!login.equals(worker.getLogin())) {

worker.send(onlineMsg);

clientSocket.close();

public String getLogin() {

return login;

private void handleLogin(OutputStream outputStream, String[] tokens) throws IOException


{

if (tokens.length == 3) {

String login = tokens[1];

String password = tokens[2];

if ((login.equals("guest") && password.equals("guest")) || (login.equals("alind") &&


password.equals("alind"))) {

String msg = "ok login\n";


outputStream.write(msg.getBytes());

this.login = login;

System.out.println("User logged in successfully: " + login);

List<ServerWorker> workerList = server.getWorkerList();

// instiinteaza utilizatorul curent atunci cand se conecteaza alti utilizatori

for (ServerWorker worker : workerList) {

if (worker.getLogin() != null) {

if (!login.equals(worker.getLogin())) {

String msg2 = "online " + worker.getLogin() + "\n";

send(msg2);

//instiinteaza ceilalti utilizatori asupra statusului in care se afla utilizatorul curent

String onlineMsg = "online " + login + "\n";

for (ServerWorker worker : workerList) {

if (!login.equals(worker.getLogin())) {

worker.send(onlineMsg);
}

} else {

String msg = "error login\n";

outputStream.write(msg.getBytes());

System.err.println("Login failed for " + login);

private void send(String msg) throws IOException {

if (login != null) {

outputStream.write(msg.getBytes());

Server

package main.java.com.muc;

import java.io.IOException;
import java.net.ServerSocket;

import java.net.Socket;

import java.util.ArrayList;

import java.util.List;

public class Server extends Thread {

private final int serverPort;

private ArrayList<ServerWorker> workerList = new ArrayList<>();

public Server(int serverPort) {

this.serverPort = serverPort;

public List<ServerWorker> getWorkerList() {

return workerList;

@Override

public void run() {

try {
ServerSocket serverSocket = new ServerSocket(serverPort);

while (true) {

System.out.println("About to accept client connection...");

Socket clientSocket = serverSocket.accept();

System.out.println("Accepted connection from " + clientSocket);

ServerWorker worker = new ServerWorker(this, clientSocket);

workerList.add(worker);

worker.start();

} catch (IOException e) {

e.printStackTrace();

public void removeWorker(ServerWorker serverWorker) {

workerList.remove(serverWorker);

ChatClient:

ChatClient
package com.muc;

import org.apache.commons.lang3.StringUtils;

import java.io.*;

import java.net.Socket;

import java.util.ArrayList;

public class ChatClient {

private final String serverName;

private final int serverPort;

private Socket socket;

private OutputStream serverOut;

private InputStream serverIn;

private BufferedReader bufferedIn;

private ArrayList<UserStatusListener> userStatusListeners = new ArrayList<>();

private ArrayList<MessageListener> messageListeners = new ArrayList<>();

public ChatClient(String serverName, int serverPort) {

this.serverName = serverName;

this.serverPort = serverPort;
}

public static void main(String[] args) throws IOException {

ChatClient client = new ChatClient("localhost", 8818);

client.addUserStatusListener(new UserStatusListener() {

@Override

public void online(String login) {

System.out.println("ONLINE: " + login);

@Override

public void offline(String login) {

System.out.println("OFFLINE: " + login);

});

client.addMessageListener(new MessageListener() {

@Override

public void onMessage(String fromLogin, String msgBody) {

System.out.println("You got a message from: " + fromLogin + " ==> " + msgBody);
}

});

if (!client.connect()) {

System.err.println("Connect failed!");

} else {

System.out.println("Connect successful!");

if (client.login("guest", "guest")) {

System.out.println("Login successful");

client.msg("alind", "Hello World!");

} else {

System.err.println("Login failed");

//client.logoff();

public void msg(String sendTo, String msgBody) throws IOException {

String cmd = "msg " + sendTo + " " + msgBody + "\n";


serverOut.write(cmd.getBytes());

public boolean login(String login, String password) throws IOException {

String cmd = "login " + login + " " + password + "\n";

serverOut.write(cmd.getBytes());

String response = bufferedIn.readLine();

System.out.println("Response line: " + response);

if ("ok login".equalsIgnoreCase(response)) {

startMessageReader();

return true;

} else {

return false;

public void logoff() throws IOException {

String cmd = "logoff\n";

serverOut.write(cmd.getBytes());

}
private void startMessageReader() {

Thread t = new Thread() {

@Override

public void run() {

readMessageLoop();

};

t.start();

private void readMessageLoop() {

try {

String line;

while ((line = bufferedIn.readLine()) != null) {

String[] tokens = StringUtils.split(line);

if (tokens != null && tokens.length > 0) {

String cmd = tokens[0];

if ("online".equalsIgnoreCase(cmd)) {

handleOnline(tokens);

} else if ("offline".equalsIgnoreCase(cmd)) {

handleOffline(tokens);
} else if ("msg".equalsIgnoreCase(cmd)) {

String[] tokensMsg = StringUtils.split(line, null, 3);

handleMessage(tokensMsg);

} catch (Exception ex) {

ex.printStackTrace();

try {

socket.close();

} catch (IOException e) {

e.printStackTrace();

private void handleMessage(String[] tokensMsg) {

String login = tokensMsg[1];

String msgBody = tokensMsg[2];

for(MessageListener listener : messageListeners) {


listener.onMessage(login, msgBody);

private void handleOffline(String[] tokens) {

String login = tokens[1];

for (UserStatusListener listener : userStatusListeners) {

listener.offline(login);

private void handleOnline(String[] tokens) {

String login = tokens[1];

for (UserStatusListener listener : userStatusListeners) {

listener.online(login);

public boolean connect() {

try {

this.socket = new Socket(serverName, serverPort);

System.out.println("Client port is " + socket.getLocalPort());


this.serverOut = socket.getOutputStream();

this.serverIn = socket.getInputStream();

this.bufferedIn = new BufferedReader(new InputStreamReader(serverIn));

return true;

} catch (IOException e) {

e.printStackTrace();

return false;

public void addUserStatusListener(UserStatusListener listener) {

userStatusListeners.add(listener);

public void removeUserStatusListener(UserStatusListener listener) {

userStatusListeners.remove(listener);

public void addMessageListener(MessageListener listener) {

messageListeners.add(listener);

}
public void removeMessageListener(MessageListener listener) {

messageListeners.remove(listener);

UserListPane

package com.muc;

import javax.swing.*;

import java.awt.*;

import java.awt.event.MouseAdapter;

import java.awt.event.MouseEvent;

import java.io.IOException;

public class UserListPane extends JPanel implements UserStatusListener {

private final ChatClient client;

private JList<String> userListUI;

private DefaultListModel<String> userListModel;


public UserListPane(ChatClient client) {

this.client = client;

this.client.addUserStatusListener(this);

userListModel = new DefaultListModel<>();

userListUI = new JList<>(userListModel);

setLayout(new BorderLayout());

add(new JScrollPane(userListUI), BorderLayout.CENTER);

userListUI.addMouseListener(new MouseAdapter() {

@Override

public void mouseClicked(MouseEvent e) {

if (e.getClickCount() > 1) {

String login = userListUI.getSelectedValue();

MessagePane messagePane = new MessagePane(client, login);

JFrame f = new JFrame("Message: " + login);

f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

f.setSize(500,500);

f.getContentPane().add(messagePane, BorderLayout.CENTER);

f.setVisible(true);

}
}

});

public static void main(String[] args) {

ChatClient client = new ChatClient("localhost", 8818);

UserListPane userListPane = new UserListPane(client);

JFrame frame = new JFrame("User List");

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setSize(400, 600);

frame.getContentPane().add(userListPane, BorderLayout.CENTER);

frame.setVisible(true);

if (client.connect()) {

try {

client.login("guest", "guest");

} catch (IOException e) {

e.printStackTrace();

}
}

@Override

public void online(String login) {

userListModel.addElement(login);

@Override

public void offline(String login) {

userListModel.removeElement(login);

MessageListener

package com.muc;

public interface MessageListener {

public void onMessage(String fromLogin, String msgBody);

}
UserStatusListener

package com.muc;

public interface UserStatusListener {

public void online(String login);

public void offline(String login);

MessagePane

package com.muc;

import javax.swing.*;

import java.awt.*;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.io.IOException;

public class MessagePane extends JPanel implements MessageListener {

private final ChatClient client;

private final String login;


private DefaultListModel<String> listModel = new DefaultListModel<>();

private JList<String> messageList = new JList<>(listModel);

private JTextField inputField = new JTextField();

public MessagePane(ChatClient client, String login) {

this.client = client;

this.login = login;

client.addMessageListener(this);

setLayout(new BorderLayout());

add(new JScrollPane(messageList), BorderLayout.CENTER);

add(inputField, BorderLayout.SOUTH);

inputField.addActionListener(new ActionListener() {

@Override

public void actionPerformed(ActionEvent e) {

try {

String text = inputField.getText();

client.msg(login, text);

listModel.addElement("You: " + text);


inputField.setText("");

} catch (IOException ex) {

ex.printStackTrace();

});

@Override

public void onMessage(String fromLogin, String msgBody) {

if (login.equalsIgnoreCase(fromLogin)) {

String line = fromLogin + ": " + msgBody;

listModel.addElement(line);

LoginWindow

package com.muc;
import javax.swing.*;

import java.awt.*;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.io.IOException;

public class LoginWindow extends JFrame {

private final ChatClient client;

JTextField loginField = new JTextField();

JPasswordField passwordField = new JPasswordField();

JButton loginButton = new JButton("Login");

public LoginWindow() {

super("Login");

this.client = new ChatClient("localhost", 8818);

client.connect();

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel p = new JPanel();


p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));

p.add(loginField);

p.add(passwordField);

p.add(loginButton);

loginButton.addActionListener(new ActionListener() {

@Override

public void actionPerformed(ActionEvent e) {

doLogin();

});

getContentPane().add(p, BorderLayout.CENTER);

pack();

setVisible(true);

private void doLogin() {

String login = loginField.getText();

String password = passwordField.getText();


try {

if (client.login(login, password)) {

//apare lista cu utilizatori

UserListPane userListPane = new UserListPane(client);

JFrame frame = new JFrame("User List");

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setSize(400, 600);

frame.getContentPane().add(userListPane, BorderLayout.CENTER);

frame.setVisible(true);

setVisible(false);

} else {

//apare un mesaj de eroare

JOptionPane.showMessageDialog(this, "Invalid login/password");

} catch (IOException e) {

e.printStackTrace();

}
public static void main(String[] args) {

LoginWindow loginWin = new LoginWindow();

loginWin.setVisible(true);

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