Laboratoare:Operatii IO avansate (2)

= Windows - I/O Completion Ports =

Mecanismul de completion ports este cel mai scalabil dintre toate cele prezentate până acum. Un server care folosește completion ports poate face față la foarte multe (zeci de mii) conexiuni simultan, fără probleme prea mari. Celelalte metode își ating limitările cu mult înainte.

Un completion port este un obiect în kernel cu care se asociază alți descriptori (fișiere, sockeți) și prin intermediul căruia se transmit notificările de completare a unor operații asincrone lansate anterior. Un completion port are asociat un pool de worker threads. Aceste threaduri așteaptă să primească notificări de completare a operațiilor asincrone. În momentul în care un thread primește o notificare va deveni activ și va lucra o perioada până se va întoarce din nou așteptând următoarea notificare.

Crearea unui completion port
Pentru crearea unui completion port se folosește funcția CreateIoCompletionPort ca în exemplul de mai jos:

Adăugarea unui descriptor la completion port
Pentru adăugarea unui descriptor deschis cu opțiunea de overlapped I/O la completion port se folosește tot funcția CreateIoCompletionPort. În această situație primul argument va fi handle-ul fișierului/socketului care se dorește adăugat, iar al doilea handle-ul completion port-ului obținut la crearea acestuia:

După cum se observă, în cazul creării unui completion port al doilea argument este NULL. La adăugarea unui handle de fișier la completion port al doilea argument este handle-ul de completion port. Al treilea argument este o cheie care va fi folosită pentru identificarea handle-ului în momentul receptionării unei notificări.

Așteptarea încheierii unei operații asincrone
Thread-urile worker sunt folosite pentru așteptarea încheierii operațiilor asincrone și prelucrările ulterioare. Thread-urile vor primi notificări de la handle-ul completion port-ului folosind funcția GetQueuedCompletionStatus:

Pe baza cheii obținute se poate determina handle-ul care a generat notificarea.

Exemplu de folosire completion ports
În exemplul de mai jos este prezentată folosirea mecanismului de completion ports în cazul operațiilor asincrone pe sockeți. Exemplul este similar cu cel prezentat în secțiunile dedicate funcțiilor de multiplexare I/O pe Linux. Există un thread worker care va aștepta primirea notificărilor la completion port, iar thread-ul principal va fi responsabil cu primirea de cereri de conexiune (apeluri accept).

= Linux - operații asincrone =

În mod clasic, operațiile de lucru cu datele aflate pe suporturi externe înseamnă utilizarea apelurilor sincrone de tipul read, write și fsync. Aceste apeluri garantează faptul că la terminarea apelului datele sunt scrise/citite (de) pe suportul extern (sau în cache-ul asociat). Un astfel de apel poate întârzia continuarea fluxului de instrucțiuni curent până la terminarea operației cerute.

Pentru fire de execuție care nu au nevoie frecvent de operații de intrare-ieșire, această abordare funcționează. În schimb, pentru aplicații specializate pe lucrul cu memoria externă, folosirea apelurilor sincrone (blocante) încetinește semnificativ execuția programului. Timpul necesar unui acces la memorie (cu atât mai mult memoria externă) depășește cu mult timpul de execuție a unei instrucțiuni strict aritmetice. <!--

Operații asincrone POSIX
Pentru aplicații specializate pe lucrul cu memoria externă există apeluri asincrone de tipul aio_read</tt> și aio_write</tt>. Aceste operații lansează în execuție o operație de lucru cu memoria externă și se întorc imediat; sistemul continuă execuția operației independent (și în paralel cu) firul de execuție care a lansat operația. Momentul terminării execuției este anunțat înapoi aplicației prin trimiterea unui semnal sau prin executarea unei rutine de către un thread.

Operațiile de forma aio_read</tt> și aio_write</tt> sunt operații POSIX. Funcțiile asociate cu operații asincrone sunt definite în aio.h</tt> și își au codul în biblioteca librt</tt>, cu care aplicațiile trebuie legate (-lrt</tt> la linking).

AIO control block
Toate operațiile asincrone de intrare-ieșire sunt controlate printr-o structură de date numită AIO control block în forma struct iocb</tt>, definită în aio.h</tt>. Prezentam in continuare o varianta usor modificata fata de cea din fisierul antet, pentru o mai buna intelegere:

Un exemplu de inițializare a unei structuri aiocb este prezentat în continuare:

Un parametru de tip struct aiocb</tt> va fi un element prezent la apelul oricărei operații asincrone.

Scrierea și citirea asincronă
Apelurile echivalente cu read</tt> și write</tt> pentru operații asincrone sunt aio_read și aio_write, descrise în continuare.

Aceste apeluri inițiază o operație asincronă și returnează imediat după punerea cererii în coada de operații, sau dacă întâlnește o eroare. Comportamentul operației este descris de câmpurile structurii aiocbp</tt> transmise ca parametru.

Astfel, pentru citire, primii aio->aio_nbytes</tt> octeți ai fișierului aio->aio_fildes</tt> sunt scriși în bufferul indicat de aio->aio_buf</tt>. Citirea începe de la poziția absolută în fișier dată de offsetul <tt>aio->aio_offset</tt>. Dacă prioritizarea operațiilor asincrone este posibilă, valoarea <tt>aio->aio_reqprio</tt> ajustează prioritatea operației înainte de punerea ei în coadă. Procesul apelant este notificat la terminarea cererii de citire în conformitate cu valoarea <tt>aio->aio_sigevent</tt>.

La scriere, primii <tt>aio->aio_nbytes</tt> octeți ai fișierului <tt>aio->aio_fildes</tt> sunt suprascriși cu datele memorate în bufferul indicat de <tt>aio->aio_buf</tt>. Scrierea începe de la poziția absolută în fișier dată de offsetul <tt>aio->aio_offset</tt>. Dacă prioritizarea operațiilor asincrone este posibilă, valoarea <tt>aio->aio_reqprio</tt> ajustează prioritatea operației înainte de punerea ei în coadă. Procesul apelant este notificat la terminarea cererii de citire în conformitate cu valoarea <tt>aio->aio_sigevent</tt>.

Pentru interogarea statusului unei cereri se folosesc apelurile <tt>aio_error</tt> sau <tt>aio_return</tt>, descrise într-o secțiune ulterioară.

Operații asincrone multiple
Pe lângă funcții clasice de lucru cu apelurile IO asincrone, standardul pune la dispoziție și o funcție care poate iniția mai multe operații asincrone, comportându-se ca un set de <tt>aio_read</tt> și <tt>aio_write</tt> într-un singur apel. Apelul lio_listio primește o listă de structuri de tip <tt>control block</tt> și le lansează în execuție:

Apelul preia lista <tt>list</tt> de cereri de tip <tt>struct aiocb</tt>, în număr de <tt>nent</tt>. Cererile IO din lista <tt>list</tt> pot fi efectuate asupra oricărui fișier, inclusiv același de mai multe ori. Tipul operației este memorat separat pentru fiecare cerere în câmpul <tt>aio_lio_opcode</tt> din control block. Câmpul mode determină comportamentul apelului după punerea în coadă a tuturor operațiilor.

Interogarea stării AIO
Întrucât apelurile de inițiere a operațiilor asincrone returnează imediat, ele nu pot oferi informații ulterioare despre starea cererii. Apelurile <tt>aio_error</tt> și <tt>aio_return</tt> permit interogarea unei cereri pentru a afla dacă aceasta a fost executată, și în caz că a fost, cu ce rezultat.

Un exemplu de folosire a acestor apeluri este prezentat în continuare (copiat cu nerușinare din pagina de manual a HP-UX):

Așteptarea operațiilor AIO
În unele cazuri așteptarea notificărilor care anunță execuția cererilor nu este o soluție bună pentru o aplicație, de exemplu pentru că aceasta nu vrea să fie întreruptă în orice moment. Pentru asemenea cazuri aplicațiile pot folosi apelul <tt>aio_suspend</tt> care supendă thread-ul curent până la încheierea operațiilor asincrone primite ca argument:

-->

Linux AIO
Implementarea curentă (glibc 2.7) a operațiilor POSIX asincrone în Linux este realizată în user-space cu ajutorul thread-urilor POSIX. Totuși, nucleul Linux 2.6 oferă interfață (apeluri de sistem) pentru operații asincrone implementate la nivelul nucleului. Există, în acest sens, biblioteci specializate care oferă interfața POSIX AIO peste API-ul nativ al nucleului Linux 2.6 (spre exemplu PAIOL - POSIX Asynchronous I/O for Linux).

Alternativ, se poate folos interfața syscall care permite accesul la apelurile de sistem expuse de kernel în absența acestora din biblioteca standard C.

Apelurile de sistem care asigură interfața cu implementarea AIO în Linux sunt prezentate, în forma de wrapper syscall mai jos:

Din păcate, documentația asociată Linux AIO este destul de redusă, paginile de manual fiind principala formă de documentare. Un exemplu complet de utilizare a interfeței Linux AIO este exemplul lui Davide Libenzi.

Structuri de bază Linux AIO
Similară cu <tt>struct aiocb</tt> este structura <tt>struct iocb</tt> folosită pentru încapsularea unei operații asincrone. Structura este definită în header-ul aio_abi.h. Pentru folosirea acesteia o aplicația va include <tt>linux/aio_abi.h</tt>. Un exemplu de inițializare a acestei structuri este:

Comenzi posibile sunt: <tt>IOCB_CMD_PWRITE</tt>, <tt>IOCB_CMD_PREAD</tt>, <tt>IOCB_CMD_PREADV</tt>, <tt>IOCB_CMD_PWRITEV</tt> și altele. Comenzile care se încheie cu <tt>V</tt> folosesc vectored I/O.

Context AIO
Orice operație sau set de operații Linux AIO sunt identificate printr-o valoare de tipul aio_context_t ce reprezintă un context de operații asincrone.

Inițializarea, respectiv distrugerea contextului se realizează cu ajutorul funcțiilor io_setup și io_destroy:

Operații AIO
Pentru realizarea unei operații asincrone se folosește funcția io_submit. Această funcția declanșează pornirea operațiilor asincrone definite în vectorul de pointeri de structuri <tt>struct iocb</tt> primit ca argument. Din punct de vedere al funcționalității, funcția este similară lio_listio. Nu există apeluri similare <tt>aio_read</tt> sau <tt>aio_write</tt>, tipul operației fiind descris de câmpul <tt>aio_lio_opcode</tt> al structurii <tt>struct iocb</tt>. Fiind un apel asincron, la fel ca funcțiile <tt>aio_read</tt>, <tt>aio_write</tt> și <tt>lio_listio</tt>, io_submit nu blochează procesul curent.

Pentru așteptarea încheierii unei operații AIO și obținerea de informații despre rezultatul acesteia se folosește funcția io_getevents. Funcția folosește structura struct io_event pentru a obține informații despre încheierea unei operații asincrone. Un exemplu de utilizare este:

Integrarea Linux AIO cu eventfd
Este utilă folosirea apelurilor de multiplexare I/O (<tt>select</tt>, <tt>poll</tt>, <tt>epoll</tt>) și pentru așteptarea încheierii operațiilor asincrone. Pentru aceasta, interfața AIO a Linux 2.6 permite integrarea API-ului de operații asincrone cu mecanismul <tt>eventfd</tt>.

Pentru aceasta se configurează flag-ul <tt>IOCB_FLAG_RESFD</tt> iar câmpul <tt>resfd</tt> al structurii <tt>struct iocb</tt> va conține un descriptor <tt>eventfd</tt> ce va fi notificat în momentul încheierii operației asincrone. Apelul <tt>io_getevents</tt> este în continuare util pentru a obține informații despre încheierea operațiilor. Mecanismul <tt>eventfd</tt> oferă doar mecanismul de așteptare a acestora.

Citirea din descriptorul <tt>eventfd</tt> reprezintă numărul de operații I/O încheiate. Această valoare va fi, de obicei, folosită ca al doilea și al treilea argument al io_getevents.

