Zásuvky Použití soketů pro práci s UDP

Proto „zostření“ tohoto protokolu pro práci s jednotlivými dokumenty, především textovými. HTTP při své práci využívá schopnosti TCP/IP, proto se podívejme na možnosti, které poskytuje Java pro práci s posledně jmenovaným.

V Javě k tomu existuje speciální balíček „java.net“ obsahující třídu java.net.Socket. Zásuvka v překladu znamená „zásuvka“ tento název byl dán analogií zásuvek na zařízení, přesně těch, kde se připojují zástrčky. Podle této analogie můžete propojit dvě „zásuvky“ a přenášet mezi nimi data. Každé hnízdo patří konkrétnímu hostiteli (Host - majitel, držitel). Každý hostitel má jedinečnou IP (Internet Packet) adresu. V současné době internet funguje pomocí protokolu IPv4, kde se IP adresa zapisuje 4 číslicemi od 0 do 255 - například 127.0.0.1 (více o distribuci IP adres čtěte zde - RFC 790, RFC 1918, RFC 2365, přečtěte si o verzi IPv6 zde - RFC 2373)

Zásuvky jsou namontovány na hostitelském portu (portu). Port je označen číslem od 0 do 65535 a logicky označuje místo, kde může být soket vázán. Pokud je port na tomto hostiteli již obsazen nějakým socketem, pak již nebude možné tam dokovat jiný socket. Po instalaci socketu má tedy velmi specifickou adresu, symbolicky zapsanou takto: například - 127.0.0.1:8888 (znamená, že socket zabírá port 8888 na hostiteli 127.0.0.1)

Abychom si usnadnili život, abychom nepoužívali nepohodlnou IP adresu, byl vynalezen systém DNS (DNS - Domain Name Service). Účelem tohoto systému je mapování symbolických jmen na IP adresy. Například adresa "127.0.0.1" na většině počítačů je spojena s názvem "localhost" (v běžné mluvě - "localhost").

Localhost ve skutečnosti znamená samotný počítač, na kterém program běží, je to také místní počítač. Veškerá práce s localhostem nevyžaduje přístup k síti a komunikaci s jinými hostiteli.

Klientský soket

Vraťme se tedy ke třídě java.net.Socket Nejpohodlnější je inicializovat ji následovně:

Public Socket (hostitel String, int port) vyvolá výjimku UnknownHostException, IOException V konstantě hostitelského řetězce můžete zadat IP adresu serveru i jeho název DNS. V tomto případě program automaticky vybere volný port na místním počítači a „našroubuje“ tam vaši zásuvku, načež dojde k pokusu o spojení s jinou zásuvkou, jejíž adresa je uvedena v inicializačních parametrech. V tomto případě mohou nastat dva typy výjimek: neznámá adresa hostitele - když v síti není počítač se stejným názvem, nebo chyba, že není spojení s tímto socketem.

Je také užitečné znát funkci

Public void setSoTimeout(int timeout) vyvolá SocketException Tato funkce nastaví časový limit pro práci se socketem. Pokud během této doby nejsou se soketem provedeny žádné akce (to znamená přijímání a odesílání dat), dojde k jeho autodestrukci. Čas je nastaven v sekundách, když je timeout nastaven na 0, zásuvka se stane „věčnou“.

U některých sítí není změna časového limitu možná nebo je nastavena v určitých intervalech (například od 20 do 100 sekund). Pokud se pokusíte nastavit neplatný časový limit, bude vyvolána příslušná výjimka.

Program, který otevírá tento typ soketu, bude považován za klienta a program, který vlastní soket, ke kterému se pokoušíte připojit, se bude nazývat server. Ve skutečnosti, analogicky se zásuvkou-zástrčkou, serverový program bude zásuvkou a klient je přesně zásuvka.

Serverový soket

Právě jsem popsal, jak navázat spojení z klienta na server, nyní jak vytvořit soket, který bude obsluhovat server. Pro tento účel existuje v Javě následující třída: java.net.ServerSocket Nejpohodlnější inicializátor pro ni je následující:

Public ServerSocket (int port, int backlog, InetAddress bindAddr) vyvolá IOException Jak vidíte, jako třetí parametr je použit objekt jiné třídy - java.net.InetAddress Tato třída poskytuje práci s názvy DNS a IP, takže výše uvedený inicializátor lze použít v programech, jako je tento: ServerSocket(port, 0, InetAddress.getByName(host)) vyvolá IOException Pro tento typ soketu je instalační port specifikován přímo, proto může během inicializace dojít k výjimce indikující, že tento port je se již používá nebo je jeho použití počítačem s bezpečnostní politikou zakázáno.

Po instalaci zásuvky se funkce zavolá

Public Socket accept() vyvolá IOException Tato funkce způsobí, že program počká, než se klient připojí k soketu serveru. Jakmile je spojení navázáno, funkce vrátí objekt třídy Socket pro komunikaci s klientem.

Klient-server přes sokety. Příklad

Jako příklad uvádíme jednoduchý program, který implementuje práci se sockety.

Na straně klienta program funguje následovně: klient se připojí k serveru, odešle data, poté data přijme ze serveru a odešle je.

Ze strany serveru to vypadá takto: server nastaví serverový soket na port 3128 a poté čeká na příchozí připojení. Po přijetí nového připojení jej server přenese do samostatného výpočetního vlákna. V novém streamu server přijímá data od klienta, přiděluje mu pořadové číslo připojení a odesílá data zpět klientovi.


Logická struktura ukázkových programů

Jednoduchý klientský program TCP/IP

