This is an old revision of the document!
Responsabili: Valeriu Stanciu, Silviu Pantelimon, Radu-Ioan Ciobanu
Î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.
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):
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 man 2 select.
#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);
Argumentele funcției select():
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).
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 poll()). Pentru lucrul cu mulțimile de descriptori preluate ca argumente de apelul select(), se pot folosi o serie de macro-uri:
Timeout-ul este de tipul struct timeval, care are definiția următoare:
#include <sys/time.h> struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ };
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).
Pornind de la codul disponibil aici, aveți de implementat următoarele cerințe: