Hlavní navigace

Sokety a C/C++: Urgentní data

Radim Dostál

Kromě "normálních" dat, která jsme doposud přenášeli od odesílatele k příjemci, existují také tak zvaná urgentní data, která jsou "nenormální", "výjimečná". I ve světě soketů platí, že všechna data jsou si rovna, ale některá jsou si rovnější. Urgentní data (zkratkou označována jako OOB - Out Of Band) jsou data, která při odesílání označíme jako urgentní. Informace o tom, zda se jedná, nebo nejedná o urgentní data, je součástí hlavičky TCP segmentu.

V rámci otevřeného spojení mám k dispozici proud dat. V jednom spojení můžeme krom tohoto „hlavního“ proudu dat vytvořit virtuální kanál obsahující jiný proud (urgentních) dat, který je na „hlavním“ proudu nezávislý.

Jak si vlastně pojem urgentních dat představit? V jednom z úvodních článků seriálu věnovaného základům práce se sokety jsem komunikaci TCP serveru a TCP klienta popsal velice obrazně pomocí potrubí (nebo hadice), kterým jsou oba počítače spojeny. Vysvětlil jsem tak pojem soket. Představme si, že trubka v sobě obsahuje jinou menší trubku. Ve vložené trubce také proudí data. Tento proud je nezávislý na proudu dat, který proudí ve vnější trubce, ale mimo vnořenou trubku. Vnořená trubka je vlastně virtuální nezávislý kanál pro přenos urgentních dat v rámci spojení, kterým proudí jinak „normální“ data.

Mechanismus urgentních dat se obvykle používá k přenosu dat, která jsou nějak významná. Představme si situaci, kdy server přijímá od klienta velké množství dat a nad těmito daty průběžně provádí nějaký náročný výpočet. Klient odešle mnoho dat, a proto je na straně serveru také mnoho dat v příchozí frontě ještě nevyzvednuto (nepřečteno). Vtom ale klient zjistí, že výsledek, který si chtěl nechat serverem spočítat, už vlastně vůbec nepotřebuje. Protože se jedná o slušného klienta, chce dát serveru jasně najevo, že má přestat počítat a zpracovávat nepřečtená data (a tím snížit zátěž serveru), protože výsledek je již k ničemu. Jak to ale udělat? Kdyby odeslal nějaký předem dohodnutý byte nebo bloku bytů, po jehož přečtení má server přestat počítat, bude tento byte zařazen na serveru na konec fronty. Server bude stejně číst data, která obdržel před příchodem pokynu přestat počítat (ten bude na konci fronty) a bude stejně všechna dříve doručená data zpracovávat. Zde se přímo nabízí použití urgentních dat. Můžeme označit onen pokyn „Přestaň počítat“ za urgentní a poslat ho oním virtuálním kanálem, který přenáší data nezávisle na kanálu „hlavním“.

Ve skutečnosti (neurčíme-li jinak) jsou urgentní data vkládána do jiné fronty příchozích dat než „normální“ data. Proto je příjemce může přečíst okamžitě.

Odeslání a příjem urgentních dat

Data, která mají být urgentní, musíme při odeslání za urgentní označit. Když jsem hovořil o funkci send, zmínil jsem se o posledním parametru udávajícím příznaky. Doposud jsme vždy zadávali 0. Chceme-li data označit za urgentní, zadáme jako příznak hodnotu makra MSG_OOB. Chceme-li přečíst urgentní data ze soketu, zadáme jako poslední parametr (příznak) funkce recv rovněž hodnotu makra MSG_OOB. Tento postup je shodný ve WinSock API i v klasickém Socket API.

Odeslání urgentních dat

recv(soc, buffer, size, MSG_OOB);

Událost příchodu urgentních dat

V souvislosti s funkcí select jsme si uváděli tři množiny. V první jsou sokety, kde select hlídá, zda je možné číst. V druhé select hlídá, zda je možné zapisovat. A ve třetí (jsem nepřesně napsal) select hlídá, zda nedošlo k chybě. To jsem neuvedl úplnou pravdu. Funkce selecthlídá, zda nedošlo na soketu k výjimečné situaci. Příchod urgentních dat výjimečná situace je. Chceme-li si selectem pohlídat urgentní data, vyplníme množinu a ukazatel na ni předáme selectu jako čtvrtý parametr.

Příjem urgentních dat

fd_set normalSet, extraSet;
// Zaplním dvě množiny. Jednu pro čtení, druhou pro výjimečné situace.
FD_ZERO(&normalSet);
FD_SET(soc, &normalSet);
FD_ZERO(&extraSet);
FD_SET(soc, &extraSet);
// Čekám, jestli nepřijdou normální data nebo urgentní data
select(soc + 1, &normalSet, NULL, &extraSet, NULL);
if (FD_ISSET(soc, &extraSet))
{
    recv(soc, buffer, size, MSG_OOB);
}
if (FD_ISSET(soc, &normalSet))
{
    recv(soc, buffer, size, 0);
}

Jsou-li v přijímací frontě urgentní data, která nejsou přečtená, a přijdou další urgentní data, „stará“ urgentní data budou zařazena do fronty k „normálním“ datům. Takže přijdou-li novější urgentní data, stará nepřečtená urgentní data se stanou normálními daty.

V tomto a v minulých dvou článcích jsme si vlastně ukázali využití všech tří množin, které se zadávají funkci select.

Příklad

Jako příklad si ukážeme server, který po jednotlivých bytech čte příchozí data. Postupně s každým byte provádí časově náročný výpočet. Až narazí mezi příchozími byty na 0, odešle všechny výsledky, které spočítal, najednou klientovi. Jako urgentní data může server obdržet počet všech čísel, které od klienta obdržel od posledního předání výsledku. V případě doručení urgentních dat klient již nechce výsledek. Server přeruší výpočet a klientovi nic neodesílá. Čeká na další posloupnost čísel. Server se ukončí v případě, že klient uzavře spojení. Takto jednoduše napsaný server není schopen obsluhovat více klientů najednou.

Všimněte si na příkladu menšího nedostatku. Ve všech příkladech v dřívějších článcích jsem posílal a přijímal textová data. Nyní posílám binární data. Problém ale je, že nijak neošetřuji situaci, kdy odesílatel a přijímající budou běžet na různých architekturách, které mají jinou reprezentaci čísla. Na podobné věci je potřeba vždy myslet.

Tabulka č. 440
Soubor OS
lin15.tgz Linux
win15.zip MS WindowsŽ

Existuje možnost, aby urgentní data a „normální“ data byla vkládána do jedné příchozí fronty. Museli bychom nastavit volbu SO_OOBINLINE. O tom si povíme příště. Podíváme se na volby soketů.

Našli jste v článku chybu?