Un protocol de rețea este,
într-o rețea de telecomunicații, o descriere formală a regulilor și convențiilor care stau la baza
comunicării între dispozitivele atașate la rețea. Protocolul determină formatul
sau structura mesajului, metodele prin care dispozitivele din rețea schimbă
informații privitoare la căile către alte rețele, temporizarea, ordinea și
controlul erorilor în comunicațiile de date, inițierea și finalizarea sesiunii pentru transferul de date și altele.
Fără protocoale calculatoarele nu ar putea construi sau
reconstrui în formatul original șirul de biți (mesajul) transmis de la un
alt calculator.
Protocoalele controlează toate aspectele
comunicațiilor de date, incluzând:
·
Cum e construită fizic rețeaua?
·
Cum sunt conectate între ele calculatoarele din rețea?
·
Cum sunt formatate datele pentru transmitere?
·
Cum sunt trimise datele?
·
Ce se întâmplă când apar erori și cum se pot corecta erorile?
Diferenţe între UDP şi TCP
UDP reprezintă un protocol mai relaxat decât
TCP iar o comparaţie între caracteristicile de bază ale celor două protocoale
va scoate în evidenţă acest aspect:
TCP
|
UDP
|
Fiabil: TCP gestionează confirmări de mesaje, retransmisii
şi timeout-uri. În cazul în care nu mai este posibilă livrarea unui mesaj va
fi resetată întreaga conexiune.
|
Nefiabil: la trimiterea unui mesaj nu se oferă nici o
garanţie în ceea ce priveşte livrarea acestuia cu succes. Nu există
conceptele de confirmări de mesaje, retransmisii şi timeout-uri.
|
Ordonat: dacă două mesaje sunt trimise pe o conexiune
într-o anumită ordine atunci aplicaţie de la celălalt capăt le va primi în
aceiaşi ordine. În cazul în care mesajele ajung în altă ordine, TCP aplică
mesanisme de resecvenţiere.
|
Neordonat: dacă două mesaje sunt trimise către acelaşi
destinatar, nu există nici o garanţie privind ordinea în care sunt recepţionate
şi nici mecanisme de resecvenţiere în cazul în care sosesc în altă ordine.
|
Încărcare mare: există o încărcare mare cu informaţii de control –
sunt necesare trei mesaje iniţiale numai pentru stabilirea conexiunii, sunt
de asemenea implementaţi algoritmi de control al fluxului şi al congestiei.
|
Încărcare mică: nu există nici o încărcare cu informaţii de
control – nici un fel de gestiune a secvenţierii, nici un fel de infprmaţie
de control al conexiunii.
|
Flux de date: datele sunt văzute ca un flux continuu, fără a
exista informaţii implicite cu privire la locul unde începe sau se sfârşeşte
un pachet. Pachetele pot fi fragmentate în bucăţi mai mari sau mai mici în
mod arbitrar.
|
Datagrame: pachetele sunt trimise ca entităţi individuale şi
se garantează că vor fi livrate nefragmentat. Pachetele au graniţe bine
determinate şi nu există posibilitatea de a fi fragmentate şi recombinate în
fluxuri de date.
|
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ă
clasaDatagramPacket.
URL este acronimul de la Uniform Resource Locator (de asemenea este şi numele
unei clase java). Un URL este unpointer către o resursă din Internet.
O bază de date reprezintă o modalitate de stocare
persistentă a informaţiilor pe un suport fizic cu posibilitate de regăsire a
acestora ulterior. Cel mai cunoscut model de baze de date este cel relaţional
în care datele sunt memorate sub formă de tabele. Bazele de date relaţionale
mai conţin pe lângă tabele funţii şi proceduri, mecanisme de gestionare a
utilizatorilor, tipuri de date, etc.
Printre cei mai cunoscuţi producători de baze de date se numără: Oracle,
Microsoft, Sybase, IBM.
Accesarea unei baze de date folosind JDBC este simplă şi implică următorii
paşi:
- Obţinerea unui obiect
de tip Connection ce încapsulează conexiunea la baza de date (în acest pas
se realizează deci conexiunea la baza de date).
- Obţinerea unui obiect
Statement dintr-un obiect de tip Connection. Acest obiect este folosit
pentru a transmite spre execuţie comenzi SQL către baza de date. Prin
intermediul acestui obiect sunt efectuate operaţii de interogare şi
modificare a bazei de date.
- Obţinerea unui obiect
ResultSet dintr-un obiect Statement. Obiectul ResultSet încapsuleaz
rezultatele operaţiilor de interogare.
- Procesarea
rezultatelor încapsulate în obiectul ResultSet.
2. Noţiuni preliminare(Socket TCP)
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.
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;
}
}
Programarea în reţea – Datagrame şi Url-uri
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.
URL este
acronimul pentru Uniform Resource Locator si reprezinta o referinta (adresa) la o resursa
aflata pe Internet. Aceasta este în general un fisier reprezentând o pagina Web
sau o imagine, însa un URL poat referi si interogari la baze de date, rezultate
ale unor comenzi (programe), etc.
Pentru a accesa o
resursă din Internet identificată printr-un URL, în java,
primul pas este de a crea un obiect URL.
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();
Baze
de date
Realizarea conexiunii
Pentru realizarea
conexiunii cel mai important parametru este URL-ul bazei de date. URL-ul identifică baza de date
la care urmează să se realizeze conexiunea.
Exemplu:
jdbc:mysql://localhost:3306/contacts/
jdbc:odbc:people
URL-ul conţine următoarele componente:
- Componenta
care specifică utilizarea driverului jdbc.
- Componenta
care specifică mecanismul de conectare la baza de date.
- Identificatorul
bazei de date. Acesta reprezintă un identificator – un nume logic – care
este mapat de către softul de administrare al bazei de date la baza de
date fizică.
Următorul cod realizează
conexiunea cu baza de date:
Connection con = DriverManager.getConnection(url,"login", "password");
public class TestConectare {
public
static void main(String[] args) {
try {
//incarcare driver petru baza de date
Class.forName("com.mysql.jdbc.Driver");
//conectare la baza de date
Connection conn =
DriverManager.getConnection("jdbc:mysql://10.3.4.1/persoane?user=student&password=pas123”);
System.out.println("Conexiune la
baza de date realizata.");
//inchide cnexiune la baza de date
conn.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
4. Operatii asupra unei
baze de date
Pentru a putea efectua
operaţii asupra unei baze de date la care s-a realizat conexiunea se lucrează
cu un obiect Statement. Acest obiect se obţine din cadrul obiectului Connection
astfel:
Statement stat =
con.createStatement();
Din acest moment
obiectul ‘stat’ va fi utilizat pentru a efectua operaţii asupra bazei de date.
Creare tabel
Se utilizează metoda
executeUpade() care va primi ca parametru un String care reprezintă o comandă SQL
validă pentru crearea unui tabel.
stat.executeUpdate(“CREATE
TABLE PERS (NUME VARCHAR(32), VARSTA INTEGER)”);
Introducere date în
tabel
Pentru adăugarea de
înregistrări în cadrul unui tabel se utilizează aceiaşi metodă executeUpdate().
stat.executeUpdate(“INSERT
INTO PERS VALUES(‘ADI’, 15)”);
Citirea conţinutului
unui tabel
Pentru citirea
conţinutului unui tabel se utilizeză metoda executeQuery() a clasei Statement.
ResultSet result =
stat.executeQuery("SELECT PROD,PRET FROM STOC");
Rezultatul interogării
bazei de date va fi returnat într-un obiec de tip ResultSet. Pentru a parcurge
, linie cu linie, a rezultatelor interogării se realizează utilizând metoda
next() din cadrul obiectului ResultSet.
In cadrul programului
următor este prezentat exemplu care realizează operaţiile de creare de tabel,
inserare de înregistrări şi interogare a unui tabel.
import java.sql.*;
public class JdbcTest
{
public static void main(String[] args)
{
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:db1");
Statement stat =
con.createStatement();
stat.executeUpdate("CREATE TABLE
STOC (PROD VARCHAR(32), PRET INTEGER)");
stat.executeUpdate("INSERT INTO
STOC VALUES ('PROD1' , 2500)");
stat.executeUpdate("INSERT INTO
STOC VALUES ('PROD2' , 7900)");
ResultSet result =
stat.executeQuery("SELECT PROD,PRET FROM STOC");
while(result.next()){
String prod = result.getString(1);
int pret = result.getInt(2);
System.out.println("Produs
= "+prod+" "+"Pret
= "+pret);
}
}
catch(Exception
e){e.printStackTrace();}
}
}
5. Alte operaţii cu baze de
date
Utilizarea clasei
PreparedStatement
Clasa
PreparedStatement extinde clasa Statement. Spre deosebire de Statement, un
obiect PreparedStatemtn primeşte o comandă SQL în momentul în care este creat.
Un obiect PreparedStatement reprezintă o comandă SQL precompilată. Această
clasă se utilizează in momentul în care se doreşte executarea unei comenzi de
mai multe ori, intrucât cresc performanţele (viteza de execuţie) , deoarece
comanda SQL este precompilată în momentul creerii obiectului PreparedStatement.
PreparedStatement ps =
con.prepareStatement("INSERT INTO STOC VALUES(?,?)");
ps.setString(1,"PROD
3");ps.setInt(2,6000);
ps.executeUpdate();
ps.setString(1,"PROD
4");ps.setInt(2,9000);
ps.executeUpdate();
Utilizarea procedurilor stocate
Procedurile stocate
reprezintă un set de comenzi SQL (comenzi de updatare şi / sau interogare) care
se vor executa împreună. Procedurile stocate sunt suportate de majoritatea
DBMS-urilor dar pot apărea variaţii în ceea ce priveşte sintaxa.
In continuare este prezentat modul în care se creeză o
procedură stocată utlizând JDBC.
String
pstoc="CREATE PROCEDURE DISP_TABLE"+
"AS "+
"SELECT PROD, PRET FROM
STOC";
Statement st = con.createStatement();
st.executeUpdate(pstoc);
In acest moment procedura DISP_TABLE va fi compilată şi
stocată în cadrul bazei de date, ea putând fi apelată ori de câte ori este
nevoie.
Executarea unei proceduri stocate se realizează astfel:
CallableStatement cs =
con.prepareCall("{call DISP_TABLE}");
ResultSet result = cs.executeQuery();