1 / 76

6 paskaita

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

beryl
Download Presentation

6 paskaita

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  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?

  5. Konkurencinio aptarnavimo serveriai Iteratyvius serverius yra lengviausuprogramuoti, 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.

  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ūktiresursų: 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 funkcionavimopablogėjimą:pav: ftp serveris gali būti toks lėtas, kad klientainutrauks užklausas.

  9. 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 =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 • Vienas vaiko procesas kiekvienam klientui • Viena gija ( thread ) kiekvienam klientui • Iš anksto sukurti (preforking) keli procesai • Iš anksto sukurtos (prethreaded) kelios gijos • 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 kreipinioaccept(),kviečiamasfork(). • UDP: porecvfrom(),kviečiamafork(). • kiekvienam procesui reikia kelių soketų. • Trumpos užklausos gali būti aptarnautos per trumpą laiką. • Tėvo procesui reikia išvalyti vaikus!!!! • (reikiawait() ).

  13. Viena gija kiekvienam klientui • Tas pats kaip ir naudojantfork – 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 A Global Variables Code Stack Process B Global Variables Code Stack pthread_create() Process A Global Variables fork() Code Stack Stack

  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čiafork()keletą kartų. • Visi vaikai kviečiaaccept(). • 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 procesasvykdo 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 kviestirecvfrom() 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 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

  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 vykdytiacceptpriimant 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 serveryjedaugumą 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 procesai 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().

  27. 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 • Galimavaikų procesų kūnus keisti slave programųkūnais panaudojant execve().

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

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

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

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

  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 • Klientassusijungia bet neatsiunčia užklausų.Serveris blokuojamasties 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šę aptarnautiklientus. 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. “I/O multiplexing” panaudojimas tinklinėse aplikacijose: • 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 vienameprocese. 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. 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 gavimuiir 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 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 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() select() blokuos procesą, kol įvyksvienas iš nurodytų įvykių: • failo deskriptorius turi duomenis galimus skaityti • failo deskriptorius turi vietos rašymui. • failo deskriptorius susidūrė su kažkokia problema • Pasibaigė taimeryje nurodytas laikas. • Atėjo signalas kurį reikia apdoroti.

  48. 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 non-blocking I/O and select() http://publib.boulder.ibm.com/infocenter/iseries/v5r3/ic2924/index.htm?info/rzab6/rzab6cmultiplex.htm

More Related