[socket] select, jak obsługiwać writefds?

0

Witam. Od dość długiego czasu szukam odpowiedzi na nękający mnie... brak wiedzy. Szukałem odpowiedzi w manualach, na forach dyskusyjnych, ircu czy nawet ostatnio skłoniłem się do wybiórczego przejrzenia książki nt. programowania sieciowego (Richarda Stevensa). Niestety...

Mój problem dotyczy funkcji select, a dokładniej parametru fd_set *writefds.

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

Jak mówi manual, select kończy działanie kiedy któryś z deskryptorów zbioru writefds jest zdolny do zapisu. W przypadku kiedy deskryptor jest uchwytem połączenia, stan "zdolny do zapisu" występuje prawie zawsze. W efekcie, select bez opamiętania kończy działanie, a pętla w której ów select jest, kręci się w kółko zabijając procesor. Oczywiście takie zachowanie jest poprawne.

// Jakiś poprawnie otwarty socket...
FD_SET(foo_client,&fdswrite);

for(;;) {
result = select(nfds+1,NULL,&fdswrite,NULL,NULL);

if(FD_ISSET(foo_client,&fdswrite))
    printf("Zabijamy procesor...\r\n");
}

Tylko takie zachowanie w przypadku serwera nie ma najmniejszego sensu. Dlatego, jeżeli by się przyjęło, taką zasadę, że zbiór fdswrite zawiera tylko te deskryptory na które chce pisać serwer (zachodzi potrzeba wysłania danych? dodajemy deskryptor do fdswrite), a gdy to uczyni deskryptor zostaje usunięty z listy. Takie rozwiązanie było by wydajne, oraz nie zabijało by procesora.

// Jakiś poprawnie otwarty socket...
FD_SET(foo_client,&fdsread);

for(;;) {
result = select(nfds+1,&fdsread,&fdswrite,NULL,NULL);

// Klient coś nam przesłał.
if(FD_ISSET(foo_client,&fdsread)) {
    result = recv();
    ...
    // Chcemy odpowiedzieć.
    FD_SET(foo_client,&fdswrite);
    add_send_buffer(foo_client,"ODPOWIEDZ.");
}

if(FD_ISSET(foo_client,&fdswrite)) {
    result = send(...,get_send_buffer(foo_client),...);
    ...
    // Jeżeli wysłano wszystko.
    if(send_buffer_sended(foo_client)) {
        FD_CLR(foo_client,&fdswrite);
        send_buffer_free(foo_client);
    }
}
}

Tylko pytanie czy takie rozwiązanie jest poprawne? W dyskusji na ircu (na kanale #4programmers (: ) można było usłyszeć, że takie rozwiązanie jak powyżej jest... niezamierzonym efektem. Niestety argumentów dlaczego, nie mogę tutaj przytoczyć z racji, iż owa rozmowa miała miejsce dość dawno.

0

szczerze, nie widze powodu dla ktorych mialbys w ogole czekac select'em na strumienie wyjsciowe. przeciez jak sam zauwazyles one sa zawsze gotowe do zapisu? nie ma to wiekszego sensu, wysylaj na biezaco o ile masz dane..

ok - wyjatkowo, jesli masz BARDZO duzo danych do wyslania, tyle ze nie mieszcza sie w buforach warstwy tcp (send/write na sockecie zwraca liczbe bajtow mniejsza niz nakazales wyslac), wtedy rzeczywiscie musisz tak to zaimplementowac i wtedy strumienie wy, te ktorym bufor sie calkiem zapelni, powinny zglaszac brak gotowosci.. [a czy to robia - szczerze - nie wiem.. zalezy sensownosci programistow ktorzy stworzyli Twoj OS]

...czyli, mowiac o rozwiazaniu "ogolnym" w stylu - mam zestaw kanalow, mam jakies wlasne bufory danych we/wy z tych kanalow - i chcesz zeby watek roboczy na biezaco odbieral/wysylal dane z Twoich buforow do/z tych strumieni -- to zwaz, ze oczekiwanie na gotowosc strumienia to nie wszystko:
warunek akcji odebrania - strumien we jest gotowy i ma dane
warunek akcji wyslania - strumien wy jest gotowy i masz dane do wyslania

u Ciebie obecnie warunek akcji wyslania = strumien wy jest gotowy, stad zarzynanie procka.

wniosek: tak, wlasnie tak powinienes zrobic. set ze strumieniami wyjsciowymi powinien zawierac tylko te, ktore Ciebie interesuja. zreszta, jest to dosc oczywiste.. w secie ze strumieniami wejsciowymi przeciez masz tak samo - nie masz wszystkich N mozliwych numerow strumieni, tylko te ktorymi jestes zainteresowany :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1