(SampleClient.java) import java. io.*; importovat java. net.* ; class SampleClient rozšiřuje vlákno ( public static void main(String args) ( try (// otevřete soket a připojte se k localhost:3128 // získání soketu serveru Socket s = new Socket("localhost" , 3128 );// vezme výstupní proud a vypíše tam první argument // zadaná během volání, adresa otevřeného soketu a jeho portu args[ 0 ] = args[ 0 ] + "\n" + s. getInetAddress() . getHostAddress() + ":" + s. getLocalPort(); s. getOutputStream() . write(args[ 0 ] . getBytes()); } }

// přečtení odpovědi byte buf = new byte [ 64 * 1024 ];

int r = s. getInputStream() . read(buf); Data řetězce = new String(buf, 0 , r); // odešle odpověď do konzole Systém. ven. println(data); ) catch (Exception e) ( System. out. println("init error: " + e);) // výjimky výstupu new SampleServer(i, server. accept()); s. getOutputStream() . write(args[ 0 ] . getBytes()); i++; ) ) catch (Exception e) ( System. out. println("init error: " + e);)) public SampleServer(int num, Socket s) ( // zkopírujte data tento. num = num; tento. s = s;// a spusťte nové výpočetní vlákno (viz funkce run()) setDaemon(true); setPriority(NORM_PRIORITY); start(); ) public void run() ( zkuste (// odebírá proud příchozích dat z klientského soketu InputStream je = s. getInputStream();// a odtud - tok dat ze serveru ke klientovi OutputStream os = s. getOutputStream();// datová vyrovnávací paměť 64 kB byte buf = nový byte [ 64 * 1024 ];// přečte 64kb z klienta, výsledkem je počet skutečně přijatých dat int r = je. read(buf);// vytvoří řetězec obsahující informace přijaté od klienta Data řetězce = new String(buf, 0 , r);// přidat data o adrese soketu: s. getOutputStream() . write(args[ 0 ] . getBytes()); } }

data = "" + num+ ": " + "\n" + data;

// výstupní data:

os. write(data. getBytes());

// ukončení spojení

s. blízko(); ) catch (Exception e) ( System. out. println("init error: " + e);)

Po kompilaci získáme soubory SampleServer.class a SampleClient.class (všechny programy zde a níže jsou zkompilovány pomocí JDK v1.4) a nejprve spustíme server:

Java SampleServer a poté po čekání na zprávu "server je spuštěn" a libovolný počet klientů: java SampleClient test1 java SampleClient test2 ... java SampleClient testN

Za zmínku stojí, že abstrakci Socket - ServerSocket a práci s datovými toky využívají C/C++, Perl, Python a mnoho dalších programovacích jazyků a API operačních systémů, takže mnohé z toho, co bylo řečeno, platí nejen pro platformě Java.

Socket vs Socket část 2, nebo řekněte „ne“ protokolu TCP - Archiv WASM.RU

V prvním díle, věnovaném základům používání soketů MSWindows v assembleru, jsme si řekli, co to jsou sockety, jak se vytvářejí a jaké parametry se uvádějí. Zároveň bylo mimochodem zmíněno nepřipojově orientovaném protokolu UDP, který nezaručuje doručení paketů, ani pořadí, v jakém dorazí na místo určení. Výukový příklad pak používal náš oblíbený protokol TCP. A u nás bylo všechno v pořádku, ale nakonec zůstala řada nevyřešených otázek, zejména jak zorganizovat vzájemnou výměnu mezi několika počítači v síti, jak něco přenést na mnoho počítačů najednou atd.

Obecně lze říci, že čtení prvního dílu není k pochopení toho aktuálního vůbec nutné, i když na něj budu neustále odkazovat. Takové věci. Haha...

Problém tedy představujeme my: máme lokální síť, řekněme, tuctu počítačů, potřebujeme zorganizovat výměnu zpráv mezi libovolnými dvěma z nich a (pokud je to žádoucí) mezi jedním a všemi ostatními.

Slyšel jsem, slyším hromadu rad, které říkají, že používejte vestavěné funkce systému Windows, jako například:

net send 192.168.0.4 Zhenya vám posílá pozdravy!

net send Node4 Čeká na vaši odpověď!

Proti tomu existují pouze dvě námitky. Za prvé, nikdy nevíte, co náš operační systém nebo jiné hotové programy umí, chceme se naučit psát vlastní programy, že? A za druhé, není skutečností, že zpráva přechází od člověka k člověku. V obecném případě operátor nemusí nic vědět... Nebo by dokonce neměl nic vědět...

Pro mě bylo nejdůležitější při nastavení tohoto úkolu zajistit možnost přenést něco na všechny počítače v síti najednou. Představte si, že jsme napsali určitý program... Kdo řekl – Trojan? Ne, ne a ne! Žádné trojské koně. Stačí například malý (velmi) účetní program. Což se po nějaké době dokázalo usadit na mnoha počítačích v naší lokální síti. A teď přichází ten určený čas, je čas bilancovat, shrnout takříkajíc výsledky za čtvrtletí... Vše je třeba udělat rychle a nejlépe zároveň. Jak to udělat v rámci materiálu, který jsme studovali v první části, zůstalo nejasné.

Odpověď jako vždy přichází z WindowsAPI. Hledáme a nacházíme. Funkce sendto() – odešle data na zadanou adresu. V čem se tedy liší od funkce, kterou jsme již studovali v první části? poslat() ? Ukazuje se, že sendto() může vysílat na speciální IP adresu. Ale pozor, toto funguje pouze pro sockety typu SOCK_DGRAM! A sokety, které byly otevřeny pomocí hodnoty SOCK_DGRAM jako parametru typu soketu, fungují prostřednictvím protokolu UDP, nikoli TCP! Tím je objasněn význam podtitulu tohoto článku... Samozřejmě, toto je pouze literární zařízení, žádný protokol není lepší nebo horší než jiný, jsou prostě... jiné, to je vše. Ačkoli oba jsou protokoly transportní vrstvy, které „...poskytují přenos dat mezi aplikačními procesy“. Oba používají protokol síťové vrstvy, jako je IP pro přenos (příjem) dat. Přes které pak (data) vstupují do fyzické úrovně, tzn. na přenosovou středu... A jaká je středa, kdo ví. Možná je to měděný kabel, nebo možná není středa, ale čtvrtek, a ne měděný kabel, ale vysílání...

Schéma interakce síťových protokolů.

UDPU ser D atagram P rotokol

TCP-T uvolnění Cřízení P rotokol

ICMP-I Internet Cřízení M esej P rotocol (protokol pro výměnu řídicích zpráv)

ARPAšaty Rřešení P rotocol (protokol zjišťování adres)

Obecně platí, že pokud vám kresba nijak nepomohla, nezáleží na tom. Je důležité pochopit jednu věc, že ​​TCP je protokol transportní vrstvy, který poskytuje spolehlivý přenos dat mezi procesy aplikace nastavení logického spojení (zdůrazňuji můj). Ale UDP není. A ještě jedna věc. Někde tam, na úrovni aplikace, v jednom z prázdných obdélníků bude naše aplikace umístěna.

Dokončíme zde úvodní část a přejdeme k tomu, abychom se od začátku podívali na to, jak ji používat.

K demonstraci veškerého materiálu slouží jako obvykle příklad školení, který si lze stáhnout< >. Přeskočíme část společnou pro všechny aplikace Windows a popíšeme pouze to, co se týká provozu soketů. Nejprve musíte inicializovat Windows Sockets DLL pomocí funkce WSAStartup() , která v případě úspěchu vrátí nulu nebo v opačném případě jeden z chybových kódů. Poté při inicializaci hlavního okna aplikace otevřete soket pro příjem zpráv:

    vyvolat soket, AF_INET, \

    SOCK_DGRAM, \ ; specifikuje typ socketu - protokol UDP!

    0; typ protokolu

    If eax != INVALID_SOCKET ; pokud není chyba

    mov hSocket, eax ; pamatovat rukojeť

Poté, jako obvykle, musíme systému Windows říci, aby posílal zprávy do určeného okna ze zásuvky, kterou jsme otevřeli:

    vyvolat WSAAsyncSelect, hSocket, hWnd, WM_SOCKET, FD_READ

Kde hSocket- deskriptor zásuvky
hWnd- manipulovat s oknem, do kterého budou zasílány procedurální zprávy
WM_SOCKET- zpráva, námi definovaná v sekci.konst
FD_READ– maska, která specifikuje události, které nás zajímají, v tomto případě jde o připravenost dat ze socketu ke čtení.

Slyším, slyším překvapený refrén se zoufalstvím v hlase: slíbili skrytou aplikaci, ale tady je hlavní okno a to všechno... Faktem je, že se bez toho neobejdete, protože... Operační systém odesílá všechny zprávy naší aplikaci prostřednictvím své procedury okna. Řešení je jednoduché. V případě potřeby skryjte toto nejdůležitější okno aplikace. Jak? Zakomentujte například řádek:

    vyvolat ShowWindow, hwnd, SW_SHOWNORMAL

nebo přesněji použijte:

    vyvolat ShowWindow, hwnd, SW_HIDE

Po tomto se spustí i naše aplikace, vytvoří se hlavní okno, z Windows se na ni odešle zpráva WM_CREATE se všemi důsledky... Jen její okno nebude vidět ani na ploše, ani na hlavním panelu. Pokud je to to, co jsi chtěl, jsem rád. Každopádně pokračujme...

Za tímto účelem převedeme číslo portu na pořadí bajtů sítě pomocí speciální funkce API:

    vyvolat htons, port

    mov sin.sin_port, sekera

    mov sin.sin_family, AF_INET

    mov sin.sin_addr, INADDR_ANY

Malá lyrická odbočka, která není nutná k pochopení smyslu tohoto článku .

Čísla portů pro naše zásuvky jsme probrali na konci první části. Je těžké dát doporučení, jaké by měly být. Jediné, co lze říci, je, že nemohou být. Není rozumné pokoušet se používat čísla portů definovaná pro široce používané služby, jako jsou:

přes protokol TCP: 20, 21 – ftp; 23 – telnet; 25 – smtp; 80 – http; 139 - Služba relace NetBIOS;

přes protokol UDP: 53 – DNS; 137, 138 – NetBIOS; 161 – SNMP;

API má samozřejmě speciální funkci getservbyport() , který po zadání čísla portu vrátí název odpovídající služby. Přesněji řečeno, funkce sama vrací ukazatel na strukturu, uvnitř které je ukazatel na toto jméno...

Můžete to nazvat takto:

    vyvolat htons, Port; převést číslo portu na pořadí bajtů sítě

    invoke getservbyport, ax, 0;

Všimněte si, o čem Win32 Programmer's Reference říká getservbyport:

“...vrací ukazatel na strukturu, která je distribuována pomocí Windows Sockets. Aplikace by se nikdy neměla pokoušet upravovat tuto strukturu ani žádnou z jejích součástí. Navíc je přidělena pouze jedna kopie této strukturytok, takže aplikace musí zkopírovat všechny potřebné informace před jakýmkoli dalším voláním funkce Windows Sockets."

A zde je samotná struktura:

  1. s_name DWORD ?; ukazatel na řetězec s názvem služby

    s_aliasy DWORD ?;

    s_port WORD ?; číslo portu

    s_proto DWORD ?;

API má také „spárovanou“ funkci, abych tak řekl: getservbyname(), která na základě názvu služby vrací informaci o použitém čísle portu.

Bohužel z těchto funkcí nebudeme moci čerpat praktické výhody. Takže vězte, že existují a zapomeňte na ně...

    invoke bind, hSocket, addr sin, sizeof sin

    If eax == SOCKET_ERROR; pokud dojde k chybě

    vyvolat MessageBox, NULL, addr ...

V tomto okamžiku lze považovat přípravné práce na vytvoření a konfiguraci přijímacího soketu pomocí datagramů za dokončené. Není potřeba nastavovat socket tak, aby naslouchal na portu pomocí funkce invoke poslouchat, jako jsme to udělali pro socket typu SOCK_STREAM v první části. Nyní v proceduře hlavního okna naší aplikace můžeme přidat kód, který se spustí, když ze soketu dorazí zpráva WM_SOCKET:

    ; pokud je přijata zpráva ze soketu (hSocket)

    Elseif uMsg == WM_SOCKET

  1. If ax == FD_READ;

  2. If ax == NULL ; žádná chyba

    ; přijímat data (64 bajtů) ze soketu do vyrovnávací paměti BytRecu

    vyvolat recv, hSocket, addr BytRecu, 64, 0;

Nyní si povíme, jak otevřít soket pro odesílání zpráv. Zde jsou všechny potřebné akce programu:

    vyvolat soket, AF_INET, SOCK_DGRAM, 0

      vyvolat htons, port

      mov sin_to.sin_port, ax

      mov sin_to.sin_family, AF_INET

      vyvolat inet_addr, addr AddressIP

      mov sin_to.sin_addr, eax

    Pokud jde o přenos dat, vše, co musíte udělat, je:

      vyvolat sendto, hSocket1, addr BytSend1, 64, 0, \

      addr sin_to, sizeof sin_to

    Hodnoty parametrů při volání této funkce API jsou následující:

    hSocket1- rukojeť do dříve otevřené zásuvky
    addrBytSend1- adresa vyrovnávací paměti obsahující data pro přenos
    64 - velikost dat ve vyrovnávací paměti, v bajtech
    0 - indikátor..., v příkladu MSDN je to jen 0
    addrsin_to- ukazatel na strukturu, která obsahuje cílovou adresu
    sizeofsin_to– velikost této struktury v bajtech.

    Pokud při provádění funkce sendto() nedošlo k žádné chybě, pak vrátí počet přenesených bajtů, jinak je výstupem SOCKET_ERROR v eax.

    Nyní je čas mluvit o stejné vysílací adrese, která byla zmíněna na začátku. Ve struktuře do pole jsme předvyplnili cílovou IP adresu, která udává, kam se mají data ve skutečnosti odeslat. Pokud je tato adresa 127.0.0.1, naše data se přirozeně nedostanou nikam dále než do našeho vlastního počítače. Literatura jasně uvádí, že paket odeslaný do sítě s adresou 127.x.x.x nebude přenášen v žádné síti. Směrovač nebo brána by navíc nikdy neměly šířit informace o směrování sítě číslo 127 – tato adresa není síťová adresa. Chcete-li odeslat „přenos“ na všechny počítače v místní síti najednou, musíte použít adresu vytvořenou z naší vlastní IP adresy, ale se všemi v nízkém oktetu, něco jako 192.168.0.255.

    To je vlastně všechno. Když se program zavře, musíte zavřít sokety a uvolnit prostředky Sockets DLL, to se provede jednoduše:

      vyvolat closesocket, hSocket

      vyvolat closesocket, hSocket1

      vyvolat WSACleanup

    Pro vícevláknové aplikace po WSACleanup operace soketu jsou dokončeny pro všechna vlákna.

    Nejtěžší částí tohoto článku pro mě bylo rozhodnutí, jak nejlépe ilustrovat použití rozhraní Windows Sockets API. Pravděpodobně jste se již setkali s jedním přístupem, kdy byl v jedné aplikaci současně používán soket pro příjem i soket pro odesílání zpráv. Další metoda se zdá neméně atraktivní, když je kód pro jednu a druhou jasně oddělen, dokonce i to, co existuje v různých aplikacích. Nakonec jsem implementoval i tuto metodu, která může být pro začátečníky srozumitelnější. Ve druhém<архиве

    Bez této funkce poslat() vytvoří SOCKET_ERROR!

    Nakonec si můžeme všimnout některých běžných problémů, které vznikají při práci se zásuvkami. Abychom zpracovali zprávu okna indikující, že se stav soketu změnil, použili jsme jako obvykle přímé zprávy z Windows do hlavního okna aplikace. Při vytváření samostatných oken pro každou zásuvku existuje jiný přístup.

    Obecně řečeno, centralizované zpracování zpráv v hlavním okně se zdá být srozumitelnější metodou, ale v praxi může být stále problémem. Pokud program používá více než jeden soket současně, musí uložit seznam deskriptorů soketu. Když se objeví zpráva ze soketů, procedura hlavního okna v seznamu vyhledá informace spojené s daným deskriptorem soketu a odešle zprávu o změně stavu dále proceduře k tomu určené. Co už tak či onak reaguje, tam něco dělá... Tento přístup nutí integrovat zpracování síťových úloh do jádra programu, což znesnadňuje vytváření knihoven síťových funkcí. Pokaždé, když jsou tyto síťové funkce použity, musí být do obslužné rutiny hlavního okna aplikace přidán další kód.

    Při druhém způsobu zpracování zpráv aplikace vytvoří skryté okno pro jejich příjem. Slouží k oddělení procedury hlavního okna aplikace od zpracování síťových zpráv. Tento přístup může zjednodušit hlavní aplikaci a usnadnit použití stávajícího síťového kódu v jiných programech. Negativní stránkou tohoto přístupu je nadměrné využívání Windows – uživatelské paměti, protože Pro každé vytvořené okno je vyhrazen poměrně velký objem.

    Jakou metodu zvolíte, je jen na vás. A ještě jedna věc. Při experimentování možná budete muset vypnout svůj osobní firewall. Například Outpost Pro 2.1.275 v režimu učení zareagoval na pokus o přenos do zásuvky, ale když byl přenos ručně povolen, data stále nedorazila. Tolik k UDP. I když to tak nemusí být. Ve stejné situaci nebyly žádné problémy s mým ZoneAlarmPro 5.0.590.

    P.S. Při dokončování druhé části článku jsem na internetu náhodou narazil na zdrojový kód trojského koně v našem oblíbeném jazyce MASM. Vše se zkompiluje a běží, jedna věc je, že se klient nechce připojit k serveru a i pod Windows 2000 sp4 občas spadne s chybou s tím, že se aplikace zavře a tak... Osobně co jako u tohoto trojana je to, že program neeviduje pouze záznamy o kliknutích nebo „vytrhává“ soubor s hesly a posílá jej e-mailem, a má širokou škálu vzdáleně ovládaných funkcí, implementovaných velmi originálním způsobem. Pokud se nám podaří celý tento byznys uvést v život, tak se snad brzy objeví třetí díl věnovaný popisu konkrétní implementace... Pro ty, kteří si oba články pozorně přečetli a pochopili fungování socketových API funkcí, je zde není tam nic složitého. Zdá se... Mimochodem, sám autor v readme píše, že to napsal (Trojan) pro vzdělávací účely. Dobře, dobře. Toto využijeme.

    Ředitel

Aplikace, které používají TCP a UDP, se zásadně liší, protože UDP je nespolehlivý, nespojovaný datagramový protokol a zásadně se liší od spojově orientovaného, ​​byte-streamově spolehlivého přenosu TCP. Existují však případy, kdy má smysl místo TCP použít UDP. Takové případy zvažujeme v části 22.4. Některé oblíbené aplikace jsou vytvořeny pomocí UDP, jako je DNS (Domain Name System), NFS (Network File System) a SNMP (Simple Network Management Protocol).

Na Obr. Obrázek 8.1 ukazuje volání funkcí pro typické schéma UDP klient-server. Klient nenavazuje spojení se serverem. Místo toho klient jednoduše odešle datagram na server pomocí funkce sendto (popsané v další části), která jako argument vezme adresu příjemce (serveru). Stejně tak server nenaváže spojení s klientem. Místo toho server pouze zavolá funkci recvfrom, která čeká, až data dorazí od nějakého klienta. Funkce recvfrom vrací adresu klienta (pro daný protokol) spolu s datagramem, takže server může odeslat odpověď přesně tomu klientovi, který odeslal datagram.

Rýže. 8.1. Funkce soketu pro model klient-server UDP

Obrázek 8.1 ukazuje časový diagram typického scénáře výměny UDP datagramů mezi klientem a serverem. Tento příklad můžeme porovnat s typickou TCP ústřednou zobrazenou na obrázku. 4.1.

V této kapitole popíšeme nové funkce používané se sokety UDP, recvfrom a sendto a přepracujeme náš model klient-server tak, aby používal UDP. Podíváme se také na použití funkce connect s UDP socketem a koncept asynchronních chyb.

8.2. funkce recvfrom a sendto

Tyto dvě funkce jsou podobné standardním funkcím čtení a zápisu, ale vyžadují tři další argumenty.

ssize_t recvfrom(int sockfd , void * buff , size_t nbytes , příznaky int,

struct sockaddr * from , socklen_t * addrlen);

ssize_t sendto(int sockfd, const void * buff, size_t nbytes, příznaky int,

const struct sockaddr * to , socklen_t addrlen);

Obě funkce vrátí počet zapsaných nebo přečtených bajtů při úspěchu, -1 při chybě.

První tři argumenty, sockfd , buff a nbytes , jsou totožné s prvními třemi argumenty funkcí čtení a zápisu: handle, ukazatel na vyrovnávací paměť pro čtení nebo zápis a počet bajtů pro čtení nebo zápis. .

Argumentu flags se budeme věnovat v kapitole 14, kde se podíváme na funkce recv, send, recvmsg a sendmsg, protože je v našem jednoduchém příkladu právě nepotřebujeme. Zatím vždy nastavíme argument flags na nulu.

Argument to funkce sendto je struktura adresy soketu obsahující adresu protokolu (jako je adresa IP a číslo portu) cíle. Velikost této struktury adresy soketu je určena argumentem addrlen. Funkce recvform vyplní strukturu adresy soketu, na kterou ukazuje argument from, adresou protokolu odesílatele datagramu. Počet bajtů uložených ve struktuře adresy soketu je také vrácen volajícímu procesu jako celé číslo, na které ukazuje argument addrlen. Všimněte si, že posledním argumentem funkce sendto je celočíselná hodnota, zatímco posledním argumentem funkce recvfrom je ukazatel na celočíselnou hodnotu (argument hodnota-výsledek).

Poslední dva argumenty funkce recvfrom jsou stejné jako poslední dva argumenty funkce accept: obsah struktury adres soketu po dokončení nám říká, kdo odeslal datagram (v případě UDP) nebo kdo inicioval připojení (v v případě TCP). Poslední dva argumenty funkce sendto jsou podobné posledním dvěma argumentům funkce connect: strukturu adresy soketu vyplníme adresou protokolu příjemce datagramu (v případě UDP) nebo adresou hostitele, se kterým se dojde k navázání spojení (v případě TCP).

Obě funkce vrátí jako hodnotu funkce délku dat, která byla přečtena nebo zapsána. Při typickém použití funkce recvfrom s protokolem datagramu je návratovou hodnotou množství uživatelských dat v přijatém datagramu.

Datagram může mít nulovou délku. Pro UDP to vrací datagram IP obsahující hlavičku IP (obvykle 20 bajtů pro IPv4 nebo 40 bajtů pro IPv6), 8bajtovou hlavičku UDP a žádná data. To také znamená, že návrat null z recvfrom je přijatelný pro protokol datagramu: neindikuje to, že druhá strana uzavřela spojení, stejně jako návrat null z čtení na soketu TCP. Protože protokol UDP není orientován na spojení, nedochází k žádné takové události, jako je uzavření spojení.

Pokud je argument from to recvfrom nulový ukazatel, pak musí být i odpovídající argument délky (addrlen) nulový ukazatel, což znamená, že nás nezajímá adresa odesílatele dat.

Obě funkce recvfrom a sendto lze použít s TCP, i když obvykle nejsou nutné.

8.3. UDP echo server: hlavní funkce

Nyní přepracujeme náš jednoduchý model klient-server z kapitoly 5 pomocí UDP. Schéma volání funkcí v našich klientských a serverových programech UDP je na obr. 8.1. Na Obr. 8.2 ukazuje použité funkce. Výpis 8.1 ukazuje funkci hlavního serveru.

Rýže. 8.2. Jednoduchý model klient-server využívající UDP

Výpis 8.1. UDP echo server

//udpcliserv/udpserv01.с

1 #include "unp.h"

3 intmain (int argc, char **argv)

6 struct sockaddr_in servaddr, cliaddr;

7 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

8 bzero(&servaddr, sizeof(servaddr));

9 servaddr.sin_family = AF_INET;

10 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

12 Bind(sockfd, (SA*)&servaddr, sizeof(servaddr));

13 dg_echo(sodkfd, (SA*)&cliaddr, sizeof(cliaddr));

Vytvořte soket UDP, vytvořte vazbu na známý port pomocí funkce vazby

7-12 UDP soket vytvoříme zadáním SOCK_DGRAM (datagramový soket IPv4) jako druhého argumentu funkce soketu. Stejně jako v příkladu serveru TCP je adresa IPv4 pro funkci vazby zadána jako INADDR_ANY a známé číslo portu serveru je konstanta SERV_PORT z hlavičky unp.h.

13 Poté je zavolána funkce dg_echo pro zpracování požadavku klienta serverem.

8.4. Server UDP echo: funkce dg_echo

Výpis 8.2 ukazuje funkci dg_echo.

Výpis 8.2. Funkce dg_echo: echo řetězce na soketu datagramu

1 #include "unp.h"

3 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)

6 socklen_t len;

7 znaků;

10 n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

11 Sendto(sockfd, mesg, n, 0, pcliaddr, len);

Čtení datagramu, reflektování odesílateli

8-12 Tato funkce je jednoduchá smyčka, ve které je další datagram přicházející na port serveru načten funkcí recvfrom a odeslán zpět pomocí funkce sendto.

Navzdory jednoduchosti této funkce je třeba vzít v úvahu řadu důležitých detailů. Za prvé, tato funkce se nikdy nedokončí. Protože UDP je protokol bez připojení, neexistuje ekvivalent příznaku konce souboru používaného v TCP.

Za druhé, tato funkce vám umožňuje vytvořit sériový server spíše než paralelní, který jsme obdrželi v případě TCP. Vzhledem k tomu, že neexistuje žádné volání funkce fork, zpracovává veškeré klientské zpracování jeden proces serveru. Obecně platí, že většina serverů TCP je paralelních a většina serverů UDP je sériových.

Pro soket na úrovni UDP jsou datagramy implicitně ukládány do vyrovnávací paměti ve formě fronty. Ve skutečnosti má každý soket UDP přijímací vyrovnávací paměť a každý datagram přicházející na tento soket je umístěn do jeho přijímací vyrovnávací paměti. Když proces zavolá funkci recvfrom, další datagram z vyrovnávací paměti je vrácen procesu v pořadí FIFO (First In, First Out). Pokud tedy do soketu dorazí mnoho datagramů dříve, než proces může číst data již zařazená do fronty pro soket, pak jsou příchozí datagramy jednoduše přidány do přijímací vyrovnávací paměti soketu. Ale tato vyrovnávací paměť má omezenou velikost. Diskutovali jsme o této velikosti ao tom, jak ji zvětšit pomocí volby SO_RCVBUF socket v části 7.5.

Na Obr. Obrázek 8.3 ukazuje zobecnění našeho modelu TCP klient-server z kapitoly 5, kde dva klienti navazují připojení k serveru.

Rýže. 8.3. Zobecnění modelu TCP klient-server se dvěma klienty

Jsou zde dva připojené sokety a každý z připojených soketů na serverovém uzlu má vlastní přijímací vyrovnávací paměť. Na Obr. Obrázek 8.4 ukazuje případ, kdy dva klienti odesílají datagramy na UDP server.

Rýže. 8.4. Zobecnění modelu klient-server UDP se dvěma klienty

Existuje pouze jeden serverový proces, který má jeden soket, na který server přijímá všechny příchozí datagramy a ze kterého odesílá všechny odpovědi. Tento soket má přijímací vyrovnávací paměť, do které jsou umístěny všechny příchozí datagramy.

Hlavní funkce ve výpisu 8.1 je závislá na protokolu (vytváří soket rodiny AF_INET a poté alokuje a inicializuje strukturu adres soketu IPv4), ale funkce dg_echo je nezávislá na protokolu. Důvodem, proč je funkce dg_echo nezávislá na protokolu, je to, že volající proces (v našem případě hlavní funkce) musí v paměti alokovat strukturu adresy soketu správné velikosti a ukazatel na tuto strukturu spolu s její velikostí je předán jako argument funkce dg_echo . Funkce dg_echo se do této struktury nikdy nehrabe: jednoduše na ni předá ukazatel na funkce recvfrom a sendto. Funkce recvfrom vyplní tuto strukturu IP adresou klienta a číslem portu, a protože stejný ukazatel (pcliaddr) je následně předán funkci sendto jako cílová adresa, datagram se tak odrazí zpět ke klientovi, který datagram odeslal.

8.5. UDP echo klient: hlavní funkce

Hlavní funkce UDP klienta je uvedena ve výpisu 8.3.

Výpis 8.3. UDP echo klient

//udpcliserv/udpcli01.c

1 #include "unp.h"

3 hlavní (int argc, char **argv)

6 struct sockaddr_in servaddr;

7 if (argc != 2)

8 err_quit("použití: udpcli ");

9 bzero(&servaddr, sizeof(servaddr));

10 servaddr.sin_family = AF_INET;

11 servaddr.sin_port = htons(SERV_PORT);

12 Inet_pton(AF_INET, argv, &servaddr.sin_addr);

13 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

14 dg_cli(stdin, sockfd, (SA*)&servaddr, sizeof(servaddr));

Vyplnění struktury adresy soketu adresou serveru

9-12 Struktura adresy soketu IPv4 je vyplněna adresou IP a číslem portu serveru. Tato struktura bude předána funkci dg_cli. Určuje, kam se mají datagramy odesílat.

13-14 Vytvoří se soket UDP a zavolá se funkce dg_cli.

8.6. UDP echo klient: funkce dg_cli

Výpis 8.4 ukazuje funkci dg_cli, která dělá většinu práce na straně klienta.

Výpis 8.4. Funkce dg_cli: klientská smyčka

1 #include "unp.h"

7 while (Fgets(sendline, MAXLINE, fp) != NULL) (

8 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

9 n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);

10 recvline[n] = 0; /* null končí */

11 Fputs(recvline, stdout);

7-12 Ve smyčce zpracování na straně klienta jsou čtyři kroky: načtení řetězce ze standardního vstupu pomocí fgets, odeslání řetězce na server pomocí sendto, čtení odražené odpovědi serveru pomocí recvfrom a uvedení odraženého řetězce na standardní výstup pomocí funkce fputs.

Náš klient nepožádal jádro, aby přiřadilo dynamicky přiřazený port jeho soketu (zatímco TCP klient to udělal při volání connect). U soketu UDP, když je poprvé zavolána funkce sendto, jádro vybere dynamicky přiřazený port, pokud k tomuto soketu již nebyl přidružen žádný místní port. Stejně jako u TCP může klient volat bind explicitně, ale to se dělá jen zřídka.

Všimněte si, že při volání funkce recvfrom jsou jako pátý a šestý argument určeny nulové ukazatele. To říká jádru, že nemáme zájem vědět, kdo odeslal odpověď. Existuje riziko, že jakýkoli proces, ať už na stejném uzlu nebo na jakémkoli jiném, může odeslat datagram na IP adresu a port klienta, který bude číst klient, za předpokladu, že se jedná o odpověď ze serveru. Touto situací se budeme zabývat v části 8.8.

Stejně jako u funkce serveru dg_echo je funkce klienta dg_cli nezávislá na protokolu, ale hlavní funkce klienta je závislá na protokolu. Hlavní funkce alokuje a inicializuje strukturu adresy soketu pro konkrétní typ protokolu a poté předá ukazatel na strukturu spolu s její velikostí funkci dg_cli.

8.7. Ztracené datagramy

Klient a server UDP v našem příkladu jsou nespolehlivé. Pokud dojde ke ztrátě klientského datagramu (řekněme, že je ignorován nějakým routerem mezi klientem a serverem), klient bude navždy zablokován ve svém volání funkce recvfrom uvnitř funkce dg_cli a čeká na odpověď ze serveru, která bude nikdy nepřijde. Podobně, pokud klientský datagram dorazí na server, ale odpověď serveru se ztratí, klient bude trvale zablokován ve svém volání funkce recvfrom. Jediným způsobem, jak této situaci předejít, je vložit časový limit do volání funkce klienta recvfrom. Na to se podíváme v části 14.2.

Pouhé uvedení časového limitu ve volání funkce recvfrom není úplné řešení. Pokud například vyprší zadaný časový limit a nepřijde žádná odpověď, nemůžeme s jistotou říci, co je špatně - buď náš datagram nedorazil na server, nebo se odpověď serveru nevrátila. Pokud by požadavek klienta obsahoval požadavek typu „převeďte určitou částku peněz z účtu A na účet B“ (na rozdíl od případu s naším jednoduchým echo serverem), pak by byl velký rozdíl mezi ztrátou požadavku a ztrátou odpovědi. . O přidání spolehlivosti do modelu klient-server UDP si povíme více v části 22.5.

8.8. Kontrola přijaté odpovědi

Na konci sekce 8.6 jsme zmínili, že každý proces, který zná klientovo dynamicky přidělené číslo portu, může posílat datagramy našemu klientovi a ty budou smíchány s normálními odpověďmi serveru. Jediné, co můžeme udělat, je upravit volání funkce recvfrom ve výpisu 8.4 tak, aby vrátilo IP adresu a port odesílatele odpovědi, a ignorovat všechny datagramy, které pocházejí z jiného serveru, než je ten, na který datagram posíláme. Zde je však několik úskalí, jak uvidíme.

Nejprve upravíme funkci hlavního klienta (viz Výpis 8.3), aby fungovala se standardním echo serverem (viz Tabulka 2.1). Jednoduše nahradíme zadání

servaddr.sin_port = htons(SERV_PORT);

úkol

servaddr.sin_port = htons(7);

Nyní můžeme s naším klientem použít jakýkoli uzel, na kterém běží standardní echo server.

Poté přepíšeme funkci dg_cli, abychom v paměti alokovali jinou strukturu adres soketu, která bude obsahovat strukturu vrácenou recvfrom. Ukážeme to ve výpisu 8.5.

Výpis 8.5. Verze funkce dg_cli, která kontroluje vrácenou adresu soketu

//udpcliserv/dgcliaddr.c

1 #include "unp.h"

3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

6 znaků sendline, recvline;

7 socklen_t len;

8 struct sockaddr *preply_addr;

9 preply_addr = Malloc(servlen);

10 while (Fgets(sendline, MAXLINE, fp) != NULL) (

11 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

12 len = servlen;

13 n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);

14 if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) (

15 printf("odpověď od %s (ignorováno)\n",

18 recvline[n] = 0; /* null končí */

19 Fputs(recvline, stdout);

Umístění jiné struktury adres soketu do paměti

9 Další strukturu adres soketu alokujeme v paměti pomocí funkce malloc. Všimněte si, že funkce dg_cli je stále nezávislá na protokolu. Protože je nám jedno, s jakým typem struktury adresy soketu máme co do činění, používáme ve volání funkce malloc pouze její velikost.

Porovnání vrácených adres

12-13 Ve volání funkce recvfrom říkáme jádru, aby vrátilo zdrojovou adresu datagramu. Nejprve porovnáme délku vrácenou funkcí recvfrom jako argument hodnota-výsledek a poté porovnáme samotné struktury adres soketu pomocí funkce memcmp.

Nová verze našeho klienta funguje skvěle, pokud je server na hostiteli s jedinou IP adresou. Tento program však nemusí fungovat, pokud má server několik síťových rozhraní (multihomed server). Tento program spustíme přístupem k uzlu freebsd4, který má dvě rozhraní a dvě IP adresy:

Macosx% hostitel freebsd4

freebsd4.unpbook.com má adresu 172.24.37.94

freebsd4.unpbook.com má adresu 135.197.17.100

Macosx% udpcli02 135.197.17.100

odpověď z 172.24.37.94:7 (ignorováno)

Podle Obr. 1.7 můžete vidět, že jsme nastavili IP adresu z jiné podsítě. To je obvykle přijatelné. Většina implementací IP přijímá příchozí IP datagram určený pro jakoukoli z IP adres hostitele, bez ohledu na rozhraní, na kterém přichází. RFC 1122 to nazývá model slabého konce systému. Pokud by systém implementoval to, co tento dokument nazývá model silného konce, přijme příchozí datagram pouze v případě, že datagram dorazí na rozhraní, ke kterému je určen.

IP adresa vrácená funkcí recvfrom (zdrojová IP adresa UDP datagramu) není IP adresa, na kterou jsme datagram odeslali. Když server odešle odpověď, IP adresa příjemce je 172.24.37.94. Funkce směrování jádra na uzlu freebsd4 vybere jako odchozí rozhraní 172.24.37.94. Protože server nepřiřadil IP adresu ke svému soketu (server ke svému soketu přiřadil generickou adresu, kterou můžeme ověřit spuštěním programu netstat na uzlu freebsd4), vybere jádro zdrojovou adresu IP datagramu. Tato adresa se stane primární IP adresou odchozího rozhraní. Pokud odešleme datagram na něco jiného, ​​než je primární IP adresa rozhraní (tedy na alternativní jméno, alias), pak náš test uvedený ve výpisu 8.5 také selže.

Jedním z řešení by bylo, kdyby klient zkontroloval název domény hostitele, který odpovídá, místo jeho IP adresy. K tomu je název serveru vyhledán v DNS (viz kapitola 11) na základě IP adresy vrácené funkcí recvfrom. Dalším řešením je nechat UDP server vytvořit jeden soket pro každou IP adresu nakonfigurovanou na hostiteli, svázat tuto IP adresu se soketem, zavolat select na každém ze všech těchto soketů (čekání na některý z nich bude připraven ke čtení) a poté odpověděl ze zásuvky připravené ke čtení. Vzhledem k tomu, že soket použitý pro odpověď je spojen s IP adresou, která byla adresou příjemce požadavku klienta (jinak by nebyl datagram do soketu doručen), můžeme si být jisti, že odesílatel odpovědi a příjemce adresa požadavku je stejná. Tyto příklady uvádíme v části 22.6.

POZNÁMKA

V systému Solaris s více síťovými rozhraními je zdrojová IP adresa odpovědi serveru adresou IP příjemce požadavku klienta. Scénář popsaný v této části platí pro implementace odvozené od Berkeley, které vybírají zdrojovou IP adresu na základě odchozího rozhraní.

8.9. Spuštění klienta bez spuštění serveru

Dalším scénářem, na který se podíváme, je spuštění klienta bez spuštění serveru. Pokud to uděláme a zadáme jeden řádek na straně klienta, nic se nestane. Klient je navždy zablokován ve svém volání funkce recvfrom a čeká na odpověď serveru, která nikdy nepřijde. Ale na tom v tomto příkladu nezáleží, protože se nyní snažíme získat hlubší porozumění protokolům a tomu, co se děje s naší síťovou aplikací.

Nejprve spustíme tcpdump na hostiteli macosx a poté spustíme klienta na stejném hostiteli a nastavíme hostitele serveru na freebsd4. Poté zadáme jeden řádek, ale tento řádek není serverem reflektován.

Macosx% udpcli01 172.24.37.94

ahoj světe vstupujeme na tento řádek,

ale nedostaneme žádnou odpověď

Výpis 8.6 ukazuje výstup tcpdump.

Výpis 8.6. výstup tcpdump, když proces serveru neběží na uzlu serveru

01 0.0 arp who-has freebsd4 tell macosx

02 0,003576 (0,0036) arp odpověď freebsd4 is-at 0:40:5:42:d6:de

03 0,003601 (0,0000) macosx.51139 > freebsd4.9877: udp 13

04 0,009781 (0,0062) freebsd4 >

První věc, které si všimneme, je, že požadavek a odpověď ARP jsou přijaty dříve, než klientský hostitel může odeslat datagram UDP hostiteli serveru. (Tuto výměnu jsme nechali ve výstupu programu, abychom znovu zdůraznili, že požadavek ARP je vždy odeslán a odpověď je přijata před odesláním IP datagramu.)

Na řádku 3 vidíme, že klientský datagram je odeslán, ale hostitel serveru odpoví na řádku 4 zprávou ICMP port nedostupný. (Délka 13 zahrnuje 12 znaků plus nový řádek.) Tato chyba ICMP se však nevrátí do klientského procesu z důvodů, které stručně uvedeme níže. Místo toho je klient trvale zablokován ve volání funkce recvfrom ve výpisu 8.4. Všimli jsme si také, že ICMPv6 má chybu „Port Unreachable“ podobnou ICMPv4 (viz tabulky A.5 a A.6), takže zde uvedené výsledky jsou podobné výsledkům pro IPv6.

Tato chyba ICMP je asynchronní chyba. Chyba byla způsobena funkcí sendto, ale funkce sendto byla dokončena normálně. Připomeňme si z oddílu 2.9, že normální návrat z výstupní operace UDP znamená pouze to, že datagram byl přidán do výstupní fronty linkové vrstvy. Chyba ICMP se nevrátí, dokud neuplyne určitý čas (4 ms pro výpis 8.6), proto se nazývá asynchronní.

Obecným pravidlem je, že asynchronní chyby se pro soket UDP nevracejí, pokud nebyl soket připojen. Ukážeme, jak volat funkci connect na soketu UDP v části 8.11. Ne každý chápe, proč bylo toto rozhodnutí učiněno při první implementaci socketů. (Aspekty implementace jsou diskutovány na stránkách 748-749.) Zvažte klienta UDP, který postupně odesílá tři datagramy na tři různé servery (tj. na tři různé adresy IP) přes jeden soket UDP. Klient vstoupí do smyčky, která volá funkci recvfrom pro čtení odpovědí. Dva datagramy jsou doručeny správně (to znamená, že server běžel na dvou ze tří uzlů), ale třetí uzel server neběžel a třetí uzel odpoví zprávou o nedostupnosti portu ICMP. Tato chybová zpráva ICMP obsahuje hlavičku IP a hlavičku UDP datagramu, který způsobil chybu. (Chybové zprávy ICMPv4 a ICMPv6 vždy obsahují hlavičku IP a celou hlavičku UDP nebo její část, aby příjemce zprávy mohl určit, který soket způsobil chybu. To je znázorněno na obrázcích 28.5 a 28.6.) Klient, který odeslal tři datagramy, by měl vědět příjemce datagramu, který chybu způsobil, přesně určil, který ze tří datagramů chybu způsobil. Ale jak může jádro sdělit tuto informaci procesu? Jediné, co může recvfrom vrátit, je hodnota proměnné errno. Funkce recvfrom však nemůže chybně vrátit IP adresu a číslo portu příjemce UDP datagramu. V důsledku toho bylo rozhodnuto, že tyto asynchronní chyby se vrátí procesu pouze v případě, že proces připojil soket UDP pouze k jednomu konkrétnímu peer.

POZNÁMKA

Linux vrací většinu chyb nedosažitelnosti portu ICMP, a to i pro nepřipojený soket, pokud není povolena volba soketu SO_DSBCOMPAT. Všechny chyby nedosažitelnosti příjemce uvedené v tabulce 1 jsou vráceny. A.5, s výjimkou chyb s kódy 0, 1, 4, 5, 11 a 12.

Vrátíme se k problému asynchronních chyb se sokety UDP v sekci 28.7 a ukážeme si jednoduchý způsob, jak tyto chyby dostat na nepřipojený soket pomocí našeho vlastního démona.

8.10. Poslední příklad UDP klient-server

Na Obr. Na obrázku 8.5 velké černé tečky znázorňují čtyři hodnoty, které je třeba nastavit nebo vybrat, když klient odesílá datagram UDP.

Rýže. 8.5. Shrnutí modelu UDP klient-server z pohledu klienta

Klient musí zadat IP adresu serveru a číslo portu, aby mohl volat funkci sendto. Klientovu IP adresu a číslo portu obvykle automaticky vybírá jádro, i když jsme si všimli, že klient může volat funkci bind. Také jsme si všimli, že pokud jsou tyto dvě hodnoty vybrány pro klienta jádrem, pak se dynamicky přiřazený port klienta vybere jednou, při prvním volání sendto a už se nikdy nezmění. Adresa IP klienta se však může změnit pro každý datagram UDP, který klient odešle, za předpokladu, že klient nesváže konkrétní adresu IP se soketem pomocí funkce vazby. Důvod je vysvětlen na Obr. 8.5: Pokud má klientský uzel více síťových rozhraní, může mezi nimi klient přepínat (na obr. 8.5 jedna adresa odkazuje na linkovou vrstvu zobrazenou vlevo, druhá na tu uvedenou vpravo). V nejhorším případě tohoto scénáře by se IP adresa klienta, zvolená jádrem na základě odchozí spojové vrstvy, změnila pro každý datagram.

Co se stane, když klient připojí IP adresu ke svému soketu, ale jádro rozhodne, že odchozí datagram by měl být odeslán z nějaké jiné linkové vrstvy? V tomto případě bude IP datagram obsahovat zdrojovou IP adresu, která se liší od IP adresy odchozí spojové vrstvy (viz Cvičení 8.6).

Na Obr. Obrázek 8.6 ukazuje stejné čtyři hodnoty, ale z pohledu serveru.

Rýže. 8.6. Shrnutí modelu UDP klient-server z pohledu serveru

Server se může naučit alespoň čtyři parametry pro každý datagram, který obdrží: zdrojovou IP adresu, cílovou IP adresu, číslo zdrojového portu a číslo cílového portu. Volání, která vracejí tyto informace na servery TCP a UDP, jsou uvedena v tabulce. 8.1.

Tabulka 8.1. Informace dostupné serveru z příchozího IP datagramu

TCP server má vždy snadný přístup ke všem čtyřem informacím pro připojený soket a tyto čtyři hodnoty zůstávají konstantní po celou dobu životnosti připojení. V případě UDP připojení však lze cílovou IP adresu získat pouze nastavením volby soketu IP_RECVDSTADDR pro IPv4 nebo IPV6_PKTINFO pro IPv6 a následným voláním funkce recvmsg namísto funkce recvfrom. Protože UDP je bez připojení, může se cílová IP adresa změnit pro každý datagram odeslaný na server. Server UDP může také přijímat datagramy určené pro jednu z adres vysílání hostitele nebo adresu vícesměrového vysílání, o kterých pojednáváme v kapitolách 20 a 21. Ukážeme si, jak určit cílovou adresu datagramu UDP v části 20.2 poté, co popíšeme funkce recvmsg.

8.11. funkce připojení pro UDP
POZNÁMKA
POZNÁMKA
POZNÁMKA

Tabulka 8.2

POZNÁMKA

Rýže. 8.7. Soket připojený UDP

Rýže. 8.8

Volání se připojuje vícekrát na soket UDP

Proces s připojeným UDP soketem může znovu volat funkci connect na tomto soketu, aby:

c – nastavit novou IP adresu a port;

c – odpojte zásuvku.

První případ, určující nový peer pro připojený soket UDP, se liší od použití funkce connect se soketem TCP: pro soket TCP lze funkci connect zavolat pouze jednou.

Pro odpojení UDP soketu zavoláme funkci connect, ale nastavíme člena rodiny struktury adres soketu (sin_family pro IPv4 nebo sin6_family pro IPv6) na AF_UNSPEC . To může mít za následek chybu EAFNOSUPPORT, ale to je normální. Je to proces volání funkce connect na již připojeném soketu UDP, který umožňuje soket odpojit.

POZNÁMKA

Manuál BSD pro funkci connect tradičně uváděl: "Datagramové sokety mohou přerušit spojení připojením k neplatným adresám, jako jsou prázdné adresy." Bohužel žádná příručka neříká, co představuje "prázdnou adresu", ani se nezmiňuje o tom, že se jako výsledek vrátí chyba (což je normální). Standard POSIX výslovně uvádí, že rodina adres musí být nastavena na AF_UNSPEC, ale pak uvádí, že toto volání pro připojení může nebo nemusí vrátit chybu EAFNOSUPPORT.

Výkon

Když aplikace zavolá funkci sendto na nepřipojeném soketu UDP, implementace jádra odvozené od Berkeley se dočasně připojí k soketu, odešlou datagram a poté se od soketu odpojí. Volání funkce sendto k odeslání dvou datagramů sekvenčně na nepřipojený soket tedy zahrnuje následujících šest kroků, které provádí jádro:

c – připojení zásuvky;

c – výstup prvního datagramu;

c – odpojení zásuvky;

c – připojení zásuvky;

c – výstup druhého datagramu;

c – odpojení zásuvky.

POZNÁMKA

Dalším bodem, který je třeba zvážit, je počet vyhledávání ve směrovací tabulce. První dočasné připojení vyhledá ve směrovací tabulce cílovou IP adresu a uloží (cache) tyto informace. Druhé dočasné připojení poznamenává, že adresa příjemce se shoduje s adresou uloženou v mezipaměti ze směrovací tabulky (předpokládáme, že oběma funkcím sendto je přidělen stejný příjemce) a nemusí znovu hledat směrovací tabulku.

Když aplikace ví, že pošle více datagramů stejnému peeru, je efektivnější explicitně připojit soket. Volání pro připojení následované dvěma voláními pro zápis bude nyní zahrnovat následující kroky provedené jádrem:

c – připojení zásuvky;

c – výstup prvního datagramu;

c – výstup druhého datagramu.

V tomto případě jádro zkopíruje strukturu adresy soketu obsahující cílovou IP adresu a port pouze jednou, a pokud je sendto voláno dvakrát, kopírování se provede dvakrát. B poznamenává, že dočasné opětovné připojení odpojeného soketu UDP představuje přibližně jednu třetinu nákladů na každý přenos UDP.

8.12. funkce dg_cli (pokračování)

Vraťme se k funkci dg_cli zobrazené ve výpisu 8.4 a přepišme ji, aby volala funkci connect. Výpis 8.7 ukazuje novou funkci.

Výpis 8.7. Funkce dg_cli volající funkci connect

//udpcliserv/dgcliconnect.c

1 #include "unp.h"

3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

6 znaků sendline, recvline;

7 Connect(sockfd, (SA*)pservaddr, servlen);

8 while (Fgets(sendline, MAXLINE, fp) != NULL) (

9 Write(sockfd, sendline, strlen(sendline));

10 n = Read(sockfd, recvline, MAXLINE);

11 recvline[n] = 0; /* null končí */

12 Fputs(recvline, stdout);

Změny oproti předchozí verzi jsou přidání volání funkce connect a nahrazení volání funkcí sendto a recvfrom voláním funkcí zápisu a čtení. Funkce dg_cli zůstává nezávislá na protokolu, protože se neponoří do struktury adres soketu předávané funkci connect. Hlavní funkce našeho klienta, znázorněná ve výpisu 8.3, zůstává stejná.

Pokud spustíme program na hostiteli macosx a zadáme IP adresu hostitele freebsd4 (na kterém není spuštěn náš server na portu 9877), dostaneme následující výstup:

Macosx% udpcli04 172.24.37.94

ahoj světe

chyba čtení: Připojení odmítnuto

První věc, které si všimneme, je, že se při spuštění klientského procesu neobjeví chyba. K chybě dojde až po odeslání prvního datagramu na server. Odeslání tohoto datagramu způsobuje chybu ICMP z hostitelského serveru. Když však klient TCP zavolá connect , uvádějící uzel serveru, na kterém není spuštěn proces serveru, připojení vrátí chybu, protože volání pro připojení způsobí odeslání prvního paketu třícestného handshake TCP a je to tento paket, který způsobí příjem segmentu RST od partnera (viz část 4.3).

Výpis 8.8 ukazuje výstup tcpdump.

Výpis 8.8. Výstup z tcpdump při spuštění funkce dg_cli

Macosx% tcpdump

01 0.0 macosx.51139 > freebsd4 9877:udp 13

02 0,006180 (0,0062) freebsd4 > macosx: icmp: freebsd4 udp port 9877 nedostupný

V tabulce A.5 také vidíme, že jádro mapuje chybu ICMP na chybu ECONNREFUSED, která odpovídá výstupu řetězce zprávy Connection odmítnuto funkcí err_sys.

POZNÁMKA

Bohužel ne všechna jádra vracejí zprávy ICMP do připojeného soketu UDP, jak jsme si ukázali v této části. Jádra odvozená z Berkeley tuto chybu obvykle vracejí, ale jádra System V nikoli. Pokud například spustíme stejného klienta na hostiteli Solaris 2.4 a použijeme funkci connect pro připojení k hostiteli, na kterém není spuštěn náš server, pak pomocí tcpdump můžeme ověřit, že hostitel serveru vrací chybu ICMP port nedostupný, ale způsobeno tím, že se funkce čtení klienta nikdy nedokončí. Tato situace byla opravena v Solaris 2.5. UnixWare nevrací chybu, zatímco AIX, Digital Unix, HP-UX a Linux ano.

8.13. Nedostatek řízení toku v UDP

Výpis 8.9

//udpcliserv/dgcliloop1.c

1 #include "unp.h"

8znakový sendline;

Výpis 8.10

//udpcliserv/dgecholoop1.c

1 #include "unp.h"

3 statický počet int;

7 socklen_t len;

8 znaků;

11 len = clilen;

17 recvfrom_int(int signo)

Výpis 8.11. Výstup na uzlu serveru

freebsd % netstat -s -p udp

Bylo přijato 71208 datagramů

0 s neúplným záhlavím

0 se špatným polem délky dat

0 se špatným kontrolním součtem

0 bez kontrolního součtu

832 spadl kvůli absenci zásuvky

0 ne pro hashované PCB

Výstup 137685 datagramů

freebsd % udpserv06 spustit náš server

klient odesílá datagramy

^C

freebsd % netstat -s -p udp

Bylo přijato 73208 datagramů

0 s neúplným záhlavím

0 se špatným polem délky dat

0 se špatným kontrolním součtem

0 bez kontrolního součtu

832 spadl kvůli absenci zásuvky

16 všesměrových/multicastových datagramů zahozeno kvůli absenci soketu

0 ne pro hashované PCB

Výstup 137685 datagramů

aix % udpserv06

^?

obdržel 2000 datagramů

UDP soket přijímá vyrovnávací paměť

Počet UDP datagramů ve frontě pro daný soket je omezen velikostí jeho přijímací vyrovnávací paměti. Můžeme to změnit pomocí volby SO_RCVBUF socket, jak jsme si ukázali v sekci 7.5. Na FreeBSD je výchozí velikost vyrovnávací paměti pro příjem UDP soketu 42 080 bajtů, což umožňuje uložit pouze 30 z našich 1400bajtových datagramů. Pokud zvětšíme velikost přijímací vyrovnávací paměti soketu, můžeme očekávat, že server přijme další datagramy. Výpis 8.12 je upravená funkce dg_echo z Výpisu 8.10, která zvyšuje velikost vyrovnávací paměti pro příjem soketu na 240 KB. Pokud tento server provozujeme na systému Sun a klienta na systému RS/6000, počet přijatých datagramů bude 103. Protože je to jen o málo lepší než předchozí příklad s výchozí velikostí vyrovnávací paměti, je jasné, že jsme nenašel řešení problému.

Výpis 8.12. funkce dg_echo, která zvětší velikost vyrovnávací paměti pro příjem soketu

//udpcliserv/dgecholooor2.c

1 #include "unp.h"

2 static void recvfrom_int(int);

3 statický počet int;

5 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)

8 socklen_t len;

9 znaků;

10 Signal(SIGINT, recvfrom_int);

11 n = 240 x 1024;

12 Setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

14 len = clilen;

15 Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

20 recvfrom_int(int signo)

22 printf("\npřijatých %d datagramů\n", počet);

POZNÁMKA

Proč jsme ve výpisu 8.12 nastavili velikost vyrovnávací paměti pro příjem soketu na 240G-1024 bajtů? Výchozí maximální velikost vyrovnávací paměti pro příjem soketu v BSD/OS 2.1 je 262 144 bajtů (256G-1024), ale kvůli způsobu alokace vyrovnávací paměti v paměti (popsaného v kapitole 2) je ve skutečnosti omezena na 246 723 bajtů. Mnoho dřívějších systémů založených na 4.3BSD omezovalo velikost vyrovnávací paměti pro příjem soketu na přibližně 52 000 bajtů.

8.14. Definování odchozího rozhraní pro UDP

Můžete také použít připojený soket UDP k určení odchozího rozhraní, které bude použito k odeslání datagramů konkrétnímu příjemci. To je způsobeno vedlejším efektem funkce connect aplikované na soket UDP: jádro vybere místní IP adresu (za předpokladu, že proces ještě nezavolal bind, aby ji explicitně nastavil). Lokální adresa se vybírá vyhledáním cílové adresy ve směrovací tabulce, přičemž se vezme primární IP adresa rozhraní, ze kterého budou podle tabulky odesílány datagramy.

Výpis 8.13 ukazuje jednoduchý UDP program, který se připojí k dané IP adrese pomocí funkce connect a poté zavolá funkci getockname, přičemž vypíše místní IP adresu a port.

Výpis 8.13. UDP program využívající funkci connect k určení odchozího rozhraní

//udpcliserv/udpcli09.c

1 #include "unp.h"

3 hlavní (int argc, char **argv)

6 socklen_t len;

7 struct sockaddr_in cliaddr, servaddr;

8 if (argc != 2)

9 err_quit("použití: udpcli ");

10 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

11 bzero(&servaddr, sizeof(servaddr));

12 servaddr.sin_family = AF_INET;

13 servaddr.sin_port = htons(SERV_PORT);

14 Inet_pton(AF_INET, argv, &servaddr.sin_addr);

15 Connect(sockfd, (SA*)&servaddr, sizeof(servaddr));

16 len = sizeof(cliaddr);

17 Getsockname(sockfd, (SA*)&cliaddr, &len);

18 printf("místní adresa %s\n", Sock_ntop((SA*)&cliaddr, len));

Pokud spustíme program na hostiteli freebsd s více síťovými rozhraními, dostaneme následující výstup:

freebsd % udpcli09 206.168.112.96

místní adresa 12.106.32.254:52329

freebsd % udpcli09 192.168.42.2

místní adresa 192.168.42.1:52330

freebsd % udpcli09 127.0.0.1

místní adresa 127.0.0.1:52331

Podle Obr. 1.7 ukazuje, že když spustíme program poprvé dvakrát, argument příkazového řádku je IP adresa v různých ethernetových sítích. Jádro přiřadí lokální IP adresu primární adrese rozhraní v odpovídající ethernetové síti. Při volání connect na UDP soketu se tomuto hostiteli nic neposílá – jde o zcela lokální operaci, která zachovává IP adresu a port peeru. Také vidíme, že volání connect na nepřipojeném soketu UDP také přiřadí dynamicky přiřazený port k soketu.

POZNÁMKA

Bohužel tato technologie nefunguje ve všech implementacích, což platí zejména pro jádra odvozená od SVR4. Například toto nefunguje na Solaris 2.5, ale funguje na AIX, Digital Unix, Linux, MacOS X a Solaris 2.6.

8.15. TCP a UDP echo server pomocí funkce výběru

Nyní zkombinujeme náš paralelní TCP echo server z kapitoly 5 a náš sériový UDP echo server z této kapitoly do jednoho serveru, který používá funkci select k multiplexování soketů TCP a UDP. Výpis 8.14 ukazuje první část tohoto serveru.

Výpis 8.14. První část echo serveru zpracovává TCP a UDP sockety pomocí funkce select

//udpcliserv/udpservselect01.c

1 #include "unp.h"

3 hlavní (int argc, char **argv)

5 int listenfd, connfd, udpfd, nready, maxfdp1;

6 znaků;

7 pid_t childpid;

10 socklen_t len;

11 const int on = 1;

12 struct sockaddr_in cliaddr, servaddr;

13 void sig_chld(int);

14 /* vytvořit TCP naslouchací soket */

15 listenfd = Socket(AF_INET, SOCK_STREAM, 0);

16 bzero(&servaddr, sizeof(servaddr));

17 servaddr.sin_family = AF_INET;

18 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

19 servaddr.sin_port = htons(SERV_PORT);

20 Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

21 Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));

22 Listen(listenfd, LISTENQ);

23 /* vytvořit soket UDP */

24 udpfd = Socket(AF_INET, SOCK_DGRAM, 0);

25 bzero(&servaddr, sizeof(servaddr));

26 servaddr.sin_family = AF_INET;

27 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

28 servaddr.sin_port = htons(SERV_PORT);

29 Bind(udpfd, (SA*)&servaddr, sizeof(servaddr));

Vytvoření TCP Listening Socket

14-22 Naslouchací soket TCP je vytvořen a přidružen k dříve známému portu na serveru. Nastavíme volbu soketu SO_REUSEADDR v případě, že na tomto portu existují spojení.

Vytvoření soketu UDP

23-29 Je také vytvořen soket UDP a přiřazen ke stejnému portu. I když je pro sokety TCP a UDP použit stejný port, není nutné před tímto voláním nastavit volbu soketu SO_REUSEADDR na vazbu, protože porty TCP jsou nezávislé na portech UDP.

Výpis 8.15 ukazuje druhou část našeho serveru.

Výpis 8.15. Druhá polovina serveru echo zpracovává TCP a UDP pomocí funkce select

udpcliserv/udpservselect01.c

30 Signál(SIGCHLD, sig_chld); /* je potřeba zavolat waitpid() */

