6 paskaita 1 Iteratyvaus aptarnavimo serveriai Iteratyvaus aptarnavimo

  • Slides: 76
Download presentation
 6 paskaita 1

6 paskaita 1

Iteratyvaus aptarnavimo serveriai Iteratyvaus aptarnavimo serverius yra paprasčiau suprojektuoti, įdiegti, palaikyti. Konkurencinio aptarnavimo serveriai

Iteratyvaus aptarnavimo serveriai Iteratyvaus aptarnavimo serverius yra paprasčiau suprojektuoti, įdiegti, palaikyti. Konkurencinio aptarnavimo serveriai siekia aptarnauti klientų užklausas “lygiagrečiai”, tuo užtikrindami vidutiniškai greitesnius atsakymo laikus į užklausas. – Iteratyvaus aptarnavimo serverius reiktų naudoti jei galime užtikrinti pakankamai trumpą serverio atsakymo laiką (atitinkamos aplikacijos, veikiančios pagal tam tikrą protokolą). 2

Konkuruojančio aptarnavimo serveriai • Pirma priežastis, reikalaujanti konkurencinio serverio aptarnavimo susijusi su greitesniais atsakymo

Konkuruojančio aptarnavimo serveriai • Pirma priežastis, reikalaujanti konkurencinio serverio aptarnavimo susijusi su greitesniais atsakymo laikais, kurie pasiekiami esant daug užklausų. • Konkurencinis aptarnavimas pagerina atsakymo laiką esant šioms sąlygoms: – Formuojant atsakymą reikalingas ženklus I/O. – Aptarnavimo laikas yra ryškiai skirtingas tarp įvairių užklausų. – Serveris sukasi kompiuteryje su keliais procesoriais. 3

Konkuruojantys ar iteratyvūs serveriai? Serveriai Aptarnauja Iteratyvūs Vieną užklausą vienu metu. Kada reiktų Kai

Konkuruojantys ar iteratyvūs serveriai? Serveriai Aptarnauja Iteratyvūs Vieną užklausą vienu metu. Kada reiktų Kai yra garantija, kad užklausa naudoti: bus apdorota per mažą laiko intervalą. Konkuruojantys: Daug užklausų vienu metu. Tais atvejais, kai negalima apriboti užklausos įvykdymo laiką. Privalumai: Triviali realizacija. Individualios klientų užklausos Lengva sutvarkyti kreipinius (pvz gali būti bet kokio sudėtingumo ir trukmės. jei yra ryšiai su duomenų baze). Problemos: Serveris yra blokuojamas kol turi Sudėtingumas susijęs su reikalų su užklausa. Jei užklausos konkurenciniu aptarnavimas užtrunka ilgiau nei leistina, joks kitas klientas negauna serviso. 4

Konkurencinio aptarnavimo serveriai Iteratyvius serverius yra lengviau suprogramuoti, tačiau konkuruojančio tipo serveriai yra greitesni.

Konkurencinio aptarnavimo serveriai Iteratyvius serverius yra lengviau suprogramuoti, tačiau konkuruojančio tipo serveriai yra greitesni. Konkuruojantys, neorientuoti į susijungimą serveriai Naudojami smarkiai apkrautiems servisams, kurie reikalauja greito apsisukimo (turnaround) – pavyzdžiui: DNS, NFS. Konkuruojantys į susijungimą orientuoti serveriai Kitais atvejais. . . Paprastai dauguma standartinių serverių yra šio tipo. Į susijungimą neorientuotus serverių atveju visas patikimumo problemas turi spręsti atitinkamas aplikacijos protokolas. 5

Reali ar tariama konkurencija Parašyti konkurencinio aptarnavimo serverį, realizuojant jį vienu procesu yra sunku.

Reali ar tariama konkurencija Parašyti konkurencinio aptarnavimo serverį, realizuojant jį vienu procesu yra sunku. Vienas procesas naudojamas tais atvejais, jei klientai turi dalintis duomenimis. Daug procesų naudojama (slave procesų), jei kiekvieno kliento aptarnavimas yra nesurištas su kitų aptarnavimu, arba jei turima keletą CPU. 6

Konkurencijos lygis Kiek gi bus konkuruojančių, vienu metu aptarnaujamų klientų? Iteratyvių serverių atveju yra

Konkurencijos lygis Kiek gi bus konkuruojančių, vienu metu aptarnaujamų klientų? Iteratyvių serverių atveju yra aptarnaujamas vienas klientas vienu metu. Neribotas klientų skaičius garantuotų puikias aptarnavimo perspektyvas, tačiau kyla problemos: – – – TCP programinė įranga riboja susijungimų kiekį. OS riboja, kiekvienam procesui atidarytų failų kiekį. OS riboja procesų kiekį. 7

Neribotos konkurencijos problemos OS gali pritrūkti resursų: atminties, procesų, soketų, buferiųiššaukdama blokavimą, strigimą, krachą,

Neribotos konkurencijos problemos OS gali pritrūkti resursų: atminties, procesų, soketų, buferiųiššaukdama blokavimą, strigimą, krachą, . . . Vieno serviso užklausos gali kliudyti kitoms pav: web servisas gali užblokuoti kitus servisus. Didelis klientų kiekis gali iššaukti funkcionavimo pablogėjimą: pav: ftp serveris gali būti toks lėtas, kad klientai nutrauks užklausas. 8

Konkurencijos kaina Jei naudojamas konkurencinio aptarnavimo serveris, kuris kuria naują procesą(fork) kiekvienai naujai komunikacijai

Konkurencijos kaina Jei naudojamas konkurencinio aptarnavimo serveris, kuris kuria naują procesą(fork) kiekvienai naujai komunikacijai – susijungimui, tai reikia suprasti, kad naujo proceso sukūrimas reikalauja ne tik resursų, bet ir laiko sukūrimui(tarkim=c) bei užkausos aptarnavimui(tarkim=p). Tarkim 2 užklausos pasirodo vienu metu. Iteratyvaus serverio atveju, jų abiejų aptarnavimo laikas =2 p. Konkurencinio aptarnavimo atveju, jis būtų apie 2 c+p. jei p < 2 c tai iteratyvus serveris yra greitesnis. Situacija dar pablogėtų, jei užklausų kiekis būtų dar didesnis. Aktyvių procesų kiekis gali viršyti CPU galimybes. Serveriai, turintys didelius apkrovimus, paprastai stengiasi išsisukti nuo procesų sukūrimo kainos. 9

