76
1 6 paskaita

6 paskaita

  • Upload
    beryl

  • View
    60

  • Download
    0

Embed Size (px)

DESCRIPTION

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

Citation preview

Page 1: 6  paskaita

1

6 paskaita                                  

                                        

              

Page 2: 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ą).

Page 3: 6  paskaita

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.

Page 4: 6  paskaita

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

Page 5: 6  paskaita

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.

Page 6: 6  paskaita

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.

Page 7: 6  paskaita

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

Page 8: 6  paskaita

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.

Page 9: 6  paskaita

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.

Page 10: 6  paskaita

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)

Page 11: 6  paskaita

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

Page 12: 6  paskaita

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

Page 13: 6  paskaita

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)

Page 14: 6  paskaita

14

Process B

GlobalVariables

Code

Stack

Stack

Process A

GlobalVariables

Code

Stack

fork()Process A

GlobalVariables

Code

Stack

pthread_create()

Page 15: 6  paskaita

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.

Page 16: 6  paskaita

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.

Page 17: 6  paskaita

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

Page 18: 6  paskaita

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

Page 19: 6  paskaita

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.

Page 20: 6  paskaita

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.

Page 21: 6  paskaita

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

Page 22: 6  paskaita

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

Page 23: 6  paskaita

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.

Page 24: 6  paskaita

24

Konkuruojantis, į susijungimą orientuotas serveris

Page 25: 6  paskaita

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

Page 26: 6  paskaita

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

Page 27: 6  paskaita

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

Page 28: 6  paskaita

28

Tipinė serverio struktūra

read(),write() via new new socket

Page 29: 6  paskaita

29

Serverio struktūra

Page 30: 6  paskaita

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

}

Page 31: 6  paskaita

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

Page 32: 6  paskaita

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;

Page 33: 6  paskaita

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

Page 34: 6  paskaita

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

Page 35: 6  paskaita

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

Page 36: 6  paskaita

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

Page 37: 6  paskaita

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.

Page 38: 6  paskaita

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

Page 39: 6  paskaita

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.

Page 40: 6  paskaita

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.  

Page 41: 6  paskaita

41

Page 42: 6  paskaita

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.

Page 43: 6  paskaita

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

Page 44: 6  paskaita

44

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

Page 45: 6  paskaita

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)

Page 46: 6  paskaita

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

Page 47: 6  paskaita

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

Page 48: 6  paskaita

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.

Page 49: 6  paskaita

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

Page 50: 6  paskaita

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

Page 51: 6  paskaita

51

Supaprastintas select pavyzdys

Page 52: 6  paskaita

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

Page 53: 6  paskaita

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

Page 54: 6  paskaita

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

Page 55: 6  paskaita

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");

Page 56: 6  paskaita

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

Page 57: 6  paskaita

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;

Page 58: 6  paskaita

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;

Page 59: 6  paskaita

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

Page 60: 6  paskaita

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

Page 61: 6  paskaita

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

Page 62: 6  paskaita

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

Page 63: 6  paskaita

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

Page 64: 6  paskaita

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

Page 65: 6  paskaita

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

Page 66: 6  paskaita

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

Page 67: 6  paskaita

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.

Page 68: 6  paskaita

68

Daugiaprotokolio serverio struktūra

Page 69: 6  paskaita

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

Page 70: 6  paskaita

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

Page 71: 6  paskaita

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

Page 72: 6  paskaita

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

}

}

Page 73: 6  paskaita

73

Multiprotokolinis DAYTIME Serveris(tęsinys)

int daytime(char buf[])

{ char *ctime();

time_t now; 

(void) time(&now);

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

}

Page 74: 6  paskaita

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.

Page 75: 6  paskaita

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.

Page 76: 6  paskaita

76