Protokol UDP 1.část - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



C/C++

Protokol UDP 1.část

cpp net

3. února 2003, 00.00 | Dnes se podíváme na protokol UDP. Shrneme si znalosti o TCP a porovnáme TCP s UDP. Podíváme se také na funkce sendto a recvfrom. Na konci článku nechybí příklady ke stažení. Dnešní článek je věnován soketům v MS Windows i soketům v Linuxu. V článku popisuji funkce pro oba operační systémy.

Dnes si povíme něco o protokolu UDP (User Datagram Protocol). Nejprve si ale shrňme (pro lepší pochopení rozdílů) naše znalosti o TCP (Transmision Control Protocol).

Shrnutí našich znalostí o TCP

Protokol TCP je protokol transportní vrstvy. Slouží k propojení dvou aplikací. Oproti tomu protokol IP, protokol nižší, tedy síťové vrstvy, slouží k spojení dvou počítačů. Protokol TCP používá jako protokol síťové vrstvy protokol IP. Tím dostáváme onu známou zkratku TCP-IP (nebo TCP/IP). Protokol TCP je spojová služba. Jak jsem již několikrát zdůrazňoval, při komunikaci pomocí TCP je nutné navázat spojení. Spojení navazuje TCP klient, který se připojuje k TCP serveru. Veškerý přenos dat mezi klientem a serverem probíhá pouze v rámci tohoto spojení. Všechna přenesená data jsou potvrzována. Jak jste si mohli všimnout, programátor používající sokety nemusí pro toto potvrzování nic udělat. V případě, že data nejsou potvrzená, budou doručená znovu. Jestliže nelze data doručit určitou dobu, selže funkce send. Tím odesílatel dat zjistí, že nedošlo k doručení dat. (U protokolu UDP si mimo jiné všimneme, že po odeslání dat již nemáme žádnou možnost nějaké kontroly jejich přijetí.) Musím jen upozornit, že data jsou přijata druhou stranou tehdy, jsou-li doručená koncové aplikaci (tedy zapsána do nějakého přijímacího bufferu), nikoliv tehdy, když je druhá strana přečte pomocí funkce recv. Pokud tedy odesíláme data, potvrzení jejich příjmu přijde v momentě, kdy je druhá strana má možnost začít číst, nikoliv v momentě, kdy je druhá strana přečte funkcí recv. Z faktu, že TCP je spojová služba vyplívají také další vlastnosti. Data předávána pomocí TCP přijdou v pořadí, ve kterém byla odeslána. Proto odešleme-li nejprve pomocí funkce send nějaký blok dat (nazvěme si ho data1), potom opět pomocí send (send zavoláme podruhé) odešleme jiná data (nazvěme si ho data2), máme jistotu, že druhá strana obdrží nejprve data1, potom data2. Druhá strana ale nijak nemůže poznat, kde končí data1 a kde začínají data2. Může všechna data přečíst jedním voláním funkce recv, nebo libovolně velké kousky přečíst opakovaným voláním recv. Proto se také někdy datům posílaným a přijímaným pomocí TCP říká proud dat (stream). Přirovnával jsem proud dat k binárnímu souboru. Když čteme binární soubor, také nejsme schopni zjistit, v jak velkých blocích dat byl zapisován. Tvoří-li data nějaké logické celky, musíme si sami zajistit jejich rozeznání (předem dohodnutá délka, speciální znaky pro ukončení atd...) Stejně tak je to s daty přijímaných pomocí protokolu TCP.

Výhody TCP:
  • Snadná detekce nedoručení dat
  • Garance správnosti pořadí přijímaných dat. (V pořadí, v jakém byly odeslány.)
  • Nemůže vzniknout duplicita dat.
  • Zajištěná správnost dat. (Společně s daty je odesílán také kontrolní součet.)