Konkurencinio aptarnavimo serverio projektavimo alternatyvos 1. Vienas vaiko procesas kiekvienam klientui 2. Viena gija

Konkurencinio aptarnavimo serverio projektavimo alternatyvos 1. Vienas vaiko procesas kiekvienam klientui 2. Viena gija ( thread ) kiekvienam klientui 3. Iš anksto sukurti (preforking) keli procesai 4. Iš anksto sukurtos (prethreaded) kelios gijos 5. Realizacija viename procese (select) 10

Koks serverio tipas geriausias duotai aplikacijai? • Daug faktorių: – Laukiamas vienalaikių klientų kiekis.

Koks serverio tipas geriausias duotai aplikacijai? • Daug faktorių: – Laukiamas vienalaikių klientų kiekis. – Atsakymo trukmė (laikas kurio reikės atsakymui paskaičiuoti ar rasti) – Skirtumai užklausų dydžiuose. – Prieinami sistemos resursai (tai resursai, kurių reikės serviso suteikimui). 11

Atskiras vaiko procesas klientui • Tradicinis Unix serveris: – – TCP: po kreipinio accept(),

Atskiras vaiko procesas klientui • Tradicinis Unix serveris: – – TCP: po kreipinio accept(), kviečiamas fork(). UDP: po recvfrom(), kviečiama fork(). kiekvienam procesui reikia kelių soketų. Trumpos užklausos gali būti aptarnautos per trumpą laiką. • Tėvo procesui reikia išvalyti vaikus!!!! – (reikia wait() ). 12

Viena gija kiekvienam klientui • Tas pats kaip ir naudojant fork – tik kuriama

Viena gija kiekvienam klientui • Tas pats kaip ir naudojant fork – tik kuriama gija kviečiant pthread_create. • Gijų sukūrimas susijęs su mažesnėm op sistemos sąnaudom, jos gali dalintis bendrai naudojamais duomenimis. • Dalinantis bendra informacija reikia nepamiršti sinchronizacijos problemų (reikia naudoti pthread_mutex) 13

pthread_create() Process A Global Variables fork() Code Process B Stack Global Variables Code Process

pthread_create() Process A Global Variables fork() Code Process B Stack Global Variables Code Process A Global Variables Code Stack 14

Prefork()’d Server • Naujo proceso sukūrimas kiekvienam klientui yra brangus. • Galima sukurti krūvą

Prefork()’d Server • Naujo proceso sukūrimas kiekvienam klientui yra brangus. • Galima sukurti krūvą procesų, kiekvienas iš kurių rūpinsis atskiru klientu. • Kiekvienas vaiko procesas bus iteratyvus serveris. 15

Prefork()’d TCP Server • Pradinis procesas sukuria soketą ir suriša (bind) jį su gerai

Prefork()’d TCP Server • Pradinis procesas sukuria soketą ir suriša (bind) jį su gerai žinomu adresu. • Procesas kviečia fork() keletą kartų. • Visi vaikai kviečia accept(). • Visi ateinantys susijungimai bus aptarnauti vieno vaiko. 16

Preforking • Kaip pažymima literatūroje, toks išankstinis vaikų prigimdymas nėra geras. • Dinaminis procesų

Preforking • Kaip pažymima literatūroje, toks išankstinis vaikų prigimdymas nėra geras. • Dinaminis procesų priskyrimas yra geresnis nei iš anksto užkoduoto skaičiaus procesų paleidimas. • Tėvo procesas paprastai tik valdo vaikus, jis nesirūpina apie klientus. • Accept() kreipinys yra bibliotekinė f-ja – o ne atominė operacija --- galimi keblumai, jei keli vaikai vienu metu vykdys accept(). 17

Prethreaded Server • Tokie pat privalumai kaip ir preforking atveju. • Gali taip pat

Prethreaded Server • Tokie pat privalumai kaip ir preforking atveju. • Gali taip pat turėti pagrindinę giją ( thread) kuri vykdo visus accept() kreipinius ir suriša kiekvieną klientą su jau egzistuojančia gija (thread). 18

Konkuruojančių serverių algoritmai • “Master and slave” (pono ir tarno? ) procesai – Dauguma

Konkuruojančių serverių algoritmai • “Master and slave” (pono ir tarno? ) procesai – Dauguma konkuruojančio tipo serverių naudoja daug procesų: • “Master” procesas ir “slave” procesai – Master procesas atidaro soketą gerai žinomame porte, laukia sekančių užklausų ir sukuria “slave” serverio procesą kiekvienos užklausos aptarnavimui. – Master procesas niekad nekomunikuoja tiesiai su klientu. – Slave procesas vykdo komunikacijas su vienu klientu ir baigiasi po užklausos aptarnavimo. 19

Konkuruojantis, į susijungimą neorientuotas serveris • Algoritmas – Master: Sukurti soketą ir surišti jį

Konkuruojantis, į susijungimą neorientuotas serveris • Algoritmas – Master: Sukurti soketą ir surišti jį su adresu, paliekant jį nesujungtą. – Master: Cikliškai kviesti recvfrom() sekančios klientų užklausos priėmimui ir sukurti slave procesą (naudojant fork() ) užklausos aptarnavimui. – Slave: skaityti kliento užklausą, formuluoti atsakymą bei jį siųsti klientui naudojant sendto(). – Slave: Uždaryti susijungimą ir pasibaigti. • komentarai – Keletas serverių yra tokio tipo. Proceso sukūrimas yra brangus. 20

Konkuruojantis, į susijungimą neorientuotas serveris create a socket bind to a well-known port while

Konkuruojantis, į susijungimą neorientuotas serveris create a socket bind to a well-known port while ( 1 ) { read a request from some client fork if ( child ){ send a reply to that client exit } } · fork (papildomi resursai), exit (kada baigti? ) Nėra dažnai naudojamas 21

Konkuruojantis, į susijungimą orientuotas serveris Master : Sukurti soketą, jį surišti su gerai-žinomu adresu(bind).