31 FD_ZERO(&rset);

32 maxfdp1 = max(listenfd, udpfd) + 1;

34 FD_SET(listenfd, &rset);

35 FD_SET(udpfd, &rset);

36 if ((nready = select(maxfdp1, &rset, NULL, NULL, NULL))

37 if (errno == EINTR)

38 pokračovat; /* zpět na for() */

40 err_sys("vybrat chybu");

42 if (FD_ISSET(listenfd, &rset)) (

43 len = sizeof(cliaddr);

44 connfd = Accept(listenfd, (SA*)&cliaddr, &len);

45 if ((childpid = Fork()) == 0) ( /* podřízený proces */

46 Close(listenfd); /* zavře zásuvku pro poslech */

47 str_echo(connfd); /* zpracovat požadavek */

50 Close(connfd); /* rodič zavře připojený soket */

52 if (FD_ISSET(udpfd, &rset)) (

53 len = sizeof(cliaddr);

54 n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA*)&cliaddr, &len);

55 Sendto(udpfd, mesg, n, 0, (SA*)&cliaddr, len);

Instalace obsluhy signálu SIGCHLD

30 Pro signál SIGCHLD je nastaven handler, protože TCP spojení bude obsluhovat podřízený proces. Tento signálový handler jsme si ukázali ve výpisu 5.8.

Příprava na vyvolání funkce Select

31-32 Inicializujeme sadu deskriptorů pro funkci select a vypočítáme maximum ze dvou deskriptorů, na které počkáme, až budeme připraveni.

Volání funkce select

34-41 Voláme funkci select a čekáme pouze na to, až bude naslouchající soket TCP nebo soket UDP připraven ke čtení. Protože naše obsluha signálu sig_chld může přerušit volání funkce select, řešíme chybu EINTR.

Zpracování nového připojení klienta

42-51 K přijetí nového klientského připojení používáme funkci accept, a když je naslouchající TCP soket připraven ke čtení, použijeme funkci fork k vytvoření podřízeného procesu a zavoláme naši funkci str_echo na podřízeném procesu. Toto je stejná posloupnost kroků, kterou jsme provedli v kapitole 5.

Zpracování příchozího datagramu

52-57 Pokud je soket UDP připraven ke čtení, datagram dorazil. Přečteme jej pomocí funkce recvfrom a pomocí funkce sendto odešleme zpět klientovi.

8.16. Resumé

Převod našeho echo klienta a echo serveru na použití UDP místo TCP byl snadný. Zároveň jsme však ztratili mnoho funkcí, které poskytuje protokol TCP: identifikace ztracených paketů a jejich opětovné vysílání, kontrola, zda pakety přicházejí od správného partnera atd. K tomuto tématu se vrátíme v sekci 22.5 a uvidíme, jak můžeme zlepšit spolehlivost UDP aplikace.

Sokety UDP mohou generovat asynchronní chyby, což jsou chyby, které jsou hlášeny nějakou dobu po odeslání paketu. TCP sokety je vždy hlásí aplikaci, ale u UDP musí být soket připojen, aby tyto chyby přijal.

UDP postrádá řízení toku, což lze velmi snadno předvést. To obvykle není problém, protože mnoho aplikací UDP je vytvořeno pomocí modelu požadavek-odpověď a nejsou navrženy tak, aby přenášely velké množství dat.

Při psaní aplikací UDP je třeba vzít v úvahu řadu dalších věcí, ale těm se budeme věnovat v kapitole 22 poté, co pokryjeme funkce rozhraní, vysílání a vícesměrového vysílání.

Cvičení

1. Řekněme, že máme dvě aplikace, jedna používá TCP a druhá UDP. Přijímací vyrovnávací paměť pro soket TCP obsahuje 4096 bajtů dat a přijímací vyrovnávací paměť pro soket UDP obsahuje dva datagramy o velikosti 2048 bajtů. Aplikace TCP volá funkci čtení s třetím argumentem 4096 a aplikace UDP volá funkci recvfrom s třetím argumentem 4096. Je mezi těmito voláními nějaký rozdíl?

2. Co se ve výpisu 8.2 stane, když nahradíme poslední argument funkce sendto (který jsme označili len) argumentem clilen?

3. Zkompilujte a spusťte UDP server z výpisů 8.1 a 8.4 a poté klienta z výpisů 8.3 a 8.4. Ujistěte se, že klient a server spolupracují.

4. Spusťte program ping v jednom okně a zadejte volbu -i 60 (odeslat jeden paket každých 60 sekund; některé systémy používají přepínač I místo i), volbu -v (tisk všech přijatých chybových zpráv ICMP) a nastavte adresu zpětné smyčky pro sebe (obvykle 127.0.0.1). Tento program použijeme k zobrazení chyby nedostupnosti portu ICMP vrácené hostitelem serveru. Poté spusťte našeho klienta z předchozího cvičení v jiném okně a zadejte IP adresu některého uzlu, na kterém není spuštěn server. co se děje?

5. Při pohledu na Obr. 8.3 jsme řekli, že každý připojený TCP soket má svůj vlastní přijímací buffer. Myslíte, že má naslouchací zásuvka vlastní přijímací vyrovnávací paměť?

6. Použijte program sock (viz část B.3) a nástroj, jako je tcpdump (viz část B.5), k ověření prohlášení v části 8.10: pokud klient používá funkci bind k navázání IP adresy na svůj soket, ale odešle datagram pocházející z jiného rozhraní, pak výsledný datagram obsahuje IP adresu, která byla přidružena k soketu, i když se neshoduje s původním rozhraním.

7. Zkompilujte programy z oddílu 8.13 a spusťte klienta a server na různých uzlech. Umístěte printf na klienta pokaždé, když je datagram zapsán do soketu. Změní to procento přijatých paketů? Proč? Zavolejte printf ze serveru pokaždé, když je datagram načten ze soketu. Změní to procento přijatých paketů? Proč?

8. Jakou největší délku můžeme předat funkci sendto pro UDP/IPv4 socket, tedy jaké největší množství dat se vejde do UDP/IPv4 datagramu? Co se mění v případě UDP/IPv6?

Upravte výpis 8.4 tak, aby odeslal jeden datagram UDP maximální velikosti, přečetl jej zpět a vytiskl počet bajtů vrácených příkazem recvfrom .

9. Upravte výpis 8.15 tak, aby odpovídal RFC 1122: IP_RECVDSTADDR by měl být použit pro soket UDP.

Na konci oddílu 8.9 jsme zmínili, že asynchronní chyby nejsou vráceny na soket UDP, pokud soket nebyl připojen. Ve skutečnosti můžeme volat funkci connect na soketu UDP (viz část 4.3). To však nepovede k ničemu podobnému jako TCP spojení: neexistuje žádné třícestné handshake. Jádro jednoduše zkontroluje, zda je známo, že cíl je nedosažitelný, pak zaznamená IP adresu a číslo portu peeru, které jsou obsaženy ve struktuře adresy soketu předané funkci connect, a okamžitě vrátí řízení volajícímu procesu.

POZNÁMKA

Přetížení funkce připojení touto novou funkcí pro zásuvky UDP může být matoucí. Pokud je konvence, že sockname je adresa místního protokolu a peername je adresa vzdáleného protokolu, pak by se tato funkce měla nazývat lépe setpeername. Podobně by se funkce bind lépe nazývala setsockname.

S ohledem na to je nutné pochopit rozdíl mezi dvěma typy UDP socketů.

c– Nepřipojený soket UDP je vytvořený výchozí soket UDP.

c– Připojený soket UDP je výsledkem volání funkce připojení na soketu UDP.

Připojený soket UDP má tři rozdíly od nepřipojeného soketu, který je vytvořen ve výchozím nastavení.

1. Nemůžeme již nastavit cílovou IP adresu a port pro výstupní operaci. To znamená, že místo funkce sendto používáme funkci write nebo send. Vše, co je zapsáno do připojeného soketu UDP, je automaticky odesláno na adresu (například adresu IP a port) určenou funkcí připojení.

POZNÁMKA

Podobně jako u TCP můžeme volat funkci sendto na připojeném soketu UDP, ale nemůžeme určit cílovou adresu. Pátý argument funkce sendto (ukazatel na strukturu adresy soketu) musí mít hodnotu null a šestý argument (velikost struktury adresy soketu) musí mít hodnotu null. Standard POSIX určuje, že když je pátý argument nulový ukazatel, šestý argument je ignorován.

2. Místo funkce recvfrom použijeme funkci read nebo recv. Jediné datagramy vrácené jádrem pro vstupní operaci na připojeném soketu UDP jsou datagramy pocházející z adresy zadané ve funkci connect. Datagramy určené pro adresu lokálního protokolu připojeného soketu UDP (jako je IP adresa a port), ale pocházející z jiné adresy protokolu, než ke které byl soket připojen pomocí funkce connect, nejsou odesílány do připojeného soketu. To omezuje připojený soket UDP a umožňuje mu vyměňovat datagramy s jedním a pouze jedním peerem.

POZNÁMKA

Přesněji řečeno, datagramy jsou vyměňovány pouze s jednou IP adresou a ne s jedním účastníkem, protože to může být multicastová IP adresa, což představuje skupinu účastníků.

3. Asynchronní chyby jsou vráceny procesu pouze pro operace na připojeném soketu UDP. V důsledku toho, jak jsme již řekli, nepřipojený soket UDP neobdrží žádné asynchronní chyby.

V tabulce 8.2 shromažďuje vlastnosti uvedené v prvním odstavci, jak jsou aplikovány na 4.4BSD.

Tabulka 8.2. TCP a UDP sokety: lze zadat adresu cílového protokolu

POZNÁMKA

POSIX určuje, že operace s pinem, která neurčuje cílovou adresu na nepřipojeném soketu UDP, musí vrátit chybu ENOTCONN spíše než chybu EDESTADDRREQ.

Solaris 2.5 umožňuje funkci sendto, která specifikuje cílovou adresu pro připojený soket UDP. POSIX určuje, že v této situaci by měla být vrácena chyba EISCONN.

Na Obr. Část 8.7 shrnuje informace o připojeném soketu UDP.

Rýže. 8.7. Soket připojený UDP

Aplikace zavolá funkci připojení a uvede IP adresu a číslo portu účastníka. Poté používá funkce čtení a zápisu k výměně dat s druhou stranou.

Datagramy přicházející z jakékoli jiné IP adresy nebo portu (který na obrázku 8.7 označíme jako "???") se neodesílají do připojeného soketu, protože zdrojová IP adresa nebo UDP port neodpovídají adrese protokolu, na kterou je soket je připojen pomocí funkce připojení. Tyto datagramy mohou být doručeny do některého jiného soketu UDP na hostiteli. Pokud pro příchozí datagram neexistuje žádný jiný odpovídající soket, UDP jej bude ignorovat a vygeneruje zprávu ICMP port nedostupný.

Shrneme-li výše uvedené, můžeme konstatovat, že UDP klient nebo server může volat funkci připojení pouze v případě, že tento proces používá UDP soket ke komunikaci pouze s jedním účastníkem. Obvykle je to UDP klient, který volá funkci připojení, ale existují aplikace, ve kterých UDP server komunikuje s jedním klientem po delší dobu (například TFTP), v takovém případě klient i server volají připojení. funkce.

Dalším příkladem dlouhodobé interakce je DNS (obrázek 8.8).

Rýže. 8.8. Příklad DNS klientů a serverů a funkce připojení

Klienta DNS lze nakonfigurovat tak, aby používal jeden nebo více serverů, obvykle uvedením IP adres serverů v souboru /etc/resolv.conf. Pokud je v tomto souboru uveden pouze jeden server (tento klient je na obrázku v levém obdélníku), klient může volat funkci připojení, ale pokud je v seznamu mnoho serverů (druhý pravý obdélník na obrázku), klient nemůže zavolejte funkci connect. Server DNS také obvykle zpracovává všechny požadavky klientů, takže servery nemohou volat funkci připojení.

Nyní si ověříme, jak se na aplikaci projeví absence jakéhokoli řízení toku v UDP. Nejprve změníme naši funkci dg_cli tak, aby odesílala pevný počet datagramů. Již nebude číst ze standardního vstupu. Výpis 8.9 ukazuje novou verzi funkce. Tato funkce odešle na server 2000 UDP datagramů o velikosti 1400 bajtů.

Výpis 8.9. Funkce dg_cli odesílá na server pevný počet datagramů

//udpcliserv/dgcliloop1.c

1 #include "unp.h"

2 #define NDG 2000 /* počet datagramů k odeslání */

3 #define DGLEN 1400 /* délka každého datagramu */

5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

8znakový sendline;

10 Sendto(sockfd, sendline, DGLEN, 0, pservaddr, servlen);

Poté upravíme server tak, aby přijímal datagramy, a počítáme počet přijatých datagramů. Server již neodráží datagramy zpět klientovi. Výpis 8.10 ukazuje novou funkci dg_echo. Když ukončíme proces serveru stisknutím klávesy přerušení na terminálu (což způsobí odeslání signálu SIGINT procesu), server vytiskne počet přijatých datagramů a ukončí se.

Výpis 8.10. Funkce dg_echo, která počítá přijaté datagramy

//udpcliserv/dgecholoop1.c

1 #include "unp.h"

2 static void recvfrom_int(int);

3 statický počet int;

5 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)

7 socklen_t len;

8 znaků;

9 Signal(SIGINT, recvfrom_int);

11 len = clilen;

12 Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

17 recvfrom_int(int signo)

19 printf("\npřijato %d datagramů\n", počet);

Nyní server provozujeme na uzlu freebsd, což je pomalý počítač SPARCStation. Klienta provozujeme na mnohem rychlejším systému RS/6000 s operačním systémem aix. Jsou propojeny přímo mezi sebou pomocí 100 Mbit/s Ethernet linky. Kromě toho spouštíme netstat -s na uzlu serveru před i po spuštění klienta a serveru, protože výstup statistiky ukáže, kolik datagramů jsme ztratili. Výpis 8.11 ukazuje výstup serveru.

Výpis 8.11. Výstup na uzlu serveru

freebsd % netstat -s -p udp

Bylo přijato 71208 datagramů

0 s neúplným záhlavím

0 se špatným polem délky dat

0 se špatným kontrolním součtem

0 bez kontrolního součtu

832 spadl kvůli absenci zásuvky

16 všesměrových/multicastových datagramů zahozeno kvůli absenci soketu

1971 zanikla kvůli plné vyrovnávací paměti soketu

0 ne pro hashované PCB

Výstup 137685 datagramů

freebsd % udpserv06 spustit náš server

klient odesílá datagramy

^C Chcete-li ukončit práci klienta, zadejte náš symbol přerušení

freebsd % netstat -s -p udp

Bylo přijato 73208 datagramů

0 s neúplným záhlavím

0 se špatným polem délky dat

0 se špatným kontrolním součtem

0 bez kontrolního součtu

832 spadl kvůli absenci zásuvky

16 všesměrových/multicastových datagramů zahozeno kvůli absenci soketu

3941 zahozeno kvůli plné vyrovnávací paměti soketu

0 ne pro hashované PCB

Výstup 137685 datagramů

Klient odeslal 2000 datagramů, ale serverová aplikace jich přijala pouze 30, což znamená ztrátovost 98 %. Server ani klient neobdrží zprávu, že tyto datagramy jsou ztraceny. Jak jsme řekli, UDP nemá schopnosti řízení toku – je nespolehlivé. Jak jsme si ukázali, pro UDP odesílatele je snadné přeplnit vyrovnávací paměť přijímače.

Pokud se podíváme na výstup netstat, vidíme, že celkový počet datagramů přijatých serverovým uzlem (nikoli serverovou aplikací) je 2000 (73 208 - 71 208). Čítač vyřazených z důvodu plné vyrovnávací paměti soketu ukazuje, kolik datagramů bylo přijato UDP a ignorováno, protože přijímací vyrovnávací paměť pro příjem soketu byla plná. Tato hodnota je 1970 (3941 - 1971), která po přičtení k výstupu počtu přijatých datagramů aplikace (30) dává celkem 2000 datagramů přijatých uzlem. Bohužel, počet datagramů zahozených z důvodu plné vyrovnávací paměti je v systému netstat. Neexistuje způsob, jak určit, které aplikace (např. které porty UDP) jsou ovlivněny.

Počet datagramů přijatých serverem v tomto příkladu není deterministický. Závisí na mnoha faktorech, jako je zatížení sítě, klientský uzel a serverový uzel.

Pokud provozujeme stejného klienta a stejný server, ale tentokrát je klient na pomalém systému Sun a server na rychlém systému RS/6000, žádné datagramy se neztratí.

aix % udpserv06

^? poté, co klient dokončí práci, zadejte náš symbol přerušení

obdržel 2000 datagramů

Na konci oddílu 8.9 jsme zmínili, že asynchronní chyby nejsou vráceny na soket UDP, pokud soket nebyl připojen. Ve skutečnosti můžeme volat funkci connect na soketu UDP (viz část 4.3). To však nepovede k ničemu podobnému jako TCP spojení: neexistuje žádné třícestné handshake. Jádro jednoduše zkontroluje, zda je známo, že cíl je nedosažitelný, pak zaznamená IP adresu a číslo portu peeru, které jsou obsaženy ve struktuře adresy soketu předané funkci connect, a okamžitě vrátí řízení volajícímu procesu.

POZNÁMKA

Přetížení funkce připojení touto novou funkcí pro zásuvky UDP může být matoucí. Pokud je konvence, že sockname je adresa místního protokolu a peername je adresa vzdáleného protokolu, pak by se tato funkce měla nazývat lépe setpeername. Podobně by se funkce bind lépe nazývala setsockname.

S ohledem na to je nutné pochopit rozdíl mezi dvěma typy UDP socketů.

c– Nepřipojený soket UDP je vytvořený výchozí soket UDP.

c– Připojený soket UDP je výsledkem volání funkce připojení na soketu UDP.

Připojený soket UDP má tři rozdíly od nepřipojeného soketu, který je vytvořen ve výchozím nastavení.

1. Nemůžeme již nastavit cílovou IP adresu a port pro výstupní operaci. To znamená, že místo funkce sendto používáme funkci write nebo send. Vše, co je zapsáno do připojeného soketu UDP, je automaticky odesláno na adresu (například adresu IP a port) určenou funkcí připojení.

POZNÁMKA

Podobně jako u TCP můžeme volat funkci sendto na připojeném soketu UDP, ale nemůžeme určit cílovou adresu. Pátý argument funkce sendto (ukazatel na strukturu adresy soketu) musí mít hodnotu null a šestý argument (velikost struktury adresy soketu) musí mít hodnotu null. Standard POSIX určuje, že když je pátý argument nulový ukazatel, šestý argument je ignorován.

2. Místo funkce recvfrom použijeme funkci read nebo recv. Jediné datagramy vrácené jádrem pro vstupní operaci na připojeném soketu UDP jsou datagramy pocházející z adresy zadané ve funkci connect. Datagramy určené pro adresu lokálního protokolu připojeného soketu UDP (jako je IP adresa a port), ale pocházející z jiné adresy protokolu, než ke které byl soket připojen pomocí funkce connect, nejsou odesílány do připojeného soketu. To omezuje připojený soket UDP a umožňuje mu vyměňovat datagramy s jedním a pouze jedním peerem.

POZNÁMKA

Přesněji řečeno, datagramy jsou vyměňovány pouze s jednou IP adresou a ne s jedním účastníkem, protože to může být multicastová IP adresa, což představuje skupinu účastníků.

3. Asynchronní chyby jsou vráceny procesu pouze pro operace na připojeném soketu UDP. V důsledku toho, jak jsme již řekli, nepřipojený soket UDP neobdrží žádné asynchronní chyby.

V tabulce 8.2 shromažďuje vlastnosti uvedené v prvním odstavci, jak jsou aplikovány na 4.4BSD.

Tabulka 8.2. TCP a UDP sokety: lze zadat adresu cílového protokolu

POZNÁMKA

POSIX určuje, že operace s pinem, která neurčuje cílovou adresu na nepřipojeném soketu UDP, musí vrátit chybu ENOTCONN spíše než chybu EDESTADDRREQ.

Solaris 2.5 umožňuje funkci sendto, která specifikuje cílovou adresu pro připojený soket UDP. POSIX určuje, že v této situaci by měla být vrácena chyba EISCONN.

Na Obr. Část 8.7 shrnuje informace o připojeném soketu UDP.

Rýže. 8.7. Soket připojený UDP

Aplikace zavolá funkci připojení a uvede IP adresu a číslo portu účastníka. Poté používá funkce čtení a zápisu k výměně dat s druhou stranou.

Datagramy přicházející z jakékoli jiné IP adresy nebo portu (který na obrázku 8.7 označíme jako "???") se neodesílají do připojeného soketu, protože zdrojová IP adresa nebo UDP port neodpovídají adrese protokolu, na kterou je soket je připojen pomocí funkce připojení. Tyto datagramy mohou být doručeny do některého jiného soketu UDP na hostiteli. Pokud pro příchozí datagram neexistuje žádný jiný odpovídající soket, UDP jej bude ignorovat a vygeneruje zprávu ICMP port nedostupný.

Shrneme-li výše uvedené, můžeme konstatovat, že UDP klient nebo server může volat funkci připojení pouze v případě, že tento proces používá UDP soket ke komunikaci pouze s jedním účastníkem. Obvykle je to UDP klient, který volá funkci připojení, ale existují aplikace, ve kterých UDP server komunikuje s jedním klientem po delší dobu (například TFTP), v takovém případě klient i server volají připojení. funkce.

Dalším příkladem dlouhodobé interakce je DNS (obrázek 8.8).

Rýže. 8.8. Příklad DNS klientů a serverů a funkce připojení

Klienta DNS lze nakonfigurovat tak, aby používal jeden nebo více serverů, obvykle uvedením IP adres serverů v souboru /etc/resolv.conf. Pokud je v tomto souboru uveden pouze jeden server (tento klient je na obrázku v levém obdélníku), klient může volat funkci připojení, ale pokud je v seznamu mnoho serverů (druhý pravý obdélník na obrázku), klient nemůže zavolejte funkci connect. Server DNS také obvykle zpracovává všechny požadavky klientů, takže servery nemohou volat funkci připojení.

Nyní si ověříme, jak se na aplikaci projeví absence jakéhokoli řízení toku v UDP. Nejprve změníme naši funkci dg_cli tak, aby odesílala pevný počet datagramů. Již nebude číst ze standardního vstupu. Výpis 8.9 ukazuje novou verzi funkce. Tato funkce odešle na server 2000 UDP datagramů o velikosti 1400 bajtů.

Výpis 8.9. Funkce dg_cli odesílá na server pevný počet datagramů

//udpcliserv/dgcliloop1.c

1 #include "unp.h"

2 #define NDG 2000 /* počet datagramů k odeslání */

3 #define DGLEN 1400 /* délka každého datagramu */

5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

8znakový sendline;

10 Sendto(sockfd, sendline, DGLEN, 0, pservaddr, servlen);

Poté upravíme server tak, aby přijímal datagramy, a počítáme počet přijatých datagramů. Server již neodráží datagramy zpět klientovi. Výpis 8.10 ukazuje novou funkci dg_echo. Když ukončíme proces serveru stisknutím klávesy přerušení na terminálu (což způsobí odeslání signálu SIGINT procesu), server vytiskne počet přijatých datagramů a ukončí se.

Výpis 8.10. Funkce dg_echo, která počítá přijaté datagramy

//udpcliserv/dgecholoop1.c

1 #include "unp.h"

2 static void recvfrom_int(int);

3 statický počet int;

5 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)

7 socklen_t len;

8 znaků;

9 Signal(SIGINT, recvfrom_int);

11 len = clilen;

12 Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

17 recvfrom_int(int signo)

19 printf("\npřijato %d datagramů\n", počet);

Nyní server provozujeme na uzlu freebsd, což je pomalý počítač SPARCStation. Klienta provozujeme na mnohem rychlejším systému RS/6000 s operačním systémem aix. Jsou propojeny přímo mezi sebou pomocí 100 Mbit/s Ethernet linky. Kromě toho spouštíme netstat -s na uzlu serveru před i po spuštění klienta a serveru, protože výstup statistiky ukáže, kolik datagramů jsme ztratili. Výpis 8.11 ukazuje výstup serveru.

Výpis 8.11. Výstup na uzlu serveru

freebsd % netstat -s -p udp

Bylo přijato 71208 datagramů

0 s neúplným záhlavím

0 se špatným polem délky dat

0 se špatným kontrolním součtem

0 bez kontrolního součtu

832 spadl kvůli absenci zásuvky

16 všesměrových/multicastových datagramů zahozeno kvůli absenci soketu

1971 zanikla kvůli plné vyrovnávací paměti soketu

0 ne pro hashované PCB

Výstup 137685 datagramů

freebsd % udpserv06 spustit náš server

klient odesílá datagramy

^C Chcete-li ukončit práci klienta, zadejte náš symbol přerušení

freebsd % netstat -s -p udp

Bylo přijato 73208 datagramů

0 s neúplným záhlavím

0 se špatným polem délky dat

0 se špatným kontrolním součtem

0 bez kontrolního součtu

832 spadl kvůli absenci zásuvky

16 všesměrových/multicastových datagramů zahozeno kvůli absenci soketu

3941 zahozeno kvůli plné vyrovnávací paměti soketu

0 ne pro hashované PCB

Výstup 137685 datagramů

Klient odeslal 2000 datagramů, ale serverová aplikace jich přijala pouze 30, což znamená ztrátovost 98 %. Server ani klient neobdrží zprávu, že tyto datagramy jsou ztraceny. Jak jsme řekli, UDP nemá schopnosti řízení toku – je nespolehlivé. Jak jsme si ukázali, pro UDP odesílatele je snadné přeplnit vyrovnávací paměť přijímače.

Pokud se podíváme na výstup netstat, vidíme, že celkový počet datagramů přijatých serverovým uzlem (nikoli serverovou aplikací) je 2000 (73 208 - 71 208). Čítač vyřazených z důvodu plné vyrovnávací paměti soketu ukazuje, kolik datagramů bylo přijato UDP a ignorováno, protože přijímací vyrovnávací paměť pro příjem soketu byla plná. Tato hodnota je 1970 (3941 - 1971), která po přičtení k výstupu počtu přijatých datagramů aplikace (30) dává celkem 2000 datagramů přijatých uzlem. Bohužel, počet datagramů zahozených z důvodu plné vyrovnávací paměti je v systému netstat. Neexistuje způsob, jak určit, které aplikace (např. které porty UDP) jsou ovlivněny.

Počet datagramů přijatých serverem v tomto příkladu není deterministický. Závisí na mnoha faktorech, jako je zatížení sítě, klientský uzel a serverový uzel.

Pokud provozujeme stejného klienta a stejný server, ale tentokrát je klient na pomalém systému Sun a server na rychlém systému RS/6000, žádné datagramy se neztratí.

aix % udpserv06

^? poté, co klient dokončí práci, zadejte náš symbol přerušení

obdržel 2000 datagramů

Zásuvky

Zásuvka je jeden konec obousměrného komunikačního kanálu mezi dvěma programy běžícími v síti. Spojením dvou zásuvek k sobě můžete přenášet data mezi různými procesy (místními nebo vzdálenými). Implementace soketu poskytuje zapouzdření protokolů sítě a transportní vrstvy.

Sokety byly původně vyvinuty pro UNIX na University of California, Berkeley. V UNIXu se metoda I/O komunikace řídí algoritmem otevření/čtení/zápisu/zavření. Aby bylo možné zdroj použít, musí být otevřen s příslušnými oprávněními a dalšími nastaveními. Jakmile je zdroj otevřený, lze z něj data číst nebo do nich zapisovat. Po použití zdroje musí uživatel zavolat metodu Close(), aby signalizoval operačnímu systému, že je se zdrojem hotovo.