Nevýhody TCP:
  • Příliš mnoho řídících informací - Hlavička TCP obsahuje mnoho informací (kontrolní součet, pořadí, a další informace nutné pro přenos v rámci spojení).
  • Velká zátěž pro síť - Krom toho, že je TCP hlavička poměrně dost velká, ještě je nutné také přenos dat opačným směrem (potvrzování). Každé potvrzení je další TCP paket poslán opačným směrem. (Potvrzování je automatické, programátor používající sokety tak, jak jsem je popsal, se o potvrzování nemusí starat, nemusí o něm dokonce ani vědět).

Existuje velmi rozšířený omyl, že v dnešním Internetu (od síťové vrstvy "nahoru") je používán pouze protokol TCP/IP. Není to pravda. TCP/IP je sice využíván velmi, ale né pouze. Velmi používanou alternativou k TCP je protokol UDP. Používány jsou samozřejmě i jiné protokoly než TCP/IP a UDP/IP.

Protokol UDP

Protokol UDP je protokol transportní vrstvy. Stejně jako TCP slouží ke komunikaci dvou aplikací. Stejně jako TCP používá IP jako síťový protokol (pro spojení dvou počítačů). Stejně jako TCP identifikuje aplikace na počítačích pomocí tak zvaného portu (číslo). UDP port je jednoznačné číslo identifikující aplikaci. Na jednom počítači nemohou dvě aplikace používat stejný UDP port. Čísla TCP portů a UDP portů jsou na sobě nezávislá. Jeden program může používat například TCP port 5000 a jiný na stejném počítači může používat UDP port 5000. Podstatným rozdílem je, že UDP není spojová služba. Tedy nenavazuje se spojení. Co z toho vyplývá? (krom toho, že nemusíme volat connect)

  • Není potvrzováno doručení UDP datagramů - prostě data pošleme a tím nad nimi ztrácíme jakoukoliv kontrolu. Možná druhé straně dojdou, možná ne.
  • Neexistuje kontrolní součet - data se (teoreticky) mohou poškodit.
  • Data mohou být doručena ve špatném pořadí. Tedy data1 a data2 (z předchozího odstavce) mohou být doručená v pořadí data2 a potom data1. Nejsme schopni to nijak ovlivnit. Samozřejmě, že jednotlivá byte v blocích dat (data1 a data2) nemohou být přeházená.
  • Strana, která přijímá, je schopná rozlišit jednotlivé datagramy. Tedy data odeslána jedním send (resp. sendto) jsou přijata jedním recv (resp. recvfrom). Ale není garantováno, že budou ve stejném pořadí! Mohou se předbíhat.
  • Data se mohou duplikovat.
Výhody UDP:
  • Malá hlavička dat
  • Malé zatížení sítě (neposílá se potvrzování)
  • Při přijímání lze rozlišit jednotlivé datagramy
Nevýhody UDP:
  • Naprosto nezabezpečený přenos (data se mohou ztratit, poškodit, duplikovat, předbíhat - nic z toho nejsme schopni zjistit)

Využití UDP se nabízí při různých "real-time" přenosech multimediálních dat. Například různé Internetové videokonference. Přenáší se velký objem dat a jejich potvrzování by bylo pro síť opravdu náročné. Použije se UDP. V případě ztráty nějakého datagramu nám například blikne obrazovka, nebo na malý okamžik neslyšíme zvuk, případně slyšíme šum. UDP má využití nejen v takových specialitách. Běžný smrtelník jej používá velmi často. Při každém překladu doménového jména na IP adresu. DNS servery totiž používají ke komunikaci protokol UDP. Takže, když jsme v našich příkladech používali funkci gethostbyname, aniž by jsme to věděli, komunikovali jsme s DNS serverem pomocí UDP.

Správci sítí nevidí UDP příliš rádi. Většinou jej (kromě dotazů a odpovědí DNS) na firewallech odchytí a zahodí.