Konkuruojantis, į susijungimą orientuotas serveris Master : Sukurti soketą, jį surišti su gerai-žinomu adresu(bind). Soketą palikti nesujungtą. Master : Soketas paliekamas pasyvioje būsenoje, laukdamas susijungimo. Master : Cikliškai vykdyti accept priimant naujas užklausas iš kliento, priėmus sukurti naują slave procesą užklausos aptarnavimui. Slave : Gauti susijungimo užklausą (soketą susijungimui) sukūrimo metu. Slave: Sąveikauti su klientu pasinaudojant susijungimu: skaityti užklausas ir siųsti atgal atsakymus. Slave : Uždaryti susijungimą ir pasibaigti. Slave procesas pasibaigia pilnai aptarnavęs vieną klientą. 22

Apibendrinimas • Į susijungimą orientuoti serveriai konkurencijai užtikrinti sukuria naują procesą kiekvienam naujam susijungimui.

Apibendrinimas • Į susijungimą orientuoti serveriai konkurencijai užtikrinti sukuria naują procesą kiekvienam naujam susijungimui. • Į susijungimą neorientuoti serveriai konkurencijai užtikrinti sukuria naują procesą kiekvienai naujai užklausai. 23

Konkuruojantis, į susijungimą orientuotas serveris 24

Konkuruojantis, į susijungimą orientuotas serveris 24

Konkuruojantis, į susijungimą orientuotas serveris Siekiant išvengti CPU resursų panaudojimo kol yra laukiama susijungimo

Konkuruojantis, į susijungimą orientuotas serveris Siekiant išvengti CPU resursų panaudojimo kol yra laukiama susijungimo užklausų, master serverio procesas naudoja blokuojantį kreipinį accept. Kaip ir iteratyvaus serverio procesas, master serverio procesas konkuruojančiame serveryje daugumą laiko praleis blokuotame būvyje po accept kreipinio. Atsiradus susijungimo užklausoms, accept kreipinys grąžins procesą iš blokuoto būvio, leisdamas master procesui vykdyti veiksmus toliau: sukurti slave procesą užklausos apdorojimui ir vėl kreiptis į accept(pereinant į blokuotą būvį kol vėl pasirodys užklausa). 25

Slave procesai sukuriami kaip master proceso vaikai naudojant fork() kreipinį. Daugumos servisų atveju vienos

Slave procesai sukuriami kaip master proceso vaikai naudojant fork() kreipinį. Daugumos servisų atveju vienos programos rėmuose yra aprašomi tiek master proceso, tiek slave procesų veiksmai. Tais atvejais, kai atskiros programos teikia didesnes galimybes suprasti slave veiksmus ar paprasčiau juos užprogramuoti, master programoje, po proceso- vaiko sukūrimo kreipiniu fork() yra atliekamas vaiko proceso vykdomo kodo keitimas kitos – slave programos kodu pasinaudojant kreipiniu execve(). 26

Konkuruojantis, į susijungimą orientuotas serveris create a socket bind to a well-known port use

Konkuruojantis, į susijungimą orientuotas serveris create a socket bind to a well-known port use listen to place in passive mode while ( 1 ) { accept a client connection fork if ( child ){ communicate with new socket close new socket exit } else { close new socket } } • Viena programa, kurioje yra ir master ir slave procesų kodai • Galima vaikų procesų kūnus keisti slave programų kūnais panaudojant execve(). 27

Tipinė serverio struktūra read(), write() via new socket 28

Tipinė serverio struktūra read(), write() via new socket 28

Serverio struktūra 29

Serverio struktūra 29

Fork() bash-3. 00$ more prc 1. c #include <sys/types. h> #include <unistd. h> #include

Fork() bash-3. 00$ more prc 1. c #include <sys/types. h> #include <unistd. h> #include <sys/wait. h> #include <stdio. h> int main ( void ) { pid_t pid; int *stat_loc; printf ("veikia tevo procesas. . . n"); printf ( "Mano PID: %dn : ", getpid() ); printf ( "------------ n"); printf ("Kuriamas vaiko procesas. . . n"); pid = fork (); system( "who"); /* vykdys abu procesai */ if ( pid < 0 ) { printf ( "Nepavyko sukurti vaiko proceso !. . n" ); exit ( 1 ); } if ( pid > 0 ) { wait( NULL ); sleep (2); printf ( "vel veikia tevo procesas ! n "); printf ( "Vaikas baige darba ( tevas ) !n Mano PID: %dn Buvusio vaiko PID: %dn", getpid(), pid ) printf ( "------------ n"); } else { printf ( "As sukurtas vaiko procesas !n Mano PID: %dn Tevo proceso PID: %dn", getpid(), getppid() ); printf ( "------------ n"); exit(2); } } 30

Procesų eiga -bash-3. 00$. /prc 1. exe veikia tevo procesas. . . Mano PID:

Procesų eiga -bash-3. 00$. /prc 1. exe veikia tevo procesas. . . Mano PID: 9464 : ------------ Kuriamas vaiko procesas. . . os pts/1 Mar 28 17: 55 (cl-02. pit. ktu. lt) vaskpaul pts/2 Mar 28 16: 05 (193. 219. 32. 94) vaskpaul pts/3 Mar 28 16: 06 (193. 219. 32. 94) matomind pts/7 Mar 28 14: 04 (193. 219. 184. 76) nijole pts/8 Mar 28 10: 36 (cl-02. pit. ktu. lt) nijole pts/10 Mar 28 10: 52 (cl-02. pit. ktu. lt) kurgremi pts/11 Mar 28 17: 05 (85. 206. 88. 138) urbojura pts/34 Mar 28 16: 08 (193. 219. 181. 195) mararemi pts/42 Mar 28 16: 13 (85. 206. 0. 83) As sukurtas vaiko procesas ! Mano PID: 9465 Tevo proceso PID: 9464 ------------ os pts/1 Mar 28 17: 55 (cl-02. pit. ktu. lt) vaskpaul pts/2 Mar 28 16: 05 (193. 219. 32. 94) vaskpaul pts/3 Mar 28 16: 06 (193. 219. 32. 94) matomind pts/7 Mar 28 14: 04 (193. 219. 184. 76) nijole pts/8 Mar 28 10: 36 (cl-02. pit. ktu. lt) nijole pts/10 Mar 28 10: 52 (cl-02. pit. ktu. lt) kurgremi pts/11 Mar 28 17: 05 (85. 206. 88. 138) urbojura pts/34 Mar 28 16: 08 (193. 219. 181. 195) mararemi pts/42 Mar 28 16: 13 (85. 206. 0. 83) vel veikia tevo procesas ! Vaikas baige darba ( tevas ) ! Mano PID: 9464 Buvusio vaiko PID: 9465 ------------ 31

