|
|
|
|
|
Protokol UDP 1.část
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ě.
Hodnocení článku |
1 |
2 |
3 |
4 |
5 Aktuální známka: 2.72 (Počet známek: 3987)
|
|
|
|
|
|