Napsal jsem, že přenos dat pomocí TCP je na rozdíl od UDP zabezpečený. Je zabezpečen proti "neinteligentnímu" útočníkovi. Tedy v podstatě proti poruchám. "Inteligentní" útočník (hacker) je schopen kromě změny dat také změnit kontrolní součet. Je schopen data na cestě odposlechnout, ale také zadržet (neposlat dál) a zpět poslat jejich potvrzení. V dnešní době se pod pojmem "bezpečný přenos dat" rozumí přenos zabezpečen proti inteligentnímu útočníkovi. V takovém případě musíme použít různá šifrování a digitální podpisy. Budu-li dále v článku nebo v seriálu používat pojem "bezpečný přenos dat", budu mít na mysli přenos dat zabezpečen proti poruchám (neinteligentnímu útočníkovi).

Protokol UDP a sokety

Podstatné rozdíly mezi TCP a UDP z pohledu programátora používajícího sokety jsou jednak ve vytvoření soketu (parametry funkce socket), dále nenavazujeme spojení (nevoláme connect). Pro odeslání dat lze použít funkci sendto. Pro příjem dat lze použít funkci recvfrom.

Prvním parametrem funkce socket (V Linuxu se mu říká doména, v MS Windows® zase rodina protokolů. Důležité je, že mají stejný význam.) bude mít stále hodnotu makra AF_INET. Druhý parametr udává typ soketu. Zde budeme předávat hodnotu makra SOCK_DGRAM. Tedy zadáme, že vytváříme "datagramový" soket. Posledním parametrem je protokol, který bude soketem používán. Budeme zadávat hodnotu makra IPPROTO_UDP. Takže datagramový soket používající protokol UDP budeme vytvářet takto:
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

Použití UDP v Linuxu
  • int sendto(int s, const void *msg, size_t len, int flags, struct sockaddr *to, socklen_t tolen); - odešle data daným soketem na danou adresu a port. Funkce je velice podobná funkci send se kterou jsme se již setkali. První 4 parametry jsou totožné jako u funkce send a také mají stejný význam. Ukazatel to odkazuje na strukturu sockaddr. My mu budeme předávat (jako vždy) ukazatel na strukturu sockaddr_in, který odpovídajícím způsobem přetypujeme. Instanci struktury sockaddr_in, na kterou se bude odkazovat parametr to zaplníme IP adresou cílového počítače a číslem UDP portu aplikace na cílovém počítači. Všimněte si, že u TCP protokolu jsme volali funkci connect, které jsme tuto instanci předali. Nyní tuto instanci píšeme při každém odesílání dat. Při každém volání sendto můžeme jedním soketem posílat data na libovolné místo. Posledním parametrem je délka struktury, na kterou se odkazuje parametr to. Funkce vrací počet skutečně odeslaných bytů nebo -1 v případě chyby. Funkce je stejně jako send deklarována v souboru sys/socket.h.
  • int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); - přijme data daným soketem. Funkce je velice podobná funkci recv se kterou jsme se již setkali. První 4 parametry jsou totožné jako u funkce recv a také mají stejný význam. Ukazatel from odkazuje na strukturu sockaddr. My budeme opět předávat přetypovaný ukazatel na strukturu sockaddr_in, který odpovídajícím způsobem přetypujeme. Musíme předat ukazatel na alokovanou strukturu, která ale nemusí být zaplněná smysluplnými údaji. Parametr fromlen je ukazatel na číslo udávající velikost struktury, na niž se odkazuje předávaný ukazatel from. Po zavolání funkce recvfrom bude struktura, na kterou se odkazuje from zaplněná adresou a UDP portem odesílatele dat. Číslo, na které se odkazuje fromlen bude po zavolání obsahovat velikost struktury dané ukazatelem from. Tedy předáme ukazatel na strukturu a ukazatel na její velikost. Funkce nám tuto strukturu zaplní a řekne nám i novou velikost. Funkce vrací počet přijatých bytů nebo -1 v případě chyby. Funkce je stejně jako recv deklarována v souboru sys/socket.h.