Kdy byly do operačního systému UNIX přidány funkce? Meziprocesová komunikace (IPC) a síťová výměna, byl vypůjčen známý vstup-výstup. Všechny prostředky vystavené pro komunikaci v systémech UNIX a Windows jsou identifikovány pomocí úchytů. Tyto deskriptory, popř rukojeti, může ukazovat na soubor, paměť nebo jiný komunikační kanál, ale ve skutečnosti ukazuje na vnitřní datovou strukturu používanou operačním systémem. Soket, který je stejným zdrojem, je také reprezentován deskriptorem. U socketů lze tedy životnost kliky rozdělit do tří fází: otevření (vytvoření) socketu, příjem ze socketu nebo odeslání do socketu a nakonec uzavření socketu.

Rozhraní IPC pro komunikaci mezi různými procesy je postaveno na I/O metodách. Usnadňují soketům odesílání a přijímání dat. Každý cíl je specifikován adresou soketu, takže tuto adresu lze zadat v klientovi pro navázání spojení s cílem.

Typy zásuvek

Existují dva hlavní typy soketů – streamové sokety a datagramové sokety.

Streamovací zásuvky

Streamový soket je soket založený na připojení sestávající z proudu bajtů, který může být obousměrný, což znamená, že aplikace může přes tento koncový bod odesílat i přijímat data.

Streamový soket zaručuje opravu chyb, zpracovává doručení a udržuje konzistenci dat. Lze se na něj spolehnout, že doručí uspořádaná duplicitní data. Stream socket je také vhodný pro přenos velkého množství dat, protože režie na vytvoření samostatného spojení pro každou odeslanou zprávu může být pro malá množství dat neúnosná. Streamové sokety dosahují této úrovně kvality pomocí protokolu Transmission Control Protocol (TCP). TCP zajišťuje, že data dorazí na druhou stranu ve správném pořadí a bez chyb.

Pro tento typ soketu je cesta vytvořena před odesláním zpráv. To zajišťuje, že obě strany zapojené do interakce přijímají a odpovídají. Pokud aplikace odešle příjemci dvě zprávy, je zaručeno, že zprávy budou přijaty ve stejném pořadí.

Jednotlivé zprávy však mohou být rozděleny do paketů a neexistuje způsob, jak určit hranice záznamů. Při použití TCP se tento protokol stará o rozbití přenášených dat do paketů odpovídající velikosti, jejich odeslání do sítě a jejich opětovné sestavení na druhé straně. Aplikace pouze ví, že posílá určitý počet bajtů do vrstvy TCP a druhá strana tyto bajty přijímá. TCP pak tato data efektivně rozkládá na pakety odpovídající velikosti, přijímá tyto pakety na druhé straně, extrahuje z nich data a kombinuje je dohromady.

Datové proudy jsou založeny na explicitních připojeních: soket A požaduje připojení k soketu B a soket B žádost o připojení buď přijme, nebo odmítne.

Pokud musí být zaručeno, že data budou doručena na druhou stranu nebo je velikost dat velká, jsou streamové sokety vhodnější než datagramové sokety. Pokud je tedy prvořadá spolehlivá komunikace mezi dvěma aplikacemi, zvolte streamové zásuvky.

E-mailový server je příkladem aplikace, která musí doručovat obsah ve správném pořadí, bez duplikace nebo opomenutí. Streamový soket spoléhá na TCP, aby zajistil, že zprávy budou doručeny do jejich cílů.

Datagramové zásuvky

Datagramové sokety se někdy nazývají nespojované sokety, to znamená, že mezi nimi není vytvořeno žádné explicitní spojení - zpráva je odeslána do určeného soketu, a proto může být přijata z určeného soketu.

Streamové sokety poskytují spolehlivější metodu než datagramové sokety, ale pro některé aplikace je režie spojená s navázáním explicitního připojení nepřijatelná (například server v denní době, který svým klientům poskytuje synchronizaci času). Koneckonců, vytvoření spolehlivého připojení k serveru vyžaduje čas, což jednoduše přináší zpoždění ve službě a úloha serverové aplikace selže. Chcete-li snížit režii, měli byste používat datagramové sokety.

Použití datagramových soketů vyžaduje, aby přenos dat z klienta na server zvládl User Datagram Protocol (UDP). V tomto protokolu jsou uvalena určitá omezení na velikost zpráv a na rozdíl od streamových soketů, které mohou spolehlivě odesílat zprávy na cílový server, datagramové sokety neposkytují spolehlivost. Pokud se data někde v síti ztratí, server nebude hlásit chyby.

Kromě dvou diskutovaných typů existuje také zobecněná forma socketů nazývaná raw nebo raw sockety.

Surové zásuvky

Hlavním účelem použití raw soketů je obejít mechanismus, kterým počítač zpracovává TCP/IP. Toho je dosaženo poskytnutím speciální implementace zásobníku TCP/IP, která přepíše mechanismus poskytovaný zásobníkem TCP/IP v jádře – paket je předán přímo aplikaci, a proto je zpracován mnohem efektivněji než při průchodu klientem hlavní zásobník protokolů.

Surový soket je podle definice soket, který přijímá pakety, obchází vrstvy TCP a UDP v zásobníku TCP/IP a posílá je přímo do aplikace.

Při použití takových soketů paket neprojde filtrem TCP/IP, tzn. není nijak zpracována a objevuje se v surové podobě. V tomto případě je odpovědností přijímající aplikace, aby řádně zpracovala všechna data a provedla akce, jako je odstranění záhlaví a analýza polí – jako je zahrnutí malého zásobníku TCP/IP do aplikace.

Nestává se však často, že budete potřebovat program, který se zabývá raw sockety. Pokud nepíšete systémový software nebo program podobný snifferu paketů, nebudete muset zacházet do takových podrobností. Raw sockety se primárně používají při vývoji specializovaných nízkoúrovňových protokolových aplikací. Například různé nástroje TCP/IP, jako je trasování trasy, ping nebo arp, používají nezpracované sokety.

Práce s raw sockety vyžaduje solidní znalost základních TCP/UDP/IP protokolů.

Porty

Port je definován tak, aby umožňoval problém současné komunikace s více aplikacemi. V podstatě rozšiřuje koncept IP adresy. Počítač, na kterém běží více aplikací současně, přijímající paket ze sítě, může identifikovat cílový proces pomocí jedinečného čísla portu určeného při navazování spojení.

Soket se skládá z IP adresy stroje a čísla portu používaného aplikací TCP. Vzhledem k tomu, že IP adresa je jedinečná na internetu a čísla portů jsou jedinečná na jednotlivém počítači, čísla soketů jsou také jedinečná na celém internetu. Tato vlastnost umožňuje procesu komunikovat přes síť s jiným procesem pouze na základě čísla soketu.

Čísla portů jsou vyhrazena pro určité služby – jedná se o známá čísla portů, jako je port 21, používaný v FTP. Vaše aplikace může používat libovolné číslo portu, které nebylo rezervováno a dosud se nepoužívá. Agentura Internet Assigned Numbers Authority (IANA) udržuje seznam běžně známých čísel portů.

Typicky se aplikace klient-server používající sokety skládá ze dvou různých aplikací – klienta iniciujícího připojení k cíli (serveru) a serveru čekajícího na připojení od klienta.

Například na straně klienta musí aplikace znát cílovou adresu a číslo portu. Odesláním požadavku na připojení se klient pokusí navázat spojení se serverem:

Pokud se události vyvinou úspěšně, za předpokladu, že je server spuštěn dříve, než se k němu klient pokusí připojit, server souhlasí s připojením. Po udělení souhlasu vytvoří serverová aplikace nový soket, který bude konkrétně komunikovat s klientem, který navázal připojení:

Nyní mohou klient a server vzájemně komunikovat, číst zprávy každý ze svého vlastního soketu a podle toho psát zprávy.

Práce se sockety v .NET

Podpora soketů v .NET je poskytována třídami v jmenném prostoru System.Net.Sockets- Začněme jejich stručným popisem.

Třídy pro práci se zásuvkami
Třída Popis
Možnost Multicast Třída MulticastOption nastavuje hodnotu IP adresy pro připojení nebo opuštění skupiny IP.
NetworkStream Třída NetworkStream implementuje třídu základního proudu, ze které se odesílají a přijímají data. Toto je abstrakce na vysoké úrovni, která představuje připojení ke komunikačnímu kanálu TCP/IP.
TcpClient Třída TcpClient staví na třídě Socket, aby poskytovala službu TCP vyšší úrovně. TcpClient poskytuje několik metod pro odesílání a přijímání dat přes síť.
TcpListener Tato třída také staví na nízkoúrovňové třídě Socket. Jeho hlavním účelem jsou serverové aplikace. Naslouchá příchozím žádostem o připojení od klientů a upozorní aplikaci na všechna připojení.
UdpClient UDP je protokol bez připojení, a proto je k implementaci služby UDP v .NET vyžadována jiná funkčnost.
SocketException Tato výjimka je vyvolána, když na soketu dojde k chybě.
Zásuvka Poslední třídou ve jmenném prostoru System.Net.Sockets je samotná třída Socket. Poskytuje základní funkcionalitu aplikace socket.

Třída zásuvky

Třída Socket hraje důležitou roli v síťovém programování a poskytuje funkce klienta i serveru. Volání metod v této třídě primárně provádějí nezbytné kontroly související se zabezpečením, včetně kontroly oprávnění zabezpečení, a poté jsou předány protějškům metod v rozhraní Windows Sockets API.

Než přejdeme k příkladu použití třídy Socket, podívejme se na některé důležité vlastnosti a metody této třídy:

Vlastnosti a metody třídy Socket
Vlastnost nebo metoda Popis
AdresaRodina Poskytuje rodinu adres soketu - hodnotu z výčtu Socket.AddressFamily.
K dispozici Vrátí množství dat dostupných pro čtení.
Blokování Získává nebo nastavuje hodnotu označující, zda je soket v režimu blokování.
Připojeno Vrátí hodnotu označující, zda je soket připojen ke vzdálenému hostiteli.
LocalEndPoint Poskytuje místní koncový bod.
Typ protokolu Udává typ protokolu soketu.
RemoteEndPoint Poskytuje koncový bod vzdáleného soketu.
Typ zásuvky Udává typ zásuvky.
Přijmout() Vytvoří nový soket pro zpracování příchozího požadavku na připojení.
Bind() Sváže soket s místním koncovým bodem, aby naslouchal příchozím požadavkům na připojení.
Blízko() Vynutí uzavření zásuvky.
Připojit() Naváže spojení se vzdáleným hostitelem.
GetSocketOption() Vrátí hodnotu SocketOption.
IOControl() Nastavuje nízkoúrovňové provozní režimy pro zásuvku. Tato metoda poskytuje nízkoúrovňový přístup k základní třídě Socket.
Poslouchat() Uvede zásuvku do režimu poslechu (čekání). Tato metoda je určena pouze pro serverové aplikace.
Dostávat() Přijímá data z připojené zásuvky.
Hlasování() Určuje stav soketu.
Vybrat() Kontroluje stav jednoho nebo více soketů.
Poslat() Odesílá data do připojené zásuvky.
SetSocketOption() Nastaví možnost zásuvky.
Vypnutí() Zakáže operace odesílání a přijímání na soketu.



Nahoru