Hlavní navigace

Sokety a C/C++: Neblokovací režim soketů

9. 6. 2003
Doba čtení: 6 minut

Sdílet

Doposud jsme mluvili o soketech, které byly v takzvaném blokovacím režimu. Snaha přečíst nějaká data, která nebyla doručena, skončí zablokováním programu (nebo přesněji vlákna). Dnes se podíváme na neblokovací režim soketů.

Po vytvoření soketu pomocí funkce socket se soket nachází v blokovacím režimu. Změna blokovacího režimu se v „klasickém“ Socket API liší od WinSock API.

Změna režimu soketu v Linuxu

V unixových systémech máme k dispozici funkci fcntl. Jedná se o velice obecnou funkci, kterou lze manipulovat s identifikátory souborů (file descriptor). Využití funkce fcntl je opravdu pestré, zde se omezíme pouze na dvě situace. Existují celkem tři varianty funkce fcntl (viz manuálové stránky). My se budeme zabývat pouze funkcí s hlavičkou:

int fcntl(int fd, int cmd, long arg); – první parametr je identifikátor souboru (file descriptor). My budeme zadávat soket. Druhým parametrem je příkaz (operace, kterou chceme provést) a třetím parametrem je argument operace. Návratová hodnota je závislá na operaci. Funkce je deklarována v hlavičkovém souboru fcntl.h

Jako příkaz nejprve zadáme makro F_GETFL. Argument příkazu není (je 0). Funkce nám vrátí příznaky zadaného soketu.

V druhém kroku nastavíme nové příznaky. K tomu použijeme příkaz F_SETFL. Argumentem jsou nové příznaky. Jednotlivé příznaky jsou reprezentovány makry. Každé makro je číslo a z těchto čísel skládáme bitovou masku. Budeme-li chtít nastavit více příznaků najednou, použijeme bitový operátor | (or). My budeme v podstatě nastavovat příznaky, které jsme získali v prvním kroku, a přidáme k nim příznak O_NONBLOCK (neblokovací režim). V případě úspěšného nastavení vrací funkce 0, jinak –1. V manuálových stránkách se také dočtete, že tímto způsobem lze nastavit pouze O_APPEND, O_NONBLOCK a O_ASYNC. Nastavení jiných příznaků je bez efektu.

Nastavení neblokovacího režimu v Linuxu

// Nastavíme soket do neblokovacího režimu
int oldFlag = fcntl(mySocket, F_GETFL, 0);
if (fcntl(mySocket, F_SETFL, oldFlag | O_NONBLOCK) == -1)
{
    // Problém
}

Chování funkcí v neblokovacím režimu v Linuxu

  • recv (recvfrom) – v případě, že data pro čtení jsou k dispozici, chovají se funkce stejně jako v blokovacím režimu. V případě, že data k dispozici nejsou, obě funkce okamžitě vrací –1 a nastaví proměnnou errno na hodnotu makra EAGAIN.
  • accept – v případě, že ve frontě požadavků na spojení existuje požadavek, chová se accept stejně jako v případě, že se soket nachází v blokovacím režimu. V případě, že požadavek na spojení není k dispozici, funkce ihned vrací –1 a nastaví chybový kód na hodnotu makra EAGAIN.
  • connect – funkce začne navazovat spojení. Při navázání spojení v rámci TCP protokolu dochází k výměně řídících dat. Zavoláme-li funkci connect nad soketem v blokovacím režimu, zablokuje funkce program, dokud není spojení navázáno. V neblokovacím režimu tomu tak není. Zavoláme-li connect na soket v neblokovacím režimu, funkce odešle požadavek na spojení a okamžitě vrátí –1. Kód chyby je nastaven na hodnotu makra EINPROGRESS. Spojení není ihned navázáno. Nelze soketem ihned odesílat a přijímat data.
  • send – je-li funkce v blokovacím režimu a nemá k dispozici odpovídající velikost bufferu pro odesílání, zablokuje program, dokud nebudou data odeslána. V neblokovacím režimu v případě, že soket nemá dostatečně velký buffer, nedojde k odeslání všech požadovaných dat. Funkce vrací počet skutečně odeslaných bytů.

Změna režimu soketu v MS WindowsŽ

Ve WinSock API máme pro změnu režimu soketu k dispozici funkci ioctlsocket. Funkce ioctlsocket neslouží obecně pro práci se soubory. Slouží jen ke změně režimu soketu.

int ioctlsocket(SOC­KET s, long cmd, u_long* argp); – funkce změní režim soketu. Prvním parametrem je identifikátor soketu, druhým parametrem je příkaz (operace, která má být se soketem provedena). Třetím parametrem je ukazatel na parametr příkazu (zadané operace). V případě úspěšného nastavení je vrácena 0. V opačném případě je vrácena hodnota makra SOCKET_ERROR.