Použití UDP v MS Windows®
  • int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen); - odešle data daným soketem na danou adresu a port. Funkce je velice podobná funkci send se kterou jsme se již setkali. První 4 parametry jsou totožné jako u funkce send a také mají stejný význam. Ukazatel to odkazuje na strukturu sockaddr. My mu budeme předávat (jako vždy) ukazatel na strukturu sockaddr_in, který odpovídajícím způsobem přetypujeme. Instanci struktury sockaddr_in, na kterou se bude odkazovat parametr to zaplníme IP adresou cílového počítače a číslem UDP portu aplikace na cílovém počítači. Všimněte si, že u TCP protokolu jsme volali funkci connect, které jsme tuto instanci předali. Nyní tuto instanci píšeme při každém odesílání dat. Při každém volání sendto můžeme jedním soketem posílat data na libovolné místo. Posledním parametrem je délka struktury, na kterou se odkazuje parametr to. Funkce vrací počet skutečně odeslaných bytů nebo hodnotu makra SOCKET_ERROR v případě chyby.
  • int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen); - přijme data daným soketem. Funkce je velice podobná funkci recv se kterou jsme se již setkali. První 4 parametry jsou totožné jako u funkce recv a také mají stejný význam. Ukazatel from odkazuje na strukturu sockaddr. My budeme opět předávat přetypovaný ukazatel na strukturu sockaddr_in, který odpovídajícím způsobem přetypujeme. Musíme předat ukazatel na alokovanou strukturu, která ale nemusí být zaplněná smysluplnými údaji. Parametr fromlen je ukazatel na číslo udávající velikost struktury, na niž se odkazuje předávaný ukazatel from. Po zavolání funkce recvfrom bude struktura, na kterou se odkazuje from zaplněná adresou a UDP portem odesílatele dat. Číslo, na které se odkazuje fromlen bude po zavolání obsahovat velikost struktury dané ukazatelem from. Tedy předáme ukazatel na strukturu a ukazatel na její velikost. Funkce nám tuto strukturu zaplní a řekne nám i novou velikost. Funkce vrací počet přijatých bytů nebo hodnotu makra SOCKET_ERROR v případě chyby.

Ti, kteří nečtou všechny články, ale jen články pro určitý operační systém mohou nyní porovnat, jak si jsou soketová API pro oba systémy velice podobná. V jednom článku totiž uvádím hlavičky funkcí pro oba operační systému.

Funkce sendto a recvfrom lze použít také pro sokety používající protokol TCP. V souvislosti s TCP jsem se o nich okrajově zmínil (napsal jsem, že existují). Nevěnoval jsem se jim více, protože jsem věděl, že se k nim dostaneme v souvislosti s UDP.

Ukázkové příklady

Pro ukázku si uvedeme UDP server v OS MS Windows® Vytvořit na základě tohoto zdrojového textu klienta pro MS Windows® a klienta se serverem pro Linux je již velmi jednoduché. Navíc jsou všechny 4 programy na konci článku k disposzici ke stažení.

UDP server v OS MS Windows®
#include <iostream>
#include <windows.h>
#include <string>

#define BUFSIZE 1000

using namespace std;

