Primul program Java
În listingul următor este prezentat cel mai simplu program java care afişează un text şi data curentă.
package isa.lab1;
import java.util.*;
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, the current date is: ");
Date data = new Date().toString());
System.out.println(data);
}
}
La începutul fiecărui fişier sursă java se regăseşte declaraţia package ce specifică pachetul (in cadrul limbajului java se foloseşte termenul de pachet pentru a denumi o colecţie de clase – în alte limbaje se foloseşte termenul de librărie) din care face parte clasa ce urmează a fi definită în continuare. În cazul în care această declaraţie lipseşte, clasa definită în cadrul fişierului va face parte din pachetul default.
În cazul în care în cadrul programului urmează să se folosească clase din cadrul unor pachete atunci acestea vor trebui specificate prin intermediul declaraţiei import. Nici această declaraţie nu este obligatorie.
IMPORTANT: Clasele din pachetul standard java.lang sunt automat importate şi accesibile în cadrul oricărui fişier sursă fără a fi nevoie de a adăuga explicit declaraţia import.
Cea mai simplă aplicaţie java trebuie să conţină cel puţin o definiţie de clasă. Spre deosebire de alte limbaje de programare limbajul java nu permite definirea de metode sau argumente care sa nu aparţină nici unei clase. Declararea unei clase java se face cu declaraţia: public class Hello, unde cuvântul cheie public este un specificator de acces ce defineşte gradul de acces al clasei (se va reveni ulterior la aceşti specificatori), iar cuvântul cheie class este folosit pentru a declara o nouă clasă.
IMPORTANT: In java se recomandă ca în cadrul unui fişier sursă se definească cel mult o singură clasă. Numele fişierului sursă trebuie să fie acelaşi cu numele clasei (adăugând extensia .java). În cazul în care se doreşte ca într-un fişier sursă să fie definite mai multe clase, atunci în acel fişier nu poate exista mai mult de o clasă publica, iar numele fişierului trebuie să fie identic cu numele clasei publice.
Un program java standard trebuie să conţină în una din clasele sale publice funcţia main a cărei semnătură este următoarea:
public static void main(String[] args)
Această clasă este punctul de start al oricărei aplicaţii standard java. În momentul lansării în execuţie a unui program java instrucţiunile din cadrul acestei funcţii sunt executate.
IMPORTANT: Este obligatoriu ca semnătura funcţie să fie exact în forma prezentată mai sus. În cazul în care lipseşte oricare dintre cuvintele cheie atunci maşina virtuala java nu va recunoaşte această metodă ca punct de start al programului.
Cuvântul cheie public specifica faptul că metoda main este accesibilă din exterior şi poate fi apelată de către „oricine”. Cuvântul cheie static specifică faptul că metoda main poate fi apeltă fără a fi nevoie să se construiască o instanţă (un obiect) de tipul clasei Hello. Argumentul args de tip vector de şiruri de caractere este folosit pentru a prelua parametrii din linia de comandă atunci când aceştia există. Chiar dacă nu dorim să specificăm parametri în linia de comandă, acest argument nu trebuie să lipsească.
Despre maşina virtuală Java
O aplicaţie de sine stătătoare, scrisă în Java, este compilată şi rulează pe o aşa numită Maşină Virtuală Java. Acest lucru face posibil ca aplicaţiile java să poată fi rulate pe diferite platforme (Sun, MacOS, Win32, Linus) fără a fi nevoie să se recompileze aceste aplicaţii pentru fiecare dintre acestea în parte. Astfel aplicaţiile java sunt independente de platformă.
Implementarea practică a conceptului independenţei de platformă sa realizat prin folosirea unui calculator virtual pe care rulează de fapt aplicaţiile compilate java. În urma compilării fişierelor sursă java nu se va genera cod executabil, pentru o platformă anume, ci se va genera un cod intermediar numit cod de octeţi (eng. byte code). Acest cod de octeţi este asemănător cu limbajul de asamblare dar nu va putea fi rulat direct pe nici un sistem de operare.
Pentru a rula un cod executabil java (aşa numitul cod de octeţi) este nevoie de un program special care va interpreta acest cod de octeţi şi va executa instrucţiuni specifice sistemului de operare pe care se află. Acest program care interpretează codul de octeţi se numeşte Maşina Virtuală Java. Maşina Virtuală Java reprezintă un calculator abstract. Ca şi calculatoarele reale, aceasta dispune de un set de instrucţiuni, un set de regiştri şi utilizează diferite zone de memorie.
Figura 1 prezintă principalele componente ale Maşinii Virtuale Java (JVM).
Figura 1. Arhitectura Maşinii Virtuale Java
Principalele componente ale JVM sunt:
- Class Loader Subsystem – aces modul este responsabil cu încărcarea claselor în memorie.
- Execution Engine – mecanismul responsabil cu executarea instrucţiunilor din cadrul claselor încărcate în cadrul JVM.
- Runtime Data Area – zona în care sunt memorate componente necesare pe parcursul rulării unui program. În această zonă sunt stocate clasele încărcate, obiectele construite, informaţii despre apelul metodelor, parametrii variabilelor, valorile returnate de metode, date despre firele de execuţie, etc.
Eliberarea memorie de obiectele nefolositoare
Toate obiectele create în cadrul maşinii virtuale pe parcursul rulării unui program sunt plasate în zona de memorie numită Heap. Ştergerea acestora din memorie este realizată automat de către o componentă a JVM numita GarbageCollector. Acest proces şterge din zona Heap toate obiectele care nu mai sunt referite în cadrul programului aflat în execuţie.
Mecanismul Garbage Collector de eliberare a memorie de obiecte care nu mai sun folositoare oferă două avantaje importante. În primul rând programatorul nu trebuie sa se îngrijească în mod explicit de eliberarea obiectelor nefolositoare. Datorită unor erori de implementare care duc la situaţii de supraîncărcare a memorie programatorul poate să îşi piardă multe ore pentru a găsi şi repara defectul. Un al doilea avantaj este acela că GC asigură integritatea programelor – nu permite ştergerea accidentală a unor obiecte sau zone de memorie care ar duce pierderea integrităţii programului sau a maşinii virtuale.
Încărcare suplimentară a procesorului dată de componenta GC este un dezavantaj al acestei abordări de eliberare automate a memoriei. Această problemă este rezolvată într-o măsură destul de mare prin dezvoltarea unor algoritmi performanţi implementaţi la nivelul GC.
Este important de reţinut faptul că, nu poate fi prezis in avans momentul în care un obiect urmează să fie eliberat din memorie. Programatorul nu trebuie să îşi bazeze arhitectura programului plecând de la anumite presupuneri asupra momentului în care un anumit obiect va fi eliberat din memorie.
IMPORTANT: Deşi mecanismul GC asigură eliberarea memorie de obiectele care nu mai sunt referite, există pericolul ca un program prost gândit şi implementat să aibă probleme de alocare a memorie, şi să se ajungă la situaţia de depăşire a memorie – fapt care duce la întreruperea programului. Acest lucru se poate întâmpla deoarece GC şterge din zona Heap doar acele obiecte care nu sunt referite de către nici o variabila.
Deşi nu se poate şti în avans exact momentul în care un obiect este eliberat din memorie, obiectele care urmează să fie şterse sunt anunţate prin apelarea metodei finalize(). Metoda finalize() se regăseşte în cadrul oricărui obiect java (fiind moştenită din cadrul clasei de bază Object) şi este apelată de către GC în momentul în care obiectul urmează să fie şters.
public class GarbageCollectorTest {
static int removedObjects;
public static class Flower{
String name;
Flower(String name){
this.name = name;
}
public void finalize(){
removedObjects++;
System.err.println("The flower "+name+" is removed. Number of removed flowers is "+removedObjects);
}
}
public static void main(String[] args) {
Flower myF = null;
for(int i=0;i<10000;i++)
myF = new Flower(" Flower "+i);
}
}
În listingul anterior este prezentat un program care demonstrează faptul că la eliberarea fiecărui obiect din memorie este apelată metoda finalize() a acestuia.
IMPORTANT: Intrarea în acţiune a GC şi eliberarea memoriei de obiecte (deci implicit apelarea metodei finalize()) nu este garantată de către maşina virtuală java. Ca urmare a acestui fapt, toate operaţiile de eliberarea de resurse care trebuiesc realizate în cadrul unei aplicaţii (de exemplu închiderea unor conexiuni de reţea, unor fişiere, închiderea unor conexiuni la baze de date, etc.) trebuie să fie realizate de programator în mod manual prin construirea unor metode care sunt apelate în mod explicit la momentul potrivit.
Limbajul de programare JAVA
In prezent Java este mai mult decat un limbaj de programare, este o colectie de tehnologie ce permite dezvoltarea si rularea de aplicatii sigure, portabile si scalabile. In functie de tipul de aplicatie ce trebuie dezvoltata un programator poate selecta una dintre urmatoarele platforme java:
- Java 2 Platform, Standard Edition (J2SE) - contine compliatoarele, uneltele, masina virtuala java, setul de librarii (Java API) pentru dezvoltatea de aplicatii si applet-uri.
- Java 2 Platform, Enterprise Edition (J2EE) – defineste standardul pentru dezvoltarea de aplicatii complexe multinivel. Se bazeaza pe J2SE si ofera in plus servicii, unelte si librarii pentru dezvoltarea de aplicatii multinivel complexe.
- Java 2 Platform, Micro Edition (J2ME) – este un set de specificatii si tehnologii ce permite dezvoltarea de aplicatii java pentru dispozitive embaded: telefoane, PDA, imprimante etc.
Java este un limbaj de programare de nivel inalt, dezvoltat de JavaSoft, companie in cadrul firmei Sun Microsystems. Dintre caracteristicile principale ale limbajului amintim:
- simplitate, elimina supraincarcarea operatorilor, mostenirea multipla si toate "facilitatile" ce pot provoca scrierea unui cod confuz.
- robustete, elimina sursele frecvente de erori ce apar in programare prin eliminarea pointerilor, administrarea automata a memoriei si eliminarea fisurilor de memorie printr-o procedura de colectare a 'gunoiului' care ruleaza în fundal. Un program Java care a trecut de compilare are proprietatea ca la executia sa nu "crapa sistemul".
- complet orientat pe obiecte - elimina complet stilul de programare procedural
- usurinta in ceea ce priveste programarea in retea
- securitate, este cel mai sigur limbaj de programare disponibil în acest moment, asigurând mecanisme stricte de securitate a programelor concretizate prin: verificarea dinamica a codului pentru detectarea secventelor periculoase, impunerea unor reguli stricte pentru rularea programelor lansate pe calculatoare aflate la distanta, etc
- este neutru din punct de vedere arhitectural
- portabililtate, cu alte cuvinte Java este un limbaj independent de platforma de lucru, aceeasi aplicatie ruland, fara nici o modificare, pe sisteme diferite cum ar fi Windows, UNIX sau Macintosh, lucru care aduce economii substantialefirmelor care dezvolta aplicatii pentru Internet.
- compilat si interpretat
- asigura o performanta ridicata a codului de octeti
- permite programarea cu fire de executie (multitheaded)
Java : un limbaj compilat si interpretat
In functie de modul de executie al programelor, limbajele de programare se împart în doua categorii :
- interpretate : instructiunile sunt citite linie cu linie de un program numit interpretor si traduse în instructiuni masina; avantaj : simplitate; dezavantaje : viteza de executie redusa
- compilate : codul sursa al programelor este transformat de compilator într-un cod ce poate fi executat direct de procesor; avantaj : executie rapida; dezavantaj : lipsa portabilitatii, codul compilat într-un format de nivel scazut nu poate fi rulat decât pe platforma pe care a fost compilat.
Programele Java sunt fi atât interpretate cât si compilate.
Codul de octeti este diferit de codul masina. Codul masina este reprezentat de o succesiune de 0 si 1; codurile de octeti sunt seturi de instructiuni care seamana cu codul scris în limbaj de asamblare. Codul masina este executat direct decatre procesor si poate fi folosit numai pe platforma pe care a fost creat; codul de octeti este interpretat de mediul Java si de aceea poate fi rulat pe orice platforma care foloseste mediul de executie Java.
Codul de octeti este diferit de codul masina. Codul masina este reprezentat de o succesiune de 0 si 1; codurile de octeti sunt seturi de instructiuni care seamana cu codul scris în limbaj de asamblare. Codul masina este executat direct decatre procesor si poate fi folosit numai pe platforma pe care a fost creat; codul de octeti este interpretat de mediul Java si de aceea poate fi rulat pe orice platforma care foloseste mediul de executie Java.
Crearea unei aplicatii simple
- Scriererea codului sursa
2. class FirstApp {
3. public static void main( String args[]) {
4. System.out.println("Hello world");
5. }
6. }
Toate aplicatiile Java contin o clasa principala(primara) în care trebuie sa se gaseasca metoda main. Clasele aplicatiei se pot gasi fie într-un singur fisier, fie în mai multe.
- Salvarea fisierelor sursa
Se va face în fisiere cu extensia .java
Fiserul care contine codul sursa al clasei primare trebuie sa aiba acelasi nume cu clasa primara a aplicatiei (clasa care contine metoda main)
Obs: Java face distinctie între literele mari si mici.
Fiserul care contine codul sursa al clasei primare trebuie sa aiba acelasi nume cu clasa primara a aplicatiei (clasa care contine metoda main)
Obs: Java face distinctie între literele mari si mici.
C:/java/FirstApp.java
- Compilarea aplicatiei
Se foloseste compilatorul Java, javac
Apelul compilatorului se face pentru fisierul ce contine clasa principala a aplicatiei. Compilatorul creeaza câte un fisier separat pentru fiecare clasa a programului; acestea au extensia .class si sunt plasate în acelasi director cufisierele sursa.
Apelul compilatorului se face pentru fisierul ce contine clasa principala a aplicatiei. Compilatorul creeaza câte un fisier separat pentru fiecare clasa a programului; acestea au extensia .class si sunt plasate în acelasi director cufisierele sursa.
javac
FirstApp.java -> FirstApp.class
- Rularea aplicatiei
Se face cu interpretorul java, apelat pentru unitatea de compilare corespunzatoare clasei principale, fiind însa omisa extensia .class asociata acesteia.
java FirstApp
Rularea unei aplicatii care nu foloseate interfata grafica, se va face într-o fereastra sistem.
Structura lexicala a limbajului
Setul de caractere
Limbajului Java lucreaza în mod nativ folosind setul de caractere Unicode. Acesta este un standard international care înlocuieste vechiul set de caractere ASCII si care foloseste pentru reprezentarea caracterelor 2 octeti, ceea ce înseamnaca se pot reprezenta 65536 de semne, spre deosebire de ASCII, unde era posibila reprezentarea a 256 de caractere. Primele 256 caractere Unicode corespund celor din ASCII, referirea la celelate facându-se prin \uxxxx, unde xxxxreprezinta codul caracterului.
Ex:
\u0030 - \u0039 : cifre ISO-Latin 0 - 9
\u0660 - \u0669 : cifre arabic-indic 0 - 9
\u4e00 - \u9fff : litere din alfabetul Han (Chinez, Japonez, Coreean)
Cuvinte cheie
Cuvintele rezervate în Java sunt cele din C++, cu câteva exceptii
Identificatorii
Sunt secvente nelimitate de litere si cifre Unicode, începand cu o litera. Identificatorii nu au voie sa fie identici cu cuvintele rezervate.
Literalii (constantele)
Literalii pot fi de urmatoarele tipuri
- literali întregi
Sunt acceptate 3 baze de numeratie : baza 10, baza 16 (încep cu caracterele 0x) si baza 8 (încep cu cifra 0) si pot fi de doua tipuri: - normali, (se reprez pe 4 octeti - 32 biti)
- lungi (8 octeti - 64 biti) : se termina cu caracterul L (sau l).
- literali flotanti
Pentru ca un literal sa fie considerat flotant el trebuie sa aiba cel putin o zecimala dupa virgula, sa fie în notatie exponentiala sau sa aiba sufixul F sau f pentru valorile normale (reprez. pe 32 biti), respectiv D sau d pentru valorile duble (reprez. pe 64 biti) - literali logici
true : valoarea booleana de adevar
false : valoarea booleana de fals
Atentie: spre deosebire de C++, literalii întregi 1 si 0 nu mai au rolul de adevarat si false - literali caracter
Un literal de tip caracter este utilizat pentru a exprima caracterele codului Unicode. Reprezentarea se face fie folosind o litera, fie o secventa escape scrisa între apostrofuri. Secventele escape permit reprezentarea caracterelor care nu au reprezentare grafica si reprezentarea unor caractere speciale precum backslash, caracterul apostrof, etc. Secvente escape predefinite în Java:
Cod
|
Secventa Escape
|
Caracter
|
\u0008
|
'\b'
|
Backspace(BS)
|
\u0009
|
'\t'
|
Tab orizontal (HT)
|
\u000a
|
'\n'
|
Linie noua - linefeed (LF)
|
\u000c
|
'\f'
|
Pagina noua - formfeed (FF)
|
\u000d
|
'\r'
|
Inceput de rand (CR)
|
\u0022
|
'\"'
|
Ghilimele
|
\u0027
|
'\''
|
Apostrof
|
\u005c
|
'\\'
|
Backslash
|
- literali siruri de caractere
Un literal sir de caractere este format din zero sau mai multe caractere între ghilimele. Caracterele care formeaza sirul de caractere pot fi caractere grafice sau secvente escape ca cele definite la literalii caracter. Daca sirul este prea lung el poate fi scris ca o concatenare de subsiruri de dimensiune mai mica. Concatenarea sirurilor se face cu operatorul + ("alfa " + " beta " + " gama "). Sirul vid este "". Orice sir este de fapt o instanta a clasei String, definita în pachetuljava.lang.
Separatori
Un separator este un caracter care indica sfîrsitul unei unitati lexicale si începutul alteia. In Java separatorii sunt urmatorii:
( ) { } [ ] ; , .
Instructiunile unui program se separa cu punct si virgulaOperatori
- atribuirea:
=
- operatori matematici:
+, -, *, /, %
Este permisa notatia prescurtata de formalval
op= rval (ex: n += 2)
Exista operatorii pentru autoincrementare si autodecrementare (post si pre)ex
: x++, ++x, n--, --n
Observatie: evaluarea expresiilor logice se face prin metoda scurtcircuitului (evaluarea se opreste în momentul în care valoarea de adevar a expresiei este sigur determinata) - operatori logici:
&&(and), ||(or), !(not)
- operatori relationali:
<, <=, >, <=, ==, !=
- operatori pe biti:
& (and), |(or), ^(xor), ~(not)
- operatori de translatie
<<, >>, >>> (shift la dreapta fara semn)
- operatorul
if-else
: expresie_logica ? val_pt_true : val_pt_false ;
- operatorul , (virgula) folosit pentru evaluarea secventiala a operatiilor
int
x=0, y=1, z=2;
- operatorul + pentru concatenarea sirurilor:
· String s="abcd"
· int x=100;
· System.out.println(s + " - " + x);
- operatori pentru conversii (cast) :
(tip_de_data)
· int i = 200;
· long l = (long)i; //widening conversion - conversie prin extensie
· long l2 = (long)200;
· int i2 = (int)l2; //narrowing conversion - conversie prin contractie
Comentarii
In Java exista trei feluri de comentarii:
- Comentarii pe mai multe linii, închise între /* si */.
- Comentarii pe mai multe linii care tin de documentatie, închise între /** si */. Textul dintre cele două secvente este automat mutat în documentatia aplicatiei de catre generatorul automat de documentatie javadoc.
- comentarii pe o singura linie care încep cu //.
Observatii:
- nu putem sa scriem comentarii în interiorul altor comentarii.
- nu putem introduce comentarii în interiorul literalilor caracter sau sir de caractere.
- secventele /* si */ pot sa apara pe o linie dupa secventa // dar îsi pierd semnificatia; la fel se întâmplă cu secventa // în comentarii care încep cu /* sau /**.
Tipuri de date
In Java tipurile de date se împart în doua categorii: tipuri primitive de date si tipuri referinta. Java porneste de la premiza ca "orice este un obiect". Asadar tipurile de date ar trebui sa fie de fapt definite de clase si toate variabilele ar trebuisa memoreze de fapt instante ale acestor clase (obiecte). In principiu acest lucru este adevarat, �nsa, pentru usurinta programarii, mai exista si asa numitele tipurile primitive de date, care sunt cele uzuale :
- aritmetice
- întregi:
byte
(1 octet), short (2), int (4), long (8)
- reale:
float
(4 octeti), double (8)
- caracter :
char
(2 octeti)
- logic :
boolean (true si false)
In alte limbaje formatul si dimensiunea tipurilor primitive de date folosite într-un program pot depinde de platforma pe care ruleaza programul. In Java acest lucru nu mai este valabil, orice dependenta de o anumita platforma specifica fiind eliminata.
Vectorii, clasele si interfetele sunt tipuri referinta. Valoarea unei variabile de acest tip este, în contrast cu tipurile primitive, o referinta (adresa de memorie) catre valoarea sau multimea de valori reprezentata de variabila respectiva.
Exista trei tipuri de date C care nu sunt suportate de limbajul Java. Acestea sunt:
Vectorii, clasele si interfetele sunt tipuri referinta. Valoarea unei variabile de acest tip este, în contrast cu tipurile primitive, o referinta (adresa de memorie) catre valoarea sau multimea de valori reprezentata de variabila respectiva.
Exista trei tipuri de date C care nu sunt suportate de limbajul Java. Acestea sunt:
pointer
, struct
si union
. Pointerii au fost eliminati din cauza ca erau o sursa constanta de erori, locul lor fiind luat de tipul referinta, iar struct si union nu îsi mai au rostul atât timp cât tipurile compuse de date sunt formate în Java prin intermediul claselor.Variabile
Variabilele pot avea ca tip fie un tip primitiv de data, fie o referinta la un obiect.
Declararea variabilelor
| Tip nume_variabila |
Initializarea variabilelor
| Tip nume_variabila = valoare |
Declararea constantelor
| final Tip nume_variabila |
Conventia de notare a variabilelor in Java este data de urmatoarele criterii:
- variabilele finale (constante) se scriu cu majuscule
- variabilele normale se scriu astfel : prima litera cu litera mica, daca numele variabilei este format din mai multi atomi lexicali, atunci primele litere ale celorlalti atomi se scriu cu majuscule, de exemplu:
3. final double PI = 3.14;
4. int valoare = 100;
5. long numarElemente = 12345678L;
6. String bauturaMeaPreferata = "apa";
In functie de locul �n care sunt declarate variabile se împart în urmatoatele categorii:
- Variabile membre, declarate în interiorul unei clase, vizibile pentru toate metodele clasei respective si pentru alte clase în functie de nivelul lor de acces (vezi "Declararea variabilelor membre")
- Variabile locale, declarate într-o metoda sau într-un bloc de cod, vizibile doar în metoda/blocul respectiv
- Parametri metodelor, vizibili doar în metoda respectiva
- Parametrii de la tratarea exceptiilor
Obs: variabilele declarate într-un
for
pentru controlul ciclului, ramân locale corpului ciclului. for(int i=0; i<100; i++) { }
int i; //ok in Java, eroare �n C++
Obs: Spre deosebire de C++ nu este permisa ascunderea unei variabile :
int x=12;
{
int x=96; //ilegal
}
Controlul executiei
Instructiunile Java pentru controlul executiei sunt asemanatoare celor din C.
Instructiuni de decizie
| if-else , switch-case |
Instructiuni de salt
| for, while, do-while |
Instructiuni pt. tratarea exceptiilor
| try-catch-finally , throw |
Alte instructiuni
| break , continue, label: , return |
Instructiuni de decizie
| |
if-else | if (exp_booleana) { /*...*/} if (exp_booleana) { /*...*/} else { /*...*/} |
switch-case
|
switch (variabila) {
case val1 : /* ... */ break;
case val2 : /* ... */ break;
/*...*/
default : /*...*/ }
|
Instructiuni de salt
| |
for |
for(initializare; exp_booleana; pas_iteratie)
Ex: for(int i=0, j=100 ; i<100 && j>0; i++, j--)
{/* ... /*}
Obs: atât la initializare cât si în pasul de iteratie pot fi mai multe instructiuni despartite prin virgula.
|
while
|
while (exp_booleana) {
/*...*/
}
|
do-while
| do {
/*...*/
}
while (exp_booleana) ;
|
Instructiuni pentru tratarea exceptiilor
| |
try-catch-finally , throw |
(vezi "Tratarea exceptiilor")
|
Alte instructiuni
| |
break |
paraseste fortat corpul iteratiei curente
|
continue |
termina fortat iteratia curenta
|
return
| return [valoare];
Termina o metoda
|
label : |
Defineste o eticheta
|
Atentie: In Java nu exista goto. Se pot însa defini etichete de forma nume_eticheta:, folosite în expresii de genul:
break nume_eticheata
sau continue nume_eticheta
Exemplu:
i=0;
eticheta:
while (i<10) {
System.out.println("i="+i);
j=0;
while (j<10) {
j++;
if (j==5) continue eticheta;
if (j==7) break eticheta;
System.out.println("j="+j);
}
i++;
}
Vectori
Crearea unui vector
Declararea vectorului se face astfel:
Tip[] numeVector; sau
Tip numeVector[];
Exemplu:
int[] intregi;
String adrese[];
Instantierea se realizeaza prin intermediul operatorului new si are ca efect alocarea memoriei pentru vector, mai precis specificarea numarului maxim de elemente pe care îl va avea vectorul.
numeVector = new Tip[dimensiune];
Exemplu:
v = new int[10]; //se aloca spatiu pentru 10 intregi
adrese = new String[100];
Obsservaţie: declararea si instantierea unui vector pot fi facute simultan astfel:
Tip[] numeVector = new Tip[dimensiune];
Dupa declararea unui vector, acesta poate fi initializat, adica elementele sale pot primi niste valori initiale, evident daca este cazul pentru asa ceva. In acest caz instantierea lipseste, alocarea memoriei facându-se automat în functie denumarul de elemente cu care se initializeaza vectorul.
Exemplu:
String culori[] = {"Rosu", "Galben", "Verde"};
int []factorial = {1, 1, 2, 6, 24, 120};
Observatii: Primul indice al unui vector este 0, deci pozitiile unui vector cu n elemente vor fi cuprinse între 0 si n-1.
Nu sunt permise constructii de genul
Tip numeVector[dimensiune]
, alocarea memoriei fac�ndu-se doar prin intermediul opearatorului new.
Exemplu:
int v[10]; //ilegal
int v[] = new int[10]; //corect
Vectori multidimensionali
In Java tablourile multidimensionale sunt de fapt vectori de vectori.
In Java tablourile multidimensionale sunt de fapt vectori de vectori.
Exemplu:
int m[][]; //declararea unei matrici
m = new int[5][10]; //cu 5 linii, 10 coloane
Observaţie:
m[0], m[1], ..., m[5]
sunt vectori de intregi cu 10 elemente
Dimensiunea unui vector
Cu ajutorul cuvântului cheie
Cu ajutorul cuvântului cheie
length
se poate afla dimensiunea unui vector. int []a = new int[5];
a.length are valoarea 5
int m = new int[5][10];
m[0].length are valoarea 10
Copierea vectorilor
Copierea unui vector ân alt vector se face cu ajutorul metodei
Copierea unui vector ân alt vector se face cu ajutorul metodei
System.arraycopy
: int x[] = {1, 2, 3, 4};
int y[] = new int[4];
System.arraycopy(x,0,y,0,x.length);
Vectori cu dimensiune variabila
Implementarea vectorilor cu numar variabil de elemente este oferita de clasa Vector din pachetul java.util. Un obiect de tip Vector contine numai elemente de tip Object.
Implementarea vectorilor cu numar variabil de elemente este oferita de clasa Vector din pachetul java.util. Un obiect de tip Vector contine numai elemente de tip Object.
Siruri de caractere
In Java, un sir de caractere poate fi reprezentat printr-un vector format din elemente de tip char, un obiect de tip String sau un obiect de tip StringBuffer.
Declararea unui sir
Daca un sir de caractere este constant atunci el va fi declarat de tipul String, altfel va fi declarat cu StringBuffer. (vezi "Clasele String, StringBuffer") Exemple echivalente de declarare a unui sir:
Daca un sir de caractere este constant atunci el va fi declarat de tipul String, altfel va fi declarat cu StringBuffer. (vezi "Clasele String, StringBuffer") Exemple echivalente de declarare a unui sir:
String str = "abc";
char data[] = {'a', 'b', 'c'};
String str = new String(data);
String str = new String("abc");
Concatenarea sirurilor
Concatenarea sirurilor de caractere se face prin intermediul operatorului +.
Concatenarea sirurilor de caractere se face prin intermediul operatorului +.
String str1 = "abc" + "xyz";
String str2 = "123";
String str3 = str1 + str2;
In Java, operatorul de concatenare + este extrem de flexibil în sensul ca permite concatenarea sirurilor cu obiecte de orice tip care au o reprezentare de tip sir de caractere.
System.out.print("Vectorul v are" + v.length + " elemente")
Folosirea argumentelor de la linia de comanda
O aplicatie Java poate primi oricâte argumente de la linia de comanda în momentul lansarii ei. Aceste argumente sunt utile pentru a permite utilizatorului sa specifice diverse optiuni legate de functionarea aplicatiei sau sa furnizeze anumitedate initiale programului.
Atentie: Programele care folosesc argumente de la linia de comanda nu sunt 100% pure Java deoarece unele sisteme de operare cum ar fi Mac OS nu au în mod normal linie de comanda.
Argumentele de la linia de comanda sunt introduse la lansarea unei aplicatii, fiind specificate dupa numele aplicatiei si separate prin spatiu. De exemplu, sa presupunem ca aplicatia Sort ordoneaza lexicografic liniile unui fisier si primeste ca argument numele fisierului pe care sa îl sorteze. Pentru a ordona fisierul "persoane.txt" lansarea aplicatiei se va face astfel:
java Sort persoane.txt
Asadar, formatul general pentru lansarea unei aplicatii care primeste argumente de la linia de comanda este:
java NumeAplicatie [arg1 arg2 . . . argn]
In cazul în care sunt mai multe, argumentele trebuie separate prin spatii iar daca unul dintre argumente contine spatii, atunci el trebuie pus între ghilimele.
Lucrarea 1. Fire de execute I
1. Obiectivul lucrării
Scopul acestei lucrări este de a prezenta modul de lucru cu fire de execuţie (thread-uri) in Java şi însuşirea tehnicilor de lucru cu fire: crearea, startarea, oprirea firelor, sincronizarea si excluderea mutuala între fire .
2. Noţiuni preliminare
De multe ori este nevoie ca un program sa fie divizat in mai multe componente care sa ruleze independent. Aceste componente se numesc fire de execuţie (eng. Thread). Un fir de execuţie reprezintă o secvenţa de instrucţiuni ce se executa in interiorul unui proces. In figura 1.1. este ilustrat schematic un program ce conţine doua fire de execuţie.
Figura. 1.1 Un program ce conţine doua fire de execuţie.
Un exemplu de aplicaţie in care firele de execuţie sunt folosite este un browser de internet. În momentul in care încărcaţi o pagină puteţi realiza scroll in pagina şi citi conţinutul acesteia deşi nu toate imaginile au fost încărcate, procesul de încărcare a acestora relizându-se în background pe fire de execuţie separate.
Trebuie făcută distincţie între fire de execuţie şi procese. Deşi ambele concepte implică execuţia în paralel a unor secvenţe de cod există o serie de desebiri fundamentale între procese şi fire de execuţie.
Procesele sunt entităţi independente ce se execută independent şi sunt gestionate de către nucleul sistemului de operare.
Firele de execuţie sunt secvenţe ale unui program (proces) ce se execută aparent în paralel în cadrul unui singur proces.
Un alt exemplu de aplicaţie în care este nevoie de fire de execuţie este o aplicaţie de tip server ce oferă servicii unor aplicaţii client. Dacă nu am avea la dispoziţie tehnologia firelor de execuţie aplicaţia server ar fi obligată să deservească cererile clienţilor în mod secvenţial în ordinea sosirii cererilor acestora. Folosind fire de excuţie aplicaţia server va putea deservi în paralel doi sau mai mulţi clienţi, alocându-se pentru fiecare cerere (client) câte un fir de execuţie. Un exemplu este un server HTTP ce transmite clienţilor resurse de tip pagini web.
2.1. Stările unui fir de execuţie
Un fir de execuţie se poate afla in una dintre următoarele 4 stări:
1. New – obiectul fir de execuţie a fost creat dar înca nu a fost startat.
2. Runnable – Firul se afla in starea in care poate fi rulat in momentul in care procesorul devine disponibil.
3. Dead – Calea normala prin care un fir se termina este prin ieşirea din metoda run(). Se poate forţa terminarea firului apelând metoda stop() dar nu se recomanda folosirea sa, fiind o metoda “deprecated” in Java2.
4. Blocked – Firul de execuţie este blocat si nu poate fi rulat, chiar daca procesorul este disponibil.
Starea blocat (blocked) prezintă cel mai mare interese. Un fir poate intra in starea blocat următoarele situaţii:
- in cadrul firului s-a apelat metoda Thread.sleep(sec) care determina blocarea firului pentru un număr specificat de secunde;
- firul a apelat o metoda sincronizata ce are monitorul acaparat de către un alt fir;
- a fost apelata metoda wait();
- firul aşteaptă după o operaţie cu fluxuri de intrare – ieşire;
3. Crearea şi startarea firelor de execuţie
După cum se ştie punctul de start al unei aplicaţii Java este funcţia main. În momentul lansării în execuţie a aplicaţiei aceasta va conţine un fir de execuţie ce va executa această funcţie main. Aşadar o aplicaţie java conţine cel puţin un fir de execuţie.
Pentru a crea fire de execuţie suplimentare in Java se pot utiliza doua procedee: extinderea clasei Thread sau implementarea interfeţei Runnable.
OBSERVATIE: O aplicaţie Java îşi termină execuţia în momentul în care toate firele de execuţie şi-au terminat execuţia (cu excepţia firelor de tip daemon la care se va reveni ulterior pe parcursul laboratorului).
În continuare vor fi descrise cele două mecanisme java prin intermediul cărora pot fi construite fire de execuţie în Java.
3.1. Extinderea clasei Thread
Un fir de execuţie poate fi construit plecând de la o clasă definită de utilizator şi moştenind (extinzând) clasa Thread. Clasa Thread este o clasă definită în cadrul pachetului java.lang (unul dintre pachetele standard Java) şi această clasă defineşte toate proprietăţile şi funcţionalităţile unui fir de execuţie.
O clasă ce extinde clasa Thread devine clasă de tip fir de execuţie astfel încât un obiect instanţă al acestei clase va putea fi folosit pentru a lansa în execuţie un fir.
class Sortare extends Thread
{
public void run()
{
// activitatile desfasurate de catre firul de executie
…
}
}
O clasă de tip fir de execuţie va trebui sa suprascrie metoda run() (moştenită din cadrul clasei Thread). În cadrul acestei metode va trebui definită secvenţă de instrucţiuni ce va fi executată de către firul de execuţie.
În momentul lansării in execuţie a unui fir se va executa secvenţa de instrucţiuni definită în cadrul metodei run().
Pentru a lansa în execuţie un fir se foloseşte metoda start() (moştenită de asemenea din cadrul clasei Thread). Această metodă este responsabilă cu iniţializarea tuturor mecanismelor necesare pentru a putea executa un fir de execuţie, după care va apela automat metoda run().
Sortare s1 = new Sortare();
s1.start();
OBSERVATIE: metoda run() nu trebuie apelata in mod explicit de către programator pentru că aceasta va fi apelată în mod automat atunci când firul de execuţie este lansat în execuţie folosind metoda start().
OBSERVATIE: un fir de execuţie o data startat nu va mai putea fi startat înca odata. Daca se încearcă apelarea metodei start() pentru un fir deja pornit, se va genera o eroare.
3.2. Implementarea interfeţei Runnable
Există situaţii în care nu poate fi extinsă clasa Thread pentru a construi un fir de execuţie (un exemplu este situaţia în care o clasa ce trebuie definită ca fir de execuţie moşteneşte deja o altă clasă – având în vedere faptul că Java nu acceptă moştenire multiplă suntem puşi în imposibilitatea de a folosi mecanismul extinderii clasei Thread pentru a construi firul de execuţie).
Cel de al doilea mecanism de creare a unui fir de execuţie este prin implementarea interfeţei Runnable.
class Sortare implements Runnable{
private Thread thread=null;
public void start(){
if(thread==null)
{
thread = new Thread(this);
thread.start();
}
}
public vod run()
{
//activitatile desfasurate de catre firul de executie
…
}
}
}
Startarea firului de execuţie se face în mod similar ca şi în cazul extinderii clasei Thread.
Sortare s1 = new Sortare();
s1.start();
Se observă că în cazul implementării interfeţei Runnable, utilizatorul defineşte propria metodă start(), în cadrul căreia se construieşte un obiect de tip Thread, ce va primi ca argument în cadrul constructorului o referinţă către obiectul curent, obiect curent ce conţine metoda run(). În momentul apelării metodei start() din cadrul obiectului de tip Thread, se va executa metoda run() din cadrul obiectului transmis ca argument la crearea acestuia.
4. Mecanisme de excludere mutuala şi sincronizare intre firele de execuţie
4.1. Excluderea mutuala utilizând cuvântul cheie synchronized
Unul dintre aspectele cele mai importante de care trebuie ţinut cont atunci când se lucrează cu fire de execuţie este modul in care acestea accesează resursele comune. Astfel in momentul in care doua sau mai multe fire împart o resursa comuna acestea trebuie sa î-şi sincronizeze accesul la acea resursa.
Un exemplu de astfel de situaţie este cel al firelor consumator\producator. Un fir introduce date intr-un buffer, iar cel de al doilea citeşte datele puse de primul fir in buffer. Evident cele doua fire nu trebuie sa acceseze simultan bufferul si trebuie sa aibă un acces sincronizat la acesta.
Prevenirea coliziunii in java se face utilizând metodele sincronizate. O metoda este sincronizata atunci când are in fata cuvântul cheie synchronized.
synchronized void f() { /* ... */ }
synchronized void g(){ /* ... */ }
Numai un singur fir poate accesa la un moment dat o metoda sincronizata a unui obiect. Fiecare obiect are inclus (automat) un monitor sau un zăvor. In momentul in care o metoda sincronizata a unui obiect este apelata, monitorul este achiziţionat de către firul care a accesat metoda. Atâta timp cat metoda sincronizata este in execuţie nici o alta metoda sincronizata nu va mai putea fi apelata de către un alt fir.
Observaţie: In momentul in care un fir încearcă sa acceseze o metoda sincronizata a unui obiect care are monitorul acaparat de către un alt fir, acesta se blochează in aşteptarea eliberării monitorului.
4.2.. Sincronizarea firelor utilizând metodele wait(), notify() si notifyAll()
Metodele wait() si notify() sunt utilizate pentru a bloca şi debloca firele de execuţie. O observaţie importantă referitoare la aceste două metode este ca ele fac parte din clasa Object. Motivul acestei poziţionări este pentru că aceste două metode manipulează monitoarele obiectelor, si, la rândul lor, monitoarele se găsesc la nivelul oricărui obiect.
Metodele wait si notify pot fi apelate doar din interiorul unor blocuri sau metode sincronizate. Ceea ce inseamnă ca aceste metode, pentru un obiect data, pot fi apelate doar de către deţinătorul monitorului obiectului.
In momentul in care un fir este blocat prin apelarea metodei wait() monitorul deţinut de respectivul fir este eliberat.
Observaţie: Metoda wait(milis) poate fi utilizata in locul metodei Thread.sleep(milis), avantajul fiind ca firul poate fi deblocat înainte de expirarea timpului de aşteptare (ceea ce in cazul sleep() nu este posibil).
Observaţie: Nu utilizaţi metodele stop() si suspend() deoarece folosirea acestora in cadrul unor blocuri sincronizate duce la deadlok întrucât monitorul nu este eliberat. In listingul următor este prezentat o scurta secvenţa care determinadeadlock-ul.
class oclasa
{ //...
synchronized void f()
{ Thread.currentThread().stop();
}
}
{ //...
synchronized void f()
{ Thread.currentThread().stop();
}
}
4.3. Sincronizarea intre firele de execuţie utilizând metoda join()
Metoda join() aparţine clasei Thread si este utilizata pentru a determina un fir de execuţie să aştepte terminarea unui alt fir de execuţie.
4.4. Pachetul java.util.concurrent
Începând cu versiunea 1.5 a Java Standard Development Kit, a fost introdus un set de pachete utilitare denumite „ Concurrency Utilities packages”. Aceste pachete conţin clase pe care programatorul le poate folosi pentru a implementa diferite mecanisme de comunicare, sincronizare şi gestionare a firelor de execuţie. Cele mai multe dintre aceste clase utilitare se regăsesc în pachetul java.util.cuncurrent. Câteva dintre funcţionalităţile noi sunt:
- Obiecte de tip Lock – lucrează într-o manieră similară cu monitoarele implicite folosite de blocurile sincronizate. La fel ca în cadrul monitoarelor implicite, doar un singur fir poate achiziţiona un Lock. Acestea suportă de asemenea wait/notify. Unul dintre principalele avantaje al obiectelor Lock este posibilitatea de a verifica disponibilitatea unui monitor înainte de a încerca achiziţionarea acestuia.
- Variabile atomice – a fost introdus un set de clase ce permite manipularea în mod atomic a tipurilor primitive şi a tipurilor referinţă.
- Obiecte de tip Executor – ce permit planificarea pentru execuţie ulterioară a task-urilor şi mecanisme pentru lucrul cu threadpool-uri.
Referinţe suplimentare cu privire la facilităţile introduse se găsesc la adresa: http://java.sun.com/developer/technicalArticles/J2SE/concurrency/
5. Setarea priorităţilor firelor de execuţie
Prioritatea unui fir de execuţie spune planificatorului de execuţie a firelor cat de important este acesta. Aceasta insemnă că dacă mai multe fire de execuţie sunt blocate in aşteptarea execuţiei, planificatorul îl va alege prima data pe cel cu prioritate mai mare. Aceasta nu înseamnă ca firele cu prioritate mai mica nu vor avea alocat timp din procesor.
Pentru a seta prioritatea unui fir se utilizează metoda setPriority(), iar pentru obţinerea priorităţii unui fir se utilizează metoda getPriority() (metodele fac parte din clasa Thread) . Firele pot primi priorităţi intre valorile MIN_PRIORITY siMAX_PRIORITY (constante definite în clasa Thread).
Observaţie: Programatorul nu trebuie sa se bazeze pe priorităţi in construirea programului, întrucât acesta poate da rezultate diferite pe sisteme diferite.
6. Terminarea unui fir de execuţie
O deosebita atenţie trebuie data modului in care un fir de execuţie se termina. Terminarea unui fir de execuţie utilizând metoda stop() nu este recomandata, de altfel, aceasta metoda este “deprecated” (nu mai este recomandată de către Sun pentru a fi utilizată). Calea recomandata de terminare a unui fir este prin revenirea normala ( return ) din run(). Aşadar in cadrul metodei run se recomandă adăugarea unor blocuri condiţinale care să determine terminarea firului de execuţie, respectiv ieşirea din metoda run.
Acest mecanism nu este util de fiecare data deoarece in momentul in care un fir este blocat ( datorita unor instrucţiuni wai(), sleep(), join(), operatii I/O ), acesta nu poate verifica nici o condiţie. Pentru a termina un fir care este blocat se utilizează metoda interrup(). Aceasta metoda determina aruncarea unei excepţii InterruptedException care trebuie prinsa.
7. Grupuri de fire de execuţie
Orice fir de execuţie aparţine unui grup (thread grup). Acesta poate fi grupul default sau poate aparţine unui grup specificat in mod explicit in momentul creierii acestuia (Obs. Un fir o data creat nu poate trece dintr-un grup in alt grup). Daca nu este specificat atunci grupul implicit din care face parte un fir este grupul sistem. De asemenea la rândul lor noile grupuri create sunt grupate ierarhic, si in vârf se afla acelaşi grup sistem.
Lucrul cu grupuri de fire este util atunci când se doreşte ca printr-o singura comanda sa se controleze toate firele aparţinand unui grup. Crearea unui grup se face utilizând clasa ThreadGrup.
ThreadGroup tg = new ThreadGroup(“myg”);
8. Swing si firele de execuţie
Metodele claselor swing nu sunt thread-safe, aceasta înseamnă ca nu sunt proiectate pentru cazul in care sunt apelate simultan de către mai multe fire de execuţie. Aşadar o data startat firul de tratare a evenimentelor Swing (apelandsetVisible(), pack() sau orice alta metoda ce face vizibila o fereastra) nu mai poate fi apelată nici o metodă a claselor Swing in siguranţă de către mai multe fire. Pentru că un fir să modifice o componenta se utilizează metoda invokeLater() astfel:
Swingutilities.invokeLater( new Runnable()
{ public void run()
{ some_window.repaint();
}});
{ public void run()
{ some_window.repaint();
}});
Exerciţii
Importaţi în mediul Eclipse proiectul ce exemplifică noţiunile prezentate în acest laborator (link proiect*).
In cadrul proiectului java se regăsesc următoarele pachete:
Notiuni exemplificate: construirea unui fir utilizand clasa Thread, startarea unui fir, construirea unui fir daemon.
Notiuni exemplificate: contruirea unui fir utilizand interfata Runnable.
Notiuni exemplificate: setarea prioritatilor firelor de executie
Notiuni exemplificate: utilizarea metodei join() pentru a pune in asteptare un fir.
Notiuni exemplificate: sincronizarea accesului la resurse comune folosind bloduri synchronized.
Notiuni exemplificate: folosirea metodelor wait() si notify() pentru blocarea si deplocarea firelor de executie.
Notiuni exemplificate: comunicarea intre fire utilizand pipe-uri.
Notiuni exemplificate: planificarea pentru executie a firelor de executie.
Notiuni exemplificate: utilizarea clasei java.util.concurrent.Semaphor introdusa in sdk 1.5.0 pentru sincronizarea firelor in accesul resurselor cumune.
Notiuni exemplificate: utilizarea claselor din pachetul java.util.concurrent pentru a construi si utiliza un thread pool
Programarea în reţea – Socket-uri
1. Scopul lucrării
Scopul acestei lucrări este insuşirea tehnicilor de programare în reţea în limbajul Java utilizând socket-uri.
2. Noţiuni preliminare
Calculatoarele conectate in reţea comunică între ele utilizând protocoalele TCP (Transport Control Protocol) şi UDP (User Datagram Protocol) conform diagramei:
Figura 1. Nivelele de omunicare în reţea
Pentru realizarea unor programe care comunică in reţea în java, se utilizează clasele din pachetul java.net . Acest pachet oferă clasele necesare pentru realizarea unor programe de reţea independente de sistemul de operare.
In tabelul următor sunt prezentate principalele clase care sunt utilizate pentru construirea unor programe de reţea.
Class
|
Scop
|
URL
|
Reprezintă un URL
|
URLConnection
|
Returnează continutul adresat de obiectele URL
|
Socket
|
Crează un socket TCP
|
ServerSocket
|
Crează un socket server TCP
|
DatagramSocket
|
Crează un socket UDP
|
DatagramPacket
|
Reprezintă o datagrama trimisă printr-un obiect DatagramSocket
|
InetAddress
|
Reprezintă numele unui pc din reţea, respectiv IP-ul corespunzător
|
Java oferă două abordări diferite pentru realizarea de programe de reţea. Cele două abordări sunt asociate cu clasele:
- Socket, DatagramSocket şi ServerSocket
- URL, URLEncoder şi URLConnection
Programarea prin socket-uri reprezintă o abordare de nivel jos, prin care, două calculatoare pot fi conectate pentru a realiza schimb de date. Ca principiu de baza, programarea prin socketuri face posibilă comunicarea în mod full-duplex între client şi server. Comunicarea se face prin fluxuri de octeţi.
Pentru ca comunicarea să se desfăşoare corespunzător, programatorul va trebui să implementeze un protocol de comunicaţie (reguli de dialog), pe care clientul şi serverul îl vor urma.
Identificare unui calculator în reţea
Orice calculator conectat la Internet este identificat in 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:193.226.5.33 si este numit adresa IP numerică. Corespunzătoare unei adrese numerice exista si o adresa IP simbolica, cum ar fi utcluj.ro.
De asemenea fiecare calculator aflat într-o reţea locala are un nume unic ce poate fi folosit la identificarea locala a acestuia.
De asemenea fiecare calculator aflat într-o reţea locala are un nume unic ce poate fi folosit la identificarea locala a acestuia.
Clasa Java care reprezinta notiunea de adresa IP este InetAddress. Pentru a construi un obiect se foloseşte comanda:
InetAddress address =InetAddress.getByName("121.3.1.2");
Pentru a vedea toate modurile în care pot fi construite obiecte de tip InetAddress studiaţi documentaţia acestei clase.
Un calculator are în general o singura legătura fizica la reţea. Orice informaţie destinata unei anumite maşini trebuie deci sa specifice obligatoriu adresa IP a acelei maşini. Insa pe un calculator pot exista concurent mai multe procese care au stabilite conexiuni în reţea, asteptând diverse informaţii. Prin urmare datele trimise către o destinaţie trebuie sa specifice pe lângă adresa IP a calculatorului si procesul catre care se îndreaptă informaţiile respective. Identificarea proceselor se realizează prin intermediul porturilor.
Orice aplicaţie care comunică în reţea este identificată în mod unic printr-un port, astfel încât pachetele sosite pe calculatorul gazdă să poată fi corect rutate către aplicaţia destinaţie.
Valorile pe care le poate lua un număr de port sunt cuprinse între 0 si 65535 (deoarece sunt numere reprezentate pe 16 biţi), numerele cuprinse între 0 si 1023 fiind însă rezervate unor servicii sistem, si, din acest motiv, nu se recomandă folosirea acestora.
Definitia socket-ului: Un socket reprezintă un punct de conexiune într-o reţea TCP\IP. Când două programe aflate pe două calculatoare în reţea doresc să comunice, fiecare dintre ele utilizează un socket. Unul dintre programe (serverul) va deschide un socket si va aştepta conexiuni, iar celălalt program (clientul), se va conecta la server şi astfel schimbul de informaţii poate începe. Pentru a stabili o conexiune, clientul va trebui să cunoască adresa destinaţiei ( a pc-ului pe care este deschis socket-ul) şi portul pe care socketul este deschis.
Principalele operaţii care sunt facute de socket-uri sunt:
- conectare la un alt socket
- trimitere date
- recepţionare date
- inchidere conexiune
- acceptare conexiuni
3. Aplicaţie client-server cu server monofir
Pentru realizarea unui program client-server se utilizează clasele ServerSocket şi Socket.
Programul server va trebui să deschidă un port şi să aştepte conexiuni. In acest scop este utilizată clasă ServerSocket. In momentul în care se crează un obiect ServerSocket se specifică portul pe care se va iniţia aşteptarea. Inceperea ascultării portuli se face apelând metoda accept(). In momentul în care un client s-a conectat, metoda accept() va returna un obiect Socket.
La rândul său clientul pentru a se conecta la un server, va trebui să creeze un obiect de tip Socket, care va primi ca parametri adresa serverului şi portul pe care acesta aşteaptă conexiuni.
Atât la nivelul serverului cât şi la nivelul clientului, odată create obiectele de tip Socket, se vor obţine fluxurile de citire şi de scriere. In acest scop se utilizeaza metodele getInputStream() şi getOuptuStream().
In listingul următor este prezentat programul server.
import java.net.*;
import java.io.*;
public class ServerSimplu {
public static void main(String[] args) throws IOException{
ServerSocket ss=null;
Socket socket=null;
try{
String line="";
ss = new ServerSocket(1900); //creaza obiectul serversocket
socket = ss.accept(); //incepe asteptarea peportul 1900
//in momentul in care un client s-a conectat ss.accept() returneaza
//un socket care identifica conexiunea
//creaza fluxurile de intrare iesire
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream())),true);
while(!line.equals("END")){
line = in.readLine(); //citeste datele de la client
out.println("ECHO "+line); //trimite date la client
}
}catch(Exception e){e.printStackTrace();}
finally{
ss.close();
if(socket!=null) socket.close();
}
}
}
Programul client este prezentat în listingul următor:
import java.net.*;
import java.io.*;
public class ClientSimplu {
public static void main(String[] args)throws Exception{
Socket socket=null;
try {
//creare obiect address care identifica adresa serverului
InetAddress address =InetAddress.getByName("localhost");
//se putea utiliza varianta alternativa: InetAddress.getByName("127.0.0.1")
socket = new Socket(address,1900);
BufferedReader in =
new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
// Output is automatically flushed
// by PrintWriter:
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())),true);
for(int i = 0; i < 10; i ++) {
out.println("mesaj " + i);
String str = in.readLine(); //trimite mesaj
System.out.println(str); //asteapta raspuns
}
out.println("END"); //trimite mesaj care determina serverul sa inchida conexiunea
}
catch (Exception ex) {ex.printStackTrace();}
finally{
socket.close();
}
}
}
Pentru verificare se va starta serverul, după care se va starta clientul.
4. Aplicatie client-server cu server multifir
Analizând programul server prezentat în secţiunea anterioarăm se observă că acesta poate servi doar un singur client la un moment dat. Pentru ca serverul să poată servi mai mulţi clienţi simultan, se va utiliza programarea multifir.
Ideea de bază este simplă, şi anume, serverul va aştepta conexiuni prin apelarea metodei accept(). In momentul in care un client s-a conectat şi metoda accept() a returnat un Socket, se va crea un fir de execuţie care va servi respectivul clientul, iar severul va reveni în aşteptare.
In programul următor este prezentat un server multifir – capabil de a servi mai multi clienţi simultan.
import java.io.*;
import java.net.*;
public class ServerMultifir
{
public static final int PORT = 1900;
void startServer()
{
ServerSocket ss=null;
try
{
ss = new ServerSocket(PORT);
while (true)
{
Socket socket = ss.accept();
new TratareClient(socket).start();
}
}catch(IOException ex)
{
System.err.println("Eroare :"+ex.getMessage());
}
finally
{
try{ss.close();}catch(IOException ex2){}
}
}
public static void main(String args[])
{
ServerMultifir smf = new ServerMultifir();
smf.startServer();
}
}
class TratareClient extends Thread
{
private Socket socket;
private BufferedReader in;
private PrintWriter out;
TratareClient(Socket socket)throws IOException
{
this.socket = socket;
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter( socket.getOutputStream())));
}
public void run()
{
try {
while (true)
{
String str = in.readLine();
if (str.equals("END")) break;
System.out.println("Echoing: " + str);
out.println(str);
}//.while
System.out.println("closing...");
}
catch(IOException e) {System.err.println("IO Exception");}
finally {
try {
socket.close();
}catch(IOException e) {System.err.println("Socket not closed");}
}
}//.run
}
5. Server HTTP
In această sectiune este creat un server HTTP care poate răspunde la cereri GET. Functia main() din cadrul clasei HttpServer startaează un fir de execuţie. In cadrul acestui fir se instanţiază un obiect ServerSocket şi se incepe ascultarea portului 80, care este portul standard pentru protocolul HTTP.
In momentul în care apare o cerere (un client se conectează pe portul 80) metoda accept() va returna un obiect Socket. In continuare se crează un obiect PrecesRequest (care este de tip fir de excutie), care va primi ca parametru, obiectul Socket returnat de metoda accept(). După crearea obiectului ProcesRequest, serverul revine în aşteptare şi va putea servi alţi clienţi.
Clasa ProcesRequest implementează o versiune simplificată a protocolului HTTP. In cadrul constructorului clasei ProcesRequest se crează fluxurile de intrare \ ieşire, după care este startat firul de execuţie. In cadrul firului de execuţie este analizată cererea primită de la client , şi în cazul în care aceasta este o cerere validă de tip GET, atunci se va transmite către client resursa solicitată.
import java.io.*;
import java.net.*;
class HttpServer extends Thread
{
//portul standard
private final static int PORT = 80;
private final String iniContext="c:/temp/ServerHTTP/webdocs";
private boolean alive;
private ServerSocket ss;
//constructor
HttpServer()throws Exception{
System.out.println("Start server http.");
ss = new ServerSocket(PORT);
alive=true;
start();
}
public void run(){
while(alive){
//asteapta conexiuni
try{
System.out.println("Server asteapta...");
new ProcesRequest(ss.accept(),iniContext);
}catch(IOException e){System.err.println("EROARE CONECTARE:"+e.getMessage());}
//..reia bucla de asteptare dupa ce am creat un fir pentru client
}
System.out.println("STOP SERVER");
}
public static void main(String[] args)throws Exception
{
try{
new HttpServer();
}catch(Exception e){e.printStackTrace();}
}
}
import java.net.*;
import java.io.*;
import java.util.*;
class ProcesRequest extends Thread
{
private PrintWriter outStr;
private BufferedReader inStr;
private Socket s;
private DataOutputStream dout;
private String iniContext;
ProcesRequest(Socket s, String iContext){
try{
outStr = new PrintWriter(new OutputStreamWriter(s.getOutputStream()),true);
inStr = new BufferedReader(new InputStreamReader(s.getInputStream()));
dout = new DataOutputStream(s.getOutputStream());
iniContext = iContext;
this.s = s;
start();
}catch(IOException e)
{System.err.println("EROARE CONECTARE: "+e.getMessage());}
}
public void run(){
try{
String fileName=null;
String request = inStr.readLine();
System.out.print(request);
if(request.lastIndexOf("GET")==0) fileName = interpretGET(request);
else throw new Exception("BAU");
byte[] data = readFile(fileName);
dout.write(data);
dout.flush();
}
catch(IOException e){outStr.println("<HTML><BODY><P>403 Forbidden<P></BODY></HTML>");}
catch(Exception e2){outStr.println("<HTML><BODY><P>"+e2.getMessage()+"<P></BODY></HTML>");}
finally{
try{s.close();}catch(Exception e){}
}
}
private String interpretGET(String rqst) throws Exception{
StringTokenizer strT = new StringTokenizer(rqst);
String tmp="";
String fileN=iniContext;
tmp=strT.nextToken();
if(!tmp.equals("GET")) throw new Exception("Comanda GET invalida .");
tmp=strT.nextToken();
if((tmp.equals("/")) || (tmp.endsWith("/"))) {
fileN = fileN+tmp+"index.htm";
System.err.println("CERERE:"+fileN);
return fileN;
}
fileN = fileN+ tmp;
System.err.println("CERERE:"+fileN);
return fileN;
}
private byte[] readFile(String fileN) throws Exception{
fileN.replace('/','\\');
File f = new File(fileN);
if(!f.canRead()) throw new Exception("Fisierul "+fileN+" nu poate fi citit");
FileInputStream fstr = new FileInputStream(f);
byte[] data = new byte[fstr.available()];
fstr.read(data);
return data;
}
}
Pentru verificarea programului anterior se va modifica variabila iniContextm din cadrul clasei HTTPServer, astfel încât aceasta să indice calea corectă catre contextul iniţial (directorul unde se află toate resursele pe care clientul le poate accesa).
6. Trimiterea obiectelor prin socket-uri
Mecanismul de serializare pune la dispoziţia programatorului o metodă prin care un obiect poate fi salvat pe disc şi restaurat atunci cand este nevoie. Tot prin acelaşi mecanism un obiect poate fi transmis la distanta catre o altă maşină utilizând socketurile.
Pentru a putea serializa un obiect acesta va trebui să implementeze interfaţa Serializable.
Pentru scrierea şi citirea obiectelor serializate se utilizează fluxurile de intrare / ieşire : ObjectInputStream si ObjectOutputStream.
Listingul următor prezintă modul in care se poate serializa / deserializa un obiect.
import java.io.*;
import java.net.*;
public class SerialTest extends Thread{
public void run(){
try{
ServerSocket ss = new ServerSocket(1977);
Socket s = ss.accept();
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
Pers p = (Pers)ois.readObject();
System.out.println(p);
s.close();
ss.close();
}catch(Exception e){e.printStackTrace();}
}
public static void main(String[] args) throws Exception{
//trimite obiect prin socket
(new SerialTest()).start();
Socket s = new Socket(InetAddress.getByName("localhost"),1977);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
Pers p = new Pers("Alin",14);
oos.writeObject(p);
s.close();
}
}
class Pers implements Serializable{
String nume;
int varsta;
Pers(String n, int v){
nume = n; varsta = v;
}
public String toString(){
return "Persoana: "+nume+" vasrta: "+varsta;
}
}
Există situaţii în care în momentul in care se selveaza starea unui obiect prin serializare, nu se doreşte salvare tuturor starilor obiectului, respectiv nu se doreşte salvarea sau transmiterea anumitor parametri ai obiectului. Pentru a bloca serializarea unui atribut al unui obiect serializabil se utilizează cuvantul cheie transient.
7. Socket-uri neblocante
Versiunea Java 2 Standad Edition 1.4 introduce un nou mecanism de comunicare în reţea prin interemediul socket-urilor neblocante – acestea permit comunicarea între aplicaţii fără a bloca procesele în apeluri de metode destinate deschiderii unei conexiuni, citirii sau scrierii de date.
Soluţia clasică pentru a construi o aplicaţie server care deserveşte mai mulţi clienţi este de a utiliza tehnologia firelor de execuţie şi de a aloca câte un fir de execuţie pentru fiecare client deservit. Folosind tehnologia socket-urilor neblocante programatorul va putea implementa o aplicaţie server pentru deservirea clienţilor fără a fi nevoit să apeleze în mod explicit la fire de execuţie pentru tratarea cererilor clienţilor. În continuare v-or fi prezentate principiile de bază ale acestei tehnologii şi modul în care pot fi construite aplicaţii client şi aplicaţii server pe tehnologia socket-urilor neblocante.
Arhitectura unui sistem ce utilizează socketuri neblocante pentru comunicare este ilustraţă în figura următoare.
Arhitectură cu socketu-uri neblocante.
Principalele operaţii ce au loc în cadrul unei aplicaţii bazată pe această tehnologie sunt:
- Clientul: trimite cereri către server
- Serverul: recepţionează cereri
- SocketChannel: permite transmiterea de date între client şi server
- Selector: reprezintă un obiect de tip multiplexor şi este punctul central al acestei tehnologii. Acesta monitorizează socket-urile înregistrate şi serializează cererile sosite de la acestea, transmiţându-le către aplicaţia server.
- Cheile reprezintă obiectele ce încapsulează cererile sosite de la clienţi.
Un algoritm general pentru a construi un server neblocant arată astfel:
create SocketChannel;
create Selector
associate the SocketChannel to the Selector
for(;;) {
waiting events from the Selector;
event arrived; create keys;
for each key created by Selector {
check the type of request;
isAcceptable:
get the client SocketChannel;
associate that SocketChannel to the Selector;
record it for read/write operations
continue;
isReadable:
get the client SocketChannel;
read from the socket;
continue;
isWriteable:
get the client SocketChannel;
write on the socket;
continue;
}
}
Implementarea serverului constă într-o buclă infinită în cadrul căreia selectorul aşteaptă producerea de evenimente. În momentul în care un eveniment s-a produs si o cheie a fost generată se verifică tipul acestei chei. Tipurile de chei posibile sunt:
- Acceptable – este asociată evenimentului de cerere de conexiune de la un client
- Connectable – este asociată evenimentului de acceptare de conexiune de către client
- Readable – citire de date
- Writable – scriere de date
Clasa Selector este responsabilă pentru menţinerea unui set de chei care pot fi active în timpul rulării programului server. În momentul în care un evenimet este generat de către un client, o cheie este construită.
Selector selector = Selector.open();
Pentru a demultiplexa datele şi a avea acces la evenimente trebuie construit un canal de comunicaţie care va trebui înregistrat în cadrul obiectului de tip selector. Fiecare canal de comunicaţie înregistrat va trebui să specifice tipul de evenimente de care este inetersat.
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false);
InetAddress lh = InetAddress.getLocalHost();
InetSocketAddress isa = new InetSocketAddress(lh, port );
channel.socket().bind(isa);
SelectionKey acceptKey = channel.register( selector, SelectionKey.OP_ACCEPT );
Un canal care citeşte şi scrie date va fi înregistrat în felul următor:
SelectionKey readWriteKey = channel.register( selector,
SelectionKey. OP_READ| SelectionKey. OP_WRITE );
SelectionKey. OP_READ| SelectionKey. OP_WRITE );
Codul aplicaţiei server ce implementează comunicarea prin socket-uri nonblocante este listat mai jos:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.spi.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
public class NonBlockingServer2 {
public static void main(String[] args) throws Exception{
// Create the server socket channel
ServerSocketChannel server = ServerSocketChannel.open();
// nonblocking I/O
server.configureBlocking(false);
// host-port 8000
server.socket().bind(new java.net.InetSocketAddress("localhost",8000));
System.out.println("Server waiting on port 8000");
// Create the selector
Selector selector = Selector.open();
// Recording server to selector (type OP_ACCEPT)
server.register(selector,SelectionKey.OP_ACCEPT);
// Infinite server loop
for(;;) {
Thread.sleep(1000);
// Waiting for events
System.err.println("wait for event...");
selector.select();
// Get keys
Set keys = selector.selectedKeys();
Iterator i = keys.iterator();
System.err.println("keys size="+keys.size());
// For each keys...
while(i.hasNext()) {
// Obtain the interest of the key
SelectionKey key = (SelectionKey) i.next();
// Remove the current key
i.remove();
// if isAccetable = true
// then a client required a connection
if (key.isAcceptable()) {
System.err.println("Key is of type acceptable");
// get client socket channel
SocketChannel client = server.accept();
// Non Blocking I/O
client.configureBlocking(false);
// recording to the selector (reading)
client.register(selector, SelectionKey.OP_READ);
continue;
}
// if isReadable = true
// then the server is ready to read
if (key.isReadable()) {
System.err.println("Key is of type readable");
SocketChannel client = (SocketChannel) key.channel();
// Read byte coming from the client
int BUFFER_SIZE = 32;
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
try {
client.read(buffer);
}
catch (Exception e) {
// client is no longer active
client.close();
e.printStackTrace();
continue;
}
// Show bytes on the console
buffer.flip();
Charset charset=Charset.forName("ISO-8859-1");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode(buffer);
System.out.println(charBuffer.toString());
continue;
}
}
System.err.println("after while keys size="+keys.size());
}
}
}
Condul aplicaţiei client ce foloseşte socketu-uri neblocante pentru comunicarea cu un server este listat mai jos:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.spi.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
public class NonBlockingClient {
public static void main(String[] args) throws IOException {
// Create client SocketChannel
SocketChannel client = SocketChannel.open();
// nonblocking I/O
client.configureBlocking(false);
// Connection to host port 8000
client.connect(new java.net.InetSocketAddress("localhost",8000));
// Create selector
Selector selector = Selector.open();
// Record to selector (OP_CONNECT type)
SelectionKey clientKey = client.register(selector, SelectionKey.OP_CONNECT);
// Waiting for the connection
while (selector.select(500)> 0) {
System.err.println("Start communication...");
// Get keys
Set keys = selector.selectedKeys();
Iterator i = keys.iterator();
// For each key...
while (i.hasNext()) {
SelectionKey key = (SelectionKey)i.next();
// Remove the current key
i.remove();
// Get the socket channel held by the key
SocketChannel channel = (SocketChannel)key.channel();
// Attempt a connection
if (key.isConnectable()) {
// Connection OK
System.out.println("Server Found");
// Close pendent connections
if (channel.isConnectionPending())
channel.finishConnect();
// Write continuously on the buffer
ByteBuffer buffer = null;
int x=0;
for (;x<7;) {
x++;
buffer =
ByteBuffer.wrap(
new String(" Client " + x + " "+x).getBytes());
channel.write(buffer);
buffer.clear();
try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}
}
channel.finishConnect();
client.close();
}
}
}
System.err.println("Client terminated.");
}
}
Exerciţii
Importaţi în mediul Eclipse proiectul ce exemplifică noţiunile prezentate în acest laborator (link proiect).
Pachetul lab.scd.socket exemplifica construirea unor aplicatii server si client ce comunica utilizand protocolul TCP.
1)
- Lansati in executie aplicatia ServerSimplu
- Fara a opri aplicatia ServerSimplu lansati in executie aplicatia ClientSimplu
2)
- Opriti aplicatiile ServerSimplu si ClientSimplu
- Lansati in executie aplicatia ServerMultifir
- Fara a opri aplicatia ServerMultifir lansati doua sau mai multe instante ale aplicatiei ClientSimplu
Pachetul lab.scd.datagrame exemplifica constuirea unor aplicatii server si client ce comunica utilizand protocolul UDP.
- Lansati in executie aplicatia MulticastServer
- Fara a opri aplicatia MulticastServer lansati in executie aplicatia MulticastClient
Pachetul lab.scd.broadcast exemplifica constuirea unor aplicatii server si client ce comunica utilizand protocolul UDP.
- Lansati in executie aplicatia QuoteServerThread
- Fara a opri aplicatia QuoteServerThread lansati in executie aplicatia QuoteClient
Pachetul lab.scd.serializare exemplifică mecanismul de serializare şi deserializare a obiectelor.
Pachetul lab.scd.net.httpexample prezinta modul in care poate fi construit in java un server HTTP. Aplicatia exemplu implementeaza partial protocolul HTTP, raspunzand la cereri de tip GET. Analizati modul de construire al aplicatiei, executati si testati aplicatia.
Pachetul lab.scd.net.url_http pezinta clasele din java.net ce pot fi utilizate pentru lucrul cu URL-uri.
Pachetul lab.scd.net.browser prezintă modul în care poate fi construit în java un browser de pagini html.
Programarea în reţea – Datagrame şi Url-uri
1. Scopul lucrării
Scopul acestei lucrări este de insuşire a următoarelor mecanisme;
- comunicarea prin UDP
- lucrul cu URL-uri
- comunicarea cu un CGI
2. Noţiuni preliminiare
Clienţii care comunică prin intermediul TCP, utilizând socket-uri, au un canal dedicat, iar transmisia datelor este sigură. Datele sunt recepţionate în ordinea în care acestea au fost trimise.
In contrast, când datele sunt transmise prin UDP, ajungerea acestora la destinaţie nu este garantată, de asemenea, ordinea de sosire la destinaţie a datagramelor poate să difere de ordinea în care acestea au fost transmise.
Avantajul lucrului cu datagrame este creşterea vitezei cu care pachetele (datagramele) ajung la destinaţie. Există cazuri în care viteza de transmisie a datelor este mai importantă decât garantarea 100% a ajungerii acestora la destinaţie. De exemplu în cazul transmiterii unui semnal audio în timp real, viteza de transmitere a acestuia este mai importantă decât garantarea ajungerii la destinaţie.
In java pentru implementarea protocolului UDP sunt utilizate clasele: DatagramPacket şi DatagramSocket. Spre deosebire de programarea TCP, în cazul UDP nu există conceptul de ServerSocket. Atât serverul cât şi clientul utilizeazăDatagramSocket pentru realizarea conexiunii. Pentru transmiterea şi recepţionarea datelor se utilizează clasa DatagramPacket.
Programarea prin url-uri se desfăşoară la un nivel mai înalt decât programarea prin socketuri. Utilizând clasa URL, se va putea deschide o conexiune către o resursa aflată pe web. O data deschisă conexiunea, către resursă, clientul va putea citi date sau trimite date către respectiva resursă.
URL este acronimul de la Uniform Resource Locator (de asemenea este şi numele unei clase java). Un URL este un pointer către o resursă din Internet.
Sintaxa generală a unui URL este:
protocol://hostname[:port]/path/filename#ref
3. Server de timp (UDP)
In cadrul acestei secţiuni este construit un server de timp care va trimite la cerere data curentă către clienţii care solicită acest lucru. De asemenea este construit şi clientul care accesează serviciile serverului de timp.
La nivelul serverului se creează un obiect DatagramSocket, care va primi ca parametru portul pe care serverul va începe ascultarea.
DatagramSocket socket = new DatagramSocket(1977);
In continuare se construieşte un obiect DatagramPacket, care va fi utilizat de către server pentru a recepţiona cererea de la client. O dată construit obiectul DatagramPacket, serverul va începe ascultarea portului 1977, prin invocarea metodei receive().
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf,buf.length);
socket.receive(packet);
In momentul în care un client doreşte să apeleze la serviciile serverului, acesta va trimite un pachet către server. Serverul citeşte din cadrul pachetului portul şi adresa clientului, şi îi va trimite acestuia un pachet ce conţine data curentă.
InetAddress address = packet.getAddress();
int port = packet.getPort();
buf = ((new Date()).toString()).getBytes();
packet = new DatagramPacket(buf,buf.length,address,port);
socket.send(packet);
Un client, pentru a se conecta la server, trebuie să creeze un obiect DatagramSocket, şi să trimită un pachet către server. Spre deosebire de server, clientul nu este obligat să specifice nici un port în momentul creierii obiectuluiDatagramSocket, întrucât se atribuie automat un port liber respectivului obiect.
import java.io.*;
import java.net.*;
import java.util.*;
public class TimeServer extends Thread{
boolean running=true;
public TimeServer() {start();}
public void run(){
try{
DatagramSocket socket = new DatagramSocket(1977);
while(running){
//asteapta client
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf,buf.length);
socket.receive(packet);
//citeste adresa si portul clientului
InetAddress address = packet.getAddress();
int port = packet.getPort();
//trimite un reply catre client
buf = ((new Date()).toString()).getBytes();
packet = new DatagramPacket(buf,buf.length,address,port);
socket.send(packet);
}
}catch(Exception ex){ex.printStackTrace();}
}
public static void main(String[] args) {
TimeServer timeServer1 = new TimeServer();
}
}
import java.io.*;
import java.net.*;
import java.util.*;
public class Client {
public static void main(String[] args) {
try{
DatagramSocket socket = new DatagramSocket();
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf,buf.length,
InetAddress.getByName("localhost"),1977);
socket.send(packet);
packet = new DatagramPacket(buf,buf.length);
socket.receive(packet);
System.out.println(new String(packet.getData()));
}catch(Exception ex){ex.printStackTrace();}
}
}
4. Lucrul cu URL-uri
In această secţiune este prezentat modul de lucru în java cu URL-uri.
Pentru a accesa o resursă din Internet identificată printr-un URL, în java, primul pas este de a crea un obiect URL.
URL utcn = new URL(“www.utcluj.ro”);
Clasa URL conţine metode prin intermediul cărora se pot afla toate componentele unui URL: getProtocol(), getPort(), getHost(), getFile(), getRef().
Odată creat obiectul URL, se utilizează metoda openStream() pentru a deschide un flux de intrare, prin intermediul căruia se citeşte conţinutul respectivului URL.
BufferedReader in = new BufferedReader(
new InputStreamReader(
utcn.openStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
5. Citirea şi scrierea către un URL
Daca se doreşte să se realizeze mai mult decât citirea conţinutului unui URL, atunci din cadrul clasei URL se poate apela metoda openConnection(). Această metodă va returna un obiect URLConnection. Acest obiect va putea fi utilizat pentru operaţii de scriere, citire precum şi interogări către un URL.
URLConnection connection = utcn.openConnection();
In continuare este prezentat un scurt program care citeşte conţinutul unui URL utilizând clasa URLConnection.
import java.net.*;
import java.io.*;
public class URLConnectionReader {
public static void main(String[] args) throws Exception {
URL utcluj = new URL(http://www.utcluj.ro);
URLConnection con = yahoo.openConnection();
BufferedReader in = new BufferedReader(
new InputStreamReader(
con.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
}
Multe pagini HTML conţin form-uri (zone de text, butoane etc.) care permit introducerea de date şi transmiterea acestora către server. După completarea câmpurilor se apasă un buton iar browserul web va transmite datele către URL-ulcorespunzător. URL-ul care recepţionează datele este un script cgi-bin. Acesta procesează datele şi transmite către client un răspuns, care de obicei este o altă pagină HTML.
Programele java pot interacţiona cu script-urile cgi-bin. Ele trebuie să fie capabile să scrie date către un URL. Acest lucru se realizează utilizând clasa URLConnection.
In listingul următor este exemplificat modul în care un program java poate interacţiona cu un script cgi-bin.
try{
String data=””;
data += URLEncoder.encode("nume") + "=" + URLEncoder.encode("Adi”);
data += "&" + URLEncoder.encode("nota”) + "=" + URLEncoder.encode("8.50”);
URL url = new URL(“http://193.226.6.117:80/test/test.php");
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
// Get the response
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
System.out.println(line);
}
rd.close();
wr.close();
}catch(Exception ex){ex.printStackTrace();}
In listingul anterior se presupune că există un script cgi-bin test.php, către care programul java trimite două variabile : nume=adi şi nota=8.50 . Scriptul cgi va citi respectivele variabile şi va transmite către programul java un răspuns. Pentru testarea listingului anterior se va reveni în cadrul laboratorului care prezintă servleturile.
In listingul următor este prezentat un exemplu complet al unui applet care citeşte conţinutul unui fişier identificat printr-un URL. In cazul de faţă appletul citeşte chiar fişierul .class propriu.
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
public class FileURL extends Applet
{
byte[] appletCode; //stocare fisier citit
public void init()
{
try {
// Ceraza obiect URL
URL url = new URL(getCodeBase(),
getClass().getName()+".class");
// Deschide conexiunea catre URL
URLConnection urlConn = url.openConnection();
// Utilizeaza ByteArrayOutputStream ca un container temporar.
// Dupa terminare citire va fi convertit catre un array
ByteArrayOutputStream tempBuffer;
tempBuffer = new ByteArrayOutputStream();
// Creaza fluxul de citire catre URL
InputStream instream = urlConn.getInputStream();
// Citeste continutul URL-ului in bufferul temporar
int ch;
while ((ch = instream.read()) >= 0) {
tempBuffer.write(ch);
}
// Converteste bufferul temporar intr-un array
appletCode = tempBuffer.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
}
public void paint(Graphics g)
{
g.setColor(Color.black);
if (appletCode == null) {
g.drawString("Nu s-a citit fisierul .class",
10, 30);
} else {
g.drawString("Lungimea fisierului class. "+
appletCode.length+" bytes .", 10, 30);
}
}
}
6. Browser java
In această secţiune este prezentat modul în care se poate construi în java un browser de internet.
import javax.swing.*;
import java.awt.*;
import javax.accessibility.*;
import javax.swing.event.*;
import javax.swing.text.*;
import java.net.*;
import java.io.*;
import java.awt.event.*;
public class Browser extends JPanel {
Browser() {
setLayout (new BorderLayout (5, 5));
final JEditorPane jt = new JEditorPane();
final JTextField input =
new JTextField("http://127.0.0.1:8080");
// read-only
jt.setEditable(false);
// follow links
jt.addHyperlinkListener(new HyperlinkListener () {
public void hyperlinkUpdate(
final HyperlinkEvent e) {
if (e.getEventType() ==
HyperlinkEvent.EventType.ACTIVATED) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Save original
Document doc = jt.getDocument();
try {
URL url = e.getURL();
jt.setPage(url);
input.setText (url.toString());
} catch (IOException io) {
JOptionPane.showMessageDialog (
Browser.this, "Can't follow link",
"Invalid Input",
JOptionPane.ERROR_MESSAGE);
jt.setDocument (doc);
}
}
});
}
}
});
JScrollPane pane = new JScrollPane();
pane.setBorder (
BorderFactory.createLoweredBevelBorder());
pane.getViewport().add(jt);
add(pane, BorderLayout.CENTER);
input.addActionListener (new ActionListener() {
public void actionPerformed (ActionEvent e) {
try {
jt.setPage (input.getText());
} catch (IOException ex) {
JOptionPane.showMessageDialog (
Browser.this, "Invalid URL",
"Invalid Input",
JOptionPane.ERROR_MESSAGE);
}
}
});
add (input, BorderLayout.SOUTH);
}
}
import javax.swing.*;
public class Start extends JFrame{
public Start(){
Browser b = new Browser();
getContentPane().add(b);
pack();
setVisible(true);
}
public static void main(String args[])
{
Start s = new Start();
}
}
Niciun comentariu:
Trimiteți un comentariu