Pentru un programator un socket arata si lucreaza mai mult ca un descriptor de nivel scazut. Acest lucru se intampla deoarece comenzi ca read() si write() lucreaza cu socket-uri la fel cum lucreaza cu fisiere si cu pipe-uri. Diferentele dintre socket-uri si descriptori normali de fisiere apar la momentul crearii unui socket si la folosirea unor anumite operatii de control al unui socket. Aceste operatii sunt diferite la socket-rui fata de descriptorii normali de fisiere datorita complexitatii sporite la stabilirea unei conexiuni de retea atunci cand facem o comparatie cu accesul normal la disc.
Pentru cele mai multe operatii folosind socket-uri, rolurile de client si server trebuiesc atribuite. Un server este un proces care executa niste functii la cererea unui client. Dupa cum vom vedea in aceasta discutie, rolurile nu sunt simetrice si nu pot fi schibate intre ele fara efort.
Aceasta descriere a folosirii socket-urilor decurge in trei etape:
Folosirea socket-urilor intr-un mediu fara conexiune sau datagrama intre procesele client si server pe aceeasi statie. In aceasta situatie, clientul nu realizeaza explicit o legatura cu severul. Bineinteles, clientul, trebuie sa cunoasca adresa serverului. In schimb, serverul, pur si simplu asteapta aparitia unui mesaj. Adresa clientului este unul dintre parametrii cererii de primire a mesajului si este folosita de server pentru a raspunde.
Folosirea socket-urilor intr-un mediu conectat intre client si server pe aceeasi masina. In acest caz, rolurile de client si server sunt stabilite mai departe prin felul in care socket-ul este instalat si folosit. Acest model este denumit de obicei model client-server orientat pe conexiune.
Folosirea socket-urilor intr-un mediu conectat intre client si server pe statii diferite. Aceasta este extensia la nivelul retelei a stagiului 2 sw mai sus.
Modul fara conexiune sau datagrama dintre client si server pe host-uri diferite nu este discutat explicit aici. Folosirea lui poate fi dedusa din prezentarile facute la Stagiul 1 si 3.
#include <sys/types.h> #include <sys/socket.h> int socketpair(int af, int type, int protocol, int sv[2])socketpair() rezulta in crearea a doua socket-uri conectate. sv[] este stringul in care sunt returnati descriptorii socket-urilor. Fiecare descriptor din sv[] este asociat cu un capat al legaturii de comunicatie. Fiecare descriptor poate fi folosit atat pentru intrare cat si pentru iesire. Acest lucru inseamna ca este posibila comunicatia intre procesul parinte si procesul fiu in ambele sensuri.
In mod normal, un descriptor este rezervat pentru procesul parinte si celalat descriptor este folosit de procesul fiu. Procesul parinte inchide descriptorul folosit de procesul fiu. Pe de alta parte, procesul fiu inchide descriptorul folosit de procesul parinte. fork() este necesar inca pentru a transmite unul dintre socket-uri unui fiu.
af reprezinta familia de domenii sau adresa careia ii apartine socket-ul. type este tipul de socket care trebuie creat.
Domeniile se refera la zona in care exista procesele care comunica. Cele mai uzuale domenii folosite includ:
Tipul socket-ului se refera la "stilul" comunicatiilor. Cele mai folosite doua valori includ:
Exemplu (imprumutat din Tutorialul 4.3BSD IPC de Stuart
Sechrest)
#define DATA1 "sirul de test 1"
#define DATA2 "sirul de test 2"
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
main()
{
int sockets[2], child;
char buf[1024];
/* Se citeste perechea de socket-uri */
if (socketpair(AF_UNIX, SOCK_STREAM,
0, sockets) < 0) {
printf("error %d on socketpair\n", errno);
exit(1);
}
/* creaza procesul fiu */
if ((child = fork()) == -1) {
printf("fork error %d\n", errno);
exit(1);
}
if (child != 0) { /* this is the parent */
/* inchide capatul socket-ului de la fiu */
close(sockets[0]);
/* citeste mesajul de la fiu */
if (read(sockets[1], buf, sizeof(buf)) < 0) {
printf("error %d reading socket\n", errno);
exit(1);
}
printf("-->%s\n", buf);
/* scrie un mesaj catre fiu */
if (write(sockets[1], DATA1, sizeof(DATA1)) < 0) {
printf("error %d writing socket\n", errno);
exit(1);
}
/* terminare */
close(sockets[1]);
} else { /* fiul */
/* inchide capatul socket-ului de la parinte */
close(sockets[1]);
/* trimite mesaj parintelui */
if (write(sockets[0], DATA2, sizeof(DATA1)) < 0) {
printf("error %d writing socket\n", errno);
exit(1);
}
/* citeste mesajul de la parinte */
if (read(sockets[0], buf, sizeof(buf)) < 0) {
printf("error %d reading socket\n", errno);
exit(1);
}
printf("-->%s\n", buf);
/* terminare */
close(sockets[0]);
}
}
#include <sys/types.h> #include <sys/socket.h> int socket(int af, int type, int protocol)socket() este asemanatoare cu socketpair() cu exceptia faptului ca un singur socket este creat in loc de doua. Acest lucru este cel mai frecvent folosit daca se doreste comunicarea cu un proces care nu este fiu. Campurile af, type si protocol sunt folosite la fel ca la apelul sistem socketpair().
In caz de succes, este returnat un descriptor de fisier pentru socket. In caz de esec, -1 este returnat si errno descrie problema.
#include <sys/types.h> #include <sys/socket.h> int bind(int s, struct sockaddr *name, int namelen)Amintiti-va ca folosind socketpair(), socket-urile puteau fi impartite numai intre procesul parinte si procesul fiu sau intre procesele fiu al aceluiasi proces parinte. Cu un nume atasat socket-ului, orice proces din sistem il poate descrie (si folosi).
La apelul sistem bind(), s este descriptorul de fisier pentru socket, obtinut prin apelul sistem socket(). name este un pointer la o structura de tip sockaddr. Daca familia adresei este AF_UNIX (cum a fost specificat la crearea socket-ului), structura este definita dupa cum urmeaza:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
name.sa_family ar trebui sa fie de tip AF_UNIX. name.sa_data ar
trebui sa contina pana la 14 octeti dintr-un nume de fisier care va
fi asignat socket-ului. namelen returneaza lungimea reala a lui
name, adica, lungimea continutului initializat al structurii de
date.
Valuarea 0 este returnata in caz de reusita. In caz de esec, -1 este returnat cu erno descriind eroare.
Exemplu:
struct sockaddr name;
int s;
name.sa_family = AF_UNIX;
strcpy(name.sa_data, "/tmp/sock");
if((s = socket(AF_UNIX, SOCK_STREAM, 0) < 0)
{
printf("socket create failure %d\n", errno);
exit(0);
}
if (bind(s, &name, strlen(name.sa_data) +
sizeof(name.sa_family)) < 0)
printf("bind failure %d\n", errno);
#include <sys/types.h> #include <sys/socket.h> int connect(int s, struct sockaddr *name, int namelen)Apelul sistem bind() permite numai specificarea unei adrese locale. Pentru a specifica partea de la distanta a unei adrese de conectare apelul sistem connect() este folosit. In apelul sistem de conectare, s este descriptorul de fisier pentru socket. name este un pointer la o structura de tip sockaddr:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
La fel ca la apelul sistem bind(), name.sa_family are trebui sa fie
de tip AF_UNIX. name.sa_data ar trebui sa contina pana la 14 octeti
dintr-un nume de fisier care va fi asignat socket-ului. namelen
returneaza lungimea actuala a numelui. Returnarea valorii 0 indica
reusita, in timp ce valoarea -1 indica esecul cu errno descriind
eroarea.
Un fragment exemplu de cod:
struct sockaddr name;
name.sa_family = AF_UNIX;
strcpy(name.sa_data, "/tmp/sock");
if (connect(s, &name, strlen(name.sa_data) +
sizeof(name.sa_family)) < 0) {
printf("connect failure %d\n", errno);
}
int sendto(int s, char *msg, int len, int flags,
struct sockaddr *to, int tolen)
Aceasta functie permite unui mesaj msg de lungime len sa fie trimis
printr-un socket cu descriptorul s socket-ului numit de to si
tolen, unde tolen esre lungimea actuala a lui to. flags va fi mereu
0 pentru scopurile noastre. Numarul de caractere trimise este
valoarea returnata de functie. In caz de eroare, -1 este returnat
si errno descrie eroarea.
Un exemplu:
struct sockaddr to_name;
to_name.sa_family = AF_UNIX;
strcpy(to_name.sa_data, "/tmp/sock");
if (sendto(s, buf, sizeof(buf), 0, &to_name,
strlen(to_name.sa_data) +
sizeof(to_name.sa_family)) < 0) {
printf("send failure\n");
exit(1);
}
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int s, char *msg, int len, int flags,
struct sockaddr *from, int *fromlen)
Aceasta functie permite unui mesaj msg de lungime maxima len sa fie
citit de la un socket cu descriptorul s din socket-ul denumit de
from si fromlen, unde fronlen este lungimea actuala a lui from.
Numarul de caractere citite de fapt de la socket este valuarea
returnata de functie. In caz de eroare, -1 este returnat si errno
descrie eroarea. flags poate fi 0, sau poate specifica lui MSG_PEEK
sa examineze un mesaj fara sa-l primeasca cu adevarat din coada.
Daca nu exista nici nu mesaj de citit, si daca socket-ul nu este setat pe modul fara blocaje (printr-un apel sistem ioctl), procesul va fi suspendat asteptand un mesaj.
Apelul sistem de intrare/iesire read() poate fi deasemenea folosit pentru a citi date de la un socket.
#include <stdio.h> void close(int s).Apelul sistem de intrare/iesire close() va inchide descriptorul socket-ului s la fel cum ar inchide orice descriptor de fisier deschis.
Exemplu - sendti() si recvfrom()
/* destinatar */
#include <sys/types.h>
#include <sys/socket.h>
struct sockaddr myname;
struct sockaddr from_name;
char buf[80];
main()
{
int sock;
int fromlen, cnt;
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sock < 0) {
printf("socket failure %d\n", errno);
exit(1);
}
myname.sa_family = AF_UNIX;
strcpy(myname.sa_data, "/tmp/tsck");
if (bind(sock, &myname, strlen(myname.sa_data) +
sizeof(name.sa_family)) < 0) {
printf("bind failure %d\n", errno);
exit(1);
}
cnt = recvfrom(sock, buf, sizeof(buf),
0, &from_name, &fromlen);
if (cnt < 0) {
printf("recvfrom failure %d\n", errno);
exit(1);
}
buf[cnt] = '\0'; /* assure null byte */
from_name.sa_data[fromlen] = '\0';
printf("'%s' received from %s\n",
buf, from_name.sa_data);
}
/* expeditor */
#include <sys/types.h>
#include <sys/socket.h>
char buf[80];
struct sockaddr to_name;
main()
{
int sock;
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sock < 0) {
printf("socket failure %d\n", errno);
exit(1);
}
to_name.sa_family = AF_UNIX;
strcpy(to_name.sa_data, "/tmp/tsck");
strcpy(buf, "test data line");
cnt = sendto(sock, buf, strlen(buf), 0, &to_name,
strlen(to_name.sa_data) + sizeof(to_name.sa_family));
if (cnt < 0) {
printf("sendto failure %d\n", errno);
exit(1);
}
}
Tipul socket-ului este specificat ca SOCK_STREAM. In consecinta, procesul care primeste un mesaj proceseaza acel mesaj dupa urmatoarele reguli:
#include <sys/types.h> #include <sys/socket.h> int listen(int s, int backlog)listen stabileste socket-ul ca un capat pasiv al conexiunii. Nu suspenda executia procesului.
Nici un mesaj nu poate fi transmis prin acest socket. Pot fi receptionate mesajele primite.
s este descriptorul de fisier asociat cu socket-ul creat folosind apelul sistem socket(). backlog esre marimea cozii de asteptare a cererilor in timp ce serverul este ocupat cu o cerere de serviciu. Valoarea maxima actuala a lui SYSTEM-IMPOSED este 5.
0 este returnat in caz de succes, -1 in caz de eroare, errno indicand problema.
Exemplu:
#include <sys/types.h>
#include <sys/socket.h>
int sockfd; /* descriptorul de fisier al socket-ului */
if(listen(sockfd, 5) < 0)
printf ("listen error %d\n", errno);
#include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *name, int *namelen)
Apelul sistem accept() realizeaza o conexiune client-server pe partea de server. (Clientul cere conexiunea folosind apelul sistem connect().) Serverul trebuie sa fi creat socket-ul folosind socket(), sa dea un nume socket-ului folosind bind(), si sa stabileasca o coada de asteptare folosind listen().
sockfd este descriptorul de fisier al socket-ului din apelul sistem socket(). name este un pointer la o structura de tip sockaddr descrisa deasupra
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
In cazul returnarii cu succes din accept(), aceasta structura va
contine adresa protocolului socket-ului clientului.
Zona de date pointata de namelen ar trebui initializara cu marimea actuala a lui name. UPON returnarii cu succes din accept, zona de date pointata de namelen va contine lungimea actuala a adresei protocolului a socket-ului clientului.
In caz de succes, accept() creaza un nou socket din aceeasi familie, tip, si protocal ca si sockfd. Descriptorul de fisier pentru acest nou socket este valoarea returnata de accept(). Acest nou socket este folosit pentru toate comunicatiile cu clientul.
Daca nu exista nici o cerere de conexiune din partea clientului, accept() se va bloca pana cand in coada apare o cerere din partea clientului.
accept() va esua in mare parte daca sockfd nu este un descriptor de fisier pentru un socket sau daca tipul socket-ului nu este SOCK_STREAM. In acest caz, accept() returneaza valoarea -1 si errno descrie problema.
#include <sys/types.h> #include <sys/socket.h> int send(int sd, char *buf, int len, int flags) int recv(int sd, char * buf, int len, int flags)In ambele cazuri, sd este descriptorul socket-ului.
Pentru send(), buf pointeaza la un buffer care contine datele ce trebuiesc trimise, len este lungimea datelor si flags va fi 0 de obicei. Valoarea returnata este numarul de octeti trimisi in caz de succes. In caz de nereusita, -1 este returnat si errno descrie eroarea.
Pentru recv(), buf pointeaza la o zona de date in care sunt copiate datele receptionate, len este marimea acestei zone de date in octeti, si flags este de obicei ori 0 ori setat pe MSG_PEEK daca datele trebuiesc retinute in sistem dupa ce sunt receptionate.Valoarea returnata este numarul de octeti primiti in caz de succes. In caz de nereusita, -1 este returnat si errno descrie eroarea.
/* Structura generica a unui program pentru stabilirea mediului client-server orientat pe conexiune. */
/* programul server */
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
struct sockaddr myname;
char buf[80];
main()
{
int sock, new_sd, adrlen, cnt;
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
printf("server socket failure %d\n", errno);
perror("server: ");
exit(1);
}
myname.sa_family = AF_UNIX;
strcpy(myname.sa_data, "/tmp/billb");
adrlen = strlen(myname.sa_data) +
sizeof(myname.sa_family);
unlink("/tmp/billb"); /* programare defensiva */
if (bind(sock, &myname, adrlen) < 0) {
printf("server bind failure %d\n", errno);
perror("server: ");
exit(1);
}
if (listen(sock, 5) < 0 {
printf("server listen failure %d\n", errno);
perror("server: ");
exit(1);
}
/* Ignorarea terminarii procesului fiu. */
signal (SIGCHLD, SIG_IGN);
/* Plaseaza serverul intr-o bucla infinita, asteptand
sa apara cereri de conexiuni de la clienti.
In practica, ar trebui sa existe o metoda curata
de a termina acest proces, dar deocamdata va ramane
rezident pana cand va fi terminat
de terminalul de start sau de super-user. */
while (1) {
if (new_sd = accept(sock, &myname, &adrlen)) < 0 {
printf("server accept failure %d\n", errno);
perror("server: ");
exit(1);
}
/* Creaza serverului-fiu procesul. Parintele nu mai
proceseaza -- se intoarce inapoi pentru a astepta
o alta cerere de conexiune. */
printf("Socket address in server %d is %x, %s\n",
getpid(), myname.sa_data, myname.sa_data);
if (fork() == 0) { /* procesul fiu */
close (sock); /* fiul nu are nevoie de el */
/* . . . . . */
cnt = read(new_sd, buf, strlen(buf));
printf ("Server with pid %d got message %s\n",
getpid(), buf);
strcpy (buf, "Message to client");
cnt = write(new_sd, buf, strlen(buf));
printf("Socket address in server %d is %x, %s\n",
getpid(), myname.sa_data, myname.sa_data);
/* . . . . . */
close (new_sd); /* inchiderea dinaintea iesirii */
exit(0);
} /* inchiderea acoladei pentru if (fork() ... ) */
} /* inchiderea acoladei pentru while (1) ... ) */
} /* inchiderea acoladei pentru main procedure */
/* programul client */
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
char buf[80];
struct sockaddr myname;
main()
{
int sock, adrlen, cnt;
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
printf("client socket failure %d\n", errno);
perror("client: ");
exit(1);
}
myname.sa_family = AF_UNIX;
strcpy(myname.sa_data, "/tmp/billb");
adrlen = strlen(myname.sa_data) +
sizeof(myname.sa_family);
if (connect( sock, &myname, adrlen) < 0) {
printf("client connect failure %d\n", errno);
perror("client: ");
exit(1);
}
/* . . . . . */
strcpy(buf, "Message sent to server");
cnt = write(sock, buf, strlen(buf));
cnt = read(sock, buf, strlen(buf));
printf("Client with pid %d got message %s\n",
getpid(), buf);
printf("Socket address in server %d is %x, %s\n",
getpid(), myname.sa_data, myname.sa_data);
/* . . . . . */
exit(0);
}
Urmatoarele pagini descriu cateva dintre rutinele disponibile pentru a realiza o conexiune de retea. Toate aceste functii sunt pentru folosirea cu domeniul AF_INET. Conceptul de adresare in retea este discutat mai intai.
O adresa IP este scrisa deobicei sub forma unei notatii punctata-zecimal de forma N1.N2.N3.N4, unde fiecare Ni este un numar zecimal cuprins intre zecimalele 0 si 255 (00 prin ff hexazecimal). Adresele sunt controlate si asignate de catre Centrul de Informatii despre Reteaua Internet al SRI International. In prezent exista definite 5 clase de retele, numite A, B, C, D si E; clasele A, B si C sunt disponibile utilizatorilor. Fiecare interfata de pe un host conectat la Internet trebuie sa aiba propria adresa IP.
Subretele. Pe masura ce numarul de host-uri dintr-o anumita retea a crescut in marime si acestea au ajuns din ce in ce mai separate geografic, consideratiile privind managementul retelei si limitarile stratului fizic precum lungimea cablului au impulsionat cercetarile in posibilele schimbari in instalarea retelelor fara a le afecta in mod drastic. Deasemenea, folosirea retelelor cu strat fizic eterogen ( de exemplu, Ethernet si Token Ring) cu aceeasi adresa IP a crescut.Conceptul de subretea a fost introdus pentru a ajuta la rezolvarea acestor probleme. Beneficiile pentru retelele de tip clasa A si clasa B valorifica efortul planificarii si implementarii subretelelor.
Ideea de baza intr-o subretea este de a partitiona portiunea din adresa IP care identifica host-ul in doua parti:
In capatul domeniului se afla doua litere care desemneaza tara
si trei litere (deobicei) care desemneaza o categorie generala din
interiorul USA. Cateva exemple:
ukuug.uk -- Grupul de utilizatori UNIX din Marea Britanie utdallas.edu -- Universitatea Texas din Dallas, USA ti.com -- Texas Instruments din USAIerarhii viitoare pot fi stabilite inauntrul acestor organizatii si sunt controlate local. Cateva exemple de host-uri:
csservr2.utdallas.edu -- host-ul csservr2 de la UT-D pac1.pac.sc.ti.com -- host-ul pac1 din domeniul pac dinauntrul domeniului pac din domeniul sc de la TI sunk.ssc.gov -- host-ul sunk de la laboratorul Superconducting SuperColliderDNS si celelate programe ajuta la intretinerea acestor conversii de nume si la translatia numelor de host-uri in adrese IP si vice versa.
Intrarile din acest fisier arata dupa cum urmeaza:
127.0.0.1 localhost 192.217.44.208 snoopy beagle hound metlife 153.110.34.18 bugs wabbit wascal 153.110.34.19 elmer 153.110.34.20 samTineti cont ca mai mult de un nume poate fi asociat cu o adresa IP data. Acest fisier este folosit la convertirea adreselor IP in nume de host-uri si vice versa.
Unor servicii de retea le-au fost asignate porturi bine
cunoscute. Asignarile de porturi unui serviciilor de retea pot fi
gasite in fisierul /etc/services. Selectarea unui port bine
cunoscut implica cautarea in acest fisier si este realizata cu
urmatoarele functii:
#include <netdb.h> struct servent *getservbyname(char *name, char *proto) struct servent *getservbyport(int port, char *proto)Cele doua optiuni pentru proto din fiecare apel sunt tcp pentru comunicatiile orientate spre siruri, si udp pentru comunicatii orientate spre datagrame. port este (cunoscut) numarul portului atunci cand numele serviciului este necesar, in timp ce name este sirul de caractere care contine numele serviciului cand numarul portului este necesar.
Valoarea returnata pentru fiecare functie este un pointer
la o structura cu urmatoarea forma:
struct servent {
char *s_name; /* numele oficial al serviciului */
char **s_aliases; /* lista de nume a serviciilor alias */
long s_port; /* unde se afla portul serviciului */
char *s_proto; /* protocolul care trebuie folosit */
};
Daca un program nu are nevoie sa comunice cu un port bine cunoscut
este posibil sa aleaga un port nefolosit pentru a fi folosit de un
program. Ilustrarea acestei tehnici se gaseste in programele
exemplu de mai jos.
#include <netinet/in.h>
/*
* Adresa Internet (o structura pentru motive istorice)
*/
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 pentru majoritatea codului tcp & ip */
};
/*
* Adresa socket-ului, in stil internet.
*/
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Completarea campurilor pentru sockaddr_in va produce o versiune de
Internet a unei adrese de socket.
#include <netdb.h> struct hostent *gethostbyname(char *name) struct hostent *gethostbyaddr(char *addr, int len, int type)name contine numele host-ului pentru care adresa IP este necesara. addr pointeaza la o structura de tip in_addr si len este marimea in octeti a acestei structuri. In aceasta discutie tipul este mereu AF_INET din moment ce discutia este limitata la folosirea adreselor IP din Internet.
Ambele apeluri returneaza un pointer la o structura de
intrare a unui host. Aceasta structura are urmatoare forma:
struct hostent {
char *h_name; /* numele oficial al host-ului */
char **h_aliases; /* lista de alias-uri */
int h_addrtype; /* tipul adresei */
int h_length; /* lungimea adresei */
char **h_addr_list;
/* lista adreselor de la serverul de nume */
#define h_addr h_addr_list[0]
/* adresa pentru compatibilitatea inversa */
};
Lasata asa cum este, comunicatia dintre, spre exemplu, calculatoarele compatibile IBM si statiile Sun Microsystems ar fi dificila in cel mai bun caz. Pentru a depasi asta, toate datele trimise printr-o retea sunt convertite la ordinea de octeti a retelei care este, in contextul discutiei de mai sus, ordinea "big-endian".
Rutinele pentru convertirea datelor dintre reprezentarea
interna a unui host si ordinea de octeti a retelei sunt:
#include <sys/types.h> #include <netinet/in.h> u_long htonl(u_long hostlong); u_short htons(u_short hostshort); u_long ntohl(u_long netlong); u_short ntohs(u_short netshort);Aceste functii sunt macrouri si rezulta in insertia codului sursa de conversie in programul apelant. Pe calculatoarele little-endian codul va schimba valorile la ordinea de octeti a retelei. Pe calculatoarele big-endian nici un cod nu va fi introdus din moment ce nu este nevoie; functiile sunt definite ca null.
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> /* structura in_addr */ unsigned long inet_addr(char *ptr) char *inet_ntoa(struct in_addr inaddr)inet_addr() converteste un sir de caractere in notatie punctata-zecimal intr-o adresa de Internet pe 32-BIT. Valoarea returnata nu este consistenta, din pacate. Valoarea returnata corect ar trebui sa fie un pointer la o structura de tip in_addr dar multe calculatoare, respectand o conventie mai veche, returneaza numai reprezentarea interna a notatiei punctate-zecimal. Paginile de manual vor clarifica situatia pentru calculatorul host pe care este folosita functia.
inet_ntoa() primeste o structura de tip in_addr ca parametru (notati ca structura in sine este folosita, nu un pointer) si returneaza un pointer la un sir de caractere continand reprezentarea punctata-zecimal a adresei de Internet.
vcclient localhost server-port-number
Daca vcclient este pornit pe un host diferit de cel pe care ruleaza
vcserver, linia de comanda pentru a porni vcclient este
vcclient server-host-name server-port-number
vcserver va ramane rezident pana cand utilizatorul in inchide. Ce-a
mai curata metoda de a face asta este
kill -15 pid
unde pid este id-ul procesului vcserver.
/* vcserver.c -- server TCP de retea (circuit virtual) */
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> /* structura sockaddr_in */
/* Aceasta intrare permite programului sa caute numele
host-ului si orice nume alias asociat cu el. */
#include <netdb.h> /* intrarile din tabel /etc/hosts */
main (int argc, char *argv[])
{
int rc, /* codul returnat de apelul sistem */
new_sd, sock, /* descriptorii socket-ului server/listen */
adrlen, /* lungimea lui sockaddr */
cnt; /* numarul de octeti de intrare/iesire */
struct sockaddr_in myname; /* numele socket-ului Internet */
struct sockaddr_in *nptr; /* pointer pentru a citi numarul portului */
struct sockaddr addr; /* numele generic al socket-ului */
char buf[80]; /* buffer-ul de intrare/iesire, cam mic */
/* Pentru cautarea in fisierul /etc/hosts . */
struct hostent *hp, *gethostbyaddr();
/* Identificarea procesului server. */
printf("\nThis is the network server with pid %d\n",
getpid() );
/* Se creaza un socket de "ascultare", ca in cazul socket-urilor de domenii din UNIX */
if (( sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
printf("network server socket failure %d\n", errno);
perror("network server");
exit(1);
/* Initializeaza campurile din structura numelui socket-ului Internet. */
myname.sin_family = AF_INET; /* Adresa Internet */
myname.sin_port = 0; /* Sistemul va asigna portul # */
myname.sin_addr.s_addr = INADDR_ANY; /* "Wildcard" */
/* Leaga adresa Internet la socket-ul Internet */
if (bind(sock, &myname, sizeof(myname) ) < 0 ) {
close(sock); /* programare defensiva */
printf("network server bind failure %d\n", errno);
perror("network server");
exit(2);
}
/* Citeste numarul portului asignat socket-ului Internet.
getsockname() obtine numarul portului asociat
cu socketul legat si il returneaza ca o parte a
informatiei din structura sockaddr addr. A se observa
ca, din moment ce numarul portului nu este trecut direct
de catre acest program oricarui client, singura cale de a-i
face "reclama" este de a-l afisa, aceasta insemnand, trimiterea lui
la iesirea stdout a utilizatorului. Diferit de aceasta afisare, acest
cod nu este intrinsec procesului care se conecteaza
*/
adrlen = sizeof(addr); /* este necesar tipul int pentru valoarea returnata */
if ( ( rc = getsockname( sock, &addr, &adrlen ) ) < 0 )
{
printf("setwork server getsockname failure %d\n",
errno);
perror("network server");
close (sock);
exit(3);
}
/* CODUL DE VERIFICARE: adresa general "addr" este folosita pentru
a returna valoarea socket-ului obtinuta de la apelul sistem
getsockname(). Afiseaza aceste informatii. In
definitia generala a structurii, tot in afara de familia
de adrese este definit ca sir de caractere. Dupa acest apel,
structura generala de adrese addr este folosita pentru a
pastra informatii despre procesul client. */
printf("\nAfter getsockname():");
printf(" server listen socket data\n");
printf("\taddr.sa_family field value is: %d\n",
addr.sa_family);
printf("\taddr.sa_data string is %d bytes long;\n",
sizeof ( addr.sa_data ) );
printf("\taddr.sa_data string is:");
for ( cnt = 0: cnt < sizeof (addr.sa_data); cnt++)
printf(" %x", addr.sa_data[cnt]);
printf("\n");
/* Acum "fa reclama" la numarul portului asignat
socket-ului. In acest exemplu, acest numar al portului trebuie sa fie
folosit ca al doilea parametru din linia de comanda
care porneste procesul client. */
/* A se observa folosirea pointerului nptr, cu o mapare
diferita a memoriei alocate, pentru a pointa la
strctura general de adrese. */
nptr = (struct sockaddr_in *) &addr; /* port # */
printf("\n\tnetwork server: server has port number: %d\n",
ntohs ( nptr -> sin_port ) );
/* Marcheaza socket-ul ca "citire-numai" sau socket pasiv */
if ( listen ( sock, 5 ) < 0 ) {
printf("network server bind failure %d\n", errno);
perror("network server");
close (sock);
exit(4);
}
/* Iesirea de verificare: informatia continuta in structura myname
(socket-ul Internet). */
printf("Server has set up client socket with values:\n");
printf("\tInternet address is %lx\n", myname.sin_addr.s_addr);
printf("\tPort number used is %d\n", myname.sin_port);
printf("\tInternet family ID is %d\n", myname.sin_family);
printf("\tValues are filled in after connection request ");
printf("is accepted.");
/* Seteaza "bucla infinita" pentru a asculta clientii. Din moment ce
structura "myname" este legata de socket-ul de ascultare,
valorile numelui structurii socket-ului si parametrului lungimii
socket-ului sunt omise de la apelul sistem acceptat. Valorile de granita
sunt folosite. */
while (1) {
if ( ( new_sd = accept ( sock, 0, 0 ) ) < 0 ) {
printf("network server accept failure %d\n", errno);
perror("network server");
close (sock);
exit(5);
}
/* Procesul fiu rezultat din apelul fork() care se ocupa de cererea de serviciu a clientului */
if ( ( fork() ) == 0 ) { /* Procesul fiu */
int pid;
pid = getpid(); /* PID-ul procesului fiu */
close (sock); /* Nu este necesara ascultarea socket-ului in procesul fiu. */
/* Afla cine este clientul. Observati folosirea
structurii generale de adrese addr pentru pastrarea informatiilor
despre clientul (conectat). */
if ((rc = getpeername( new_sd, &addr, &adrlen )) < 0) {
printf("network server %d getpeername failure %d\n",
pid, errno);
perror("network server");
close(new_sd);
exit(6);
}
/* Doar pentru zambete, "anunta" clientul. Observati ca,
din moment ce pointerul nptr este de tip struct sockaddr_in,
numele campurilor asa cum au fost definite in modelul
sockaddr_in pot fi folosite pentru accesarea valorilor din
structura generica addr. */
printf("\n\tnetwork server %d:", pid);
printf(" client socket from host %s\n",
inet_ntoa ( nptr -> sin_addr ) );
printf("\t has port number %d\n",nptr -> sin_port);
/* Acum gaseste toate numele asociate cu clientul; acesta
este motivul pentru cautarea in declaratiile din fisierul
/etc/hosts. */
if (( hp = gethostbyaddr (&nptr -> sin_addr,4,AF_INET))
!= NULL ) {
printf ("\tfrom hostname: %s\n\twith aliases: ",
hp -> h_name );
while ( *hp -> h_aliases )
printf ("\n\t\t\t%s", *hp -> h_aliases++ );
printf("\n\n");
}
else {
printf("network server %d ", pid);
printf("gethostbyaddr failure %d\n", h_errno);
perror("network server");
}
/* Schimba date cu clientul. Goleste buffer-ul intai. */
do {
/* Fa alegerea, in functie de dotarile sistemului.
Functiile lui System V nu au fost testate
in aceasta editie. */
bzero( buf, sizeof(buf)); /* zero buf, BSD call. */
/* memset (buf,0,sizeof(buf)); /* zero buf, S5. */
/* Citeste mesajul de la clientul de la distanta; daca lungimea mesajului
= 0, iesi. */
if (( cnt = read (new_sd, buf, sizeof(buf))) < 0 ) {
printf("network server %d ", pid);
printf("socket read failure &d\n", errno);
perror("network server");
close(new_sd);
exit(7);
}
else
if (cnt == 0) {
printf("network server received message");
printf(" of length %d\n", cnt);
printf("network server closing");
printf(" client connection...\n");
close (new_sd);
continue; /* iesi din bucla */
}
else {
/* Afiseaza mesajul primit de la client. Trimite
un mesaj inapoi. */
printf("network server %d received message",pid);
printf(" of length %d\n", cnt);
printf("network server %d received", pid));
printf(" the message %s\n", buf);
bzero (buf, sizeof(buf)); /* zero buf, BSD. */
/* memset(buf,0,sizeof(buf)); /* zero buf, S5. */
strcpy(buf, "Message from server to client");
write (new_sd, buf, sizeof(buf));
} /* sfarsitul ramurei else de la afisarea mesajului */
} /* sfarsitul buclei do */
while (cnt != 0); /* conditia buclei do */
exit(0); /* Iesirea procesului fiu */
} /* Terminarea conditiei adevarate a procesului if-child */
else /* Nu e procesul fiu; trebuie sa fie procesul parinte */
close (new_sd); /* Parintele nu are nevoie de socket-ul de lucru. */
} /* sfarsitul lui while (1) */
} /* sfarsitul procedurii principale */
/* vcclient.c -- Clientul retelei TCP ( circuit virtual) */
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> /* structura sockaddr_in */
/* Aceasta intrare permite programului sa caute numele
host-ului si a oricarui alias asociat cu el. */
#include <netdb.h> /* intrarile din tabelul /etc/hosts */
main (argc, argv)
int argc;
char *argv[];
/* Parametrii din linia de comanda asteptati:
argv[0] -- numele executabilului
argv[1] -- numele host-ului la care se doreste conexiunea
argv[2] -- numarul portului care va fi folosit de client:
valoarea este numarul portului asignat serverului
de catre sistemul host-ului server.
Nu este elegant, dar este foarte folositor pentru corectarea
codului de conectare. De exemplu, daca numele host-ului ( argv[1] )
ar fi fost specificat ca "localhost" si clientul si serverul
ar putea sa ruleze pe acelashi sistem dar codul conectivitatii retelei
ar fi fost exersat in intregime. */
{
int sock, /* descriptorul socket-ului */
val, /* variabila scratch */
cnt; /* numarul de octeti de intrare/iesire */
struct sockaddr_in myname; /* numele socket-ului Internet (addr) */
struct sockaddr_in *nptr; /* pointer pentru a obtine numarul portului */
char buf[80]; /* bufferul de intrare/iesire, cam mic */
/* Pentru cautarea in fisierul /etc/hosts . */
struct hostent *hp, *gethostbyaddr();
/* Verifica daca utilizatorul a dat toti parametrii
la linia de comanda. Daca da, converteste argv[2] la intreg; copie-l
in campul sin_port al structurii myname. Foloseste
functia htons pentru a se asigura ca valoarea este memorata
in ordinea de octeti a retelei. */
if ( argc < 3 ) {
printf("network client failure: required parameters");
printf(" missing from the command line\n");
printf("network client: usage");
printf("[executable-name] [host name] [port number]\n");
exit(1);
}
/* Ca si la domeniul UNIX, creaza un socket client care sa ceara
servicii */
if (( sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
printf("network client socket failure %d\n", errno);
perror("network client");
exit(2);
}
/* Converteste numarul portului dat de utilizator in intreg.
Nu exista nici un test al consistentei la aceasta setare. */
myname.sin_port = htons( atoi(argv[2]) ); /* Portul serverului # */
myname.sin_family = AF_INET; /* Domeniul Internet */
/* Numai in scopuri de afisare, afiseaza numele host-ului
si numarul convertit al portului. */
printf("network client %s will try to connect to host %s\n",
argv[0], argv[1]);
printf("network client %s will use port number %d\n",
argv[0], ntohs ( myname.sin_port ) );
/* Obtine informatii despre host-ul serverului. */
hp = gethostbyname ( argv[1] );
/* Acesta este codul principal de corectare; daca devine necesar
sa fie inserat (sau daca e mai comod sa fie la locul lui!!!
Nu-l scoate-ti cand totul merge bine.
Mai degraba, lasati-l la loc ca un comentariu. */
if ( hp == NULL ) {
printf("network client gethostbyname failure %d\n",
errno);
perror("network client");
close ( sock );
exit(3);
}
else {
printf("\nServer information obtained via");
printf(" gethostbyname:\n");
printf("\tThe official host name is %s\n", hp -> h_name);
printf("\tThe address type is %d\n", hp -> h_addrtype);
printf("\tThe length of the address is %d bytes\n",
hp -> h_length);
printf("\tThe first host address is %lx\n",
ntohl ( * (int * ) hp -> h_addr_list[0] ) );
printf("\tAlias names for the host are:\n");
while ( *hp -> h_aliases )
printf( "\t\t%s\n", *hp -> h_aliases++ );
}
/* Foloseste fie memcpy sau bcopy, in functie de nevoie
(System V vs BSD). */
bcopy ( hp -> h_addr_list[0], &myname.sin_addr.s_addr,
hp -> h_length );
/* memcpy ( &myname.sin_addr.s_addr, hp -> h_addr_list[0],
hp -> h_length ); */
/* Mai mult cod de corectat: Verifica continutul structurii myname
inainte de a incerca conectarea la serverul (de la distanta). */
printf("\nInformation provided in client's");
printf(" connect request\n");
printf("\tRemote host address is %lx\n",
ntohl ( myname.sin_addr.s_addr ) );
printf("\tPort number supplied is %d\n",
ntohs ( myname.sin_port ) );
printf("\tInternet family ID is %d\n", myname.sin_family);
printf("\tsin_zero character string is: %s\n",
myname.sin_zero);
/* Realizeaza conexiunea socket-ului cu serverul (de la distanta). */
if ( ( connect ( sock, &myname, sizeof(myname) ) ) < 0 ) {
printf("network client %s connect failure %d\n",
argv[0], errno);
perror("network client");
close (sock);
exit(4);
}
/* Schimba date cu clientul. Goleste octetii din buffer intai. */
/* Alege, in functie de dotarile sistemului. Functiile
System V nu au fost inca testate. */
bzero ( buf, sizeof( buf) ); /* zero buffer, BSD. */
/* memset ( buf, 0, sizeof( buf) ); /* zero buffer S5. */
strcpy ( buf, "Message from client to server" );
write ( sock, buf, sizeof(buf) );
/* Acum citeste mesajul trimis inapoi de server. */
if ( ( cnt = read (sock, buf, sizeof(buf) ) ) < 0 ) {
printf("network client socket read failure &d\n", errno);
perror("network client");
close(sock);
exit(5);
}
else
printf("network client received the message %s\n", buf);
/* Acum trimite un mesaj cu 0 octeti. */
bzero ( buf, sizeof( buf) ); /* zero buffer, BSD. */
/* memset ( buf, 0, sizeof( buf) ); /* zero buffer S5. */
write ( sock, buf, 0 );
close (sock);
exit(0);
} /* sfarsitul procedurii principale */