int main(int argc, char *argv[])
{
    WORD wVersionRequested = MAKEWORD(1,1); // Číslo verze
    WSADATA data;                        // Struktura s info. o knihovně; 
    sockaddr_in sockName;                // "Jméno" soketu
    sockaddr_in clientInfo;              // Informace o klientovi
    SOCKET Socket;                       // Soket
    int port;                            // Číslo portu
    char buf[BUFSIZE];                   // Přijímací buffer
    int size;                            // Počet přijatých bytů
    int addrlen;                         // Velikost adresy vzdáleného počítače
    int count = 0;                       // Počet "připojení"
    std::string respond;                 // Řetězec s odpovědí 
	
    if (argc != 2)
    {
        cerr << "Syntaxe:\n\t" << argv[0]
                << " " << "port" << endl;
        return -1;
    }
    // Připravíme sokety na práci
    if (WSAStartup(wVersionRequested, &data) != 0)
    {
        cout << "Nepodařilo se inicializovat sokety" << endl;        
        return -1;
    }    
    port = atoi(argv[1]);
 //Vytvoříme soket-viz minulé díly.Tentokrát se bude jednat o UDP komunikaci.
    if ((Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
    {
        cerr << "Nelze vytvořit soket" << endl;
        WSACleanup();
        return -1;
    }
    // Zaplníme strukturu sockaddr_in
    // 1) Rodina protokolů
    sockName.sin_family = AF_INET;
    // 2) Číslo portu, na kterém čekáme
    sockName.sin_port = htons(port);
    // 3) Nastavení IP adresy lokální síťové karty, přes kterou je možno se
    //    připojit. Nastavíme možnost připojit se odkudkoliv. 
    sockName.sin_addr.s_addr = INADDR_ANY;
    // Přiřadíme soketu jméno
    if (bind(Socket, (sockaddr *)&sockName, sizeof(sockName)) == SOCKET_ERROR)
    {
        cerr << "Problém s pojmenováním soketu." << endl;
        WSACleanup();
        return -1;
    }
    // Nemusíme vytvářet frontu požadavku na spojení a vybírat z ní požadavky
    // jako u TCP. Už teď jsme přiipraveni přijímat data.        
    do
    {
        // Poznačím si velikost struktury clientInfo.
        addrlen = sizeof(clientInfo);
        if ((size = recvfrom(Socket, buf, BUFSIZE - 1, 0,
                          (sockaddr *)&clientInfo, &addrlen)) == SOCKET_ERROR)
        {
                cerr << "Nepodařilo se přijmout data" << endl;
                closesocket(Socket);
                WSACleanup();                
                return -1;
        }
        // Zjistím IP klienta.
        cout << "Někdo poslal data z adresy: " 
                << inet_ntoa((in_addr)clientInfo.sin_addr) << endl;        
        cout << "Přijato: " << size << endl;
        // Připravím odpověď
        buf[size] = '\0';
        cout << buf << endl;
        respond = "Děkuji za zaslaný datagram\nJeho obsah byl:\n";
        respond += buf;
        respond += "\n";
        // Odešlu poděkování
        if (sendto(Socket, respond.c_str(), respond.size(), 0, 
                (sockaddr *)&clientInfo, addrlen) == SOCKET_ERROR)
        {
                cerr << "Problém s odesláním dat" << endl;
                closesocket(Socket);
                WSACleanup();
                return -1;
        }
        cout << "Odesláno: " << endl << respond << endl;
    }while (++count != 10);
    cout << "Končím" << endl;
    closesocket(Socket);
    WSACleanup();    
    return 0;
}

Příklady

Již jsem napsal, že sendto a recvfrom lze použít i u TCP. Ukazuje se tím síla soketů. Stejné rozhraní (funkce) se používají pro úplně odlišné komunikační protokoly. Také jsem několikrát zdůraznil, že u UDP nevoláme connect, protože při komunikaci pomocí UDP se spojení nenavazuje. Přesto i na datagramový soket lze zavolat funkci connect. V tom jsem vám trochu lhal. Stále ale platí, že v UDP se spojení nenavazuje. Funkce connect má v souvislosti s UDP trochu jiný význam. O tom si povíme příště. Použitím connect u protokolu UDP nám umožní například použití nám známých funkcí send a recv i u protokolu UDP. Jak příště uvidíme, funkce send a recv neslouží pouze pro TCP. Lze je použít i pro UDP. V příštím článku si vlastně dokážeme, že příklady z mých předchozích článků lze s nepatrnými změnami předělat tak, aby používaly UDP. Tomu tedy říkám síla soketů a skvěle navržené API. O tom ale až příště.

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » C/C++  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: