Documente Academic
Documente Profesional
Documente Cultură
Pachetul java.util
5.1. Dictionary
5.2. Hashtable
5.3. Properties
Clasa Properties este subclasă din Hashtable, furnizând mapare eficientă a cheii String către
valoarea String. Pentru a adăuga perechi cheie/valoare a unor proprietăţi obiectului myProp
se apelează metoda myProp.put(key,value) care este moştenită din java.util.Hashtable. Se
poate utiliza această clasă a obiectului pentru cheie şi valoare, deşi când se doreşte execuţia
metodei de regăsire getProperty(key) se întoarce un şir ca reprezentare a valorii. Obiectele
properties sunt utile când se doreşte tipărirea reprezentării perechilor de cheie/valoare.
Exemplu:
import java.util.Properties;
class PropDemo {
97
static Properties prop=new Properties();
public static void main (String args [ ] ) {
prop.put("nume","introduceti numele");
prop.put("specializare"," specializare ");
prop.put("vechime","neprecizata");
Properties loc=new Properties(prop);
loc.put("nume","Popescu Ion");
loc.put("specializare","economist");
loc.put("vechime","24");
System.out.println("numele"+loc.getProperty("nume"));
System.out.println("Specialist in "+ loc.getProperty ("specializare"));
System.out.println("vechime"+ loc.getProperty ("vechime"),"???");
}
}
5.4. Enumeration
98
5.5. StringTokenitzer
Exemplu:
import java.util. StringTokenitzer;
public class PrintProp () extends java.applet.Applet {
String propString;
public PrintProp (){
java.util.Properties p = System.getProperties();
propString=p.toString();
}
99
Deoarece StringTokenitzer este de fapt o Enumeration, putem doar citi valorile în st o
singură dată. Acesta este motivul pentru care de fiecare dată appletul este redesenat prin
paint(), creăm apoi un nou StringTokenitzer şi extragem valorile dorite pentru a putea desena
pe ecran.
5.6. Vectorul
Un Vector este tabloul de dimensiune ajustabilă care poate ţine obiecte (nu date) de tip
primitiv. Totuşi se pot acoperi tipuri de date primitive în clase furnizate de java.lang. Pentru
optimizarea managementului memoriei se poate uttiliza Vector adăugând şi ştergând dinamic
obiecte dintr-un vector. Un exemplu de utilizare este java.util.Stack (stiva ) iar un al exemplu
este dat de GUI.
5.7. Stiva
Stiva (Stack) este o listă de obiecte cu operaţii clare, bine definite. Se poate "împinge" un
obiect în vârful stivei, sau s epoate trage obiectul din listă ştergâdu-l dar întorcând o referinţă
către el, pentru a o putea folosi. Adăugarea/ştergerea are loc din vârful listei (metoda FIFO-
First In First Out). Stiva este utilă pentru implementarea controlului incuibat sau recursiv şi a
astructurilor de date. Fiecare sistem run-time modern foloseşte stiva pentru a ţine urma
variabilelor locale.
Stack este subclasă a lui Vector, furnizând metodele:
push() adaugă element în stivă;
pop() şterge element în stivă;
empty() indică prin valoare booleană stiva plină (false) sau nu (true);
peek() se întoarce a vârful elementelor din stivă fără a le şterge
search() verifică stiva pentru a afla dacă există un obiect şi întoarce numărul de pop
solicitaţi pentru a ajunge în vârful stivei sau -1 la stivă goală.
Exemplu;
import java.util.Stack;
import java.util.EmptyStackException;
class StackDemo {
static void shopush(Stack St, int a ) {
St.push(new Integer (a));
System.out.println("push ("+a+");
System.out.println("stiva"+St);
100
System.out.println("stiva"+St);
showpush(St,42);
showpush(St,11);
showpush(St,55);
showpush(St,5);
showpop(St);
showpop(St);
showpop(St);
showpop(St);
try{
showpop(St);
}catch (EmptyStackexception e){
System.out.println("stiva goala");
}
}
}
Capitolul VI
Pachetul java.io
Pachetul java.io conţine clasele şi interfeţele necesare accesului secvenţial şi direct la fişierele
de intrare-ieşire (I/O), cuprinzând o varietate de tipuri de clase ale fluxului intrărilor şi-
ieşirilor care facilitează toate modurile de I/O specifice aplicaţiei inclusiv cele de intrare-ieşire
ale consolei şi firele de comunicaţie via "pipes".Din motive de securitate appleturile nu au
permisiunea citirii, scrierii, creării sau verificării accesibilităţii sau existenţei fişierelor sau
directoarelor. Acest tip de funcţionalitate este destinat aplicaţiilor stand-alone.
Socket este o abstractizare utilă pentru manipularea reţea a intrărilor şi ieşirilor. I/O ale reţelei
sunt similare cu fişierele I/O, fiind tratate ca fişiere la care atât clientul cât serverul au acces.
[Dada_99], [Rons_00]
Client-server este arhitectura de comunicaţii unde o entitate (clientul) solicită servicii
(întoarce nişte informaţii, execută un program, etc) din altă entitate (serverul) care prelucrează
serviciile solicitate. In programarea client-server, clientul şi serverul sunt 2 aplicaţii separate,
comunicând cu altele prin reţea.
Programul server stă în inactivitate, rulând într-o buclă în care "ascultă" portul, aşteptând să
primească o cerere pentru servicii. Serverul nu iniţiază comunicaţia ci aşteaptă clientul pentru
a-I cere un serviciu şi la acel punct prelucrează serviciile cerute, întorcând informaţiile către
client. Programul client pe de altă parte, iniţiază comunicaţia, trimiţând cereri către server.
Clientul trebuie să cunoscă numărul portului şi adresa de reţea a serverului pentru a putea
trimite cererea.
Portul este un număr ce permite distincţia între mesajele trimise de un program faţă de cele
ale altui program, dat fiind faptul că pe o maşină rulează mai multe tipuri de client şi server.
Programul server primeşte mesaje succesive la care sunt ataşate numărul de port cu care
programul client este legat. Un program poate utiliza un număr de port.
In stiva de protocul TCP/IP este fixat numărul de porturi ce pot fi utilizate pentru
comunicaţie. Numerele de port 0-1024 sunt rezervate pentru servicii binecunoscute
(telnet=23, HTTP=80,etc). Numerele de port între 1024 şi 65535 sunt disponibile
programatorului, dacă nu sunt utilizate de alte programe.[Muhr_98]
101
Protocolul este un set specific de reguli de comunicaţie. Portul, sockets şi modelul client-
server oferă fundaţia cu care poate fi construită comunicaţia între programe. Protocolul
stabileşte cine poate iniţia comunicaţia, ce tipuri de mesaje pot fi trimise, ce conţinut pot avea
şi care parte răspunde de acest conţinut. Dacă datele trimise violează protocolul, oprirea este
semnificativă, deoarece nu poate fi interpretată mai departe. Protocolul poate specifica ce
parte violează protocolul dacă primeşte informaţiile necesare. Crearea aplicaţiilor client-
server implică mai degrabă urmarea unor protocoale binecunoscute (HTTP,FTP, telnet) sau
crearea unui protocol cu care atât clientul cât şi serverul sunt familiarizaţi.[Rado_96]
HTTP formează bazele WWW şi este un exemplu preferat pentru aplicaţiile client-server. Un
program Web server este el însuşi disponibil pe host, la portul 80 şi rulează ca daemon
(program background) ascultând cererile făcute de client. Web browserul este program client.
Pentru a regăsi o pagină pe host, se transmite browsaerului (via URL) adesa network şi
calculatorul unde serverul rulează (implicit portul 80). URL conţine informaţii adiţionale (de
obicei calea relativă) utilizată pentru a forma cererea specificată. Web browserul trimite
cererea serverului la adresa reţea şi al portul lui.[Raco_00]. Cererea se regăseşte pe maşina
host sub forma unui mesaj ataşat la portul serverului. Mesajul conţine adresa reţea şi numărul
de port al programului client astfel încât serverul să poată replica. Serverul, văzând cererea
venită pe port, procesează şi apoi trimite răspuns în pagina Web a programului client, la
adresa reţea a browserului şi la portul lui.Chiar dacă se utilizează un număr de port >1024
trebuie să se asigure că alt program nu a ales deja acest port.pentru un socket se trimite email
către everyone, ce adresează serverul Web, sau se utilizează fără precauţii,d epinstând dacă
apar erori.
Stream (flux) este o cale imaginară ce transportă date între două puncte: sursa şi destinaţia
-cele două puncte finale ale comunicaţiei. Acestea pot fi entităţi ce dau programului intrări
sau primesc ieşiri (fişier, consolă, tastatură, pachet de reţea, etc.). La cel mai înalt nivel sunt
două tipuri de flux: de intrare, prin care programul poate citi date şi de ieşire, prin care
programul poate scrie date şi sunt reprezentate de clasele abstracte java.io.InputStream
respectiv java.io.OutputStream.
try {
//... cod ce proiecteaza exception
}
catch (Exception ex) {
System.err.println(ex);
}
System.out System.err sunt print streams adicǎ instanţe de java.io.PrintStream1. Cea mai
mare parte a pachetului java.io constă în 10 clase definite ca subclase ale java.io.InputStream
respectiv 7 clase definite ca subclase ale java.io.OutputStream. Comportamentul particular al
claselor Stream permite conectarea acestora, ceea ce dă funcţionalitate combinată, furnizată
de câteva clase diferite de flux, ce procesează acelaşi flux de date.
Aproape orice operaţie care implică intrări sau ieşiri are o clasă de nesiguranţă, pentru că
depinde de hardware şi de condiţiile sistemului, ambele departe de controlul programatorului.
1
Elliotte Rusty Harold Java™ I/O, 2nd Edition, O'Reilly&Soons, 2006
102
Ca rezultat, metodele virtuale definite în orice clasă din java.io sunt declarate cu clauze throw
IOException. [Chan_95]. IOException nu este o subclasă a lui RuntimeException.La apelul
unei metode ce poate proiecta o excepţie (alta decât RuntimeException) se poate prinde sau
ignora această excepţie.
java.io.InputStream şi java.io.OutputStream sunt clase de bazǎ, abstracte pentru diferite
subclase care posedǎ abilitǎţi specializate. Aceste subclases includ:
BufferedInputStream
BufferedOutputStream
ByteArrayInputStream
ByteArrayOutputStream
DataInputStream
DataOutputStream
FileInputStream
FileOutputStream
FilterInputStream
FilterOutputStream
ObjectInputStream
ObjectOutputStream
PipedInputStream
PipedOutputStream
PrintStream
PushbackInputStream
SequenceInputStream
Pachetul java.util.zip conţine clase ce citesc în input stream data în format comprimat şi le
returneazǎ în format decomprimat iar pentru clase output stream citesc data în format
decomprimat şi scriu în format comprimat.
CheckedInputStream
CheckedOutputStream
DeflaterOutputStream
GZIPInputStream
GZIPOutputStream
InflaterInputStream
ZipInputStream
ZipOutputStream
Pachetul java.util.jar include douǎ clase stream pentru citirea fişierelor din arhive JAR:
JarInputStream şi JarOutputStream
Pachetul java.security conţine cupluri de clase stream utilizate la calcularea mesajelor
digest: DigestInputStream şi DigestOutputStream.
Java Cryptography Extension (JCE) adaugǎ douǎ clase pentru încriptare şi decriptare:
CipherInputStream şi CipherOutputStream
Câteva clase stream random sunt ascunse (hiding) în interiorul pachetului,
sun.net.TelnetInputStream şi sun.net.TelnetOutputStream, în mod deliberat deoarece sunt
prezente ca instanţe de java.io.InputStream sau java.io.OutputStream.
Clasele java.io.Reader şi java.io.Writer sunt superclase abstracte pentru clasele ce citesc şi
scriu date character-based, cu rol data notabil de manipulare a conversiei între diferite seturi
de caractere. Nucleul Java API include 9 clase de citire si 8 se scriere, în pachetul java.io:
BufferedReader
BufferedWriter
CharArrayReader
CharArrayWriter
103
FileReader
FileWriter
FilterReader
FilterWriter
InputStreamReader
LineNumberReader
OutputStreamWriter
PipedReader
PipedWriter
PrintWriter
PushbackReader
StringReader
StringWriter
Semnǎtura le deosebeşte, în timp ce int trimis de metoda write( ) OutputStream este redusǎ la
modulo 256 înainte de ieşire, int trimis de write( ) din Writer este redusǎ modulo 65,536.
java.io.Writer are în plus 2 metode write( ) ce iau date din şir:
public void write(String s) throws IOException
public void write(String s, int offset, int length) throws IOException
IOExceptions
Cum operaţiile de intrare ieşire proiecteazǎ erori, ele sunt manipulate de IOException.
IOException gestioneazǎ excepţiile în blocuri try/catch block, cu excepţia claselor
PrintStream şi PrintWriter. Inconvenientul de a avea bloc try/catch la apel System.out.println(
), a determinat Sun sǎ construiascǎ PrintStream (apoi PrintWriter) pentru a prinde excepţii în
metode print( ) sau println( ). Verificarea excepţiilor în metode print( ) sau println( ) se poate
face cu apel la metoda checkError( ): public boolean checkError( )
IOException are multe subclasses (15 în java.io înafara metodelor ce dau excepţii specifice
ale subclaselor IOException (EOFException sau UnsupportedEncodingException când se
citeşte un text cu caracter necunoscut). Constructorii:
public IOException( )
public IOException(String message)
Primul creazǎ IOException cu mesaj vid al doilea dǎ detailii despree eroare, IOException
având metode uzuale moştenite de la clasele exception ca: toString( ) şi printStackTrace( ).
104
System.out este prima instanţǎ de clasǎ OutputStream şi dǎ un câmp static din clasa
java.lang.System class. Este instanţǎ de java.io.PrintStream, subclasǎ a java.io.OutputStream.
"Hello World!" devine:
byte[] hello = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10,
13};
System.out.write(hello);
Clasa Console
Java 6 adaugǎ java.lang.Console2 un singleton ce are o instanţǎ doar şi întotdeauna aplicǎ
acelaşi shell la System.in, System.out, şi System.err point to. Regǎsirea singurei instanţe a
clasei se face cu metoda staticǎ System.console( ):
Console theConsole = System.console( );
Metoda întoarce null dacǎ ruleazǎ în alt mediu (telefon mobil sau web browser care nu are
console. Metoda simplǎ readLine( ) întoarce un singur şir de la consolǎ, fǎrǎ line-break:
public String readLine( ) throws IOError
Sirul prompt este interpretat ca şi printf( ) şi nu are echivalent scanf( ). Valoarea returnatǎ este
similarǎ cu readLine( ). Console are 2 metode readPassword( ):
public char[] readPassword( )
public char[] readPassword(String prompt, Object... formatting)
Ieşirea:
2
Motivaţia este datǎ de controversele cu comunitǎţile Python şi Ruby
105
0.0 0.0 0.0
1.0 0.0 1.1
2.0 0.0 2.2
3.0 0.1 3.3
...
Se poate forţa data scrisǎ înainte de line break invocând metoda flush( ):
formatter.flush( );
formatter.close( );
Exemplu:
import java.io.*;
class Homework {
public static void main(String[] args) {
Console console = System.console( );
String input = console.readLine(
"Please enter a number between 1 and 10: ");
int max = Integer.parseInt(input);
for (int i = 1; i < max; i++) {
console.printf("%d\n", i*i);
}
}
}
C:\>java Homework
Please enter a number between 1 and 10: 4
1
4
9
Output Stream
Clasa java.io.OutputStream declarǎ metode întorc void() şi tratează IOException în caz de
eroare.
write(int b) -scrie un singur bit pe fluxul de ieşire b şi ermite scrierea expresiei fără
a o transforma înapoi la byte;
write (byte b[ ] ) - scrie un tablou complet de bit pe fluxul de ieşire b;
write(byte b[ ] int off, int len) - scrie subrang de len biţi din b, începând de la
b[off];
skip(long n ) - încearcă să sară peste n biţi din b şi întoarce întreg reprezentând
numărul de biţi săriţi cu succes;
flush() - finalizează starea ieşirii şi şterge bufferul;
close() - închide fluxul de ieşire caz în care viitorul write(0 generează IOException.
es the three basic methods you need to write bytes of data onto a stream. It also has
methods for closing and flushing streams:
Syntaxǎ:
public abstract void write(int b) throws IOException
106
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public void flush( ) throws IOException
public void close( ) throws IOException
Clasa FileOutputStream suprascrie cele 5 metode cu metode ce apeleazǎ cod native ce scrie
fişiere, OutputStream este abstractǎ, subclasele specifice OutputStream sunt ascunse. De
examplu, metoda getOutputStream( ) din java.net.URLConnection are semnǎtura:
public OutputStream getOutputStream( ) throws IOException
Depinzând de tipul de URL asociat obiectului URLConnection, clasa actualǎ returnatǎ pentru
output stream poate fi sun.net.TelnetOutputStream, sun.net.smtp.SmtpPrintStream,
sun.net.www.http.KeepAliveStream, sau altceva complet. Clasa java.io.DataOutputStream
class nu declarǎ metoda close( ), dar apeleazǎ metoda moştenitǎ din superclasa ei
public abstract void write(int b) throws IOException
import java.io.*;
public class AsciiChart {
public static void main(String[] args) {
for (int i = 32; i < 127; i++) {
System.out.write(i);
// break line after every eight characters.
if (i % 8 == 7) System.out.write('\n');
else System.out.write('\t');
}
System.out.write('\n');
}
}
Scrierea tablourilor de biţi
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
Fişierele au blocuri de dimensiune 1024, 2048, or 4096 bytes, reţeaua oferǎ buffer de 128 sau
256 bytes şi se recomandǎ 128 bytes la conexiune reţea şi 1024 bytes la fişiere.
107
catch (IOException ex) {
System.err.println(ex);
}
}
}
Inchiderea Output Streams
public void close( ) throws IOException
La apel out.close( ) din OutputStream, se închide fluxul dar se şi elibereazǎ resursele associate
cu acesta. System.out este excepţie parţialǎ a PrintStream.
Fişierele sunt închise prin:
try {
OutputStream out = new FileOutputStream("numbers.dat");
// Write to the stream...
out.close( );
}
catch (IOException ex) {
System.err.println(ex);
}
Initialize this to null to keep the compiler from complaining
// about uninitialized variables
OutputStream out == null;
try {
out = new FileOutputStream("numbers.dat");
// Write to the stream...
}
finally {
if (out != null) out.close( );
}
Closeable Interface
Java 5 oferǎ Closeable interface pe care o implementeazǎ OutputStream:
package java.io;
public interface Closeable {
void close( ) throws IOException;
}
InputStream, Channel, Formatter, şi alte fluxuri ce pot fi închise implementeazǎ aceastǎ
interfaţǎ.
108
Subclasarea OutputStream
OutputStream este abstractǎ iar clasele specifice o extend. FileOutputStream utilizeazǎ cod
native la scrierea fişierelor, ByteArrayOutputStream utilizaezǎ Java pur la scriere şi
expandare byte array. Suprascrierea variantelor de metodǎ write( ) din OutputStream, dǎ o
metodǎ abstractǎ, şi 2 concrete:
public abstract void write(int b) throws IOException
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public void write(byte[] data, int offset, int length) throws IOException {
for (int i = offset; i < offset+length; i++) write(data[i]);
}
109
package com.elharo.io.ui;
import javax.swing.*;
import java.io.*;
public class JStreamedTextArea extends JTextArea {
private OutputStream theOutput = new TextAreaOutputStream( );
public JStreamedTextArea( ) {
this("", 0, 0);
}
public JStreamedTextArea(String text) {
this(text, 0, 0);
}
public JStreamedTextArea(int rows, int columns) {
this("", rows, columns);
}
public JStreamedTextArea(String text, int rows, int columns) {
super(text, rows, columns);
setEditable(false);
}
public OutputStream getOutputStream( ) {
return theOutput;
}
private class TextAreaOutputStream extends OutputStream {
private boolean closed = false;
public void write(int b) throws IOException {
checkOpen( );
// recall that the int should really just be a byte
b &= 0x000000FF;
// must convert byte to a char in order to append it
char c = (char) b;
append(String.valueOf(c));
}
private void checkOpen( ) throws IOException {
if (closed) throw new IOException("Write to closed stream");
}
public void write(byte[] data, int offset, int length)
throws IOException {
checkOpen( );
append(new String(data, offset, length));
}
public void close( ) {
this.closed = true;
}
}
}
110
reference to the output stream for the area, then use the usual write( ) methods to write into
the text area. Often, these steps will take place at different times in different methods.
6.4.1. FileInputStream
Dacă fişierul nu este găsit constructorul FileInputStream() ca proiecta
FileNotFoundException. Dacă fişierul este găsit dar nu este citibil, se va proiecta
IOException. Când FileInputStream este construit dintr-un şir, parametrul constructorului
poate fi numele simplu al fişierului din directorul curent sau calea completă sau relativă către
fişier.
Exemplu:
FileInputStream fis=new FileInputStream("fis_intrare");
byte b[ ]=new byte[100];
// citirea a 100 biti in b
int numByteRead=fis.read(b);
System.out.println(new String(b,0,0, numByteRead));
System.out.println(numByteRead+"biti cititi");
6.4.2. FileOutputStream
Deschiderea unui fişier pentru scriere se realizează cu:
FileOutputStream f_ies=new FileOutputStream("fis_iesire");
Dacă fişierul nu este găsit când constructorul FileOutputStream este apelat, acesta se crează
iar dacă nu s-a precizat calea validă, constructorul proiectează FileNotFoundException. Dacă
fişierul este găsit dar nu este citibil, se va proiecta IOException. Calea sistemului este
dependentă de sistem. Dacă se deschide un fişier pe UNIX, cu cale stil Windows ca separator
(folosind \), se va proiecta IOException. Soluţia cea mai comodă este utilizarea lui / (slash)
ca separator deoarece Java garantează tratarea caracterului ca separator de fişier, translatând
111
dacă este necesar, pentru a menţine independenţa sitemului. Pentru construcţia
FileInputStream şi FileOutputStream nu este neapărat necesară utilizarea lui String deoarece
poate fi trecută opţional în FileDescriptor sau în obiectul File al constructorului. Odată
construit FileInputStream şi FileOutputStream (ca obiecte) se poate accesa fişierul utilizând
metodel implementate în clasă. Aceste două clase au funcţionalitate limitată deoarece nu se
pot scrie şiruri obiecte sau alte date de tip primitiv în şi din aceste clase de flux.
FileInputStream şi FileOutputStream manipulează doar biţi.
Exemplu:
import java.io.*;
class FileOutputStream S {
public static byte getInput()[ ] throws Exception {
byte buffer [ ]= new byte{12];
for (int i =0; i<12;i++){
buffer [i]= (byte)System.in.read();
}
return buffer;
}
public static void main (String args [ ] ) throws Exception {
byte buf[ ]=getInput();
OutputStream f0= new FileOutputStream("fis1.txt');
OutputStream f1= new FileOutputStream("fis2.txt');
OutputStream f2= new FileOutputStream("fis3.txt');
for (int i =0; i<12;i++){
f0.write(buf [i]));
}
f0.close();
f1.write(buf);
f1.close();
f1.write(buf,12/4,12/2);
f1.close();
}
}
FileOutputStream nu se folosesc în operaţii de adăugare.
6.4.3. ByteArrayInputStream
112
System.out.println((char)c));
}else{
System.out.println(Character.toUppercase((char)c));
}
System.out.println();
}
6.4.4. ByteArrayOutputStream
Exemplu:
import java.io.*;
import java.util.*;
class ByteArrayOutputStream S {
public static void main (String args [ ] ) throws Exception {
int i;
ByteArrayOutputStream f0= new ByteArrayInputStream (12);
System.out.println("10 caractere si return");
while (f0.size() !=0) {
f0.write(System.in.read());
}
System.out.println("Buffer a sir");
System.out.println(f0.toString());
System.out.println("in tablou");
byte b[ ]=f0.toByteArray();
for (int i =0; i<b.lenght;i++){
System.out.println((char)b[i]));
}
System.out.println();
System.out.println("Intr-un OutputStream");
OutputStream f2= new File OutputStream{"test.txt");
f0.writeTo(f2);
System.out.println("facem reset");
while (f0.size() != 10) {
f0.write(System.in.read());
}
System.out.println("gata'");
}
}
113
FilteredStream extinde fluxurile de bază furnizând sincronizarea.
Pentru citirea respectiv scrierea datelor de nivel mai înalt sunt definite clasele
DataInputStream şi DataOutputStream. DataInputStream permite citirea şi DataOutputStream
permite scrierea sub forma reprezentării binare a tipurilor primitive. De aceea
DataInputStream are metode instanţiate ca şi readInt() care citeşte 32 biţi şi îî interpretează
ca Int, readDouble() care citeşte 64 biţi şi îî interpretează ca Double. In mod analog,
DataOutputStream furnizează metode instanţiate pentru writeInt() şi writeDouble().
In mare parte DataInputStream şi DataOutputStream urmează o proiectare paralelă şi totuşi,
DataInputStream furnizează metoda readLine() care nu face parte din DataInputStream şi
care citeşte o linie oprindu-se la sfârşitul ei (\n sau sfârşit de fişier). Această metodă devine o
modalitate convenabilă de citire a liniilor de text în fişiere ASCII ale intrărilor standard.
Metoda readLine() citeşte caractere 8-biţi ASCII nu 16-bit Unicode iar şirul returnat conţine
partea Unicode a caracterelor ASCII citite. La DataOutputStream se foloseşte metoda
PrintStream() care permite scrierea unui şir şi astfel DataInputStream este optimizat pentru
text introdus în timp ce DataOutputStream.nu. Fluxul de ieşire standard System.out este
declarat ca PrintScreen, care optimizează ieşirile text, în timp ce intrarea standard este
declarată InputStream, înţelegând că trebuie conectată DataInputStream pentru a facilita
intrările apelate de readLine().
6.5.1. PrintStream
Această clasă a fluxului de ieşire optimizează textul afişat şi nu vine în pereche, deoarece
PrintStream este prin natura sa flux de ieşire. PrintStream furnizează metode ca print() şi
println() ce vor afişa fluxul unui şir (String) ca reprezentare a obiectului sau a tipului
primitiv. Astfel se pot conecta FileOutputStream, DataOutputStream şi PrintStream pentru a
adăuga posibilităţi de utilizare pentru print() şi println(). Standard output stream (System.out)
şi standard error stream (System.err) sunt subclase ale lui PrintStream. Clasa are doi
constructori:
PrintStream(OutputSream out);
PrintStream(OutputSream out, boolean autoflush);
Unde autoflush controlează ieşirea de date de câte ori apare "Xn".
Caracterele puse în flux pentru print() şi println() sunt ieşiri care utilizează 8-biţi ASCII mai
degrabă decât 16-biţi Unicode. Primii 8 biţi ai fiecărui caracter sunt neafectaţi, fiind folosiţi
ultimii 8, dat fiind faptul că setul de caractere ASCII este un subset al setului Unicode. Pentru
caracterele din setul non-Latin este o problemă utilizarea metodelor print() şi println().
[Naugh_96]
114
BufferedInputStream(InputStream in, int dimens)
unde dimens este independentă de sistemul de operare pe care se lucrează
BufferedInputStream oferă metoda mark(), ce permite poziţionarea curentă în flux şi reset()
pentru poziţionarea pointerului înapoi la poziţia marcată prin metoda mark().
PushbackInputStream este o implementare a lui InputStream şi permite citirea unui
caracter şi apoi aşezarea lui înapoi în fluxul de date de intrare. Incercarea de a pune mai multe
caractere determină IOException iar construtorul este:
PushbackInputStream ( InputStream in)
In plus oferă metoda unread(char c) care acceptă argument caracter, ce se va pune înapoi în
fluxul de intrare.
SequenceInputStream este o clasă a lui InputStream care suportă concatenarea mai multor
InputStream în unul singur.Construtorul ei este:
SequenceInputStream ( InputStream s0, InputStream s1);
SequenceInputStream ( Enumeration e);
Aceste două fluxuri sunt utile în comunicaţia fir-cu-fir iar ideea este împrumutată de la
utiliarul UNIX- pipe, unde ieşirile unui program sunt trimise ca intrări ale altui program.
Diferenţa este dată de faptul că UNIX pipe este destinat comunicaţiei între procese în timp ce
Piped sunt destinate comunicaţiei între fire (interthread). Cele două clase pot fi folosite
astfel:
PipedInputStream b Input= new PipedInputStream ();
PipedOutputStream ies Output= new PipedOutputStream (b);
După ce este setată relaţia se poate instanţia Thread ies ce trimite date altui Thread b iar când
ies scrie date în iesOutput, b poate citi date din bInput.
StreamTokenitzer este o clasă Enumeration (enumeraţie), virtual identică cu
StringTokenitzer, extrăgând un token (simboluri) dintr-un flux de intrare.[An_96]
File este obiectul ce permite verificarea şi execuţia diverselor operaţii cu fişiere (diferite de
citire şi scriere) prin care se poate verifica accesibilitatea fişierului cu exists(), canRead(),
canWrite(), se poate crea un director cu metoda mkDir(), se poate redenumi un fişier
renameTO() sau se poate obţine lista fişierelor dintr-un director - list(). Precizăm că
directorul nu se poate şterge.
Exemplu:
import java.io.File;
class FileTest {
private p(Strings) {
System.out.println(s);
}
public static void main(String args{[ ]) {
File f1= new File("/java/Exemplu");
p("nume Fisier"+f1.getName());
p("cale Fisier"+f1.getPath());
p("cale absoluta Fisier"+f1.getAbsolutePath());
p("parinte"+f1.getParent());
115
p(f1.exists() ? "exista" : "nu exista";
p(f1.canRead() ? "pot citi" : "nu pot citi ";
p(f1.canWrite() ? "pot scrie" : "nu pot scrie ";
if (f1.isDirectory()) {
System.out.println("Director "+dirname);
String s[ ]=f1.list();
for (int i=0; i<s.length;i++){
File f= new File(dirname+"/"+s[i]);
if (f1.isDirectory()) {
Uneori este de dorit limitarea fişierelor transmise prin list la cele cu structura delimitată prin
pattern, caz în care se implementează interfaţa FilenameFilter cu o singură metodă accept ()
apelată odată pentru fiecare fişier din listă. Restricţiile se aplică fisierului cu sufix identic cu
extensia dată.[An-96]
Exemplu:
Modificăm exemplul anterior, astfel:
import java.io.*;
public class NumaiExt implements FilenameFilter {
String ext;
public NumaiExt (String ext) {
this.ext="."+ext;
}
public boolean accept( File dirname, String Nume) {
return name.endsWith(ext);
}
}
RandomAccess este obiectul ce permite citire şi scriere simultană a unui fişier fără a deschide
fluxuri separate. Astfel s epoate specifica modul se acces al fişierului ("rw") şi clasa vine cu
metodele de citire a tipurilor primitive. De asemenea se poate seta pointerul de fişier cu
metoda seek().
116
FileDescriptor este identificator unic de fişiere şi în plus, identificator unic de intrare
standard, ieşire standard şi eroare standard.FileDescriptor este opac şi nu se poate face nimic
pentru a-l altera în schimb se poate folosi pentru a construi un obiect de tip File,
FileInputStream, FileOutputStream sau RandomAccessFile. Aceste clase furnizează metoda
getFD() prin care se obţine obiectul FileDescriptor ce identifică în mod unic fişierul File la
care este ataşat. In plus, se poate apela la FileDescriptor ca metodă valid() care întoarce o
valoare booleană ce indică FileDescriptor valid.
Clasa FileDescriptor defineşte 3 obiecte constante FileDescriptor.in, FileDescriptor.out şi
FileDescriptor.err iar comparând fluxurile FileDescriptor ne putem convinge că sunt
conectate a aceste puncte finale ale fluxului standard.
Există mai multe metode de îmbinare a fluxurilor dar tipic se crează un flux proiectat pentru
a fi ataşat unui punct final cert (ca şi Socket sau FileInputStream) şi apoi se trece acest
parametru la constructorul unei alte clase a fluxului proiectat pentru filtrarea datelor într-un
anume mod.
Filtru este clasa de flux care alterează transmisia datelor (BufferedInputStream şi
BufferedInputStream) sau care furnizează metode ce permit vizualizarea într-un mod
convenabil a datelor (DataInputStream şi PrintStream).
Deseori se citeşte un fişier text utilizând DataInputStream sau se scrie cu PrintStream pentru a
folosi avantajele metodelor centrate pe text, furnizate de aceste clase. Deoarece fluxul nu
citeşte sau scrie în/din fişiere, este necesară crearea unui FileInputStream sau
FileOutputStream sau se pot chiar combina aceste fluxuri, tipic prin constructor încuibat
apelat, astfel:
Exemplu:
// cream FileInputStream şi îl înconjurăm cu DataInputStream
DataInputStream dis=new DataInputStream (new FileInputStream(
"FisierIn");
Se preferă totuşi buffere I/O iar ideea este de a avea un flux ce are de a face cu fişierele, unul
ce este conectat la manipularea bufferului şi un flux conectat pentru a oferi abilităţi de
abordare a datelor de la cel mai înalt punct de vedere. Astfel utilizăm constructori imbricaţi:
Exemplu:
// cream FileInputStream şi îl înconjurăm cu BufferedInputStream
// şi apoi cu DataInputStream
DataInputStream dis=new DataInputStream (
new BufferedInputStream (
new FileInputStream("FisierIn")));
117
Aceşti constructori imbricaţi crează confuzii dacă nu se ţine cont de cele două aspecte
funcţionale:[Hoff_96]
1. Constructorul cel mai interior este invocat primul. Fluxul ce este cel mai direct în
legătură cu punctul final de transmisie a datelor va fi construit primul (punctul
final fiind fişierul). Când se deschide un flux de fişier, primul flux ce se va
construi este cel în legătură directă cu fişierul. Ultimul flux construit este cel
despre care se "vorbeşte" mai mult.
2. Când se cunoaşte funcţionalitatea fiecărui flux, nu este o problemă aşezarea lui în
transmisia datelor.
Exemplu:
import java.io.*;
class WordCount {
public static int words=0;
public static int lines=0;
public static int chars=0;
public static void Wco(InputStream f) throws Exception {
boolean lastNotWhite =false;
int c=0;
String whiteSpace=" \+\n\r";
while ((c=f.read()) != -1){
chars==;
if (c=="\n"){
words++;
}
lastNotWhite=false;
}else{
lastNotWhite=true;
}
}
}
public static void main(String args[ ] ){
FileInputStream f;
try{
if (args.length==0 ){
f= FileInputStream(System.in);
Wco(f);
//lucram cu lista de fişiere
} else {
for (int i=0;I<args.length;i++){
f=new FileInputStream(args[i] );
Wco(f);
}
}
}catch(IOException e){
return;
}
System.out.println(lines+" si cuvinte"+words+
"si caractere"+chars):
}
118
}
119
Capitolul VII
Programarea în reţea
Pachetul .net
Introducere
Ca şi oricare alt limbaj, Java asigură o modalitate de exprimare a conceptelor. Toate limbajele
de programare folosesc abstractizarea iar complexitatea problemei pe care o rezolvăm este
direct proporţională cu modul şi gradul de abstractizare. Programatorul trebuie să realizeze
asocierea dintre modelul maşinii şi modelul problemei care se rezolvă. Alternativa la
modelarea maşinii este modelarea problemei. Abordarea programării orientate obiect acordă
utilizatorului instrumente ce asigură reprezentarea elementelor în spaţiul problemei. Această
reprezentare asigură programatorului libertatea de a nu fi constrâns într-o problemă
particulara. Ideea este că programul poate adăuga tipuri noi de obiecte de care are nevoie,
pentru a rezolva o anumită problema. Astfel atunci când citim codul sursă ce descrie soluţia
problemei, citim de fapt cuvinte care descriu problema.
Atuurile pe care le are Java la nivelul lucrului în reţea sunt diminuarea, eliminarea unora
dintre aceste probleme şi transferarea lor pe cât posibil, în modelele abstractizate şi preluate
de JVM . Modelul de programare utilizat este cel al unui fişier. Conceptul de multithreading ,
care este incorporat, este foarte util, mai ales atunci când avem de-a face cu o alta problemă
legată de reţea - conectarile multiple care au loc în acelaşi timp.
120
CalcS.bruceeckel.com . Genul de nume este similar cu cel care care-l folosim atunci când
trimitem un e-mail, şi care de obicei este incorporat într-o adresa www (World -Wide -Web).
2. sub forma de 4 numere despărţite prin punct, de exemplu, 123.243.23.121.
In ambele cazuri, adresa de IP este reprezentată pe 32 biti (nici unul din numere nu poate
depăşi 255).
Java suportă nume Internet prin InetAddress clasă ce este comprimată la 32 biţi ca host
identifier şi 32 de biţi ca selector de port. [java_1]
InetAddress nu are constructor vizibil şi este necesară utilizarea metodelor factoriale, statice
ca şi: getLocalHost, getByName, getAllByName pentru a crea instanţe la InetAddress. Dacă
getByName nu poate rezolva cererea apare excepţia UnknownHoseException.
InetAddress are câteva metode non-statice:
getHostName() care dă numele de host asociat cu InetAddress;
getAddress() dă 4 elemente de tablou byte ce reprezintă Inetwork byte order
toString() - întoarce un şir ca listă de nume host şi adresă IP.
Java implementează Datagram în vârful protocolulul TCP/IP UDP utilizând 2 clase
DatagramPacket (care conţine date şi DatagramSocket (care reprezintă mecanismul de
transmisie şi recepţie a DatagramPacket.
DatagramPacket are 2 constructori:[java_8], [java_2]
DatagramPacket (byte ibuf[ ], int ilenght);
DatagramPacket (byte ibuf[ ], int ilenght, InetAddress iadr, int iport);
Pentru a accesa informaţiile se oferă câteva metode:
getAddress() întoarce destinaţia InetAddress şi se foloseşte la trimitere
getPort() întoarce întreg, portul InetAddress şi se foloseşte la trimitere
getData() înroarce elemente de tablou byte ale datelor conţinute în Datagram
după ce au fost primite.
getLenght() înroarcelungimea datei valide conţinută în tabloul byte, ce va fi
returnată de getData(). De obicei lungimea datelor diferă de tabloul byte.
121
if (args.length != 1) {
System.err.println (
“Usage :WhoAmI MachineName”);
System.exit (1);
}
InetAddress a=
InetAddress.getByName (args[0]);
System.out.println(a) ;
}
}
Daca numele calculatorului este de exemplu “CalcS”, atunci după conectarea la ISP, execut
programul java WhoAmI CalcS şi vom obţine un raspuns de forma :
CalcS / 165.234.28.75 ( şi evident că adresa e alta de fiecare data).
Dacă se cunoaste această adresa se poate conecta la serverul web personal prin URL-ul :
http:// 165.234.28.75 doar atat cât sunt conectaţi la Internet.
Aceasta metodă este binevenită atunci când se doreste de exemplu, împarţirea unei informaţii
cu alţii sau pentru a testa configurarea propriului web site înainte de a-l pune pe un server
real.
Clienti şi servere
Ideea de baza într-o reţea este aceea de a permite conectarea, comunicarea dintre 2 sau mai
multe calculatoare, însă pentru a putea comunica între ele se pune problema cum se găsesc
calculatoarele între ele. Calculatorul căutat este serverul, iar cel care caută este clientul.
Aceasta delimitare este importantă doar până în momentul în care clientul încearca să se
conecteze la server. Odata realizată conectarea, apare un proces de comunicare bidirectională,
şi nu mai are importanţă faptul că unul din calculatoare joacă rolul de client iar celalalt de
server. Sarcina serverului este să “asculte” conectarea, lucru realizat de obiectul sever special
creat. Sarcina clientului este aceeea de a incerca stabilirea conectării la server, acest lucru
fiind realizat prin intermediul obiectului client special creat. După conectare, aceasta permite
realizarea de operaţii asemanatoare cu citirea sau scrierea într-un fişier.
Poate aparea situaţia în care nu avem la dispoziţie un calculator server, unul client şi o reţea,
necesare pentru testarea programului. Creatorii protocolului IP au luat în considerare aceasta
problemă şi au creat o adresa specială numită localhost ce asigură testarea programului fara
existenta unei reţele (loopback local al adresei ).
Modul generic de a produce această adresă în Java este :
InetAddress addr = InetAddress.getByName (null) ;
122
InetAddress.getByName (“127.0.0.1”).
Toate cele trei forme au acelaşi rezultat.
Portale
O adresă IP nu este suficientă pentru a identifica un server în mod unic, datorită faptului că
pot exista mai multe servere pe acelaşi calculator. Fiecare maşina IP conţine de asemenea şi
portale care trebuiesc alese pentru că atât clientul cât şi serverul să se poată conecta. Portalul
nu are o locaţie fizică în calculator ci este o abstractizare a software-ului. Programul client ştie
cum să se conecteze la calculator, prin adresa IP, dar se pune problema cum să se conecteze la
un anumit serviciu (unul din cele multe existente pe un calculator). Aici apar numerele
portalului, deoarece atunci când se cere un anumit portal, se cere de fapt conectarea la un
anumit serviciu asociat cu numărul portalului. In mod tipic, fiecare serviciu este asociat cu un
număr unic de portal pe un anumit calculator server. Aflarea orei, este un exemplu simplu de
serviciu. Este problema clientului să ştie ce număr de portal corespunde serviciului pe care
vrea sa-l solicite. Serviciile de sistem îşi rezerva folosirea portalurilor intre 1 şi 1024 .
Socket-uri
Denumirea nu este tocmai potrivită, din moment ce nu este un socket ci genereaza un obiect
Socket atunci când cineva se conectează. ServerSocket creaza un “server” fizic sau un socket
ce ascultă pe calculatorul host. Acest socket asculta conectarea, şi returneaza un socket stabilit
prin intermediul metodei accept() . Partea derutantă este că ambele socket-uri, şi cel ce
123
asculta şi cel stabilit sunt asociate cu acelaşi server socket . Socketul ce asculta poate accepta
doar noi cereri de conectare, dar nu şi pachete de date.
In exempul următor, redăm folosirea cea mai simplistă a socketurilor într-o situaţie client –
server. Serverul aşteaptă conectarea, după care foloseşte Socket-ul produs de conectare
pentru a crea un InputStream şi un OutputStream. Apoi, tot ceea ce citeşte din
InputStream transmite sub forma de ecou în OutputStream până când citeşte linia END,
când închide conectarea. Clientul face conectarea la server, apoi crează un OutputStream .
Liniile textului sunt transmise prin OutputStream. Clientul crează de asemenea un
InputStream pentru a auzi ceea ce “spune“serverul, care în acest caz sunt doar cuvintele
retransmise. Atât clientul cat şi serverul folosesc acelaşi număr de portal, iar clientul foloseşte
adresa de loopback local pentru a se conecta la server, pe aceeaşi maşină, deci nu trebuie
testat pe o reţea particulară.
// un server foarte simplu care reda doar ecoul a ceea ce transmite clientul
import java.io.* ;
import java.net.* ;
public class JabberServer {
// alegem un portal în afara intervalului 1- 1024
public static final int PORT = 8080 ;
public static void main (String [] args)
throws IOExcepţion {
ServerSocket s = new ServerSocket (PORT) ;
System.out.println(“Started: ” + s ) ;
try {
System.out.println(
“Connection accepted : ” + socket ) ;
BufferedReader în =
new BufferedReader (
new InputStreamReader(
socket.getInputStream() ) ) ;
PrintWriter out =
new PrintWriter (
new BufferedWriter (
new OutputStreamWriter(
socket.getOutputStream( ) ) ) , true ) ;
while (true) {
String str = in.readLine () ;
if (str.equals (“END”) ) break ;
System.out.println (“Echoing: ” + str) ;
out.println(str) ;
}
124
// intodeauna trebuiesc inchise cele 2 socket-uri
} finally {
System.out.println (“closing … ” ) ;
socket.close ();
}
} finally {
s.close() ;
}}}
Explicaţia codului:
Se observă că ServerSocket are nevoie doar de numărul portalului, nu şi de adresa IP din
moment ce rulează pe această maşina. Când se face apel la accept(), metoda se blochează
până când un client încearcă să se conecteze. In realitate este o aşteptare până la conectare,
dar celelalte procese pot rula. Când conectarea are loc, accept() returnează un obiect Socket
ce reprezintă conectarea. Responsabilitatea eliberării socket-urilor este împarţita cu multă
atentie . Astfel dacă constructorul de ServerSocket eşuază, programul iese pur şi simplu.
Trebuie să avem în vedere faptul că un constructor de ServerSocket nu lasă deschise nici un
socket de reţea în cazul în care eşuaza. In acest caz, avem throws IOExcepţion, deci, un bloc
de try nu este necesar. Daca constructorul ServerSocket îşi realizează sarcina, atunci
apelurile la celelalte metode trebuie trecute într-un bloc try–finally, pentru a se asigura că
indiferent cum e părăsit blocul, ServerSocket-ul este închis corect. Aceeasi logică se
foloseste şi pentru Socketul retunat de accept() . Daca accept () eşuaza atunci trebuie să ne
asiguram că Socketul nu mai există sau nu reţine resurse, adică nu e nevoie să fie curăţat.
Daca nu apare nici o problemă, atunci instrucţiunile ce urmează trebuiesc încorporate într-un
bloc try-finally pentru a ne asigura de faptul că dacă una dintre ele eşueaza, vom avea
Socketul curat. Sockeet-urile folosesc resurse din afara memoriei, şi datorită faptului că în
Java nu există destructori, acestea trebuie curatate iar operaţia nu intră în sarcina lui Garbage
Collector.
Atat ServerSocket cat şi Socket-ul produse de accept() sunt afişate în System.out. Aceasta
inseamna că metodele lor toString() sunt automat apelate. Astfel rezultă:
ServerSocket [addr = 0.0.0.0, PORT = 0 , localport = 8080]
Socket [addr = 127.0.0.1,PORT = 1077, localport = 8080]
Părtile urmatoare din program sunt asemanatoare cu deschiderea fişierelor pentru citire şi
scriere cu diferenţa că InputStream şi OutputStream sunt create din obiectul Socket. Atat
obiectele InputStream cat şi OutputStream sunt convertite în obiecte Reader şi Writer
folosind clasele specifice de convertire InputStreamReader şi OutputStreamWriter.
Ori de cate ori se face referire la out, bufferul sau trebuie umplut astfel încăt informaţia să
treca prin reţea. Umplerea este foarte importanta în acest caz deoarece atat serverul cat şi
clientul asteapta o linie de la cealaltă parte, înainte de a conţinua. Daca nu are loc “umplerea”,
informaţia nu va fi trimisa prin reţea până ce bufferul nu se umple, ceea ce duce la apariţia
unor probleme. Atunci când se scriu programe pentru reţea trebuie tratate cu mare atentie
umplerile automate. De fiecare dată când se umple bufferul, un pachet de date este creat şi
trimis.In acest caz, este tocmai ceea ce se doreşte, deoarece dacă pachetul ce conţine linia nu
este trimis, atunci reacţiile dintre server şi client nu mai au loc. Practic, sfârşitul unei linii este
sfârşitul mesajului. Dar în multe cazuri mesajele nu sunt delimitate de linii, de aceea este
avantajos să nu se folosească umplerea automată a bufferului, ci să se lase bufferul construit
să decida de la sine când să formeze şi să trimită pachetul. Asfel pot fi construite pachete mai
mari, iar procesul va fi mai rapid. Bucla while citeşte linii din BufferedReader şi scrie
125
informaţii la System.out şi PrintWriter. out. Când clientul trimite linia ce conţine “END”
programul iese din bucla şi închide Socket-ul.
Explicaţia codului:
In main () se pot observa toate cele trei modalităţi de a produce InetAddress-ul adresei IP
prin loopback local: folosind null, localhost sau adresa explicita 127.0.0.1. Dacă se doreşte
126
conectarea la un calculator prin reţea, se înlocuieşte cu adresa IP a calculatorului respectiv.
Când InetAddress addr este afişata prin intermediul metodei toString() , se obţine: localhost
/ 127.0.0.1. Prin atribuirea lui getByName() a valorii null, se caută automat localhost-ul şi se
obţine adresa speciala 127.0.0.1 Se remarcă faptul că Socketul este creat atat de InetAddress
cat şi de numărul portalului. Pentru a intelege ce inseamna de fapt afisarea unuia dintre aceste
obiecte Socket, să reamintim că o conexiune Internet este determinata în mod unic de
urmatoarele 4 tipuri de date : clientHost, clientPortNumber, serverHost, şi
serverPortNumber.
Clienti multipli
Serverul JabberServer funcţioneaza, dar poate servi doar un singur client pe moment. într-un
server tipic se doreste deservirea mai multor clienti în mod concurent. Raspunsul il reprezinta
multithreading-ul , iar în limbajele ce nu suporta multithreading apar o serie de complicatii.
Ideea de baza este de a crea un singur ServerSocket în partea de server şi de a apela accept()
să astepte pentru o noua conectare. când metoda accept() se intoarce, se ia socket-ul rezultat
şi se foloseşte pentru crearea unui nou thread a carui sarcina este de a servi clientul particular.
Apoi se apelează accept () din nou, pentru a astepta un nou client.
Codul pentru partea de server este similar cu codul pentru JabberServer (din exemplul
anterior), cu excepţia că toate operatiile de deservire a unui client au fost mutate în interiorul
unei clase de thread separate.
127
Exemplu:
// server ce foloseste multithreading pentru a face faţă cererilor oricator clienti
import java.io. * ;
import java.net. * ;
class ServeOneJabber extends Thread {
private Socket socket ;
private BufferedReader în ;
private PrintWriter out ;
public ServeOneKubber (Socket s )
throws IOExcepţion {
socket = s ;
in =
new BufferedReader (
new InputStreamReader (
socket.getInputStream () ) ) ;
// nu permitem umplerea automata
out =
new PrintWritwer (
new BufferedWriter (
new OutputStreamWriter (
socket.getOutputStream () ) ), true) ;
// daca oricare din apelarile anterioare produce excepţion
// apelantul este responsabil de inchiderea socket-ului ;
//altfel , thread-ul il va inchide el
start ( ) ; // apeleaza run ( )
}
public void run ( ){
try {
while (true) {
String str = in.readLine ( ) ;
if str.equals (“END”) ) break ;
System.out.println ( “Echoing : ” + str) ;
out.println (str) ;
}
System.out.println ( “Closing…: ” ) ;
} catch (IOExcepţion e) {
} finally {
socket.close ( ) ;
} catch ( IOExcepţion e) { }
}
}
}
public class MultiJabberServer {
static final int PORT = 8080 ;
public static void main (String [] args)
throws IOExcepţion {
ServerSocket s = new ServerSocket (PORT) ;
System.out.println ( “Server Started” ) ;
try {
while ( true ) {
128
// blocam pana la aparitia unei conectari
Socket socket = s.accept ( ) ;
try {
new ServeOneJubber (socket) ;
} catch (IOExcepţion e) {
// daca nu reuseste , inchide socket-ul, daca nu, i-l va inchide thread-ul
socket .clse () ;
}}
} finally {
s.close () ;
}
}
}
129
private static int threadCount ( ) {
return threadcount ;
}
public JubberClientThread (InetAddress addr) {
System.out.println (“Making client ” + id) ;
threadcount + + ;
try {
socket =
new Socket (addr, MultiJubberServer. PORT) ;
} catch (IOExcepţion e) {
// daca crearea socketului esuaza , nu este nevoie de curatire
}
try {
in =
new BufferedReader (
new InputStremReader (
socket.getInputStream ( ) ) ) ;
// scoatem umplerea automata
out =
new PrintWriter (
new BufferedWriter (
new OutputStreamWriter (
socket.getOutputStream ( ) ) ) , true) ;
start ( ) ;
} catch (IOExcepţion e) {
// socketul trebuie inchis în cazul esuarilor, altele decat ale constructorului
try {
socket.close ( ) ;
} catch (IOExcepţion e2 ) {}
}
// altfel socketul va fi inchis de metoda run ( ) a thread-ului
}
public void run ( ) {
try {
for (int i = 0 ; i < 25 ; i++) {
out.println (“Client ” + id + “:” + i) ;
String str = in.readLine ( ) ;
System.out.prinln (str) ;
}
out.println (“”END) ;
} catch (IOExcepţion e ) {
} finally {
try {
socket.close () ;
} catch (IOExcepţion e ) { }
threadcount -- ;
}}
}
public class MultiJubberClient {
static final int MAX_THREADS = 40 ;
130
public static void main (String [] args)
throws IOExcepţion , InterruptedExcepţion {
InetAddress addr =
InetAddress.getByName (null) ;
while (true) {
if (JubberClientThread.threadCount ( ) < MAX_THREADS )
new JubberClientThread (addr) ;
Thread.currentThread ( ). Sleep (100);
}}
}
Datagrams
131
Exemplu:
Crearea de DatagramPacket pentru String
//: Dgram.java
import java.net.*;
public class Dgram {
public static DatagramPacket toDatagram(
String s, InetAddress destIA, int destPort) {
byte[] buf = new byte[s.length() + 1];
s.getBytes(0, s.length(), buf, 0);
return new DatagramPacket(buf, buf.length,
destIA, destPort);
}
public static String toString(DatagramPacket p){
return
new String(p.getData(), 0, p.getLength());
}
} ///:~
Porturile TCP şi UDP sunt considerate unice şi se poate lucra simultan cu TCP şi UDP
132
// Extrage address si port din datagram primit si trimis ca ecou:
DatagramPacket echo =
Dgram.toDatagram(echoString,
dp.getAddress(), dp.getPort());
socket.send(echo);
}
} catch(SocketException e) {
System.err.println("Can't open socket");
System.exit(1);
} catch(IOException e) {
System.err.println("Communication error");
e.printStackTrace();
}
}
public static void main(String[] args) {
new ChatterServer();
}
} ///:~
ChatterServer conţine un singur DatagramSocket pentru primirea mesajelor, în loc sǎ
creeze câte unul de fiecare datǎ când primeşte un nou mesaj. Acest DatagramSocket este
utilizat repetat, are numǎr de port deoarece acest server şi clientul trebuie sǎ aibǎ adresǎ
exactǎ când doresc sǎ trimitǎ datagram. Se dǎ numǎr de port şi nu adresǎ Internet deoarece el
rezidǎ me acea maşinǎ pentru care se cunoaşte adresa Internet (default localhost). In bucla
infinitǎ while loop, socket-ul anunţǎ receive( ), când apar blockurile pânǎ cǎnd datagram
ajunge la receptorul destinaţie DatagramPacket dp. Pachetul este convertit în String
împreunǎ cu along with informaţii despre adresa Internet şi socket de unde vine pachetul
(destinaţia acestuia). Aceste informaţii sunt afişate apoi un extra string este adǎugat pentru a
indicate faptul cǎ începe ecoul înapoi de la server. In aceastǎ demonstraţie datagram rpimite
şi trimise rezidǎ pe localhost, dar numǎrul de port pentru fiecare client este diferit . pentru a
trimite un mesaj înapoi la clientul original este necesar sǎ se cunoascǎ adresa Internet şi
numǎrul de port al clientului, informaţii conţinute în DatagramPacket care au fost trimise
odatǎ cu mesajul, astfel încât pot fi obţinute/extrase cu getAddress( ) and getPort( ), care
sunt utilizate pentru a construi DatagramPacket echo, trimise înapoi la acelaşi socket ce a
fǎcut recepţia. In plus, când socketul trimite datagram, adaugǎ automat adresa Internet şi
numǎrul de port al maşinii clientului, deci când se recepţioneazǎ mesajul, se poate folosi
getAddress( ) şi getPort( ) pentru a afla de unde vine datagram.
//: ChatterClient.java
// ChatterServer cu clienti multipli, fiecare cu datagram proprii.
import java.lang.Thread;
import java.net.*;
import java.io.*;
public class ChatterClient extends Thread {
// Can listen & send on the same socket:
private DatagramSocket s;
private InetAddress hostAddress;
private byte[] buf = new byte[1000];
private DatagramPacket dp =
new DatagramPacket(buf, buf.length);
private int id;
public ChatterClient(int identifier) {
133
id = identifier;
try {
// Auto-assign port number:
s = new DatagramSocket();
hostAddress =
InetAddress.getByName("localhost");
} catch(UnknownHostException e) {
System.err.println("Cannot find host");
System.exit(1);
} catch(SocketException e) {
System.err.println("Can't open socket");
e.printStackTrace();
System.exit(1);
}
System.out.println("ChatterClient starting");
}
public void run() {
try {
for(int i = 0; i < 25; i++) {
String outMessage = "Client #" +
id + ", message #" + i;
// Make and send a datagram:
s.send(Dgram.toDatagram(outMessage, hostAddress, ChatterServer.INPORT));
// Block until it echoes back:
s.receive(dp);
// Print out the echoed contents:
String rcvd = "Client #" + id +
", rcvd from " +
dp.getAddress() + ", " +
dp.getPort() + ": " +
Dgram.toString(dp);
System.out.println(rcvd);
}
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++)
new ChatterClient(i).start();
}
} ///:~
ChatterClient este creat ca Thread deci pot fi multipli clienţi pe server, DatagramPacket
primit aratǎ similar cu cel pentru ChatterServer. Constructortl DatagramSocket este creat
fǎrǎ argumente pentru cǎ nu dǎ un numǎr particular de port. Adresa Internet este “this
machine” (localhost) numǎr particular de port este automat atribuit ca output.
DatagramSocket, unic pentru server, va fi folosit la primirea /trimiterea datagram,
hostAddress este adresa Internet a maşinii host parte a DatagramPacket. Intotdeauna hostul
are cunoscutǎ adresa şi portul pentru clienţii ce iniţiazǎ conversaţii cu host, numǎr de
134
identificare unic (la fel ca şi portul, atribuit automatla firul ce furnizeazǎ identificatro unic). In
run( ), este creat mesaj String ce conţine numǎrul de identificare al firului şi numǎrul
mesajului de pe firul current. Acest String este folosit la crearea datagram trimisǎ la adresa
hostului; portul este luat direc ca şi constantǎ în ChatterServer. Odatǎ trimis mesajul este
receive( ) replicat pe server.
Aceste exemplu demonstrează modul de creare a unei aplicaţii client-server în Java, folosind
obiectul Socket furnizat de pachetul java.net . Pentru a realiza exemplul este necesar accesul
la site-ul şi serverul HTTP pe care rulează programul.
Rolul lui este de a ţine evidenţa numărului d epersoane ce utilizează Java enable browser
pentru a vizita pagina. Dezavantajul acestui program este faptul ca ţine numai urma traficului
disponibil java. Clientul java este un applet şi numai un browser enable Java îl poate executa.
Avantajul programului este că poate fi rulat chiar şi atunci când nu se pot rula programe CGI.
Pentru a rula programe CGI administratorul sistem trebuie să precizeze unul sau mai multe
directoare în care sunt rezidente programe CGI şi să garanteze accesul al aceste directoare. Alt
avantaj este faptul că dacă clientul reîncarcă pagina Web, counterul nu va fi incrementat.
Construit pe model client-server, sunt necesare 2 programe: unul client (appletul Java) şi unul
server (aplicaţie de sine stătătoare) [Naugh_96]
CounterServer.java
import java.io.RandomAccessFile;
import java.io.Print Stream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
//programul server
public class CounterServer {
Socket clientSock;
// fisier de memorat in contul curent
String counterFileName;
//numar de port
int ourPort;
135
portNumber");
while (true) {
//blocaj pana s eprimeste cererea
client sock=serverSock.accept();
If (counterFile.length()==0){
//fisier inexistent
//scrie 1 in fisier
counterFile.writeBytes("1");
}else{
//citeste fisierul intr-un String
String line= counterFile.readLine();
try {
//incrementeaza counter
counter = 1+Integer.parseInt(line);
136
//repune pointer la inceput de fisier
counterFile.seek(0);
ps=new PrintStrem(client.Sock.getOutputStream());
// tipareste counterul in Socket (pentru a-l regasi clientul
ps.print(counter);
}catch(NumberFormatException e) {
//data in vcounter nu este sir intreg
System.out.println ("Sir necoresp."+counterFileName);
System.exit (-1);
}finally{
//se inchide socket
try {
client Sock.close();
}catch (IOException alreadyClosed) {
}
}
}
}
}
}
Explicaţia codului
Serverul lucrează prin memorarea unui counter în fişier, dar de fapt memorează întregul ca şir
de caractere(decimal digits) unde fiecare caracter este un bit in fişier. Motivul reprezentării
decimal digits este simplitatea faţă de reprezentarea binară a întregului. In acest mod, clientul
paote citi fişierul fără a scrie un program de citire. Când serverul ia cererea de la client, el
citeşte date în fişier, incrementează counter, memorează noua valoare înapoi în fişier şi
transmite valoarea la client. Apoi se reîntoarce pentru a asculta o nouă cerere.
Sunt 4 metode în clasă:
main() care ia parametri din linia de comandă, indicând numele utilizat pentru
fişier şi portul ce va fia scultat. Apoi instanţiază obiectul din clasa CounterServer() şi
dispune să ia cererea takeRequest()
CounterServer(0 constructor pentru clasă
takeRequest() simpla aşteptare a cererii, care apelează updatecounter() când
funcţionează şi apoi sare înapoi pentru a aştepta noua cerere.
updateCounter() este de fapt prelucrarea de servicii pentru serverul pentru care
este creat. El citeşte datele din fişier, actualizează counterul şi trimite informaţiile
înapoi la client.
Metoda main() are o structură caracteristică de manipulare a excepţiilor. In loc să plaseze
fiecare metodă ce poate cauza excepţii într-un bloc try.catch, utilizează un singur bloc
try.catch şi prinde toate excepţiile individuale la sfârşit.Aceasta face codul mai uşor de citit şi
se orientează spre excepţiile dorite de CounterServer(). Utilizăm un constructor pentru
137
obiectul din clasa CounterServer, fiind necesar pentru a aputea accesa variabilel de instanţă şi
pentru a apela metodele de instanţă.
Metoda takeRequest() este o metodă server clasică. Programul server trebuie să acceseze un
obiect din clasa java.net.Socket, legat rapid într-un port specificat. Acest bloc aşteaptă
conexiunea şi chiar dacă aparent este o buclă infinită nu se monopolizează procesorul datorită
metodei ServerSocket.accept() Când spunem că metoda blochează până la primirea
conexiunii, de fapt aşteaptă până este trimis un mesaj de client. Acest mesaj activează CPU
pentru a procesa, iar în realiteate takeRequest itereaza o dată pe proces.Când se prezintă o
cerere, ServerSocket.accept() instanţiază şi întoarce un nou socket client (din casa Socket) ce
va fi utilizat pentru comunicaţia ulterioară cu clientul.
Sunt 2 tipiuri de socketuri utilizate ServerSocket , pentru acceptarea conexiunii şi care nu are
asociate InputStream şi OutputStream, pentru că nu există conexiuni în acest loc. Aceasta are
loc după ce conexiunea este cerută de client, când obiectul Socket full-fledget (complet
maturizat) este construit şi comunicaţia este reală.
Metoda updateCounter() este declarată sincronizată din cauză că poate determina
inconsistenţă dacă mai mulţi clienţi cer actualizarea counterului în acelaşi timp.
Ca regulă generală se recomandă metodă/bloc syncronized de oricâte ori de încearcă
citirea/scrierea într-un fişier şi din aceste motive este şi private.[Orfa_00]
RandomAccesFile este un obiect ce manipulează fişiere I/O dat fiind faptul că se doreşte
abilitate la citire/scriere în acest fişier. Putem utiliza DataInputStream pentru citire şi
PrintStream pentru scrierea în fişier. Citirea unui şir din RandomAccesFile este similară cu
readLine(). Deoarece nu cunoaştem cu cât creşte dimensiunea şirului se preferă scrierea
întregului conţinut al fişierului peste vechea valoare semnificativă. Astfel se crează un nou
obiect PrintStrem cu grijă pentru a nu-l pierde, pasând socket-ului client OutputStream în
constructorul PrintStream.
Observăm că este util de conceput un socket ca fişier în care server-ul şi client-ul au acces iar
socket-ului client are un OutputStream cu care poate scrie, trimiţând date către client. Un
obiect din clasa PrintStream este de apt un Stream(flux) ce poate scrie cu uşurinţă text.
Legând PrintStream cu ieşirea socket-ului facilităm "tipărirea" textului în socket-ul clientului.
Construind un PrintStream pentru socket-ul OutputStrem, va trimite date prin reţea ca simplă
tipărire la consolă.Apoi creăm programul client care este standalone.Blocul finally din
updateCounter() se asigură că socket-ul este închis iar dacă deja operaţia este realizată, nu
execută nimic.
Exemplu:
CounterClient.java
import java.io.applet.Applet;
import java.io.DataInput Stream;
import java.io.IOException;
import java.net.Socket;
//programul client
public class CounterClient extends Applet {
int counter;
138
int port=Integer.parseInt(getParameter("port"));
}finally{
//inchide socket
try{
s.close();
}catch (IOException allreadyClosed) {
//deja inchis
}
}
}
public void paint (Graphics g){
//tipareste conteinerul
g.drawString(Integer.toString(counter),5,size(),height);
}
}
Explicaţia codului
Appletul client ia DNS din host şi îl utilizează pentru a crea socket cu care va comunica cu
serverul. Crearea unui socket dă serverului posibilitatea preluării serviciilor. Clientul primeşte
ieşirile serverului şi le afişează în fereastra appletului, după care închide socketul.Din motive
de securiatte nu poate deschide un socket al host-ului în acre appletul este încărcat, motiv
pentru care appletul nu trebuie aşezat în acelaşi host ca şi fişierul HTML care îl referă. Apelul
getCodeBase().getHost() dă numele host-ului în care este încărcat fişierul .class. Deoarece
deschidem un socket pe acest host, pentru a face conexiune proprie, aplicaţia server trebuie
pusă pe acelaşi host ca şi fişierul .class.
139