Referinte
socluri BSD
=====================
-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 {
short sa_family; /*
familia de adrese */
char sa_data[14];/*
pana la 14 octeti ai adresei directe */
}
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 {
short sun_family;
/* AF_UNIX */
char
sun_path[108-4]; /* nume de cale */
}
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 {
short sin_family;
/* AF_INET */
u_short sin_port;
/* numarul de port */
struct in_addr
sin_addr; /* evzi mai jos */
char sin_zero[8];
/* neutilizat */
}
struct in_addr {
union {
struct { u_char
s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct {
u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr
S_un.S_addr /* poate fi folosit in majorita-
tea
codurilor tcp/ip */
}
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 {
struct timeval
it_interval;/* interval timer */
struct timeval
it_value;/* valoare curenta */
}
struct timeval {
long tr_sec;/*
secunde */
long tr_usec;/*
milisecunde */
}
"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 *argv[];
{
struct hostent *hostp;
struct servent *servp;
struct sockadrr_in server;
int sock;
static struct timeval timeout = {
5, 0 }; /* 5 secunde */
fd_set rmask, xmask, mask;
char buf[BUFSIZ];
int nfound, bytesread;
if (argc != 3) {
(void) fprintf(stderr,
"usage: %s service host\n",argc[0]);
exit(1);
}
if ((sock = socket(AF_INET,
SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("socket");
exit(1);
}
if (isdigit(argv[1] [0])) {
static struct servent s;
servp = &s;
s.s_port =
htons((u_short)atoi(argv[1]));
} else if ((servp =
getservbyname(argv[1],"tcp")) == 0) {
fprintf(stderr,"%s:unknown
service\n",argv[1]);
exit(1);
}
if ((hostp =
gethostbyname(argv[2])) == 0) {
fprintf(stderr,"%s:unknown
host\n",argv[2]);
exit(1);
}
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) {
(void ) close (sock);
perror("connect");
exit(1);
}
FD_ZERO(&mask);
FD_SET(sock, &mask);
FD_SET(fileno(stdin), &mask);
for(;;) {
rmask=mask;
nfound=select(FD_SETSIZE,&rmask,(fd_set
*)0,(fd-set *)0,&timeout);
if (nofound < 0) {
if (errno == EINTR) {
printf("interrupted system call\n");
continue;
}
/* Exista o problema! */
perror("select");
exit(1);
}
if (nfound == 0) {
/* timer ajuns la
zero */
printf("Please type something!\n");
continue;
}
if
(FD_ISSET(fileno(stdin),&rmask)) {
/* data de la
tastatura */
if (!fgets(buf,
sizeof buf, stdin)) {
if
(ferror(stdin)) {
perror("stdin");
exit(1);
}
exiot(0);
}
if (write(sock,
buf, strlen(buf)) < 0) {
perror("write");
exit(!);
}
}
if
(FD_ISSET)(sock,&rmask)) {
/* data din retea
*/
bytesread =
read(sock, buf, sizeof buf);
buf[bytesread]='\0';
printf("&s: got %d bytes:%s\n", argv[0], bytesread, buf);
}
}
}/* 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 *argv[];
{
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 = { 0,
500000 };/* 0.5 secunde */
char buf[BUFSIZ];
if (argc != 2) {
(void )
fprintf(stderr,"usage: %s service\n",argv[0]);
exit(1);
}
if ((request_sock = socket(AF_INET,
SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("socket");
exit(1);
}
if (isdigit(argv[1] [0])) {
static struct servent s;
servp = &s;
s.s_port =
htons((u+short)atoi(argv[1]));
} else if ((servp =
getservbyname(argv[1],"tcp")) == 0) {
fprintf(stderr,"%s: unknown
service \n");
exit(1);
}
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) {
perror("bind");
exit(1);
}
if (listen(request_sock, SOMAXCONN) <
0) {
perror("listen");
exit(1);
}
FD_ZERO(&mask);
FD_SET(request_sock, &mask);
maxfd = request_sock;
for(;;) {
rmask = mask;
nfound = select(maxfd+1,
&rmask, (fd_set *)0, (fd_set *)0, &timeout);
if (nfound < 0) {
if (errno == EINTR) {
printf("interrupted system call\n");
continue;
}
/* Exista o
problema! */
perror("select");
exit(1);
}
if (nfound == 0) {
/* timeout */
printf(".");fflush(stdout);
continue;
}
if (FD_ISSET(request_sock,
&rmask)) {
/* o noua legatura
este disponibila pe soclu */
addrlen =
sizeof(remote);
new_sock =
accept(request_sock,
(struct
sockaddr *)&remote, &addrlen);
if (new_sock < 0) {
perror("accept");
exit(1);
}
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);
}
for (fd=0; fd <=maxfd
;fd++) {
/* testarea soclurilor
ce au date disponibile */
if (FD_ISSET(fd,
&rmask)) {
/*
Proceseaza datele */
bytesread =
read(fd, buf, sizeof buf - 1);
if
(bytesread<0) {
perror("read");
/*citire
esuata */
}
if
(bytesread<=0) {
printf("server:
end of file on &d\n,fd);
FD_CLR(fd,
&mask);
if
(close(fd)) perror("close");
continue;
}
buf[bytesread]
= '\0';
printf("%s
:%d bytes from %d: %s\n"),
argv[0],
bytesread, fd, buf);
/* datele
sunt trimise inapoi */
if (write
(fd, buf, bytesread) != bytesread)
perror("echo");
}
}
}
} /* main - server.c */
|