Echo serveris (TCP) /* echo serveris */ #include <sys/types. h> #include <sys/socket. h> #include

Echo serveris (TCP) /* echo serveris */ #include <sys/types. h> #include <sys/socket. h> #include <stdio. h> #include <netinet/in. h> #include <netdb. h> #define MAXLINE 100 #define SERV_PORT 33333 int main(int argc, char *argv[]) { int listenfd, connfd; struct sockaddr_in servaddr, cliaddr; pid_t childpid; char buf[80], line[100]; int s, cc; 32

Echo serveris tęsinys listenfd = socket(AF_INET, SOCK_STREAM, 0); if(!listenfd) { perror("Socket could not be

Echo serveris tęsinys listenfd = socket(AF_INET, SOCK_STREAM, 0); if(!listenfd) { perror("Socket could not be created"); exit(1); } memset(&servaddr, 0, sizeof servaddr); servaddr. sin_family = AF_INET; servaddr. sin_port = htons(SERV_PORT); servaddr. sin_addr. s_addr= htonl(INADDR_ANY); bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); listen(listenfd, 5); 33

Echo serveris tęsinys for (; ; ) { s=sizeof(cliaddr); connfd= accept(listenfd, (struct sockaddr *)

Echo serveris tęsinys for (; ; ) { s=sizeof(cliaddr); connfd= accept(listenfd, (struct sockaddr *) &cliaddr, &s); if ( (childpid = fork()) == 0) { /* child process */ close(listenfd); /* close listening socket */ for(; ; ) { memset(&buf, 0, 80); cc =0; if ((cc = read(connfd, buf, sizeof(buf), 0)) < 0) { perror("Client: recvn"); exit(4); } if (cc > 0 ) printf("priemiau %dn", cc); if (buf[0] == '#') { printf("Bye for now!n"); close(connfd); exit(1); } if (cc = write(connfd, buf, cc, 0) < 0) { perror("Client: send"); exit(5); } } close (connfd); } } } 34

Tcp_cli. c (kliento programa) #include <sys/types. h> #include <sys/socket. h> #include <stdio. h> #include

Tcp_cli. c (kliento programa) #include <sys/types. h> #include <sys/socket. h> #include <stdio. h> #include <netinet/in. h> #include <netdb. h> #define MAXLINE 100 #define SERV_PORT 33333 int main(int argc, char *argv[ ]) { int sockfd; struct sockaddr_in servaddr; char buf[80], line[100]; int s, cc; if (argc != 2) printf("usage: tcpcli <IPaddress>"); sockfd = socket(AF_INET, SOCK_STREAM, 0); memset(&servaddr, 0, sizeof servaddr); servaddr. sin_family = AF_INET; servaddr. sin_port = htons(SERV_PORT); servaddr. sin_addr. s_addr= inet_addr (argv[1]); connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); 35

Tcpcli. c tęsinys for(; ; ) { cc = getline(line, MAXLINE); if (cc =

Tcpcli. c tęsinys for(; ; ) { cc = getline(line, MAXLINE); if (cc = write(sockfd, line, cc, 0) < 0) { perror("Client: send"); exit(5); } if (line[0] == '#') { printf("Bye for now!n"); close(sockfd); sleep (1); exit(4); } memset(&buf, 0, 80); if ((cc = read(sockfd, buf, sizeof(buf), 0)) < 0) { perror("Client: recv"); exit(4); } printf("%sn", buf); } } } 36

Problemos • Klientas susijungia bet neatsiunčia užklausų. Serveris blokuojamas ties read kreipiniu. • Klientas

Problemos • Klientas susijungia bet neatsiunčia užklausų. Serveris blokuojamas ties read kreipiniu. • Klientas siunčia užklausą, bet negali nuskaityti atsakymo. Serveris blokuojasi ties write kreipiniu. • Gali susidaryti mirties taško situacija, jei programa ar grupė programų negal vykdyti jokių veiksmų, nes yra užblokuotos – laukia įvykio, kuris niekad neįvyks. Serverio atveju, tai gali susilpninti jo galimybes atsakyti į užklausas. 37

Procesų “prigaminimas” Master serverio procesas vykdo fork() n kartų. n slave procesų yra pasiruošę

Procesų “prigaminimas” Master serverio procesas vykdo fork() n kartų. n slave procesų yra pasiruošę aptarnauti klientus. Slave procesai vykdo veiksmus kaip n iteratyvių serverių. vaikų (slave) procesai paveldi tėvo pasyvų soketą, slave procesai gali visi laukti naudodami accept tame pačiame sokete. UDP atveju, slave procesai patys gali kviesti recvfrom tame pačiame sokete. vengiant problemų, susijusiu su atminties išnaudojimu, slave procesai gali būti periodiškai pakeičiami. UDP atveju, gali būti susiduriama su buferio perpildymo problemomis. Procesų prigaminimas gali sumažint šią problemą. 38

Dinaminis “prigaminimas” Procesų išankstinis “prigaminimas” gali išsilieti į papildomą CPU darbą, jei daug slave

Dinaminis “prigaminimas” Procesų išankstinis “prigaminimas” gali išsilieti į papildomą CPU darbą, jei daug slave procesų vienu metu laukia tame pačiame sokete. Jei serveris yra smarkiai apkrautas geriau yra turėti daug iš anksto paruoštų slave procesų. Jei serveris nėra apkrautas, šių slave procesų kiekis turėtų būt nedidelis. Kai kurie serveriai (Apache) priderina konkurencijos lygį su apkrovos intensyvumu. 39

Pavėlintas fork() Vietoj to, kad tuoj pat vykdyt fork(), master procesas gali ištirti užklausą:

Pavėlintas fork() Vietoj to, kad tuoj pat vykdyt fork(), master procesas gali ištirti užklausą: Kartais gali būti greičiau užklausą apdoroti master procese, nei priskirti ją vaikui. Esant ilgesnėms užklausoms jas aptarnauti gali būti pavedama vaikui. Jei yra sunku nustatyti užklausos aptarnavimo laiką, gali būt fiksuojama užklausos aptarnavimo pradžia ir jei per tam tikrą laiką užklausos aptarnauti nepavyksta – užklausos tolesnis aptarnavimas deleguojamas vaiko procesui. 40

41

41

“I/O multiplexing” panaudojimas tinklinėse aplikacijose: a. b. c. d. e. Kai klientas valdo kelis

“I/O multiplexing” panaudojimas tinklinėse aplikacijose: a. b. c. d. e. Kai klientas valdo kelis deskriptorius( paprastai – interaktyvų įvedimą ir tinklinį soketą), I/O multiplexing turėtų būti naudojamas. Galimas, tačiau retas atvejas, kad klientas vienu metu valdo keletą soketų. Jei TCP serveris valdo klausantį soketą ir susijungusius soketus, I/O multiplexing paprastai yra naudojamas. Jei serveris valdo ir TCP ir UDP servisus, I/O multiplexing paprastai yra naudojamas. Jei serveris apdoroja daug servisų ir gal būt daug protokolų (pav. inetd daemon), I/O multiplexing paprastai yra naudojamas. 42

Konkurencija naudojant vieną procesą. Kartais konkurencinis klientų aptarnavimas realizuojamas viename procese. Pvz – operacinei

Konkurencija naudojant vieną procesą. Kartais konkurencinis klientų aptarnavimas realizuojamas viename procese. Pvz – operacinei sistemai gal būt neužtenka resursų sukurti naują procesą, atitinkantį naujam susijungimui. Dar svarbiau, dauguma aplikacijų reikalauja, kad serveris dalytųsi informacija tarp susijungimų. Nors gali nebūti galimybės pasiekti realios konkurencijos tarp procesų, kurie dalosi atmintimi, galima sukurti konkurencijos regimybę, jei bendra apkrova neviršija serverio galimybių jas aptarnauti. Tai realizuojama naudojant UNIX procesą, kuris naudoja select() sisteminį kreipinį. 43

Į susijungimus-orientuoto serverio proceso struktūra, kai konkurencija realizuojama viename procese, valdančiame daug soketų. 44

Į susijungimus-orientuoto serverio proceso struktūra, kai konkurencija realizuojama viename procese, valdančiame daug soketų. 44

Konkuruojantis, į susijungimą orientuotas serveris (vienas procesas) • Algoritmas – Sukurti soketą ir surišti

Konkuruojantis, į susijungimą orientuotas serveris (vienas procesas) • Algoritmas – Sukurti soketą ir surišti jį su adresu. Pridėt šį soketą prie sarašo soketų, kurie gali būti naudojami I/O. – Naudoti select() laukiant I/O viename iš saraše nurodytų soketų. – Jei originalus soketas pasirengęs I/O, naudoti accept() sekančio susijungimo gavimui ir pridėti šį soketą prie sarašo soketų, kuriuose galimas I/O. – Jei bet kuris kitas( ne originalus) yra pasiruošęs skaitymui iš jo, naudoti read() sekančiai užklausai priimti, suformuoti atsakymą ir panaudoti write() atsakymo nusiuntimui atgal. – Tęsti select() veiksmus. . . 45

Problema? Galima situacija, kai tenka skaityti iš daugiau nei vieno šaltinio. Pavyzdžiui, norima suprojektuoti

Problema? Galima situacija, kai tenka skaityti iš daugiau nei vieno šaltinio. Pavyzdžiui, norima suprojektuoti serverį, kuris atidaro du soketus ir perduoda pranešimus iš vieno soketo į kitą. Kadangi serveris nežino, kada duomenys pasirodys šiuose soketuose, jis negali pradėti skaityti viename iš šių soketų, nes užsiblokuos nežiūrint į tai, kad kitame sokete pranešimas pasirodė. Problema sprendžiama naudojant sisteminį kreipinį select. Šis kreipinys leidžia vartotojo procesui nurodyti op sistemos branduoliui, kad reikia laukti bet kokio iš daugelio galimų įvykių pasirodymo ir tik tada pažadinti procesą, kai vienas iš šių įvykių pasirodo. Taigi, panaudodami select galime instruktuoti branduolį pranešti procesui kada duomenys pasirodo viename iš soketų. 46

Sisteminis kreipinys select • select() kreipinys leidžia valdyti keletą soketų vienu metu. Jis gali

Sisteminis kreipinys select • select() kreipinys leidžia valdyti keletą soketų vienu metu. Jis gali pranešti, kurie soketai yra pasiruošę rašymui, skaitymui ar yra “pakibę” dėl klaidų. int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout); • Aplikacijos gali norėti atsakinėti keliems soketams: – Jos neturi blokuotis kuriame nors sokete laukdamos įvykio. • I/O multipexing naudojant select() • Informuojama OS apie soketus, kuriais esam susidomėję. • Kviečiama select kuri praneša, kai kuris nors soketas turi “kabantį” I/O. – Atrenkamas soketas pasiruošęs I/Ovykdymui. Susijusios funkcijos: FD_SET(fd, &fdset) - prideda fd į rinkinį FD_CLR(fd, &fdset) - išvalo fd iš rinkinio FD_ISSET(fd, &fdset) – tikrina, ar fd yra nustatytas rinkinyje. FD_ZERO(&fdset) - išvalo failų deskriptorių rinkinį. 47

Select() Berkeley soketai turi sisteminį kreipinį select() blokuos procesą, kol įvyks vienas iš nurodytų

Select() Berkeley soketai turi sisteminį kreipinį select() blokuos procesą, kol įvyks vienas iš nurodytų įvykių: 1. failo deskriptorius turi duomenis galimus skaityti 2. 3. failo deskriptorius turi vietos rašymui. failo deskriptorius susidūrė su kažkokia problema 4. Pasibaigė taimeryje nurodytas laikas. 5. Atėjo signalas kurį reikia apdoroti. 48

select (continued) Basinė veiksmų seka naudojant select: setup; for ever { clear readfds and

select (continued) Basinė veiksmų seka naudojant select: setup; for ever { clear readfds and writefds; for each client { set bits in readfds and writefds; } set readfds bit for passive socket; result = select(. . . ); for each client { if bits set in readfds or writefds deal with it; } if bit for passive socket is set in readfds, accept new connection; }. . . • select grąžins 0 jei pasibaigs nustatytas laikas • select grąžins -1 klaidos atveju 49

Socket flow of events: Server that uses nonblocking I/O and select() http: //publib. boulder.

Socket flow of events: Server that uses nonblocking I/O and select() http: //publib. boulder. ibm. com/infocenter/iseries/v 5 r 3/ic 292 4/index. htm? info/rzab 6 cmultiplex. htm 50

Supaprastintas select pavyzdys 51

Supaprastintas select pavyzdys 51

Konkurencija naudojant vieną procesą. create a socket bind to a well-known port while (

Konkurencija naudojant vieną procesą. create a socket bind to a well-known port while ( 1 ) { use select to wait for I/O if ( original socket is ready ) { accept a new connection and add to read list } else if ( a socket is ready for read ) { read data from a client if ( data completes a request ) { do the request if ( reply needed ) add socket to write list } } else if ( a socket is ready for write ) { write data to a client if ( message is complete ) { remove socket from write list } else { adjust write parameters and leave in write list } } } 52

ECHO Serveris naudojant vieną procesą int main(int argc, char *argv[]) { char *service =

ECHO Serveris naudojant vieną procesą int main(int argc, char *argv[]) { char *service = "echo"; struct sockaddr_in fsin; int msock; fd_set rfds; fd_set afds; int alen; int fd, nfds; msock = passive. TCP(service, QLEN); nfds = getdtablesize(); FD_ZERO(&afds); FD_SET(msock, &afds); while (1) { memcpy(&rfds, &afds, sizeof(rfds)); if (select(nfds, &rfds, (fd_set *)0, (struct timeval *)0) < 0) errexit("select: %sn", strerror(errno)); if (FD_ISSET(msock, &rfds)) { int ssock; alen = sizeof(fsin); ssock = accept(msock, (struct sockaddr *)&fsin, &alen); if (ssock < 0) errexit("accept: %sn", strerror(errno)); FD_SET(ssock, &afds); } for (fd=0; fd < nfds; ++fd) if (fd != msock && FD_ISSET(fd, &rfds)) if (echo(fd) == 0) { (void) close(fd); FD_CLR(fd, &afds); } } } int echo(int fd) { char buf[BUFSIZ]; int cc; cc = read(fd, buf, sizeof buf); if (cc < 0) errexit("echo read: %sn", strerror(errno)); if (cc && write(fd, buf, cc) < 0) errexit("echo write: %sn", strerror(errno)); return cc; } 53

Serveris. c #include <sys/types. h> #include <sys/socket. h> #include <netinet/in. h> #include <sys/time. h>

Serveris. c #include <sys/types. h> #include <sys/socket. h> #include <netinet/in. h> #include <sys/time. h> #include <netdb. h> char *Get. Host. By. Name(char *); int main() { int sock, listener. TCP, listener. UDP; struct sockaddr_in addr, inaddr; char buf[256], name[256], *ip; struct fd_set readset; struct timeval timeout; int bytes_read, max, len; // Sukuriam TCP soketa listener. TCP = socket(AF_INET, SOCK_STREAM, 0); if(listener. TCP < 0) { perror("TCP socket"); exit(1); } // Sukuriam UDP soketa listener. UDP = socket(AF_INET, SOCK_DGRAM, 0); if(listener. UDP < 0) { perror("UDP socket"); exit(1); } 54

Serveris. c (tęsinys) if(listener. TCP > listener. UDP) max = listener. TCP; else max

Serveris. c (tęsinys) if(listener. TCP > listener. UDP) max = listener. TCP; else max = listener. UDP; addr. sin_family = AF_INET; addr. sin_addr. s_addr = htonl(INADDR_ANY); // Pririsame TCP soketa prie atitinkamo adreso addr. sin_port = htons(2125); if(bind(listener. TCP, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("TCP bind"); exit(1); } // Pririsame UDP soketa prie atitinkamo adreso addr. sin_port = htons(2126); if(bind(listener. UDP, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("UDP bind"); exit(1); } // Sukuriame prisijungimo uzklausu eiles listen(listener. TCP, 5); while(1) { printf("waiting. . . n"); 55

Serveris. c (tęsinys) while(1) { timeout. tv_sec = 300; timeout. tv_usec = 0; //

Serveris. c (tęsinys) while(1) { timeout. tv_sec = 300; timeout. tv_usec = 0; // Inicializuojame soketu aibes masyva FD_ZERO(&readset); FD_SET(listener. TCP, &readset); FD_SET(listener. UDP, &readset); // Nustatome soketu busenas if(select(max+1, &readset, NULL, &timeout) <= 0) { perror("select"); exit(1); } // Jei atejo kliento UDP uzklausa if(FD_ISSET(listener. UDP, &readset)) { // Gauname uzklausa len = sizeof(inaddr); bytes_read = recvfrom(listener. UDP, buf, sizeof(buf), 0, (struct sockaddr *)&inaddr, &len); if(bytes_read < 0) { perror("recvfrom"); exit(1); } buf[bytes_read]=''; // Konvertuojame domeno varda i IP adresa ip = Get. Host. By. Name(buf); // Siunciame atsakyma sendto(listener. UDP, ip, strlen(ip), 0, (struct sockaddr *)&inaddr, len); } 56

Serveris. c (tęsinys) // Jei atejo kliento TCP uzklausa if(FD_ISSET(listener. TCP, &readset)) { //

Serveris. c (tęsinys) // Jei atejo kliento TCP uzklausa if(FD_ISSET(listener. TCP, &readset)) { // Sukuriame atskira soketa bendravimui su klientu sock = accept(listener. TCP, NULL); if(sock < 0) { perror("TCP accept"); exit(1); } break; } } switch(fork()) { case -1: perror("fork"); break; 57

Serveris. c (tęsinys) // Vaiko procesas case 0: close(listener. TCP); close(listener. UDP); buf[0]='�'; //

Serveris. c (tęsinys) // Vaiko procesas case 0: close(listener. TCP); close(listener. UDP); buf[0]=''; // Kol neatejo atsijungimo komandos is kliento, gauname uzklausas while(strcmp(buf, "end")) { // Gauname uzklausa bytes_read = recv(sock, buf, sizeof(buf), 0); if(bytes_read <= 0) { perror("recv"); exit(1); } buf[bytes_read]=''; if(strcmp(buf, "end")) { strcpy(name, buf); printf("client '%s' connectedn", name); // Siunciame atsakyma ip = Get. Host. By. Name(buf); send(sock, ip, strlen(ip), 0); } } printf("client '%s' disconnectedn", name); close(sock); _exit(0); break; 58

Serveris. c (tęsinys) // Tevo procesas default: close(sock); } } close(listener. TCP); close(listener. UDP);

Serveris. c (tęsinys) // Tevo procesas default: close(sock); } } close(listener. TCP); close(listener. UDP); return 0; } // Konvertuoja domeno varda i IP adresa char *Get. Host. By. Name(char *addr) { struct hostent *hptr; struct in_addr ia; char *buf; hptr = (struct hostent *)gethostbyname(addr); if(hptr == NULL) { perror("gethostbyname"); exit(1); } ia. s_addr = *(unsigned long *)(hptr->h_addr_list[0]); buf = (char *)inet_ntoa(ia. s_addr); return buf; } 59

client. UDP. c #include <sys/types. h> #include <sys/socket. h> #include <netinet/in. h> #include <stdio.

client. UDP. c #include <sys/types. h> #include <sys/socket. h> #include <netinet/in. h> #include <stdio. h> char buf[256]; int main(int argc, char *argv[]) { int sock; struct sockaddr_in addr; int bytes_read, len, i; if(argc < 2) { printf("per mazai parametrun"); exit(1); } // Sukuriam UDP soketa sock = socket(AF_INET, SOCK_DGRAM, 0); if(sock < 0) { perror("socket"); exit(1); } 60

 // Aprasome serverio adresa addr. sin_family = AF_INET; addr. sin_port = htons(2126); addr.

// Aprasome serverio adresa addr. sin_family = AF_INET; addr. sin_port = htons(2126); addr. sin_addr. s_addr = htonl(INADDR_LOOPBACK); len = sizeof(addr); printf("siunciame URL uzklausa: %sn", argv[1]); // Siuncia uzklausa sendto(sock, argv[1], strlen(argv[1]), 0, (struct sockaddr *)&addr, len); // Gauna atsakyma bytes_read = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &len); if(bytes_read < 0) { perror("recvfrom"); exit(1); } buf[bytes_read]=''; // Isvedame rezultata printf("IP adresas: %sn", buf); close(sock); return 0; } 61

client. TCP. c #include <sys/types. h> #include <sys/socket. h> #include <netinet/in. h> #include <stdio.

client. TCP. c #include <sys/types. h> #include <sys/socket. h> #include <netinet/in. h> #include <stdio. h> char buf[256]; int main(int argc, char *argv[]) { int sock; struct sockaddr_in addr; int bytes_read, i; if(argc < 2) { printf("per mazai parametrun"); exit(1); } // Sukuriam TCP soketa sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); exit(1); } 62

 // Aprasome serverio adresa addr. sin_family = AF_INET; addr. sin_port = htons(2125); addr.

// Aprasome serverio adresa addr. sin_family = AF_INET; addr. sin_port = htons(2125); addr. sin_addr. s_addr = htonl(INADDR_LOOPBACK); // Prisijungiame prie serverio if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("connect"); exit(1); } printf("siunciame URL uzklausa: %sn", argv[1]); // Siunciame uzklausa send(sock, argv[1], strlen(argv[1]), 0); // Gauname atsakyma bytes_read = recv(sock, buf, sizeof(buf), 0); if(bytes_read <= 0) { perror("recv"); exit(1); } buf[bytes_read]=''; // Isvedame rezultata printf("IP adresas: %s", buf); // Laukiame "enter" mygtuko paspaudimo printf("npaspauskite 'enter' atsijungimui. . . "); getchar(); // Siunciame atsijungimo komanda serveriui send(sock, "end", 3, 0); close(sock); return 0; } 63

sesija Klientai nijole>. /client. UDP. exe os. pit. ktu. lt siunciame URL uzklausa: os.

sesija Klientai nijole>. /client. UDP. exe os. pit. ktu. lt siunciame URL uzklausa: os. pit. ktu. lt IP adresas: 193. 219. 33. 13 nijole>. /client. TCP. exe os. pit. ktu. lt siunciame URL uzklausa: os. pit. ktu. lt IP adresas: 193. 219. 33. 13 paspauskite 'enter' atsijungimui. . . nijole>. /client. UDP. exe os. pit. ktu. lt siunciame URL uzklausa: os. pit. ktu. lt IP adresas: 193. 219. 33. 13 nijole> Serveris nijole>. /server_UDP_TCP. exe waiting. . . client 'os. pit. ktu. lt' connected waiting. . . client 'os. pit. ktu. lt' disconnected ^C 64

Serverio mirties taško problema Netinkamai veikiantis klientas gali iššaukti mirties tašką vieno-proceso serveryje, jei

Serverio mirties taško problema Netinkamai veikiantis klientas gali iššaukti mirties tašką vieno-proceso serveryje, jei šis serveris naudoja sistemines funkcijas, kurios gali užsiblokuoti komunikuojant su klientu. • Mirties taško problema yra labai aktuali serveriuose, nes vieno kliento netinkama veikla gali susilpninti serverio aptarnavimo pajėgumus arba neleisti serveriui aptarnauti kitų klientų. 65

Daugiaprotokoliai(TCP, UDP) serveriai Daugumoje atvejų atitinkama aplikacija yra realizuojama serveriu, kurio komunikacijos yra palaikomos

Daugiaprotokoliai(TCP, UDP) serveriai Daugumoje atvejų atitinkama aplikacija yra realizuojama serveriu, kurio komunikacijos yra palaikomos kažkurio konkretaus transporto protokolo(pavyzdžiui: DAYTIME servisas). Pagrindinis privalumas naudojant atskirą serverį atskiro protokolo atveju yra kontrolės galimybė: galima lengvai sužinoti, kokius protokolus kompiuteris palaiko, sužinojus kokie serveriai sistemoje sukasi. Gaunami du paprastesniu standartiniu algoritmu realizuojami serveriai. Pagrindinis minusas tokio taikymo(atskiras serveris atskiram protokolui) yra tame, kad abiejuose serveriuose veiksmai yra dubliuojami. Kadangi dauguma servisų yra galimi tiek naudojant TCP, tiek UDP protokolą, kiekvienas iš šių servisų reikalaus dviejų serverių. 66

Daugiaprotokoliai(TCP, UDP) serveriai Kadangi tiek UDP, tiek TCP serveriai naudos tą patį bazinį algoritmą,

Daugiaprotokoliai(TCP, UDP) serveriai Kadangi tiek UDP, tiek TCP serveriai naudos tą patį bazinį algoritmą, realizuojantį atitinkamos aplikacijos protokolą, atsakymo formulavimui, tai šie abu serveriai turės savyje talpinti tą patį kodą. Jei dvi programos turi tą patį kodą duoto serviso atlikimui, tai programinės įrangos valdymas ir tikrinimas tampa varginančiu. Be to, sistemos administratorius privalo sekti, kad du serveriai, teikiantys tą patį servisą skirtingais protokolais vienu metu iš tikrųjų duotų tą patį servisą. Kitas minusas kyla iš to, kad atskiro serverio naudojimas kiekvienam protokolui susijęs su resursų naudojimu: yra nenaudingai išnaudojamos tiek procesų lentelės(gaunasi daugiau procesų), tiek kiti sisteminiai resursai. 67

Daugiaprotokolio serverio struktūra 68

Daugiaprotokolio serverio struktūra 68

 Multiprotokolinis DAYTIME Serveris int main(int argc, char *argv[]) { char *service = "daytime";

Multiprotokolinis DAYTIME Serveris int main(int argc, char *argv[]) { char *service = "daytime"; /* service name or port number */ char buf[LINELEN+1]; /* buffer for one line of text */ struct sockaddr_in fsin; /* the request from address */ int alen; /* from-address length */ int tsock; /* TCP master socket */ int usock; /* UDP socket */ int nfds; fd_set rfds; /* readable file descriptors */ 69

Multiprotokolinis DAYTIME Serveris(tęsinys) tsock = passive. TCP(service, QLEN); usock = passive. UDP(service); nfds =

Multiprotokolinis DAYTIME Serveris(tęsinys) tsock = passive. TCP(service, QLEN); usock = passive. UDP(service); nfds = MAX(tsock, usock) + 1; /* bit number of max fd */ FD_ZERO(&rfds); while (1) { FD_SET(tsock, &rfds); FD_SET(usock, &rfds); if (select(nfds, &rfds, (fd_set *)0, (struct timeval *)0) < 0) errexit("select error: %sn", strerror(errno)); 70

Multiprotokolinis DAYTIME Serveris(tęsinys) if (FD_ISSET(tsock, &rfds)) {int ssock; /* TCP slave socket */ alen

Multiprotokolinis DAYTIME Serveris(tęsinys) if (FD_ISSET(tsock, &rfds)) {int ssock; /* TCP slave socket */ alen = sizeof(fsin); ssock = accept(tsock, (struct sockaddr *)&fsin, &alen); if (ssock < 0) errexit("accept failed: %sn", strerror(errno)); daytime(buf); (void) write(ssock, buf, strlen(buf)); (void) close(ssock); } 71

Multiprotokolinis DAYTIME Serveris(tęsinys) if (FD_ISSET(usock, &rfds)) { alen = sizeof(fsin); if (recvfrom(usock, buf, sizeof(buf),

Multiprotokolinis DAYTIME Serveris(tęsinys) if (FD_ISSET(usock, &rfds)) { alen = sizeof(fsin); if (recvfrom(usock, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &alen) < 0) errexit("recvfrom: %sn", strerror(errno)); daytime(buf); (void) sendto(usock, buf, strlen(buf), 0, (struct sockaddr *)&fsin, sizeof(fsin)); } } } 72

Multiprotokolinis DAYTIME Serveris(tęsinys) int daytime(char buf[]) { char *ctime(); time_t now; (void) time(&now); sprintf(buf,

Multiprotokolinis DAYTIME Serveris(tęsinys) int daytime(char buf[]) { char *ctime(); time_t now; (void) time(&now); sprintf(buf, "%s", ctime(&now)); } 73

Bendrai naudojamas kodas multiprotokolinių serverių architektūra leidžia programuotojui sukurti vieną procedūrą, kuri atsako į

Bendrai naudojamas kodas multiprotokolinių serverių architektūra leidžia programuotojui sukurti vieną procedūrą, kuri atsako į užklausas pagal duotos aplikacijos algoritmą, ir kviesti šią procedūrą, nežiūrint to, kokiu transportu naudojantis ši užklausa atvyko (UDP ar TCP). Paprastai atsakymo pateikimas reikalauja ne vienos procedūros. Akivaizdu, kad kodo laikymas vienoje vietoje ir taikymas to paties kodo abiejų transpotų atveju, garantuoja tiek priežiūros palengvinimą, tiek tai kad pateikiami atsakymai bus identiški. 74

Konkurencinio aptarnavimo multiprotokoliniai serveriai Iteratyvūs serveriai netaikomi, kai servisas reikalauja ilgų skaičiavimų užklausos aptarnavimui

Konkurencinio aptarnavimo multiprotokoliniai serveriai Iteratyvūs serveriai netaikomi, kai servisas reikalauja ilgų skaičiavimų užklausos aptarnavimui įvykdyti. Šiais atvejais multiprotokolinis priėjimas gali būti panaudotas, kad šios užklausos būtų aptarnautos konkurenciškai. Paprasčiausiu atveju, multiprotokolinis serveris gali sukurti naują procesą kiekvieno TCP susijungimo apdorojimui konkurenciškai, o UDP užklausas aptarnautų iteratyviai. 75

 76

76