Lucrarea 5. Codificarea datelor în mesaje 1. Scopul lucrării Lucrarea își propune studiul modului în care se face codificarea datelor de tip întreg, șir de caractere și variabile booleene în mesajele transmise la distanță la nivelul protocoalelor de aplicație. 2. Noțiuni teoretice Două programe care comunică la distanță în cadrul sistemului distribuit trebuie să se înțeleagă asupra modului de codificare a informației transmise prin canalul de comunicație. Acest acord privind forma și semnificația datelor schimbate reprezintă un protocol, care în cazul unei anumite aplicații concrete poartă numele de protocol de aplicație. Protocoalele de aplicație sunt definite sub formă de mesaje discrete formate dintr-o secvență de câmpuri, fiecare conținând informație codificată pe șiruri de biți. Protocolul specifică modul în care informația este codificată și aranjată în mesaj de către partea emițătoare, respectiv interpretată de partea receptoare. Există protocoale de aplicație predefinite, standardizate, dar se pot și defini liber de proiectanți în cazuri izolate. Prin socluri se pot transmite octeți de date, ca atare vom considera că mesajele sunt formate din șiruri de octeți. Java necesită ca toate tipurile de date (întregi, șiruri de caractere) să fie în prealabil convertite în secvențe de octeți. Codificarea întregilor În cazul primitivelor de tip întreg partea emițătoare și receptoare trebuie să convină în prealabil asupra următoarelor lucruri: − numărul de octeți al fiecărui tip de întreg (fig. 1) − ordinea de transmitere a octeților (fig. 2) − dacă numărul este cu semn sau fără semn (primitivele întregi în Java sunt numere cu semn în complement față de 2) Codificarea șirurilor de caractere Cea mai simplă metodă de codificare a caracterelor utilizate în texte constă în utilizarea unui set de caractere standard asupra căruia cad de acord cele două părți implicate în transfer. Asemenea seturi de caractere sunt de exemplu ASCII, Unicode UTF-8 compatibil cu ASCII, UTF-16 etc. Când șirurile de caractere într-o anumită ordine indiană sunt încărcate într-o gazdă cu o altă ordine indiană, atunci șirul trebuie convertit înainte de a face prelucrarea în regulă. Metoda getBytes() a clasei String întoarce o secvență de octeți conținând șirul de caractere codificat corespunzător setului implicit de pe platformă. Fig. 1. Numărul de octeți al primitivelor de tip întreg Fig. 2. Ordinea de transmitere a octeților numărului int 12345678 (în hexazecimal 00BC614E) Codificarea variabilelor booleene Variabilele booleene se tratează ca și biți individuali ai unei primitive întregi. Fiecare bit al unei date de tip întreg poate reprezenta câte o valoare booleană (0 pentru fals, 1 pentru adevărat). Setarea unui anumit bit se face prin efectuarea unei operații SAU cu o mască care conține 1 pe poziția respectivă și 0 în rest. Ștergerea se face cu operația ȘI efectuată asupra măștii negate. 3. Aplicația propusă Aplicația își propune codificarea într-un mesaj a câte unei valori corespunzătoare celor patru tipuri de primitive de tip întreg, urmată de decodificarea și afișarea mesajului rezultat obținut. Ordinea octeților este de tip big-endian. Se implementează o metodă encodeIntBigEndian() care poate codifica orice valoare aparținând unui anumit tip de primitivă întreagă, respectiv o metodă decodeIntBigEndian() pentru decodificarea unei submulțimi a secvenței de octeți într-o variabilă de tip long. Pașii programului sunt: 1. Se definesc valorile de codificat, câte una pentru fiecare tip de primitivă întreagă, și se calculează numărul de octeți al fiecărei variabile. 2. Se definește o metodă pentru transformarea fiecărui octet al unui tablou întrun string în vederea afișării: public static String byteArrayToDecimalString(byte[] bArray) 3. Se definește metoda encodeIntBigEndian(). 4. Se definește metoda decodeIntBigEndian(). 5. Se pregătește tabloul care va găzdui mesajul (secvența de întregi). 6. Se codifică datele. 7. Se tipărește conținutul mesajului codificat. 8. Se decodifică și se tipăresc câmpurile tabloului. 9. Se demonstrează diferența dintre interpretarea unui octet ca întreg cu semn, respectiv întreg fără semn. Legat de problema interpretării diferite a unui număr întreg cu semn față de unul fără semn, este de menționat faptul că dacă dorim să decodificăn N octeți ca o valoare fără semn, atunci rezultatul decodificării trebuie plasat într-un tip de primitivă întreagă care utilizează cel puțin N+1 octeți [1]. 4. Partea practică 1. Scrieți, compilați și rulați pe calculatorul gazdă local programul specificat la punctul 3. Interpretați rezultatul. 2. Procedura precedentă reprezintă o codificare ”manuală” a datelor într-un mesaj, dar Java dispune și de mecanisme mai ușor de folosit. Rescrieți aplicația folosind pentru compunerea mesajului clasele DataOutputStream și ByteArrayOutputStream. Prima pune la dispoziție metodele writeByte(), writeShort(), writeInt(), writeLong() care inserează într-un flux o valoare întreagă reprezentată în complement de 2 pe numărul de octeți corespunzător în ordinea big-endian. A doua dispune de metoda toByteArray() care convertește o secvență de octeți din flux întrun tablou de octeți. BIBLIOGRAFIE 1. K. L. Calvert, M. J. Donahoo, TCP/IP Sockets in Java, Morgan Kaufmann, 2008. 2. G. Coulouris, J. Dollimore, T. Kindberg, Distributed Systems: Concepts and Design, AddisonWesley, 2011. 3. Tutorial Java: www.learn-java-tutorial.com.