Změnu režimu soketu na neblokovací režim provedeme pomocí příkazu (operace), jejíž číslo je dáno hodnotou makra FIONBIO. Chceme-li zapnout blokovací režim, je argumentem operace (na nějž se odkazuje třetí parametr funkce ioctlsocket) 0. Chceme-li zapnout neblokovací režim, je argumentem operace jakákoliv nenulová hodnota.

Další možné operace jsou FIONREAD pro zjištění, zda jsou k dipozici nějaká data ke čtení, nebo SIOCATMARK pro zjištění, zda jsou přečtena všechna urgentní data. O urgentních datech si povíme příště.

Chování funkcí v neblokovacím režimu v MS WindowsŽ

  • recv (recvfrom) – v případě, že existují data pro čtení, chová se funkce stejně jako v blokovacím režimu. V případě, že data pro čtení nejsou k dispozici, funkce okamžitě vrací hodnotu makra SOCKET_ERROR a zavolání funkce WSAGetLastError vrací hodnotu makra WSAEWOULDBLOCK.
  • accept – v případě, že je fronta požadavků na spojení neprázdná, funkce se chová stejně jako v blokovacím režimu. V případě, že požadavek na spojení není, funkce okamžitě vrací hodnotu makra INVALID_SOCKET a zavolání funkce WSAGetLastError vrací hodnotu makra WSAEWOULDBLOCK.
  • connect – funkce začne navazovat spojení. Při navázání spojení v rámci TCP protokolu dochází při navazování spojení k výměně řídících dat. Zavoláme-li funkci connect nad soketem v blokovacím režimu, funkce zablokuje program, dokud není spojení navázáno. V neblokovacím režimu tomu tak není. Zavoláme-li connect na soket v neblokovacím režimu, funkce odešle požadavek na spojení a okamžitě vrátí hodnotu makra SOCKET_ERROR. Zavolání funkce WSAGetLastError vrací hodnotu makra WSAEWOULDBLOCK. Spojení není ihned navázáno. Nelze soketem ihned odesílat a přijímat data.
  • send – je-li funkce v blokovacím režimu a nemá k dispozici odpovídající velikost bufferu pro odesílání, zablokuje program, dokud nebudou data odeslána. V neblokovacím režimu v případě, že soket nemá dostatečně velký buffer, nedojde k odeslání všech požadovaných dat. Funkce vrací počet skutečně odeslaných bytů.

Nastavení neblokovacího režimu v MS WindowsŽ

// Nastavíme soket do neblokovacího režimu
u_long arg = 1;
// Operace je FIONBIO. Parametr je ukazatel na nenulové číslo.
if (ioctlsocket(mySocket, FIONBIO, &arg) == SOCKET_ERROR)
{
    // Problém
}

Synchronizace

V tomto článku jsem při popisu chování funkcí (zvláště connect) v neblokovacím režimu napsal něco ve stylu „Nelze soketem ihned odesílat a přijímat data“. Takže za chvíli by to mělo jít. Jak ale zjistit, zda ta chvíle již uběhla? Vraťme se k minulému článku, kde jsme se seznámili s funkcí select. Funkci select předáváme tři množiny. Druhá množina je množina soketů, kde chceme být upozorněni na událost, že na soket lze zapisovat. Minule se někomu mohlo zdát (probírali jsme blokovací režim), že tento parametr je k ničemu. Tento parametr lze využít u neblokovacího režimu. Například lze zavolat funkciconnect a v době, kdy dochází k navazování spojení, provádět jinou činnost. Chci-li zjistit, jestli je spojení již navázáno, případně počkat, až bude navázáno, použiji select. Stejně tak lze zjistit, jestli má soket již volný buffer pro odesílání. V ukázkových příkladech ke stažení si všimněte, jak volám funkci select.

root_podpora

Tabulka č. 439
Soubor OS
lin14.tgz Linux
win14.zip MS WindowsŽ

Ukázkový příklad je jednoduchý program, který se připojí k adrese zadané jako parametr z příkazového řádku a portu 2902 (viz příklad z minulého článku), odešle textový pozdrav a dále vypisuje na standardní výstup vše, co přijde od serveru. Pokud nepřijdou 15 sekund žádná data, program se ukončí. Vše je řešeno pomocí neblokovacího režimu.

Příště se podíváme na takzvaná urgentní data. Vysvětlíme si, co tento pojem znamená a k čemu jsou urgentní data dobrá. Když jsem dříve psal o funkcích recv a send, okrajově jsem se zmínil o čtvrtém parametru těchto funkcí. Uvedl jsem, že se jedná o příznaky, a napsal jsem, že prozatím budeme vždy za tento parametr dosazovat 0. Příště se na něj podíváme podrobněji.

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