Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pc:laboratoare:08 [2019/10/25 14:44]
127.0.0.1 external edit
pc:laboratoare:08 [2022/05/02 11:48] (current)
vlad_andrei.badoiu [Exerciții]
Line 1: Line 1:
-===== Laboratorul 08=====+===== Laboratorul 08 - TCP și multiplexare I/O ===== 
 +Responsabili:​ Valeriu Stanciu, Silviu Pantelimon, Radu-Ioan Ciobanu
  
 +==== Obiective ====
 +
 +În urma parcurgerii acestui laborator, studenţii vor fi capabili să utilizeze multiplexarea pentru crearea unor aplicații server ce pot răspunde cererilor primite de la un număr variabil de clienți.
 +
 +==== Multiplexarea I/O ====
 +
 +Serverul din cadrul laboratorului trecut putea să lucreze cu un număr fixat de clienți, respectând o ordine strictă a operațiilor. La început, apela //​accept()//​ pentru toți clienții (1 sau 2), apoi primea și trimitea date pe sockeții activi (într-o anumită ordine). Altfel, ar apărea o problemă atunci când serverul se află blocat într-un apel //​accept()//​ și totuși dorește să primească date cu //recv()// în același timp (sau invers). Situația devine și mai complicată dacă dorim ca serverul să funcționeze cu un număr variabil de clienți, care să se poată conecta/​deconecta la/de la server oricând, chiar și după ce alți clienți au început să trimită/​primească date.
 +
 +În cazul clienților,​ am văzut data trecută că aveau, de asemenea, o ordine precisă a operațiilor:​ citire de la tastatură, trimitere pe socket, citire de pe socket, afișare. Din acest motiv, când primul client trimitea un mesaj, cel de-al doilea nu îl primea până nu trimitea și el un mesaj la rândul lui.
 +
 +Am întâlnit trei tipuri de apeluri blocante, care sunt de fapt citiri din descriptori (de sockeți sau fișiere):
 +  * //​accept()//​ - citire de pe socketul inactiv pe care ascultă serverul
 +  * //recv()// / //​recvfrom()//​ - citire de pe sockeți activi
 +  * //scanf()// / //fgets()// / //read(0, ...)// - citire de la tastatură.
 +
 +După cum am putut vedea, toate situațiile prezentate sunt generate, de fapt, de o problemă comună: un program se află blocat într-o citire pe un descriptor, dar primește date pe un alt descriptor. Avem nevoie, deci, de un mecanism care să ne permită să citim exact de pe descriptorul pe care au venit date.
 +
 +Solutia este reprezentată de funcția //​select()//,​ care ajută la controlarea mai multor descriptori (de fișiere sau sockeți) în același timp. Pentru mai multe informații,​ putem accesa [[http://​man7.org/​linux/​man-pages/​man2/​select.2.html|man 2 select]].
 +
 +<code C>
 +#include <​sys/​time.h>​
 +#include <​sys/​types.h>​
 +#include <​unistd.h>​
 +
 +int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
 +</​code>​
 +
 +Argumentele funcției //​select()//:​
 +  * //numfds// - valoarea cea mai mare + 1 a unui descriptor din oricare cele 3 mulțimi
 +  * //readfds// - mulțimea cu descriptori de citire
 +  * //​writefds//​ - mulțimea cu descriptori pentru scriere
 +  * //​exceptfds//​ - mulțimea cu descriptori pentru care sunt în așteptare excepții
 +  * //timeout// - timpul maxim în care apelul //​select()//​ trebuie să întoarcă (daca este //NULL//, se blochează până când apare un eveniment pe cel puțin un descriptor).
 +
 +Apelul //​select()//​ primește ca argumente pointeri spre trei mulțimi de descriptori (de citire, scriere sau excepții). Dacă utilizatorul nu este interesat de anumite condiții, argumentul corespunzător va fi setat la //NULL// (la server, pe noi ne interesează doar mulțimea de citire).
 +<​note>​Atenție,​ //​select()//​ modifică mulțimile de descriptori:​ după apel, ele vor conține numai descriptorii pe care s-au primit date. Astfel, trebuie să ținem copii ale mulțimilor originale.</​note>​
 +
 +
 +Fiecare mulțime de descriptori este, de fapt, o structură care conține un tablou de măști de biți. Dimensiunea tabloului este dată de constanta //​FD_SETSIZE//​ (o valoare uzuală a acestei constante este 1024; pentru lucrul cu descriptori mai mari de aceasta valoare, recomandam [[http://​man7.org/​linux/​man-pages/​man2/​poll.2.html|poll()]]). Pentru lucrul cu mulțimile de descriptori preluate ca argumente de apelul //​select()//,​ se pot folosi o serie de macro-uri:
 +  * //void FD_ZERO(fd_set *set)// - șterge în întregime mulțimea de descriptori de fișiere //​set// ​
 +  * //void FD_SET(int fd, fd_set *set)// - adaugă descriptorul //fd// în mulțimea //set//
 +  * //void FD_CLR(int fd, fd_set *set)// - șterge descriptorul //fd// din mulțimea //set//
 +  * //int FD_ISSET(int fd, fd_set *set)// - testează dacă descriptorul //fd// aparține sau nu mulțimii //set//; întoarce o valoare diferită de 0 in caz afirmativ
 +
 +Timeout-ul este de tipul //struct timeval//, care are definiția următoare:
 +<code C>
 +#include <​sys/​time.h>​
 +struct timeval {
 +  long    tv_sec; ​        /* seconds */
 +  long    tv_usec; ​       /* microseconds */
 +};
 +</​code>​
 +
 +Un exemplu de server TCP ce folosește apelul //​select()//​ pentru multiplexare se află în arhiva suport a acestui laborator. În mulțimea de citire a serverului se află inițial descriptorul pentru socketul inactiv. Apoi, pe măsură ce se conectează clienții, în mulțime vor fi adăugați și descriptorii pentru sockeții activi (cei pe care se trimit/​primesc date la/de la clienți).
 +
 +<​note>​
 +Functia **send** va intrerupe programul daca este apelata cu un descriptor de fisiere al unei conexiuni inchise. Pentru a evita acest efect pasati ca parametru **flags**, valoarea: **MSG_NOSIGNAL**
 +</​note>​
 +
 +==== Exerciții ====
 +
 +Pornind de la codul disponibil [[https://​gitlab.cs.pub.ro/​protocoale-de-comunicatie/​pcom-laboratoare-public/​-/​tree/​master/​lab8|aici]],​ aveți de implementat următoarele cerințe:
 +
 +  - Modificați programul client astfel încât să se comporte ca în laboratorul trecut (să citească de la tastatură și să trimită serverului, apoi să primească de la server și să afișeze). Modificați și programul server astfel încât să funcționeze cu 2 clienți: să trimită clientului 1 ce a primit de la clientul 2 și invers.
 +  - Modificați programul client astfel încât să multiplexeze între citirea de la tastatură (vom adăuga descriptorul 0 în mulțimea de citire pentru //​select()//​) și citirea de pe socket. Din acest moment, eliminăm neajunsul ordonării acțiunilor clienților.
 +  - Modificați programul server ca să funcționeze cu mai multi clienți. Clienții vor trimite în mesaj și destinația mesajului (acest lucru se poate face și fără modificarea codului clienților,​ vedeți exemplul). În cadrul acestui laborator, putem folosi descriptorul socketului întors de //​accept()//​ ca identificator pentru un client (în aplicații reale, clienții nu au acces la aceste valori). Exemplu: clientul cu socketul 5 poate trimite (mesaj citit de la tastatură) "4 ce mai faci", iar serverul parsează mesajul și îl trimite clientului conectat pe socketul 4 (puteți să lucrați și cu o structură de mesaj).
 +  - **(Bonus)** Modificați programul server ca să trimită (la conectarea) clienților lista cu clienții deja conectați, apoi să trimita clienților conectați update-uri despre ce client a mai intrat/​ieșit din sistem (puteți să folosiți același sistem de identificatori pentru clienți ca la punctul 3).
 +
 +
 +<​note>​
 +O posibila solutie a laboratorului se gaseste [[https://​ocw.cs.pub.ro/​courses/​_media/​pc/​laboratoare/​lab8_sol.zip|aici]]
 +</​note>​
  
pc/laboratoare/08.1572003870.txt.gz · Last modified: 2020/03/29 15:52 (external edit)
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0