380 likes | 504 Views
Omreženje. Omreženje (networking). Ap likacije. Sistemske knjižnice (libc). Vmesnik sistemskih klicev. Vhod-izhod. Vezano na procese. Datotečni sistemi. razvrščevalnik. M oduli. Omreženje. Upravljanje s pomnilnikom. Gonilniki naprav. Medprocesna komunikacija.
E N D
Omreženje (networking) Aplikacije Sistemske knjižnice (libc) Vmesnik sistemskih klicev Vhod-izhod Vezano na procese Datotečni sistemi razvrščevalnik Moduli Omreženje Upravljanje s pomnilnikom Gonilniki naprav Medprocesna komunikacija Arhitekturno odvisna koda Aparaturna oprema
Struktura omreženja • Omreženje je ena od najbolj pomembnih funkcionalnosti sistema Linux. • Podpira standardne internetne protokole za UNIX komunikacije. • Implementira tudi protokole, ki niso značilni za sisteme UNIX ( PC omrežja). • Nad protokolom usmerjanja so zgrajeni protokoli UDP, TCP in ICMP. • Interno je omreženje v jedru Linux vgrajeno v treh programskih plasteh: • Vmesnik vtičnic (socket interface) • Gonilniki protokolov • Gonilniki omrežnih naprav
Kaj je vtičnica (Socket)? • To je abstrakcija povezovanja • Temelji na TCP in UDP • TCP je povezavno usmerjen protokol za prenos toka bytov., smiseln pri zanesljivih povezavah(“end to end” protocol) • UDP je storitev z datagrami, ki ne potrebuje sočasne povezave (connectionless) • Oba potrebujeta specifikacijo IP naslova in številke vrat • Ko tvorimo vtičnico, ima pridružen protokol (TCP ali UDP), ne pa naslova ali številke vrat • Vtičnica mora biti “navezana” na naslov oziroma vrata šele kasneje
Uporaba vtičnic za omreženje Proces -pošiljatelj Proces -prejemnik Prostor Uporabnika Prostor jedra Vtičnica (socket) Povezava Omrežje
Tvorba vtičnice Uporabimo sistemski klic socket() • socket(intprotocolFamily, int type, int proto) • protocolFamily = PF_INET za IP • type je SOCK_STREAM (TCP) ali SOCK_DGRAM (UDP) • proto je “end-to-end “ protokol IPPROTO_TCP ali IPPROTO_UDP (0 pomeni privzeto)
Tvorba vtičnice (nadaljevanje) • Klic socket() vrne “opisnik” • Opisnik je celo število, ki da “ročico” na vtičnico • Podobno, kot dobimo ročico pri klicu open() • To število mora biti pozitivno. Vrednost -1 pomeni neuspešen poskus tvorbe vtičnice • Po zaključku moramo vtičnico zapreti: • close(int socket)
Naslovi vtičnic • structsockaddr • { • unsigned short sa_family; /* AF_INET */ • char sa_data[14]; /* Protocol-specific address formation */ • }; • To je “generičen” podatkovni tip, ki vsebuje le družino naslovov (AF_INET za IP) in naslov, specifičen za protokol • Od 14 bytov jih 4 porabimo za IP naslov in 2 za številko vrat
Naslovi vtičnic • structsockaddr_in • { • unsigned short sin_family; /* AF_INET */ • unsigned short sin_port; /* Port (16-bits) */ • structin_addrsin_addr; /* Internet address (32-bits) */ • char sin_zero[8]; /* Not used */ • }; • sockaddr_inje internetna verzija strukture sockaddr. Lahko naredimo eksplicitno konverzijo v generičnosockaddr • Je prave velikosti in ima kompatibilno prvo polje
Povezan odjemalec in strežnik Asimetrična relacija med odjemalcem in strežnikom • Strežnik • Pasivno čaka na delo. • Ne ve, odkod bo prištel zahtevek za delo. • Odjemalec • Se aktivno poveže z znanim strežnikom.
Delovanje povezanega strežnika in odjemalca Operacije strežnika create SOCKET Operacije odjemalca BIND a ‘well-known’ port number to the socket Establish a LISTEN queue for connections create SOCKET Kot zahteva aplikacija ACCEPT a connection CONNECT to server’s port READ from connection WRITE to connection WRITE to connection READ from connection ‘END-OF-FILE’ CLOSE connection
Povezan odjemalec • Potrebni so naslednji koraki: • 1) Tvorba vtičnice • 2) Vzpostavitev povezave s klicem connect() • 3) Komunikacija s klici send() and recv() • 4) Zapiranje vtičnice s klicem close() • Klic connect: • int connect(int socket, structsockaddr *destAddress, unsigned intaddressLen) • socket je opisnik vtičnice, • sockaddrje naslov ciljne vtičnice, • lenjesizeof(structsockaddr_in)
Klicanje send/recv • int send(int socket, const void *msg, unsigned intmsgLen, int flags) • intrecv(int socket, void *rcvBuffer, unsigned intbufferLen, int flags) • msgje kazalec na obvestilo, msgLenje njegova dolžina; • rcvBuffersprejema vnašane byte do največ bufferLen; • flags=0 pomeni privzeto (default) obnašanje • send blokira proces, dokler niso prenešeni vsi byti; • recvblokira proces, dokler ni prenešenih vsaj nekaj bytov • send in recvvrneta število dejansko prenešenih ali sprejetih bytov; –1 pomeni napako • Če recvvrne 0, pomeni, da je bila na drugem koncu povezava zaprta
Povezan odjemalec (nadaljevanje) • Dobiti moramo IP naslov in številko vrat strežnika • Strežniški računalnik se lahko tudi spremeni • Trdo zakodiranje IP naslova ni sprejemljivo • Tipično prebere odjemalec ime strežnika iz argumentov v ukazni vrstici • gethostbyname():preslikava iz imena računalnika v IP naslov (pri tem uporablja /etc/hosts (ali NIS hosts map)) • Številke vrat se redko spreminjajo • Trdo zakodiranje številke vrat v kodo je pogosto zadovoljivo • Alternativno lahko uporabimo getservbyname()zato, da dobimo številko vrat za dani servis s pomočjo /etc/services (ali NIS servicess map)
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define SERVER_PORT 6543 #define SERVER_ADDR ‘137.68.17.7” Program odjemalca • main() { • int sockid; • struct sockaddr_in serv_addr; • bzero((char *) &serv_addr, sizeof(serv_addr)); • serv_addr.sin-family = AF_INET; • serv_addr.sin_addr.s_addr = inet_addr(SERVER_ADDR); • serv_addr.sin_port = htons(SERVER_PORT); • sockid = socket(IF_INET, SOCK_STREAM, 0); • connect(sockid, &serv_addr, sizeof(serv_addr)); • send(sockid, msg, sizeof(msg),0); • recv(sockid, *buffer, sizeof(buffer), 0); • close(sockid); • exit(0); • }
Gradnja strežnika • Zaenkrat uporabimo TCP (UDP kasneje) • Koraki bodo naslednji: • 1) Tvorimo vtičnico TCP/IP • 2) Dodelimo številko vrat s klicem bind() • 3) Dovolimo prihajajoče povezave s klicem listen() • 4) In ponavljamo sledeče: • a) Klic accept() da za vsako povezavo novo vtičnico • b)Preko vtičnice komuniciramo s send() inrecv() • c) Na koncu prekinemo povezavo z odjemalcem s klicem close()
Sistemski klic bind() • int bind(int socket, struct sockaddr *localAddr, unsigned int addressLen) • Dodeli vtičnici številko vrat • Ko bind() uspe, bodo vsi nadaljnji zahtevki connect() za računalnik na tem naslovu in vratih šli preko te vtičnice • Če nastavimo naslov na INADDR_ANY , bodo šle vse povezave za ta vrata na to vtičnico ne glede na uporabljen internetni naslov (uporabno, če ima računalnik več internetnih naslovov)
Sistemski klic listen() • Ko je vtičnica tvorjena in povezana, lahko začnemo poslušati prihajajoče zahtevke • int listen(int socket, intqueueLimit) • socket je opisnik vtičnice • queueLimitje največje štrvilo prihajajočih povezav, ki lahko čakajo v vrsti • Ta vtičnica služi le za vzpostavitev povezave • Vtičnico, ki jo uporabimo za poslušanje (listen) klicev, nikoli ne uporabljamo za komunikacijo s klici send() ali recv; To vtičnico rabimo le za čakalno vrsto dohodnih povezav. S klicem accept() pa zahtevke jemljemo iz vrste
Sistemski klic accept() • accept() vzame iz vrste naslednji zahtevek za povezavo • intaccept(socket, structsockaddr *clientAddr, • unsigned int *addrLen) • Če je vrsta prazna, klic accept() blokira proces do naslednjega zahtevka • socket je vtičnica “listen” • clientAddrzapolnimo z naslovom odjemalca • addrLenzapolnimo z največjo dolžino clientAddr, accept() pa jo zmanjša na dejansko velikost clientAddr • Accept ob uspehuvrne nov opisnik vtičnice, ob neuspehu pa -1
Interakcija strežnika • Ko (preko accept()) dobimo povezavo, lahko interaktiramo z odjemalcem preko send() in recv() • Enako kot na strani odjemalca • Uporabljamo vtičnico, ki jo vrne accept(), ne vtičnice za poslušanje (listen) • Če imamo več odjemalcev, z vsakim interaktiramo posamično z ustrezno vtičnico • Ko interakcijo z določenim odjemalcem zaključimo, zapremo ustrezno vtičnico • Če strežnik ne želi več sprejemati novih povezav, zapremo vtičnico za poslušanje
Program strežnika • main() { • intsockid, newsockid, childpid; • struct sockaddr_in cli_addr, serv_addr; • sockid = socket(AF_INET, SOCK_STREAM, 0); • bzero((char *) &serv_addr, sizeof(serv_addr)); • serv_addr.sin-family= AF_INET; • serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); • serv_addr.sin_port = htons(SERVER_PORT); • bind(sockid, &serv_addr, sizeof(serv_addr)); • listen(sockid, 5); • for (; ; ) { • /* wait for a client connects */ • newsockid = accept(sockid, &cli_addr, sizeof(cli_addr)); • if ((childpid = fork()) == 0) { /* child process */ • close(sockid); • serveit(newsockid); • exit(0); • } • close(newsockid); • } • }
Interoperabilnost • Podatki o omrežju • htons() host to network short • htonl() host to network long • ntohl() network to host long • ntohs() network to host short • To pošiljamo z enega računalnika drugemu kot večbytno dvojiško vrednost • Te funkcije uporabimo kot zadnjo operacijo pred pošiljanjem podatkov in kot prvo pri prejemanju podatkov
Okvirjenje (framing) in analiza • Pri protokolu TCP (in ne pri UDP) ni nujno, da en klic send() ustreza enemu klicu recv() • To pomeni, da morajo podatki vsebovati tudi vgrajene podatke o okvirjenju’ (na primer presledke, ničle ipd.) • Prejemnik mora vedeti, kako izluščiti informacije iz prejemanih podatkov
Primer iterativnega strežnika • char inbuf[1000]; /* Data from client */ • char outbuf[1000]; /* data to client */ • while(1) { • fd = accept (sock, &client, &client_len); • while( ( read (fd, inbuf, 1000) > 0) { • /* Process the data in inbuf, placing the result in outbuf */ • ... • write (fd, outbuf, 1000); • } • /* When client close its end of the connection, we close our end. • Otherwise we would eventually run out of file descriptors. • Then we loop round to pick up another connection. */ • close (fd); • }
Konkurenčni strežnik • Problem z iterativnimi strežniki • Če iterativni strežnik vzdržuje dolgoživljenske povezave z odjemalci, bi novi odjemalci lahko čakali nedoločen čas • Konkurenčni strežnik • Strežnik, ki lahko sočasno podpira več odjemalcev. • Dva pristopa za konkurenčni strežnik • Za vsakega odjemalca en proces-otrok • Uporabimo sistemski klic fork(). • Paziti moramo na “mrliče” (zombies)! • En sam strežni proces • Uporabimo sistemski klic select().
Konkurenčni strežnik- za vsakega odjemalca en otrok • sock = socket ( ... ); • bind (sock, ... ); • listen (sock, 2); • while (1) { • fd = accept ( sock, ... ); • if (fork() == 0) { • /* Child - process the request */ • ... /* use fd to say to the client */ • exit(0); /* Child is done */ • } else • close (fd); /* Parent does not use the connection */ • }
Strežnik z enim procesom • En proces s klicem select( ) • Strežnik multipleksira obvestila, ki jih prejema od povezanih odjemalcev • Po vsaki interakciji z odjemalcem se vrne v glavno zanko • Prednost • Manjša poraba sistemskih virov (pomnilnik, procesne tabele), saj imamo le en proces. • Slabost • Strežnik mora pomniti stanje povezave za vsakega odjemalca posebej
Sistemski klic select() • select(intnfds, *readfds, *writefds, *exceptfds, structtimeval *timeout) • readfds, writefdsin exceptfdskažejo na množice opisnikov • nfdsje največja številka opisnika fd number (plus 1) • Vrne enega iz množic opisnikov readfds {writefds | exceptfds} , ki je pripravljen za branje ali pisanje • Sicer blokira proces do izteka časa ( timeout) • Če je timeout =0, se takoj vrne • Iče je timeout=NULL, blokira proces do nadaljnjega • Makroji za brisanje, nastavljanje in testiranje posameznih opisnikov v množici • FD_ZERO(&fdset) /* Remove all descriptor from set fdset */ • FD_CLR(fd, &fdset) /* Remove descriptor fd from set fdset */ • FD_SET(fd, &fdset) /* Add descriptor fd to set fdset */ • FD_ISSET(fd, &fdset) /* True if fd is present in the set fdset */
Primer: select() • fd_set myset; • FD_ZERO(&myset); /* Put file descriptors 3 and 4 into myset */ • FD_SET(3, &myset); • FD_SET(4, &myset); • select (5, myset, NULL, NULL, NULL); • if (FD_ISSET(3, &myset)) { • /* read from descriptor 3 ... */ } • else if (FD_ISSET(4, &myset)) { • /* read from descriptor 4 ... */ } • else { • printf(“This should never happen”); }
Še o sistemskem klicu select() • Ker je množica opisnikov pri sistemskem klicu prekrita, moramo posebej pomniti njeno kopijo • Primer: po klicu ostanejo setirani le opisniki, pripravljeniza branje • use memcpy(&set1, &set2, sizeof set2) • Po povratku iz klica ni neposredne indikacije, kateri opisnik se je vzbudil. Vsakega posebej morqamo preveriti (polling).
Nepovezana odjemalec in strežnik • Strežniki ima vtičnico, na kateri prejema datagrame • Vtičnici lahko pošilja datagrame več odjemalcev. Datagrami prihajajo na strežnik v poljubnem, prepletenem vrsten redu • Strežnik ne pomni stanja (stateless server) • Strežnik preprosto prebere datagram, tvori odgovor in ga pošlje odjemalcu ter nanj pozabi • Primeri strežnikov: daytime, echo • Strežni pomni stanje (stateful server) • Lahko bi za vsakega odjemalca pomnil stanje v neki strukturi • Ali pa za vsakega odjemalca tvori proces-otrok • Lažje vzdrževanje stanja o vsakem odjemalcu • Primer: strežnik TFTP
Nepovezana odjemalec in strežnik (2) • Nezanesljivost nepovezanega strežnika in odjemalca (na primer UDP) • V protokolu nimamo vgrajenega mehanizma za odkrivanje (in popravljanje) problemov zaradi izgubljenih paketov in paketov v zamešanem zaporedju. • Predpostavljamo, da je sistem dostavljanja dovolj zanesljiv in sprejemamo tveganje, da aplikacija pade, če omrežje pade. • Na aplikacijskem nivoju lahko vgradimo mehanizem “timeout/retransmit”. • Pomenska razlika med strežnikom in odjemalcem ni velika • Tako odjemalec kot strežnik preprosto tvorita vtičnico, povežeta vrata in nato pošiljata in prejemata datagrame. • Vprašanje je le, kdo prej začne • Strežnik ne kliče listen() in accept() • Odjemalec ne kliče connect()
Delovanje nepovezanega strežnika in odjemalca Server Client 1 Client 2 create SOCKET create SOCKET BIND well-known port number BIND any port number SEND datagram RECEIVE datagram create SOCKET SEND datagram BIND any port number RECEIVE datagram SEND datagram RECEIVE datagram SEND datagram RECEIVE datagram
Nepovezana odjemalec in strežnik (4) • Tvorba vtičnice za datagrame • int sock; • sock = socket (AF_INET, SOCKDGRAM, 0); • Strežnik poveže “dobro znano” številko vrat na to vtičnico • Odjemalcu ni potreben klic bind(). Sistem opravi to avtomatsko. • Pošiljanje in prejemanje datagramov • Klicev read() in write() pri nepovezanih vtičnicah ne moremo uporabljati • uporabimo • sendto(sock, buff, count, flags, addr, addrlen); • recvfrom(sock, buff, count, flags, addr, &addrlen); • Prvi trije argumenti so enako kot pri write() in read() • Z zastavicami definiramo način dostave, običajno pa so nič • Ohranja meje sporočil: evsak recvfrom() ustreza enemu sendto(); razbijanje obvestil v koščke ni dopustno
Nepovezana odjemalec in strežnik • send() in recv() • send(sock, data_addr, data_len, flags); • recv(sock, data_addr, data_len, flags); • V seznamu argumentov ne podajamo naslova drugega računalnika. • Uporabljamo le pri datagramskih vtičnicah, kjer smo uporabljali klic connect() in vnaprej specificirali naslov drugega računalnika • sendmsg() in recvmsg() • sendmsg(sock, msg_info, flags); • recvmsg(sock, msg_info, flags); • msg_infokaže na strukturo , ki podpira operacije gather-write in scatter-read • gather-write: collection of separate data buffers, at different places in memory, can be gathered together into a single datagram • scatter-read: single datagram may be split up and delivered in to several separate buffers
Podrobnosti UDP • sendto() poveže vrata na uporabljeno vtičnico • Odjemalec nikoli ne pozna številke vrat,, je pa nanje navezana vtičnica • recvfrom() uporablja isto vtičnico, zato opazuje ista vrata, ki jih uporablja sendto() • Datagrami so lahko tudi izgubljeni, zato se lahko zgodi, da se klic recvfrom() nikoli ne zaključi in program se obesi; uporabiti moramo “timeout” • Strežnik se lahko blokira na klicu recvfrom() namesto na accept() • UDP strežnik uporablja le eno vtičnico (v nasprotju s strežnikom TCP) • En sam send in receive, v nasprotju s strežnikom TCP, kjer nadaljujemo sprejem, dokler odjemalec ne zaključi povezave
Podrobnosti UDP • Dva klica recvfrom() ne bosta nikoli vrnila podatke istega sendto() • UDP nima medpomnilnika za ponovno pošiljanje tako kot TCP (zato ni popravka napake) • Ko izvedemo klic sendto(), je obvestilo že odletelo ven • Ko podatki pridejo po protokolu TCP ali UDP, se uvrstijo v FIFO strukturo, ki čaka na klice recv() oziroma recvfrom()
Vrata TCP in UDP • Sta alternativi! • Lahko imamo TCP vrata 8888 in UDP vrata 8888 • So različna in zato niso v konfliktu • Če poskusimo connect() na vratih 1234 na računalnik , na katerem imamo le vrata UDP 1234 , povezava ne bo uspela • Torej, s TCP vrati se lahko pogovarjamo z uporabo TCP, za UDP vrata pa potrebujemo UDP