Opis problemu
Załóżmy, że napisaliśmy program działający jako serwer pewengo protokołu (np. xmpp), który oczekuje na komunikaty od użytkownika, ale jednocześnie co stały okres czasu wysyła mu pewne informacje (np. o zmianie statusu innych użytkowników).
Poniższy kod pokazuje działanie najprostszego programu czytającego dane z deskryptora i wykonującego na nich operacje:
1 2 |
char c[20]; int i; while( i=read(sock, &c, 20)>0) do_something(&c,i); |
Problem, na który trafiamy to np.:
- odbiór danych od wielu użytkowników (z wielu różnych socketów) w jednym procesie,
- jednoczense oczekiwania na dane od strony użytkownika i niezależna od tego możliwość wysłania do użytkownika pewnych danych.
Do dyspozycji mamy jeden deskryptor, więc to co chcemy zrobić to oczekiwać na dane z niego pochodzące, ale z możliwością przerwania tego oczekiwania i wykonania innego zadania (np. zapisania do socketu komunikatu lub odczytania z innego socketu komunikatu).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
char c[20]; int i; while (1) { i=read(sock1, c,20); if (i>0) do_something(c,i) else if (i==-1) perror("Blad read(sock1,...)"); i=read(sock2, c,20); if (i>0) do_something(c,i) else if (i==-1) perror("Blad read(sock2,...)"); } |
W powyższym przykładzie funkcja read(sock1,c,20) zadziała w sposób blokujący – tj. do czasu aż nie otrzymamy danych w sock1, funkcja zablokuje działanie programu.
Rozwiązanie 1
Poniżej prezentujemy metodę na ustawienie deskryptora tak by nie był blokujący (tj. by operacja odczytu nie zatrzymywała działania aplikacji):
1 2 3 4 5 6 |
char c[20]; int i; fcntl(sock1,F_SETFL, fcntl(sock1,F_GETFL)|O_NONBLOCK ); //ustawiamy sock1 by nie był blokujący fcntl(sock2,F_SETFL, fcntl(sock2,F_GETFL)|O_NONBLOCK ); //ustawiamy sock2 by nie był blokujący while(1) { .... |
Rozwiązanie 2
Funkcja select czeka aż jeden z grupy deskryptorów readfds będzie gotowy do odczytu lub jeden z grupy deskryptorów writefds będzie gotowy do zapisu (lub jeden z grupy deskryptorów exceptfds) będzie miał wyjątki. Struktura tiemout zawiera maksymalny czas czekania.
W przypadku gdy którąś grupę deskryptorów ustawimy na NULL, select nie będzie na nie czekała.
Funkcja:
- int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) – funkcja pozwalająca na monitorowanie kilku deskryptorów (należących do zbioru o nazwie exceptfds) czekająca aż jeden z nich będzie gotowy do wykonania operacji I/O (tj. deskryptory readfds do operacji read, writefds do operacji write), w przypadku braku gotowości, funkcja kończy swoje działanie po upłynięciu czasu określonego w timeout),
- int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask) – funkcja dla chętnych.
Makra FD:
- void FD_CLR(int fd, fd_set *fdset) – usuwa deskryptor fd ze zbioru fdset,
- int FD_ISSET(int fd, fd_set *fdset) – sprawdza czy deskryptor fd jest elementem zbioru fdset,
- void FD_SET(int fd, fd_set *fdset) – dodaje deskryptor fd do zbioru fdset,
- void FD_ZERO(fd_set *fdset) – czyści zbiór fdset,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
int main() { ....... ....... //deklaracja grupy deskryptorów o nazwie Read0 oraz struktury timeval fd_set Read0; struct timeval tv; while(1) { // ustawiamy tv na 4 sekundy tv.tv_sec = 4; tv.tv_usec = 0; //zerujemy Read0 FD_ZERO(&Read0); //dodajemy sock1 i sock2 do grupy Read0 FD_SET(sock1, &Read0); FD_SET(sock2, &Read0); //wywołyjemy blokującą funkcję select select(FD_SETSIZE, &Read0, NULL, NULL, &tv); if( FD_ISSET(sock1,&Read0) ) { read(sock1,c,40); do_something(....) } if( FD_ISSET(sock2,&Read0) ) { read(sock2,c,40); do_something(....) } } } |
Zadania:
- Napisz aplikację serwera i klienta, która bedzie działała jak “mini czat”. Serwer po uruchomieniu będzie nasłuchiwał na porcie 10000, a po połączeniu klienta pozwoli na komunikację użytkownikowi serwera i użytkownikowi klienta poprzez przesyłanie całych linii tekstu odczytanego z klawiatury.