Folosind integrarea operațiilor asincrone cu <tt>eventfd</tt> și mecanismele de multiplexare I/O (<tt>select</tt>, <tt>poll</tt>, <tt>epoll</tt>) se poate aștepta unificat încheierea unei operații asincrone sau sosirea de date pe sockeți. (Hint: util pentru Tema 5)

= Zero-copy I/O =

Linux - splice
Este un apel de sistem ce permite transferul de date intre 2 descriptori de fișier, din care cel puțin unul este pipe. Avantajul este ca nu se folosește un buffer (byte array) în userspace. Pentru o scurtă introducere puteți citi descrierea de apelului splice pe Wikipedia, iar pentru o mai bună aprofundare a avantajului oferit de apel, citiți descrierea de pe LKML.


 * dacă descriptorul <tt>fd_in</tt> reprezintă un pipe, atunci pointer-ul la offset <tt>off_in</tt> trebuie să fie NULL
 * altfel:
 * dacă <tt>off_in</tt> este NULL, atunci datele sunt citite de la <tt>fd_in</tt> de la offset-ul curent, acesta modificându-se corespunzător
 * altfel, <tt>off_in</tt> trebuie sa fie un pointer la un întreg care reprezintă offset-ul de start de la care se va face citirea, iar offset-ul propriu descriptorului <tt>fd_in</tt> rămâne neschimbat
 * comportamentul de mai sus este valabil și pentru <tt>fd_out</tt> și <tt>off_out</tt>, la scriere
 * parametrul <tt>len</tt> specifica numărul maxim de octeți transferați
 * masca de biți <tt>flags</tt> poate specifică o operație non-blocantă sau hint-uri pentru nucleu. Citiți pagina de manual a funcției pentru detalii.

Exemplu:

Windows - TransmitFile
Apelul TransmitFile este folosit pentru a eficientiza transmiterea de fișiere în rețea. Transmit File folosește cache-ul sistemului de operare. Este o operație zero-copy: nu necesită alocarea de buffere în user-space și diminuează numărul de apeluri de sistem.

Pentru a transmite un fișier, acesta trebuie deschis folosind flag-ul <tt>FILE_FLAG_OVERLAPPED</tt>. Apelul TransmitFile primește ca argument socket-ul pe care se realizează comunicația și handle-ul fișierului. Un exemplu de utilizare a apelului este prezentat mai jos:

O funcție similară este funcția TransmitPackets care transmite date stocate în memorie pe un socket folosind cache-ul intern al sistemului de operare. Datele sunt reprezentate de o structură TRANSMIT_PACKETS_ELEMENT.

POSIX - sendfile
Operația similară în POSIX este asigurată de funcția sendfile.

= Vectored I/O =

Vectored I/O (sau scatter/gather I/O) reprezintă o metodă prin intermediul căreia un singur apel permite scrierea de date din mai multe buffere către un flux de ieșire sau citirea de date de la un flux de ieșire în mai multe buffere. Bufferele sunt precizate ca un vector de buffere, de unde și denumirea de vectored I/O.

Apelurile din clasa vectored I/O sunt utile în momentul în care datele sunt disparate/dezasamblate în memorie și se dorește "concatenarea" acestora într-un singur flux de scriere sau "desfacerea" acestora dintr-un flux de citire. Un exemplu îl reprezintă pachetele de rețea în care headerele, datele și trailerele se găsesc, de obicei, în locații de memorie diferite pentru a facilita prelucrarea acestora. Folosirea Vectored I/O permite asamblarea/dezasamblarea pachetului în/din mai multe zone de memorie printr-o singură operație. Nu este nevoie de crearea unui buffer nou cu pachetele concatenate, drept pentru care Vectored I/O poate fi considerat o formă de zero-copy.

Apeluri:
 * UNIX: readv, writev.
 * Windows (fișiere) ReadFileScatter, WriteFileGather.
 * Windows (sockeți) WSARecv,WSASend.

readv/writev
Funcțiile readv și writev sunt folosite în sistemele Unix ca operații de tipul <tt>vectored I/O</tt>. Structura de bază folosită de aceste funcții este <tt>struct iovec</tt>:

Un apel <tt>readv</tt> sau <tt>writev</tt> va permite recepționarea/transmiterea unui număr de buffere reprezentate de structura <tt>struct iovec</tt>. Funcțiile întorc numărul total de octeți citiți sau scriși.

= Exerciții =

Exerciții de laborator

 * Folosiți arhiva de sarcini a laboratorului.
 * Puteți folosi fișierele tags din directoarele <tt>lin/</tt>, respectiv <tt>win/</tt> pentru o parcurgere rapidă a surselor folosind vim (fișierul <tt>tags</tt>) sau Emacs (fișierul <tt>TAGS</tt>).

Linux

 * 1) (3 puncte) Asynchronous I/O (KAIO)
 * 2) * Parcurgeți fișierul <tt>kaio.c</tt>.
 * 3) * Completați funcțiile zonele lipsă pentru a programa scrierea a 4 fișiere cu numele date de variabila <tt>files</tt>.
 * 4) * Folosiți API-ul KAIO (<tt>io_setup</tt>, <tt>io_destroy</tt>, <tt>io_submit</tt>, <tt>io_getevents</tt>).
 * 5) * Folosiți _doar_ <tt>io_getevents</tt> pentru așteptarea încheierii operațiilor asincrone.
 * Hints:
 * 1) ** Parcurgeți secțiunea Linux AIO.
 * 2) ** Consultați exemplul lui Davide Libenzi.
 * 3) * Compilați și rulați programul. Va trebui să aveți 4 fișiere de dimensiune 8192 octeți create în <tt>/tmp</tt>.
 * 4) (1 punct) Asynchronous I/O (KAIO) + eventfd
 * 5) * Copiați fișierul <tt>kaio.c</tt> de la exercițiul anterior.
 * 6) * Folosiți <tt>eventfd</tt> pentru așteptarea operațiilor asincrone.
 * 7) ** Completați funcția <tt>wait_aio</tt>.
 * 8) ** Folosiți flag-ul <tt>IOCB_FLAG_RESFD</tt> și completați corespunzător câmpul <tt>aio_resfd</tt> al structurii <tt>struct iocb</tt>.
 * Hints:
 * 1) ** Parcurgeți secțiunea Linux AIO.
 * 2) ** Consultați exemplul lui Davide Libenzi.
 * 3) * Compilați și rulați programul. Va trebui să aveți 4 fișiere de dimensiune 8192 octeți create în <tt>/tmp</tt>.
 * 4) (1.5 puncte) Vectored I/O
 * 5) * Parcurgeți fișierele <tt>../sock_util/sock_util.h</tt>, <tt>../sock_util/sock_util.c</tt>, <tt>vectored_client.c</tt> și <tt>vectored_server.c</tt>.
 * 6) * Completați funcțiile <tt>send_buffers</tt>, respectiv <tt>receive_buffers</tt> pentru a realiza o comunicație client-server folosind vectored I/O (operațiile readv și writev).
 * 7) ** Va trebuie să transmiteți/recepționați bufferele <tt>header</tt>, <tt>data</tt>, <tt>trailer</tt>.
 * 8) * Pentru testare, porniți serverul pe o consolă și clientul în altă consolă.
 * 9) * Mesajul transmis de client va trebui să fie identic cu cel primit de server.
 * Hints:
 * 1) ** Parcurgeți secțiunea Vectored I/O.

