6 paskaita

Preview:

DESCRIPTION

6 paskaita. Iteratyvaus aptarnavimo serveriai. Iterat yvaus aptarnavimo server ius yra paprasčiau suprojektuoti, įdiegti, palaikyti. - PowerPoint PPT Presentation

Citation preview

1

6 paskaita                                  

                                        

              

2

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ą).

3

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.

4

Konkuruojantys ar iteratyvūs serveriai?

Serveriai Iteratyvūs Konkuruojantys:

Aptarnauja Vieną užklausą vienu metu. Daug užklausų vienu metu.

Kada reiktų naudoti:

Kai yra garantija, kad užklausa bus apdorota per mažą laiko intervalą.

Tais atvejais, kai negalima apriboti užklausos įvykdymo laiką.

Privalumai: Triviali realizacija.

Lengva sutvarkyti kreipinius (pvz jei yra ryšiai su duomenų baze).

Individualios klientų užklausos gali būti bet kokio sudėtingumo ir trukmės.

Problemos: Serveris yra blokuojamas kol turi reikalų su užklausa. Jei užklausos aptarnavimas užtrunka ilgiau nei leistina, joks kitas klientas negauna serviso.

Sudėtingumas susijęs su konkurenciniu aptarnavimu

5

Konkurencinio aptarnavimo serveriai

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

Konkuruojantys, neorientuoti į susijungimą serveriaiNaudojami 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.

6

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.

7

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į.

8

Neribotos konkurencijos problemos OS gali pritrūkti resursų:

atminties, procesų, soketų, buferių-iššaukdama blokavimą, strigimą, krachą, ...

 Vieno serviso užklausos  gali kliudyti kitomspav: 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.

9

Konkurencijos kainaJei 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 =2p. Konkurencinio aptarnavimo atveju, jis būtų apie 2c+p.

        jei p < 2c 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.

10

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)

11

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).

12

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() ).

13

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)

14

Process B

GlobalVariables

Code

Stack

Stack

Process A

GlobalVariables

Code

Stack

fork()Process A

GlobalVariables

Code

Stack

pthread_create()

15

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.

16

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.

17

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().

18

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).

19

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.

20

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.

21

Konkuruojantis, į susijungimą neorientuotas serveris

create a socketbind to a well-known portwhile ( 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

22

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ą.

23

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.

24

Konkuruojantis, į susijungimą orientuotas serveris

25

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).

26

Slave procesaiSlave 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().

27

Konkuruojantis, į susijungimą orientuotas serveris

create a socketbind 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().

28

Tipinė serverio struktūra

read(),write() via new new socket

29

Serverio struktūra

30

Fork()bash-3.00$ more prc1.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: %d\n :", 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: %d\n Buvusio vaiko PID: %d\n", getpid(), pid )

printf ( "------------------------ \n"); } else { printf ( "As sukurtas vaiko procesas !\n Mano PID: %d\n Tevo proceso PID: %d\n", getpid(), getppid() );

printf ( "------------------------ \n");exit(2); }

}

31

-bash-3.00$ ./prc1.exeveikia 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: 9465Tevo 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------------------------

Procesų eiga

32

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 33333int main(int argc, char *argv[]) { int listenfd, connfd; struct sockaddr_in servaddr, cliaddr; pid_t childpid; char buf[80], line[100];int s,cc;

33

Echo serveris tęsinyslistenfd = 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);

34

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: recv\n"); exit(4); } if (cc > 0 ) printf("priemiau %d\n", 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);} }}

35

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 33333int 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));

36

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("%s\n", buf); }}}

37

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.

38

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ą.

39

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.

40

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.  

41

42

“I/O multiplexing” panaudojimas tinklinėse aplikacijose:

a. Kai klientas valdo kelis deskriptorius( paprastai – interaktyvų įvedimą ir tinklinį soketą), I/O multiplexing turėtų būti naudojamas.

b. Galimas, tačiau retas atvejas, kad klientas vienu metu valdo keletą soketų.

c. Jei TCP serveris valdo klausantį soketą ir susijungusius soketus, I/O multiplexing paprastai yra naudojamas.

d. Jei serveris valdo ir TCP ir UDP servisus, I/O multiplexing paprastai yra naudojamas.

e. Jei serveris apdoroja daug servisų ir gal būt daug protokolų (pav. inetd daemon), I/O multiplexing paprastai yra naudojamas.

43

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į.

44

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

45

• 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...

Konkuruojantis,į susijungimą orientuotas serveris (vienas procesas)

46

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ų.

47

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š rinkinioFD_ISSET(fd, &fdset) – tikrina, ar fd yra nustatytas rinkinyje. FD_ZERO(&fdset) - išvalo failų deskriptorių rinkinį.

48

Select()

Berkeley soketai turi sisteminį kreipinį select()

select() blokuos procesą, kol įvyks vienas iš nurodytų įvykių:

1. failo deskriptorius turi duomenis galimus skaityti

2. failo deskriptorius turi vietos rašymui. 3. failo deskriptorius susidūrė su kažkokia problema

4. Pasibaigė taimeryje nurodytas laikas.

5. Atėjo signalas kurį reikia apdoroti.

49

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

50

Socket flow of events: Server that uses non-blocking I/O and select()

http://publib.boulder.ibm.com/infocenter/iseries/v5r3/ic2924/index.htm?info/rzab6/rzab6cmultiplex.htm

51

Supaprastintas select pavyzdys

52

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 } } }

53

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 = passiveTCP(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, (fd_set *)0, (struct timeval *)0)

< 0) errexit("select: %s\n", strerror(errno)); if (FD_ISSET(msock, &rfds)) { int ssock; alen = sizeof(fsin); ssock =

accept(msock, (struct sockaddr *)&fsin, &alen); if (ssock < 0) errexit("accept: %s\n", 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: %s\n",

strerror(errno));

if (cc && write(fd, buf, cc) < 0) errexit("echo write: %s\n", strerror(errno)); return cc; }

54

Serveris.c

#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <sys/time.h>#include <netdb.h>char *GetHostByName(char *);

int main(){ int sock, listenerTCP, listenerUDP; 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 listenerTCP = socket(AF_INET, SOCK_STREAM, 0); if(listenerTCP < 0) { perror("TCP socket"); exit(1); }

// Sukuriam UDP soketalistenerUDP = socket(AF_INET, SOCK_DGRAM, 0); if(listenerUDP < 0) { perror("UDP socket"); exit(1); }

55

Serveris.c (tęsinys)if(listenerTCP > listenerUDP) max = listenerTCP; else max = listenerUDP; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY);

// Pririsame TCP soketa prie atitinkamo adresoaddr.sin_port = htons(2125);

if(bind(listenerTCP, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("TCP bind"); exit(1); }

// Pririsame UDP soketa prie atitinkamo adreso addr.sin_port = htons(2126); if(bind(listenerUDP, (struct sockaddr *)&addr, sizeof(addr)) < 0)

{ perror("UDP bind"); exit(1); }

// Sukuriame prisijungimo uzklausu eiles listen(listenerTCP, 5);

while(1) { printf("waiting...\n");

56

Serveris.c (tęsinys)

while(1) { timeout.tv_sec = 300; timeout.tv_usec = 0;// Inicializuojame soketu aibes masyva FD_ZERO(&readset); FD_SET(listenerTCP, &readset); FD_SET(listenerUDP, &readset);// Nustatome soketu busenas

if(select(max+1, &readset, NULL, NULL, &timeout) <= 0) { perror("select"); exit(1); }

// Jei atejo kliento UDP uzklausaif(FD_ISSET(listenerUDP, &readset)) {

// Gauname uzklausa len = sizeof(inaddr);

bytes_read = recvfrom(listenerUDP, buf, sizeof(buf), 0, (struct sockaddr *)&inaddr, &len);

if(bytes_read < 0) { perror("recvfrom"); exit(1); } buf[bytes_read]='\0';

// Konvertuojame domeno varda i IP adresa ip = GetHostByName(buf);

// Siunciame atsakyma sendto(listenerUDP, ip, strlen(ip), 0, (struct sockaddr *)&inaddr, len); }

57

Serveris.c (tęsinys)// Jei atejo kliento TCP uzklausa

if(FD_ISSET(listenerTCP, &readset)){ // Sukuriame atskira soketa bendravimui su klientu sock = accept(listenerTCP, NULL, NULL); if(sock < 0) { perror("TCP accept"); exit(1); } break; } }

switch(fork()) { case -1: perror("fork"); break;

58

Serveris.c (tęsinys)// Vaiko procesas

case 0: close(listenerTCP); close(listenerUDP); buf[0]='\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]='\0';if(strcmp(buf, "end"))

{ strcpy(name, buf);

printf("client '%s' connected\n", name);

// Siunciame atsakyma ip = GetHostByName(buf); send(sock, ip, strlen(ip), 0); } }

printf("client '%s' disconnected\n", name); close(sock); _exit(0); break;

59

Serveris.c (tęsinys)// Tevo procesasdefault: close(sock); } }

close(listenerTCP); close(listenerUDP); return 0; }// Konvertuoja domeno varda i IP adresa

char *GetHostByName(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;}

60

clientUDP.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 parametru\n");

exit(1); }// Sukuriam UDP soketa sock = socket(AF_INET, SOCK_DGRAM, 0); if(sock < 0) { perror("socket"); exit(1); }

61

// 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: %s\n", 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]='\0'; // Isvedame rezultata printf("IP adresas: %s\n", buf); close(sock); return 0;}

62

clientTCP.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 parametru\n");

exit(1); }

// Sukuriam TCP soketa sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); exit(1); }

63

// 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: %s\n", 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]='\0'; // 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;}

64

sesija

nijole>./clientUDP.exe os.pit.ktu.ltsiunciame URL uzklausa: os.pit.ktu.ltIP adresas: 193.219.33.13nijole>./clientTCP.exe os.pit.ktu.ltsiunciame URL uzklausa: os.pit.ktu.ltIP adresas: 193.219.33.13paspauskite 'enter' atsijungimui...nijole>./clientUDP.exe os.pit.ktu.ltsiunciame URL uzklausa: os.pit.ktu.ltIP adresas: 193.219.33.13nijole>

nijole>./server_UDP_TCP.exewaiting...client 'os.pit.ktu.lt' connectedwaiting...client 'os.pit.ktu.lt' disconnected^C

KlientaiServeris

65

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ų.

66

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ų.

67

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.

68

Daugiaprotokolio serverio struktūra

69

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 */

70

Multiprotokolinis DAYTIME Serveris(tęsinys)

tsock = passiveTCP(service, QLEN); usock = passiveUDP(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, (fd_set *)0, (struct timeval *)0) < 0)

errexit("select error: %s\n", strerror(errno));

71

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: %s\n", strerror(errno)); daytime(buf);

(void) write(ssock, buf, strlen(buf)); (void) close(ssock); }

72

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: %s\n", strerror(errno));

daytime(buf);

(void) sendto(usock, buf, strlen(buf), 0, (struct sockaddr *)&fsin, sizeof(fsin)); }

}

}

73

Multiprotokolinis DAYTIME Serveris(tęsinys)

int daytime(char buf[])

{ char *ctime();

time_t now; 

(void) time(&now);

sprintf(buf, "%s", ctime(&now));

}

74

Bendrai naudojamas kodasmultiprotokolinių 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.

75

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.

76