Hlavní navigace

Sokety a C/C++: Funkce select

2. 6. 2003
Doba čtení: 5 minut

Sdílet

V minulém dílu jsme si ukázali, jak seskupit sokety do množiny. Nyní si předvedeme, k čemu je množina soketů dobrá.

Pro práci s více sokety najednou slouží funkce select. Funkce select je schopna pracovat se třemi skupinami soketů. V první skupině hlídá, zda v některém soketu nejsou příchozí data, která je možno číst. Ve druhé skupině hlídá, zda do některého soketu je možné data zapisovat. Sokety ve třetí skupině jsou kontrolovány, zda se nenacházejí v chybovém stavu.

Funkce select v Linuxu

V množinách, které jsou předávány funkcí select jako parametry, jsou celá čísla, která mohou představovat jakýkoliv identifikátor souboru (file descriptor). Protože tento seriál je zaměřen na sokety, budeme dále hovořit pouze o soketech. Funkce select je ale obecnější. Například v manuálových stránkách select je uveden příklad použití funkce select se standardním vstupem.

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

 – druhý parametr je ukazatel na množinu obsahující sokety (obecně jakékoliv file descriptory), z nichž chceme číst. Třetím parametrem je ukazatel na množinu soketů (obecně jakýchkoliv file descriptorů), do kterých chceme zapisovat. Čtvrtým parametrem je ukazatel na množinu soketů (obecně jakýchkoliv file descriptorů), u kterých chceme zjistit, zda nastala nějaká chyba. Ve třech množinách jsou vlastně celá čísla. Vezmeme nejvyšší z těchto čísel, zvýšíme ho o 1 a máme číslo, které předáme jako první parametr. Posledním parametrem je ukazatel na strukturu udávající, jak dlouho má funkce select tento dohled provádět. Funkce vrací –1 v případě chyby. Jinak vrací počet soketů, na nichž došlo k událostem, které mají být sledovány. V případě, že vyprší časový limit daný posledním parametrem, funkce select vrací 0, protože počet soketů na kterých došlo ke změně, je 0.

Funkce select v MS Windows Ž

V množinách, které jsou předávány funkcí select jako parametry, jsou celá čísla, jež mohou představovat POUZE soket. Zde je velký rozdíl oproti unixovým systémům.

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout); – prvním parametrem je jakékoliv celé číslo. Parametr je zde pouze z důvodu kompatibility s klasickým soketovým rozhraním. V dokumentaci je přímo napsáno, že prvním parametrem může být jakékoliv číslo. A je to pravda. Zkoušel jsem zadat 0 (což by v klasickém soketovém API určitě nešlo) a vše fungovalo tak, jak má. Druhý parametr je ukazatel na množinu obsahující sokety, z nichž chceme číst. Třetím parametrem je ukazatel na množinu soketů, do kterých chceme zapisovat. Čtvrtým parametrem je ukazatel na množinu soketů, u kterých chceme zjistit, zda nastala nějaká chyba. Posledním parametrem je ukazatel na strukturu udávající, jak dlouho má funkce select tento dohled provádět. Funkce vrací hodnotu makra SOCKET_ERROR v případě chyby. Jinak vrací počet soketů, na nichž došlo k událostem, které mají být sledovány. V případě, že vyprší časový limit daný posledním parametrem, funkce select vrací 0, protože počet soketů, na kterých došlo ke změně je 0.

Jak vlastně s funkcí select pracovat?

Nejprve musíme zaplnit tři množiny podle toho, jaké události chceme sledovat. Vlastně nám stačí zaplnit jen jednu. Místo dalších můžeme předat NULL. Poté zavoláme funkci select. Funkce zablokuje chod programu a čeká, dokud nenastane nějaká sledovaná událost na soketech. Je-li posledním parametrem NULL, funkce čeká, dokud doopravdy k události nedojde. Je-li jako poslední parametr předán ukazatel na strukturu s časovým údajem, funkce čeká, dokud nenastane nějaká požadovaná událost na soketech nebo dokud nevyprší časový limit. Po skončení volání funkce select zjistíme z návratové hodnoty, zda došlo k chybě nebo na kolika soketech se událo něco rozhodujícího. Každá ze tří množin obsahuje po skončení volání funkce select jen ty sokety, na kterých nastala požadovaná událost. Nikdy nesmíme zapomenout, že nemuselo dojít pouze ke změně na jednom soketu.

My budeme používat asi nejvíce první množinu (druhý parametr funkce select). Funkce nám pohlídá, zda na soketech v této množině jsou k dispozici nějaká data, zda na soket přišel požadavek na spojení nebo zda druhá strana spojení přerušila. Zjistíme vlastně, na které sokety můžeme v blokovacím režimu zavolat recv nebo accept a přitom mít jistotu, že tyto funkce nebudou na nic čekat.

Ukázkový kousek zdrojového textu

// Struktura s časovým limitem
timeval tv;
// Množina
fd_set myset;
// Množina obsahuje náhodná data. Odstraním je.
FD_ZERO(&myset);
// Zaplnění množiny sokety
FD_SET(socket, &myset);
FD_SET(jinySocket, &myset);
// Vyplním časový údaj (například na 3 minuty)
tv.tv_sec = 180;// Počet sekund
tv.tv_usec = 0;// Počet mikrosekund
// Zavolám select (V Linuxu musím mít nastavenou proměnnou max.)
int ret = select(max, &myset, NULL, NULL, &tv);
if (ret == -1)
{
  // Nastala chyba
}
if (ret == 0)
{
  // Vypršel časový limit
}
if (FD_ISSET(soket, &myset))
{
  /* Obsluha soketu. Zde je možné vytvořit nové vlákno nebo proces,
který obslouží událost na soketu. Server pak je vícevláknový.
*/
}
if (FD_ISSET(jinySoket, &myset))
{
  /* Obsluha soketu. Zde je možné vytvořit nové vlákno nebo proces,
který obslouží událost na soketu. Server pak je vícevláknový.
*/
}

Příklad serveru

Jako ilustrační příklad si předvedeme jednoduchý server, který bude čekat na TCP portu číslo 2902 na spojení. V případě navázání spojení bude s klientem komunikovat tak dlouho, dokud klient neuzavře spojení. Nebude-li se serverem tři minuty nikdo komunikovat, uzavře všechna spojení a ukončí se. Server očekává, že mu jednotliví klienti posílají text. Text, který server obdrží od jednoho klienta, bude rozeslán všem klientům. Jedná se vlastně o velice jednoduchý chat server. K serveru se lze připojit například telnetem nebo si můžete sami naprogramovat jednoduchého klienta na základě článku TCP klient v Linuxu.

CS24_early

V programu používám šablonu vector a standardní algoritmus for_each. Server je jednovláknový. Mohli bychom například pomocí fork vytvořit nový proces pokaždé, kdy by server obdržel data. S paralelismem ale přicházejí problémy se synchronizací. Tématem seriálu jsou sokety, proto se synchronizací zabývat nebudeme.

Tabulka č. 437
Soubor OS
lin13.tgz Linux
win13.zip MS Windows Ž

Příště se podíváme na práci se sokety v neblokovacím režimu.

Byl pro vás článek přínosný?