Windows

 * 1) (1 punct) I/O completion ports
 * 2) * Anlizați conținutul fișierului <tt>include/iocp.h</tt>.
 * 3) * Intrați în directorul <tt>iocp/</tt>.
 * 4) * Analizați conținutul fișierului <tt>iocp.c</tt>.
 * 5) * Completați cele 4 funcții definite în <tt>iocp.c</tt>.
 * 6) * Nu compilați fișierul. Acesta va fi compilat la exercițiul următor.
 * (Hints)
 * 1) ** Parcurgeți secțiunea Windows - I/O Completion Ports.
 * 2) (3 puncte) Operații I/O asincrone cu I/O completion ports
 * 3) * Intrați în directorul <tt>aio_cp/</tt>.
 * 4) * Analizați conținutul fișierului <tt>aio.c</tt>.
 * 5) * Scopul exercițiului este folosirea I/O completion ports pentru așteptarea încheierii operațiilor I/O asincrone (overlapped I/O).
 * 6) * Implementați funcțiile <tt>init_io_async</tt>, <tt>do_io_async</tt> și <tt>wait_io_async</tt>.
 * Hints:
 * 1) ** Pentru inițializarea structurilor <tt>OVERLAPPED</tt> se recomandă implementarea funcției <tt>init_overlapped</tt>.
 * 2) ** Urmăriți indicațiile din exemplele de cod.
 * 3) ** Parcurgeți secțiunea Windows - I/O Completion Ports
 * 4) ** Parcurgeți secțiunea Windows - I/O asincron (overlapped) a laboratorului trecut.
 * 5) * Compilați și rulați programul.
 * 6) (1.5 puncte) Zero-copy (<tt>TransmitFile</tt>)
 * 7) * Intrați în directorul <tt>transmit/</tt>.
 * 8) * Parcurgeți fișierele <tt>sock_util/sock_util.h</tt>, <tt>sock_util/sock_util.c</tt>, <tt>server.c</tt> și <tt>transmit_client.c</tt>.
 * 9) * Completați funcțiile care lipsesc.
 * 10) ** Clientul face transmitere folosind <tt>TransmitFile</tt>.
 * 11) ** Serverul recepționează informațiile folosind <tt>recv</tt> și <tt>WriteFile</tt>.
 * Hints:
 * 1) ** Parcurgeți secțiunea Windows - TransmitFile.
 * 2) ** Nu este nevoie să folosiți câmpul <tt>LPOVERLAPPED</tt> al funcției <tt>TransmitFile</tt> (puteți folosi <tt>NULL</tt>).

