z8t4td
-Apeluri si comunicatia intre procese sub sistemul de operare-
-Berkeley Unix-
Scopul acestui articol este acela de a crea o privire de ansamblu asupra comunicatiilor interprocese sub Berkeley Unix. Va fi acordata o aten tie speciala acelor apeluri de sistem care sunt in legatura cu crearea, gestiunea si utilizarea soclurilor. De asemenea va fi discutata problema semnalelor si a apelurilor din alte sisteme de operare, ceea ce va folosi celor ce lucreaza in proiectarea retelelor. Mai multe informatii despre toate apelurile de sistem mentionate mai jos pot fi gasite in Manualul
Programatorului Unix.
1. Crearea soclurilor
------------------
Cel mai general mecanism de comunicatie interproces oferit de Berkeley
Unix este soclul ('socket'- engl.). Un soclu este elemtul primar pentru comunicatie. Doua procese pot comunica creand socluri si trimitand mesaje intre ele, prin intermediul acestora. Exista mai multe tipuri de socluri, diferite prin modul in care este definit spatiul de adresa si prin tipul de
comunicatie ce se poate stabili intre socluri. Un soclu este definit in mod
unic de o tripla <domeniu, tip, protocol>. Pentru a folosi un soclu la distanta, trebuie sa-i asignam un nume. Forma pe care acest nume o reprezinta este determinata de domeniul de comunicatii sau de familia de adrese de care apartine soclul. De asemenea exista un tip abstract sau stil
de comunicatie asociat cu fiecare soclu. Acesta determina semantica comuni catiei pentru soclul respectiv. In sfarsit, exista un protocol specific care
este utilizat impreuna cu soclurile. Un soclu poate fi creat cu apelul sistem "socket", specificand adresa de familie dorita (domeniul),
tipul soclului si protocolul folosit de acesta :
int socket_descriptor,domain,type,protocol; socket_descriptor=socket(domain,type,protocol);
Acest apel returneaza un intreg scurt, pozitiv, numit descriptor de soclu, care poate fi utilizat ca parametru pentru a apela (adresa) soclul in apeluri
sistem ulterioare. Descriptorii de soclu sunt similari descriptorilor de fi siere returnati de apelul sistem "open". Fiecare apel "open"
sau "socket" va returna cel mai mic intreg nefolosit. Astfel, un numar dat semnifica fie
un fisier deschis, fie un soclu sau nimic (dar niciodata amandoi termenii).
Descriptorii de soclu sau cei de fisier pot fi utilizati interschimbabil in
multe apeluri sistem.
De exemplu, apelul sistem "close" e utilizat pentru a distruge soclurile.
1.1. Domenii
------ Domeniul de comunicatie sau familia de adrese de care apartine un soclu specifica un anumit format de adrese. Toate operatiile cu un soclu creat vor interpreta adresa furnizata in concordanta cu acest format specificat.
Diferitele formate de adrese sunt definite ca constante in fisierul header
<sys/socket.h> (includeti neaparat <sys/types.h> inainte de <sys/socket.h>).
Ca exemple avem AF_UNIX (nume de cale Unix), AF_INET (adrese Internet DARPA) si AF_OSI (asa cum sunt specificate de standardele internationale OSI).
AF_UNIX si AF_INET sunt cele mai importante familii de adrese. Forma generala
a unei adrese este reprezentata de structura "sockaddr" definita in
<sys/socket.h> :
struct sockaddr A short sa_family; /* familia de adrese */ char sa_dataa14i;/* pana la 14 octeti ai adresei directe */
S
Cand se creaza un soclu, initial el nu are o adresa asociata. Oricum, deoarece un proces sau un "host" de la distanta nu poate gasi un soclu
daca nu are o adresa, este important sa legam o adresa de soclul creat. Un soclu
nu are un nume pana cand nu este legat explicit de o adresa prin apelul sistem "bind".
status=bind(sock,address,addrlen) int status; /* status returneaza 0 pentru succes, -1 in caz contrar */ int sock; /* descriptor returnat de apelul socket(,,) */ struct sockaddr *address; int addrlen; /* dimensiunea adresei in octeti */
Acest apel esueaza daca adresa este deja folosita, adresa nu este in formatul corect pentru familia de adrese specificata, sau soclul are deja asignata o adresa.
1.1.1. Domeniul UNIX
------------ In domeniul UNIX, un soclu e adresat de un nume de cale UNIX, care poate avea o lungime de pana la 108 caractere. Legarea unui nume de cale de un soclu rezida in alocarea unui INODE si a unei intrari a unui nume de cale in fisierul sistem. Aceasta necesita scoaterea numelui de cale din structura de fisiere (utilizand apelul sistem "unlink") cand soclul este distrus.
Fisierul creat este utilizat numai pentru a furniza un nume soclului si nu joaca nici un rol in transferurile de date. Cand utilizam socluri in domeniul
UNIX, este de preferat sa utilizam numai nume de cai pentru directoare
(ca /tmp) legate direct de discul local. Domeniul UNIX permite comunicatia numai pentru procese ce ruleaza pe aceiasi masina. Structura "sockaddr_un"
utilizata pentru a defini formatul de adresa UNIX poate fi gasita in
<sys/un.h>.
struct sockaddr_un A short sun_family; /* AF_UNIX */ char sun_patha108-4i; /* nume de cale */
S
Nota : Cand specificati lungimea adresei de domeniu UNIX pentru apeluri sis tem, utilizati "sizeof(struct sockaddr_un)". Utilizand lungimea unui
"sockaddr" apelul va esua.
1.1.2. Domeniul Internet
---------------- In domeniul DARPA Internet, adresa este alcatuita din doua parti - o adresa de "host" (care consista intr-un numar de retea si un numar
de "host") si un numar de port (cunoscut sub numele de "transport suffix"). Aceasta adresa de "host" permite comunicatia proceselor de pe diferite masini.
In schimb numarul de port functioneaza ca un "mail box" care permite
adrese multiple pe acelasi "host". Structura care descrie o adresa in domeniul
In ternet este definita in fisierul <netinet/in.h>.
struct sockaddr_in A short sin_family; /* AF_INET */ u_short sin_port; /* numarul de port */ struct in_addr sin_addr; /* evzi mai jos */ char sin_zeroa8i; /* neutilizat */
S struct in_addr A union A struct A u_char s_b1,s_b2,s_b3,s_b4; S S_un_b; struct A u_short s_w1,s_w2; S S_un_w; u_long S_addr;
S S_un;
#define s_addr S_un.S_addr /* poate fi folosit in majorita tea codurilor tcp/ip */
S
De multe ori este folositor de a se lega un serviciu specific la un port mai des folosit, permitand proceselor la distanta sa localizeze usor serverul folosit. Exemple de porturi des folosite sunt portul 79 pentru serviciul
"finger" si portul 513 pentru login-ul de la distanta. Nucleul rezerva primele 1024 numere de port pentru folosinta proprie. In directorul
/etc/services exista o baza de date de servicii ale retelei care poate fi in terogata utilizand apelurile sistem "getservbyname" si "getservbyport"
(a se vedea "getservent(3*n)" si "services(5)"). Fiecare din aceste
apeluri retur neaza un pointer la structura "servent" definita in <netdb.h>.
Daca campul port din parametrii adresei este specificat ca fiind zero, sistemul va asigna un numar de port nefolosit. Numarul de port asignat poate fi gasit cu apelul sistem "getsockname". In domeniul Internet, protocoalele UDP
si TCP pot utiliza acelasi numar de port. Nu va aparea nici un conflict de nume de oarece porturile legate de socluri cu protocoale diferite nu pot comunica.
Porturile cu un numar mai mic de 1024 sunt rezervate - numai procesele ce ru leaza ca "superuser" se pot lega la ele.
Adresa de "host" Internet este specificata prin 4 octeti. Ei sunt
reprezen tati tipizat printr-o notatie cu '.', a.b.c.d. Octetii de adresa sunt re prezentati prin numere intregi, separate prin puncte, de la rangul mai mare la cel mai mic. Aceasta ordonare se numeste "network order" si
reprezinta ordinea in care adresele sunt transmise in retea. De exemplu, adresa Internet pentru "host"-ul garfield.cs.wisc.edu este 128.105.1.3 care corespunde
intre gului fara semn 80690103 in hexa sau 2154365187 in zecimal. Cu toate astea, unele "host"-uri (cum ar fi VAX-urile) au ordinea octetilor inversata,
trimi tand mai intai octetii mai putin semnificativi. Cand un cuvant este transmis de al un astfel de "host" (ori cand un program C trateaza un cuvant
ca o sec venta de octeti) octetul cel mai putin semnificativ este transmis primul
(are adresa cea mai mica). Astfel este necesar sa se inverseze octetii de adresa memorati intr-un intreg, inainte de a-i transmite. Rutinele sistem
"htonl" si "ntohl" sunt folosite la conversia intregilor
"long" (32 de biti) de la un "host" pentru o retea si viceversa. Similar, "htos"
si "ntohs" inverseaza octetii intregilor scurti (16 biti) in cazul numerelor de porturi.
Apelurile sistem care returneaza sau cer adrese Internet si numere de port,
(cum ar fi "gethostbyname" sau "bind", care a fost descris
mai inainte), lucreaza numai in format "network order", deci in mod normal nu trebuie
sa va faceti probleme despre acestea. Dar, daca va trebui sa vizualizati aceste valori sau sa le folositi intr-un program, va trebui sa le convertiti de la
/ la ordinea de "host".
O adresa de "host" Internet se compune in doua parti - un numar de
retea si o adresa locala. Exista trei formate de adrese Internet (vezi inet(3*n)), si fiecare interpreteaza diferit cei 32 de biti de adresa. Adresa de 'Clasa A' are numarul de retea compus dintr-un singur octet (cel mai semnificativ) si
o adresa de "host" de 3 octeti. Adresele de 'Clasa B' au cate 2 octeti
atat pentru numarul de retea cat si pentru numarul de "host", iar adresele
de
'Clasa C' au 3 octeti pentru numarul de retea si unul pentru numarul de "host"
(astfel o retea de 'Clasa C' poate avea maxim 256 de "host"-uri. Bitii
cei mai semnificativi dintr-o adresa ii determina clasa: adresele incepand cu bitul
"0" sunt adrese de clasa A; "network address" ocupa primul octet al adresei,
cei lalti 3 servind la identificarea"host"-ului. Daca primii doi biti
ai unei adre se sunt "10" atunci este vorba de o adresa de clasa B; pentru acest
tip,
"network address" ocupa primii doi octeti ai adresei ramanand doi
octeti pentru identificarea "host"-ului.Cand primii trei biti sunt "110"
adresa respectiva apartine clasei C. "Network address" ocupa acum primii 3 octeti ai
adresei, iar "host address" va folosi doar ultimul octet. Sub standardul IPv4
mai exista si clasa D, avand drept element de identificare primii 3 biti ai primului octet care trebuie sa fie "111". Aceasta este o adresa de tip "multicast"
si nu se mai supune regulii "network address"/"host address". Drept exemplu
vom considera adresele utilizabile in reteaua U.T. "Gh. Asachi" Iasi:
-adresa destinatie pentru serverul SIGMA : 193.226.26.110 apartine clasei C;
-adresa destinatie pentru serverul EUREKA: 193.226.26.106 apartine clasei C.
Astfel pot exista 128 de retele de clasa A si 2^14=16348 retele de clasa B.
Apelurile sistem "gethostbyaddr" si "gethostbyname" pot
fi folosite pentru a vizualiza adresa de "host" (vezi gethostent(3*n) si host(5)). Fiecare
din aceste apeluri returneaza un pointer la o structura "hostent" definita
in
<netdb.h>. Adresele de "host" sunt returnate in format "network
order", format valabil pentru un apel "bind". Pentru a copia adresa de "host"
in structura sockaddr_in trebuie utilizata rutina sistem "memcpy". Daca
in apelul
"bind" se foloseste ca parametru o adresa de "host" 0 (se
foloseste variabila
INADDR_ANY), va fi furnizata automat adresa de "host" locala. Un alt motiv pentru a utiliza variabila INADDR_ANY este acela ca un "host"
poate avea mai multe adrese Internet ; adresele asignate in acest mod vor recepta orice mesaj cu o adresa Internet valida.
1.2. Stiluri de comunicatie
--------------------- Campul de tip al apelului "socket" specifica stilul de comunicatie care va fi folosit in comunicatiile pe soclu. Aceste tipuri sunt definite ca constante in <sys/socket.h>. Tipurile urmatoare sunt in mod frecvent
folo site: SOCK_STREAM (stream), SOCK_DGRAM (datagram), SOCK_RAW (interfata proto col "raw"), SOCK_RDM (reliable datagram) si SOCK_SEQPACKET (stream
cu pachete sectionate). Fiecare dintre aceste tipuri este suportat de cate un protocol diferit. Pentru inceput ne vom concentra asupra constantelor SOCK_STREAM si
SOCK_DGRAM.
1.2.1. Soclurile DATAGRAM
----------------- Tipul SOCK_DGRAM furnizeaza un model de comunicatie de tip datagram.
Un serviciu datagram se realizeaza fara conectare si este nesigur
(unreliable-engl.). Mesaje independente (si de obicei scurte), numite datagrame, sunt acceptate de protocolul de transport si trimise la adresa specificata. Aceste mesaje se pot pierde, copia, sau pot fi receptionate in alta ordine,astfel ca nu exista garantia sigurantei comunicatiei.
O caracteristica importanta a datagramelor este aceea ca limitele mesa jelor sunt mentinute la receptie. Aceasta inseamna ca datagrame individuale
(mesaje trimise cu apeluri separate) vor fi memorate separat cind vor fi receptionate. Un apel "revcfrom" pentru un soclu datagram va returna
numai urmatorul datagram disponibil. Tipul SOCK_DGRAM poate fi utilizat in domeniul
UNIX sau Internet. Cind este folosit in domeniul Internet el este suportat de protocolul de transport Internet numit UDP. Apelul :
sock = socket(AF_INET, SOCK_DGRAM, 0)
va returna un soclu datagram UDP.
1.2.2. Soclurile STREAM
--------------- Tipul STREAM_SOCK asigura un model de comunicatii de tip stream. Acest serviciu este sigur si este orientat pe conexiune. Data este transmisa intr -un stream cu octet de control al fluxului si in duplex total (pe conexiuni separate). Protocolul de transport suportat de acest tip de soclu asigura faptul ca datele sunt transmise in ordine fara pierderi, erori sau duplicate.
Altfel, el abandoneaza conexiunea si raporteaza eroarea utilizatorului.
Limitele mesajelor nu sunt pastrate de soclul stream. Inainte ca data sa fie
transmisa sau receptionata pe un astfel de soclu soclul trebuie sa fie trecut in modul de conexiune activa sau pasiva a soclului utilizind apelurile sistem
"listen", "accept",sau "connect" discutate in
sectiunea 2. Abstractizarea
SOCK_STREAM poate fi utilizata atit in domeniul UNIX cit si in domeniul
Internet. In domeniul Internet acesta este suportat de protocolul de transport
TCP.Apelul
sock = socket(AF_INET, SOCK_STREAM, 0)
returneaza un soclu stream TCP.
1.3. Protocoale
--------- Un protocol este un set de conventii de comunicatie care stabileste reguli pentru schimbul de informatii intre doua parti. Protocoalele de transport de date care suporta stilurile de comunicatii descrise mai sus sunt implementate in nucleul UNIX. Aceste protocoale realizeaza semanticile definite de tipul soclului. "User Datagram Protocol" (UDP),"Transmition
Control
Protocol" (TCP) si "Internet Protocol" (IP) apartin toate familiei
de proto coale Internet. Fiecare membru al acestei familii de protocoale suporta un tip diferit (vezi TCP (4*p), UDP (4*p) si IP (4*p)). UDP suporta socluri datagram,
TCP suporta socluri stream, iar tipul SOCK_RAW asigura o interfata "raw"
cu IP.
TP,protocolul de transport bazat pe conexiuni ISO suporta abstractizarea
SOCK_SEQPACKET, desi nu este implementat in Berkeley UNIX.
La ora actuala doar un singur protocol este suportat de un tip de soclu intr-un domeniu dat. Protocolul ce va fi utilizat este specificat in cimpul de protocol al apelului "socket". Sunt trei cai de specificare al
unui pro tocol : prima, daca este furnizat un 0, sistemul furnizeaza protocolul im plicit pentru acel domeniu si tip de soclu ; aceasta este cea mai folosita metoda. A doua metoda,este cea folosind constante predefinite ca IPPROTO_UDP, care pot fi gasite in < netinet/in.h>. A treia metoda necesita consultarea bazei de date de protocoale din dirctorul /etc/protocols prin apelurile
"getprotobyname" si "getprotobynumber" (vezi getprotoent(3*n)
si protocols(5)).
2. Stabilirea conexiunilor
---------------------- Soclurile stream trebuie sa stabileasca o conexiune inainte de transferul datelor. Un soclu stream poate fi in doua stari : activa sau pasiva. Un so clu este initial activ si devine pasiv in momentul unui apel "listen".
Numai soclurile pasive pot fi mentionate intr-un apel "connect" si numai
soclurile pasive pot fi mentionate intr-un apel "accept". Aceste apeluri de
stabiliri de conexiuni sunt folosite numai pentru socluri stream, dar si soclurile da tagram pot fi folosite in apeluri "conect" pentru a stabili in mod
permanent destinatia pentru viitoarele apeluri "send".
2.1. Conexiunea activa
---------------- Un soclu activ stabileste o conexiune cu un soclu pasiv utilizind apelul sistem "connect".
status = connect(sock, name, namelen ) int sock; struct sockaddr* name; int namelen;
Parametrul "name" este adresa pentru un soclu la distanta, interpretata in concordanta cu domeniul de comunicatie cu care a fost creat soclul. Daca domeniul este AF_UNIX, acesta trebuie sa fie structura sockaddr_un; daca domeniul este AF_INET, el trebuie sa fie o structura sockaddr_in.
2.2. Conexiunea pasiva
---------------- Un soclu devine un capat pasiv al unei conexiuni, dupa un apel "listen"
status = listen(sock, queuelen) int sock, queuelen ;
"Listen" initializeaza o coada pentru asteptarea cererilor de conexiune.
Parametrul queuelen specifica maxim permis de conexiuni ca pot fi introduse in coada. Lungimea maxima a cozii este limitata implicit de sistem la valoarea
5. Constanta SOMAXCONN din <sys/socket.h> defineste acest maxim. O conexiu ne poate fi stabilita utilizind apelul sistem "accept".
new_socket = accept(old_sock, namelen) int new)socket, old)sock; /* numele soclului pereche in noua conexiune */
int *namelen ; /* lungimea numelui in octeti */
"Accept" scoate prima cerere de conexiune primita din coada si returneaza
un descriptor pentru noul soclu cu care se conecteaza. Acest soclu are ace leasi proprietati ca si vechiul soclu. Adresa soclului de la capatul activ este returnata in parametrul "name". "Namelen" este valoarea
parametrului care trebuie initializat cu lungimea adresei structurii care este pasata; la retur va fi setat cu lungimea actuala a adresei returnate. Vechiul soclu ramane neafectat si poate fi utilizat pentru alte conexiuni. Daca nu avem nimic in coada, apelul "accept" se blocheaza.Daca e necesar, poate
fi exe cutat un apel "select" pentru a vedea daca exista vreo cerere de conectare
(vezi sectiunea 4.2.). Un soclu cu conexiunile facute va trece pe starea gata de citire.
3. Transferul datelor
----------------- Perechile de apeluri (read, write), (recv, send), (recvfrom, sendto)
(recvmsg, sendmsg) si (readv, writev) pot fi utilizate pentru transferurile de date pe soclu.Apelurile cele mai adecvate depind de functionalitatea ceru ta. "Send' si "secv" sunt utilizate de obicei cu socluri stream.
Pot fi uti lizate si cu socluri datagram daca transmitatorul a facut inainte un apel
"connect" sau daca receptorul nu este interesat sa stie cine este
transmitato rul. "Sendto" si "recvfrom" sunt utilizate cu socluri datagram.
"Sendto" permite specificarea destinatiei datagramului, in timp ce "recvfrom"
retur neaza numele soclului departat care transmite mesajul. "Read" si "write" pot fi utilizate cu orice tip de socluri. Aceste apeluri pot fi alese din motive de eficienta. "Writev" si "readv" fac posibila depunerea si luarea datelor in/din buffere separate. "Sendmsg" si "recvmsg"
permit de punerea/luarea datelor ca si posibilitatea de a schimba drepturi de acces.
Apelurile "read", "write", "readv" si "writev"
pot primi ca prim argument fie un descriptor de soclu, fie unul din fisier ; toate celelalte apeluri necesita un descriptor de soclu. count = send(sock, buf, buflen, flags) int count, sock, buflen, flags; char *buf;
count = recv(sock, buf, buflen, flags) int count, sock, buflen, flags; char *buf;
count = sendto(sock, buf, buflen, flags, to, tolen) int count, sock, buflen, flags, tolen; char *buf; struct sockaddr *to;
count=recvfrom(sock, buf, buflen, flags, from, fromlen) int count, sock, buflen flags, *fromlen; char *buf; struct sockaddr *from;
Pentru apelurile de transmisie, "count" returneaza numarul de octeti acceptati de suportul de transport sau -1 daca a fost detectata local vreo eroare. O valoare pozitiva returnata nu reprezinta o indicatie a succesului transferului de date. Chiar daca apelul "send' nu s-a blocat, el poate
sa nu fi acceptat toti octetii din bufferul de date (vezi sectiunea 4.1.).
Valoarea returnata trebuie sa fie verificata astfel incit daca au ramas oc teti netransmisi, ei sa fie transmisi in final, daca este necesar. Pentru apeluri de receptie, "count" returneaza numarul de octeti receptionat
sau -1 daca s-a detectat o eroare. Primul parametru pentru fiecare apel este un descriptor de soclu valid. Parametrul buf este un pointer al bufferului de date al apelantului. In apelurile "send", parametrul buflen reprezinta
numa rul de octeti ce sunt transmisi ; in apelurile "receive', el indica dimensi unea bufferului de receptie si numarul maxim de octeti pe care apelantul poa te sa-i receptioneze. Parametrul "to" in apelul "sendto"
specifica adresa destinatarului (in conformitate cu familia de adrese de care apartine), iar
"tolen" specifica lungimea ei. Parametrul "from" in apelul
"recvfrom" speci fica adresa sursei mesajului. "Fromlen" este parametrul valoare/rezultat
ca re da initial dimensiunea structurii pointate de "from" si apoi este
modificat pentru a returna lungimea actuala a adresei.
Parametrul "flags", care este de obicei 0 ca argument, permite operatii peciale pe soclurile stream.Este posibila transmiterea de date "out-fo-band" sau culegerea mesajului receptionat fara a-l citi propriu-zis. Fanioanele
MSG_OOB si MSG_PEEK sunt definite in <sys/socket.h>. Datele "out-of-band" sunt date cu prioritate mare (cum ar fi un caracter de intrerupere) pe care utilizatorul ar dori sa le proceseze inaintea tuturor datelor din stream.
Daca sunt prezente date "out-of-band" poate fi trimis catre utilizator
un semnal SIGURG. Semantica curenta pentru date "out-of-band" este determinata de protocolul folosit. Protocoalele ISO le trateaza ca date expeditive, pe cind portocoalele Internet le trateaza ca date urgente.
Daca oricare dintre aceste apeluri (ca si oricare altul) este intrerupt de un semnal, ca SIGALRM sau SIGIO, apelul va returna -1 si variabila errno
va fi setata la EINTR (variabilele definite in <errno.h>. Apelul sistem va
fi repornit automat, insa inainte trebuie resetata variabila errno la 0.
4. Sincronizare
----------- In mod implicit, toate apelurile de citire/scriere se blocheaza : apelu rile de citire nu se termina pina cind macar un octet de date este disponibil pentru citire, iar apelurile de scriere se blocheaza pina cind exista un spatiu suficient in buffer pentru a accepta citiva sau toti octetii care au fost transmisi. Unele aplicatii trebuie sa serviseze mai multe conexiuni in retea in acelasi timp, executind operatii pe conexiune cind li s-a permis.
Sunt trei tehnici care pot suporta astfel de aplicatii : socluri fara blocare semnalizari asincrone si apelul sistem select. Apelul sistem "select" este de departe cel mai des folosit, soclurile fara blocare nu sunt de obi cei folosite, iar semnalizarile asincrone sunt rar folosite.
4.1. Optiuni de soclu
--------------- Apelurile sistem "getsockopt" si "setsockopt" pot fi utilizate
pentru a inspecta/seta optiuni speciale asociate soclurilor. Acestea pot fi optiuni generale pentru toate soclurile sau pentru implementari specifice de socluri.
Exemple de optiuni luate din <sys/sockets.h> sunt SO_DEBUG si SO_USEADDR
(permite reutilizarea adreselor locale).
"Fnctl" si "ioctl" sunt apeluri sistem care fac posibil
controlul fisierelor soclurilor si perifericelor intr-o mare varietate de moduri.
status = fnctl(sock, command, argument) int status, sock, command, argument; status = ioctl(sock, request, buffer) int status, sock, request; char *buffer;
Parametrii "command" si "argument" ai apelului fnctl pot
fi luati ca si constante din <fnctl.h>. Constantele predefinite pentru parametrul "request" din apelul "ioctl" se gasesc in <sys/ioctl.h>. Acest parametru
specifica cum va fi utilizat argumentul "buffer".
Fiecare din aceste apeluri poate fi utilizat pentru a activa pe soclu semnalizarile asincrone. Oricind soseste o data intr-un astfel de soclu va fi livrat procesului un semnal SIGIO. Procesul trebuie sa aiba deja un handler pentru acest semnal (vezi sectiunea 6.1).Acest handler poate apoi sa citeas ca datele din soclu.Executia programului va continua de la punctul de intre rupere. Secventa de apel :
fnctl(sock, F_SETOWN,getpid()); fnctl(sock, F_SETFL, FASYNC);
activeaza intrarile/iesirile asincrone pe soclul dat. primul apel este nece sar pentru a specifica procesul de semnalizat. Al doilea semnalizeaza des criptorului de stare al fanionului sa deblocheze SIGIO.
Aceiasi secventa de apel poate fi utilizata pentru a face un soclu sa nu se blocheze, singura modificare fiind ca se foloseste fanionul FNDELAY in locul fanionului FASYNC, in al doilea apel. In acest caz, daca o operatie de citire/scriere s-ar bloca in mod normal, este returnat -1 si variabila sistem externa errno este setata la EWOULDBLOCK. Acest cod de eroare poate fi verificat si se poate lua o masura necesara, functie de cerinte.
4.2. Multiplexarea descriptorilor de fisiere
-------------------------------------- Apelul sistem "select" face posibila multiplexarea sincrona a descriptori lor de fisier si de soclu. Poate fi utilizata pentru a determina cind sunt date de citit sau cind este posibil de a transmite date.
nfound=select(numdes, readmask, writemask, exceptmask, timeout) int nfound, numdes; fd_set *readmask, *writemask, *exceptmask; struct timeval *timeout;
Mastile din acest apel sunt parametri valoare/rezultat prin care sunt indi cati fiecare descriptor de soclu sau fisier. Posibilitatea unei operatii specifice - citire, scriere, sau prezenta unei conditii de exceptie - este investigata prin setarea bitului corespunzator pentru acest soclu in masca co respunzatoare. Poate fi utilizat un pointer nul daca conditia data nu este de interes. De exemplu, daca "writemask" e zero, soclurile nu vor
fi verifi cate pentru scriere.
Tipul "fd_set"este definit in <sys/types.h> ca o structura ce
contine un sin gur camp, care este un sir de intregi. Sirul este interpretat ca o masca de
biti, cu cate un bit pentru fiecare descriptor de fisier/soclu posibil. (Re prezentarea mastii ca o structura si nu ca un simplu sir permite ca valorile
"fd_set" sa fie asignate fara a necesita apelul "memcpy").
In <sys/types.h> sunt definite 4 macrouri pentru setarea, resetarea, si testarea bitilor in masca.
FD_SET(n,p) /* seteaza bitul n */
FD_CLEAR(n,p) /* reseteaza bitul n */ result=FD_ISSET(n,p) /* testeaza bitul n */
FD_ZERO(p) /* reseteaza toti bitii */ int n; fd_set *p;
Intr-o masca data pot fi setati mai multi biti, dar fiecare trebuie sa cores punda unui descriptor valid. Parametrul "numdes" indica faptul ca
trebuie exa minati bitii de la zero la numdes-1. Constanta predefinita FD_SETSIZE, defi nita in <sys/types.h>, indica numarul maxim de descriptori ce pot fi reprezen tati de o structura "fd_set". Astfel, setarea "numdes = FD_SETSIZE"
ne va a sigura ca toti descriptorii vor fi urmariti. Parametrul "timeout"
este un pointer la o structura "timeval" definita in <sys/time.h>. Este
utilizata pen tru a specifica intervalul de timp maxim pe care il va astepta apelul inainte
de a returna valoare. Daca timeout = 0 (pointer NULL) "select" se
va bloca pe timp nedefinit. Daca timeout pointeaza spre o structura timeval iniitializata cu zero, atunci apelul este returnat imediat, chiar daca nu exista descripto ri cata. "Select" se intoarce cind o conditie a fost descoperita pentru
unul sau mai multe socluri sau cind timpul specificat s-a scurs. Valoarea de retur
"nfound" indica numarul de conditii satisfacute. Mastile sunt modificate
pen tru a indica acele socluri pentru care conditia a fost indeplinita.
Motivul pentru care descriptorii de fisier au fost inscrisi in masti ce merg cu apelul select este de a raspunde de o activitate interactiva. De exem plu :
FD_SET(fileno(stdin),readmask)
poate fi utilizat pentru a verifica daca a fost tastat ceva pe terminal.
5. Distrugerea conexiunilor
----------------------- O conexiune poate fi distrusa folosind apelul "close" pentru a inchide
unul din soclurile implicate : status = close(sock) int status, sock;
Semantica precisa a inchiderii conexiunii este determinata de protocolul res ponsabil. distrugerea se poate produce fara pierderi de date (sigur) sau cu
pierderea datelor in tranzit.
Apelul "shut_down" poate fi folosit sa inchida selectiv un soclu full-dup lex(cu conexiuni separate. status = shut_down(sock, how) int status, sock, how;
Parametrul "how" specifica fie ca data nu va mai fi trimisa din
soclu (0) sau ca data nu va mai fi receptionata (1), ori ca soclul va fi inchis com plet (2).
In cazul soclurilor create in domeniul UNIX trebuie folosit apelul
"unlink" pentru a indeparta numele de calea la care a fost legat soclul,
din structura de fisiere: status = unlink(pathname) char* pathname;
Aceste nume de cale nu sunt scoase automat cind soclul este inchis.
6. Semnale
------ Berkeley UNIX asigura un set de semnale care pot fi livrate unui proces din diverse motive, cum ar fi o intrerupere de la tastatura sau aparitia unei erori pe bus. Aceste semnale - spre exemplu SIGIO si SIGALRM - sunt definite in <signal.h>. De obicei, actiunea ior implicita este aceea ca livrarea acestor semnale determina terminarea procesului in cauza. Aceasta actiune poate fi schimbata astfel incit semnalul sa fie captat sau ignorat. Daca semnalul este captat, un handler de semnal este declarat la locatia la care se transfera controlul la momentul intreruperii. Sosirea unui semnal va fi astfel similara unei intreruperi hardware. Cind un semnal este livrat unui proces, starea semnalului este salvata, semnalul este blocat pentru viitoa rele incercari de semnalizare, iar controlul programului este transferat handler-ului desemnat. Daca din handler se iese normal, semnalul se poate activa din nou, iar executia programului se reia din punctul in care fusese intrerupta. Daca semnalul e blocat totusi se activeaza, el este trecut in coada pentru o tratare ulterioara.
6.1. Handler de semnal
---------------- Un handler de semnal poate fi declarat fie cu semnalul "signal", fie
cu
"sigvec". "Signal" este o versiune simplificata a apelului
mai general
"sigvec".
oldhandler=signal(sig,handler) int sig; int *handler(), *oldhandler();
Parametrul "sig" este o constanta predefinita ce poate fi gasita
in
<sig.h> si descrie semnalul. Handler este numele rutinei care va fi apelata
cand semnalul va fi transmis procesului. SIG_DFL (actiune implicita) si
SIG_IGN (ignorare) pot fi de asemenea specificate ca argumente ale acestui parametru. Valoarea returnata va fi handler-ul anterior, daca exista.
Rutina handler este de forma : sighandler (sig, code, scp) int sig, code; struct sigcontext *scp;
Handler-ul pentru semnalul SEGIO este de preferat sa scoata toate datele ce pot fi in soclu inainte de iesirea din rutina. Aceasta inlatura posibili tatea pierderii accidentale a datelor din cauza unui semnal pierdut. Daca se manifesta mai mult de un eveniment pentru un semnal, cind acesta este blocat,
numai un semnal este salvat pentru a fi transmis procesului.
6.2. Blocarea semnalelor
------------------ Exista o masca globala care specifica ce semnal este blocat la un moment dat. Apelul sistem "sigblock" poate fi utilizat pentru blocarea semnalelor,
in timp ce apelurile "sigsetmask" si "sigpause" pot fi utilizate
pentru a de bloca semnalele prin restaurarea mastilor originale. In <signal.h> exista
un macro "sigmask" care face usoara setarea mastii de semnal pentru apelul
"sigblock". El este definit ca :
#define sigmask(m) (1<<((m)-1)) oldmask = sigblock(mask) int oldmask, mask;
oldmask = sigsetblock(mask) int oldmask, mask;
result = sigpause(mask) int result, mask; /* apelul returneaza intotdeauna EINTR */
Pentru "sigblock", parametrul "mask" specifica acele semnale
ce trebuie blo cate. "sigsetmask" si "sigpause" seteaza "mask"
ca fiind masca de semnale cu renta. Apelul "sigsetmask" se termina imediat, in timp ce "sigpause"
asteap ta sosirea unui semnal. Pentru sectiuni critice, unde este necesar a bloca un semnal ca SIGIO, poate fi folosita urmatoarea secventa :
newmask = sigmask(SIGIO); oldmask = sigblock(newmask);
.........
......... sigsetmask(oldmask);
7. Timer-e
-------
Exista doua apeluri sistem care pot fi folosite pentru transmiterea unui semnal SIGALRM procesului apelant, acestea fiind "alarm" si "setitimer".
"Alarm", care face ca un singur semnal SIGALRM sa fie transmis la
un proces cind timpul specificat expira, nu asigura o rezultie mai mica de 1 secunda.
"Setitimer', pe de alta parte, asigura intreruperi de clock periodice,
(via
SIGALRM), la intervale regulate si are o rezolutie de minimum 10 ms. Aceasta este ideala pentru actualizarea timer-elor de program.
status = setitimer(which, value, oldvalue) int status, which; struct itimeval,*value, *oldvalue;
Este necesar sa apelam numai o data "setitimer" si ea va trimite
semnale
SIGALRM la intervale regulate. Trebuie, in acest caz, declarat un handler pentru a prelucra acest semnal, altfel acesta va termina procesul. Parametrul
"which" specifica care este intervalul de timp intre apeluri. In <sys/time.h> sunt definite doua posibilitati : ITIMER_REAL si ITIMER_VIRTUAL. ITIMER_REAL va decrementa timer-ul un timp real, iar ITIMER_VIRTUAL va decrementa timer -ul numai cind procesul este activ. Ceilalti doi parametrii sunt pointeri la o structura definita in <sys/time.h> :
struct itimerval A struct timeval it_interval;/* interval timer */ struct timeval it_value;/* valoare curenta */
S struct timeval A long tr_sec;/* secunde */ long tr_usec;/* milisecunde */
S
"Setitimer" seteaza intervalul pentru timer la valoarea specificata
de
"value" si returneaza valoarea anterioara in "oldvalue".
Un semnal SIGALRM
(SIGVALRM pentru ITIMER_VIRTUAL) va fi trimis procesului cind valoarea de timp specificata de de "value->it_itvalue" devine zero. Timer-ul va
fi incarcat apoi cu valoarea specificata in "value->it_interval",dupa care
ciclul se reia.
Astfel, pentru a asigura o intrerupere de ceas periodica, este necesar numai
apelul "setitimer", cu "value->it_interval" si "value->it_value"incarcate
cu valorile dorite. Daca "it_interval" este zero, timer-ul va activa
intreruperea numai o data. O valoare zero in "it_value" va inactiva timer-ul. Exista
3 macrouri in <sys/time.h> care sunt folositoare in manipulaera structurilor
"timeval". Acestea sunt "timerisset", "timerclear"
si "timercmp".
8. Programe exemplu
--------------- Urmatoarele 2 programe exemplifica folosirea soclurilor stream sub TCP.
Primul program, "client.c", stabileste conexiunea la un port si un
"host" specificate ca argumente in linia de comanda, si apoi asteapta intr-o bucla, trimitand pe soclu tot ce vine de la tastatura si afisand toate caracterele
ce vine pe conexiune. Bucla se termina cand la tastatura va fi apasat "Ctrl+D"
(caracterul EOF). Al doilea program, "server.c", creaza un server
primitiv, cu ecou.El asteapta o conexiune TCP la un port specificat in linia de comanda
si citeste date de pe soclul creat. Datele sunt afisate si apoi sunt trimise
inapoi conexiunii de unde au venit. Aceste programe utilizeaza apelul "select" cu o masca de citire :"client.c" il utilizeaza pentru a alege intre
tastatura si conexiunea de retea, pe cand "server.c" il utilizeaza pentru a
alege intre conexiunile existente si cererile pentru noi conexiuni. Amandoua programele
blocheaza iesirile in mod neconditionat. Un client ce trimite un bloc mare de date la server si apoi nu reuseste sa citeasca de la server ecoul poate cauza
agatarea server-ului. De fapt, deoarece un client poate fi blocat in trimiterea de date catre server, si astfel nu poate receptiona ecoul, blocarea totala este posibila. Aceste probleme pot fi rezolvate (cu costul maririi complexitatii programului),buffer-ind datele intern si utilizand masti de scriere in ape lurile "select".
8.1. CLIENT.C
------- #include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#incluse <netinet/in.h>
#include <errnon.h>
#include <ctype.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
main(argc, argv) int argc; char *argvai;
A struct hostent *hostp; struct servent *servp; struct sockadrr_in server; int sock; static struct timeval timeout = A 5, 0 S; /* 5 secunde */ fd_set rmask, xmask, mask; char bufaBUFSIZi; int nfound, bytesread;
if (argc != 3) A
(void) fprintf(stderr, "usage: %s service host\n",argca0i); exit(1);
S if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) A perror("socket"); exit(1);
S if (isdigit(argva1i a0i)) A static struct servent s; servp = &s; s.s_port = htons((u_short)atoi(argva1i));
S else if ((servp = getservbyname(argva1i,"tcp")) == 0) A fprintf(stderr,"%s:unknown service\n",argva1i); exit(1);
S if ((hostp = gethostbyname(argva2i)) == 0) A fprintf(stderr,"%s:unknown host\n",argva2i); exit(1);
S memset((void *) &server, 0, sizeof server); server.sin_family = AF_INET; memcpy((void *) &server.sin_addr,hostp->h_addr,hostp->h_length); server.sin+port = servp->s_port; if (connect(sock, (struct sockkaddr *)&server,sizeof server) < 0) A
(void ) close (sock); perror("connect"); exit(1);
S
FD_ZERO(&mask);
FD_SET(sock, &mask);
FD_SET(fileno(stdin), &mask); for(;;) A rmask=mask; nfound=select(FD_SETSIZE,&rmask,(fd_set *)0,(fd-set *)0,&timeout); if (nofound < 0) A if (errno == EINTR) A printf("interrupted system call\n"); continue;
S
/* Exista o problema! */ perror("select"); exit(1);
S if (nfound == 0) A
/* timer ajuns la zero */ printf("Please type something!\n"); continue;
S if (FD_ISSET(fileno(stdin),&rmask)) A
/* data de la tastatura */ if (!fgets(buf, sizeof buf, stdin)) A if (ferror(stdin)) A perror("stdin"); exit(1);
S exiot(0);
S if (write(sock, buf, strlen(buf)) < 0) A perror("write"); exit(!);
S
S if (FD_ISSET)(sock,&rmask)) A
/* data din retea */ bytesread = read(sock, buf, sizeof buf); bufabytesreadi='\0'; printf("&s: got %d bytes:%s\n", argva0i, bytesread, buf);
S
S
S/* main - client.c */
8.2. SERVER.C
------- #include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
main(argc, argv) int argc; char *argvai;
A struct servent *servp; struct sockaddr_in server, remote; int request_sock, new_sock; int nfound, fd, maxfd, bytesread, addrlen; fd_set rmask, mask; static struct timeval timeout = A 0, 500000 S;/* 0.5 secunde */ char bufaBUFSIZi;
if (argc != 2) A
(void ) fprintf(stderr,"usage: %s service\n",argva0i); exit(1);
S if ((request_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) A perror("socket"); exit(1);
S if (isdigit(argva1i a0i)) A static struct servent s; servp = &s; s.s_port = htons((u+short)atoi(argva1i));
S else if ((servp = getservbyname(argva1i,"tcp")) == 0) A fprintf(stderr,"%s: unknown service \n"); exit(1);
S memset((void *) &server, sizeof server); server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = servp->s_port; if (bind(request_sock,(struct sockaddr *)&server, sizeof server) <0)
A perror("bind"); exit(1);
S if (listen(request_sock, SOMAXCONN) < 0) A perror("listen"); exit(1);
S
FD_ZERO(&mask);
FD_SET(request_sock, &mask); maxfd = request_sock; for(;;) A rmask = mask; nfound = select(maxfd+1, &rmask, (fd_set *)0, (fd_set *)0, &timeout); if (nfound < 0) A if (errno == EINTR) A printf("interrupted system call\n"); continue;
S
/* Exista o problema! */ perror("select"); exit(1);
S if (nfound == 0) A
/* timeout */ printf(".");fflush(stdout); continue;
S if (FD_ISSET(request_sock, &rmask)) A
/* o noua legatura este disponibila pe soclu */ addrlen = sizeof(remote); new_sock = accept(request_sock,
(struct sockaddr *)&remote, &addrlen); if (new_sock < 0) A perror("accept"); exit(1);
S printf("connection from host %s,port &d, socket %d\n", inet_ntoa(remote.sin_addr), ntohs(remote.sin_port), new_sock);
FD_SET(new_sock, &mask); if (new_sock > maxfd) maxfd = new_sock;
FD_CLR(request_sock, &rmask);
S for (fd=0; fd <=maxfd ;fd++) A
/* testarea soclurilor ce au date disponibile */ if (FD_ISSET(fd, &rmask)) A
/* Proceseaza datele */ bytesread = read(fd, buf, sizeof buf - 1);
if (bytesread<0) A perror("read");
/*citire esuata */
S if (bytesread<=0) A printf("server: end of file on &d\n,fd);
FD_CLR(fd, &mask); if (close(fd)) perror("close"); continue;
S bufabytesreadi = '\0'; printf("%s :%d bytes from %d: %s\n"), argva0i, bytesread, fd, buf);
/* datele sunt trimise inapoi */ if (write (fd, buf, bytesread) != bytesread) perror("echo");
S
S
S
S /* main - server.c */