Extra

 * 1) (Windows) Operații asincrone pe sockeți și I/O completion ports.
 * 2) * Intrați în directorul <tt>sock_cp/</tt>.
 * 3) * Parcurgeți fișierele <tt>sock_util/sock_util.h</tt>, <tt>sock_util/sock_util.c</tt>, <tt>client.c</tt>.
 * 4) * Analizați conținutul fișierului <tt>iocp_server.c</tt>.
 * 5) * Completați fișierul <tt>iocp_server.c</tt>.
 * 6) * Scopul exercițiului este crearea unui server care să multiplexeze mai multe conexiuni de la clienți folosind operații asincrone pe socketi și I/O completion ports. Serverul _doar_ va citi informații de la clienți pe care le va afișa la ieșirea standard.
 * 7) * Serverul dispune de două thread-uri: un thread acceptă conexiuni pe care le adaugă la I/O completion port și inițiază operație asincronă de citire; un thread worker așteaptă notificarea de la I/O completion port, o tratează și apoi inițiază o nouă operație asincronă.
 * Hints:
 * 1) ** Folosiți structura <tt>struct overlappedContext</tt> pentru a reține informațiile despre un apel asincron.
 * 2) ** Se recomandă folosirea structurii pe post de cheie pentru a putea demultiplexa notificarea sosită la I/O completion port.
 * 3) * Urmăriți indicațiile din comentariile din cod.
 * 4) * Folosiți fișierul <tt>Makefile</tt> pentru a obține executabilele <tt>client.exe</tt> și <tt>iocp_server.exe</tt>.
 * 5) * Pentru testare porniți serverul și mai multe instanțe de client.
 * 6) (Linux) Funcții de multiplexare I/O
 * 7) * Intrați în directorul <tt>include/</tt>.
 * 8) ** Analizați conținutul fișierelor <tt>mpx_types.h</tt>, respectiv <tt>mpx_funs.h</tt>.
 * Hints:
 * 1) *** Funcțiile descrise în <tt>mpx_funs.h</tt> se doresc a fi generice. Uneori implementarea va fi ineficientă.
 * 2) *** Separația <tt>mpx_funs.h</tt> de <tt>mpx_types.h</tt> este puțin forțată, dar menține o anumită simplitate.
 * 3) * Intrați în directorul <tt>mpx/</tt>.
 * 4) * Analizați conținutul fișierelor <tt>mpx_select.c</tt>, <tt>mpx_poll.c</tt> respectiv <tt>mpx_epoll.c</tt>.
 * 5) * Implementați funcțiile definite în <tt>mpx_select.c</tt>.
 * Hint:
 * 1) ** Urmăriți indicațiile din comentariile asociate funcțiilor.
 * 2) * Implementați funcțiile definite în <tt>mpx_poll.c</tt>.
 * Hints:
 * 1) ** Se presupune că se așteaptă doar notificări pentru citire (<tt>POLLIN</tt>) sau scriere (<tt>POLLOUT</tt>).
 * 2) ** Urmăriți indicațiile din comentariile asociate funcțiilor.
 * 3) * Implementați funcțiile definite în <tt>mpx_epoll.c</tt>.
 * Hints:
 * 1) ** Interfața oferită peste <tt>epoll</tt> presupune așteptarea unui singur tip de eveniment pe un descriptor (<tt>EPOLLIN</tt> sau <tt>EPOLLOUT</tt>). Funcția <tt>epoll_del_</tt> va elimina complet descriptorul fișierului.
 * 2) ** Urmăriți indicațiile din comentariile asociate funcțiilor.
 * 3) (Linux) Multiplexare I/O peste socketi
 * 4) * Intrați în directorul <tt>sock_mpx/</tt>.
 * 5) * Parcurgeți fișierele <tt>../sock_util/sock_util.h</tt>, <tt>../sock_util/sock_util.c</tt>, <tt>client.c</tt>.
 * 6) * Analizați conținutul fișierului <tt>mpx_server.c</tt>.
 * 7) * Completați fișierul <tt>mpx_server.c</tt>.
 * 8) * Scopul exercițiului este crearea unui server care să multiplexeze mai multe conexiuni de la clienți. Serverul _doar_ va citi informații de la clienți pe care le va afișa la ieșirea standard.
 * Hints:
 * 1) ** Serverul va multiplexa socketul listener și socketii de conectare. Dacă socketul listener este gata de citire atunci se acceptă o nouă conexiune; dacă un socket de conectare e gata de citire se citește informația de la acesta.
 * 2) ** Urmăriți indicațiile din comentariile din cod.
 * 3) * Folosiți fișierul <tt>Makefile</tt> pentru a obține executabilele <tt>client</tt> și <tt>mpx_server</tt>.
 * 4) * Pentru testare porniți serverul și mai multe instanțe de client.

= Soluții =


 * Soluții exerciții laborator 11

= Resurse utile =


 * Linux AIO
 * linux-aio mailing list
 * Biblioteca PAIOL - POSIX Asynchronouos I/O for Linux
 * libaio - O biblioteca (veche) pentru operații AIO cu suport în nucleul Linux
 * eventfd
 * Exemplu de cod integrare eventfd cu Linux AIO
 * select/poll/epoll
 * Linux System Programming - Chapter 2 - File I/O (Multiplexed I/O)
 * Linux System Programming - Chapter 4 - Advanced File I/O (The Event Poll Interface)
 * Beginning Linux Programming, 4th Edition - Chapter 15: Sockets (select)
 * Un articol interesant despre epoll, la un nivel mai inalt
 * Windows Overlapped I/O
 * Syncrhonization and Overlapped Input and Output
 * Synchronous and Asynchronous I/O
 * Programming with Asynchronous Sockets
 * Asynchronous Input/Output and Completion Ports - Windows System Programming, 3rd Edition - Chapter 14
 * Windows I/O Completion Ports
 * MSDN - I/O Completion Ports
 * Inside I/O Completion Ports
 * O introducere în Completion Ports
 * Tutorial IOCP și sockeți
 * General
 * Asynchronous I/O - Wikipedia
 * libevent - bibliotecă de operații asincrone
 * C10K - Problema celor 10.000 de clienți