Poznáváme C# a Microsoft .NET – 71. díl – Práce s protokoly UDP a TCP

Tímto dílem, který pokračuje v sérii o síťovém programování, se již dostaneme na nižší úroveň abstrakce a seznámíme se třídami pro práci s konkrétními síťovými protokoly – UDP a TCP.

Síťové protokoly UDP a TCP

Komunikace ve světě počítačových sítí funguje na různých úrovních, kde v jednotlivých úrovních nebo lze také říci vrstvách pracují příslušné protokoly poskytující určité služby. Až doposud jsme se zabývali protokoly na nejvyšší úrovni. V případě protokolu HTTP jsme poměrně jednoduše žádali o nějaký zdroj (soubor) metodou GET popřípadě jsme zdroji zaslali metodou POST nějaká data na zpracování. To jsou služby, které nám zmíněný protokol nabízí za pomoci stejně pojmenovaných příkazů.

Ovšem to jakým způsobem jsou data po síti přenášena již není úlohou protokolů typu HTTP, ale protokolů pracujících na nižších úrovních síťového modelu. Na nižší úrovni, jejíž protokoly mají na starost přenos vlastních dat z jednoho síťového uzlu do uzlu druhého pracují protokoly UDP (User Datagram Protocol) a TCP (Transmission Control Protocol ). Každý pracuje na jiném principu a hodí se do odlišných situací i když je třeba uznat, že protokol TCP je používaný častěji (např. HTTP, FTP, telnet atd.).

Jak název napovídá, tak protokol UDP pracuje na principu takzvaných datagramů, což je jednotka dat s určeným cílem, putující nezávisle po síti. Důležité je vědět, že čas doručení a doručení jako takové vůbec není garantováno, tudíž se protokol UDP používá u všech takových aplikací, kde není 100% ní doručení vyslaných dat potřeba – internetové rádio či TV atd.

Naproti tomu protokol TCP je takzvaně spojový, což znamená, že při komunikaci mezi dvěma uzly je navázáno stálé aktivní spojení. Také na rozdíl od protokolu UDP je tento protokol odpovědný za správné doručení všech zaslaných dat. Všechny tyto výhody dělají z protokolu TCP pro většinu aplikací lepší protokol, ovšem na druhou stranu tyto výhody stojí určitou režii, která má za následek nižší rychlost přenosů oproti protokolu UDP.

UDP a TCP v prostředí .NET

Po seznámení se s protokoly UDP a TCP, přejdeme k další části, kterou samozřejmě není nic jiného než to, jaké třídy a jakým způsobem tyto třídy využijeme k zajištění síťové komunikace pomocí zmiňovaných protokolů. Jak třídy pro využití protokolu UDP tak třídy pro využití protokolu TCP sídlí ve stejném jmenném prostoru, kterým je System.Net.Sockets.

UDP

Nejprve si vezmeme na pomyslnou mušku protokol UDP. Třída, která nám umožňuje využití tohoto protokolu V prostředí .NET je třída UdpClient. Jak již bylo zmíněno, tak protokol UDP je nespojový, tudíž pokud chceme odeslat datagram na vzdálený počítač nemusíme s ním navázat spojení a rovnou metodě Send předat data a cíl na který chceme datagram zaslat. Při definici cíle je potřeba zadat kromě IP adresy také UDP port. Pomocí UDP portů jsou totiž adresovány jednotlivé služby, které na počítači běží. Úřad IANA (Internet Assigned Number Authority) přiřazuje čísla těchto portů obvyklým službám. Pro tyto služby jsou vyhrazeny čísla do 1024, takže se těmto číslům při implementaci svých aplikací raději vyhněte.

Nyní již první zdrojový kód ukazující práci s třídou UdpClient.

UdpClient client = new UdpClient();
        byte[] data = { 1, 2, 3 };
        client.Send(data, data.Length, "127.0.0.1", 2000);

Jak vidíte, tak datagram, který bude odeslán je představován polem bajtů, které předáme instanční metodě Send spolu s cílem datagramu. Je také možné cíl metodě Send nezadávat a to v případě využití metody Connect, kterou určíme se kterým vzdáleným uzlem bude instance třídy UdpClient asociována.

Je dobré také vědět, že v případě této asociace s konkrétním uzlem je jakýkoli datagram z jiného uzlu při příjmu odmítnut. Tím se dostáváme k tomu, že třída UdpClient slouží jak k odesílání datagramů, tak i k jejich přijímání, k čemuž využijeme metody Receive. Následující příklad ukazuje použití instance této třídy v jednom vlákně k příjmu a v druhém vlákně k odeslání datagramu.

public class UDPExample
    {
        public static void Run()
        {
            //naslouchani spustime ve vlastnim vlakne
            Thread listener = new Thread(new ThreadStart(DoListening));
            listener.Start();
            //pockame vterinu a zasleme data
            Thread.Sleep(1000);
            SendData();
        }
       
        static void DoListening()
        {

            UdpClient client = null;
            try
            {
                //zacneme poslouchat na portu 3000
                client = new UdpClient(3000);
                //budeme primat z jakekoli adresy a portu
                IPEndPoint host = new IPEndPoint(IPAddress.Any, 0);
                Console.WriteLine("{0} : UDP posluchac ceka na data..", DateTime.Now.ToString());
                //pockame na prijeti dat
                byte[] received = client.Receive(ref host);
                //ziskame z bajtu retezec
                string receivedString = Encoding.ASCII.GetString(received);
                Console.WriteLine("{0} : UDP posluchac prijal od {1} data : {2}", DateTime.Now.ToString(), host.ToString() , receivedString);
            }
            catch (SocketException ex)
            {

                Console.WriteLine("Doslo k vyjimce z duvodu : {0}", ex.SocketErrorCode);
            }
            finally
            {
                if (client != null)
                {
                    client.Close();
                }
            }
        }

        static void SendData()
        {
            UdpClient client = null;
            try
            {
                client = new UdpClient();
                //pripojime se na cil, kteremu chceme zaslat data
                client.Connect(IPAddress.Loopback, 3000);
                //enkodujeme retezec a zasleme na cil
                byte[] data = Encoding.ASCII.GetBytes("Ahooj");
                client.Send(data, data.Length);
            }
            catch (SocketException ex)
            {
                Console.WriteLine("Doslo k vyjimce z duvodu : {0}", ex.SocketErrorCode);
            }
            finally
            {
                if (client != null)
                {
                    client.Close();
                }
            }
        }
       
    }

V případě, že instanci třídy UdpClient používáme k příjmu datagramů využijeme přetížené verze konstruktoru přijímající číslo portu na kterém bude nasloucháno. Po tomto vytvoření instance je potřebné metodě Receive předat instanci třídy IPEndPoint určující od kterého příjemce bude datagram přijat. Po úspěšném přijetí datagramu tato instance obsahuje údaje o uzlu odesílatele, což se hodí hlavně v případě jako je ten náš, kde jsme určili, že přijmeme datagram z jakékoli adresy a portu.

TCP

V případě realizace síťových aplikací, které pro komunikaci hodlají využívat protokol TCP nás budou zajímat dvě třídy. Jedná slouží k naslouchání na příchozí připojení od klientů – posluchač a druhá, která je právě tím klientem, jež se na otevřený port pomocí posluchače připojí. K realizaci onoho zmíněného posluchače slouží třída TcpListener a k vytvoření klienta zase třída TcpClient, což je, jak jste si jistě všimli, změna oproti protokolu UDP, kde obě role mohla zastoupit jedna třída. Stejně jako v případě protokolu UDP jsou i zde přípojné body určeny nejen počítačem, ale také portem určujícím službu. Úřad IANA k číslování jednotlivých TCP portů přistupuje stejně jak u protokolu UDP, takže opět doporučuji se vyvarovat použití portů nižších než 1024 ve vaší aplikaci.

Vrhněme se rovnou na zdrojový kód příkladu, který si následně vysvětlíme.

public class TCPExample
    {
        public static void Run()
        {
            Thread listenerThread = new Thread(DoListening);
            listenerThread.Start();
            Thread.Sleep(1000);
            SendData();
        }

        static void DoListening()
        {
            //vytvorime intanci posluchace pro urcity TCP port
            TcpListener listener = new TcpListener(IPAddress.Loopback, 2000);
            Console.WriteLine("TCP posluchac ceka na pripojeni..");
            TcpClient client = null;
            try
            {
                //zacneme naslouchani na urcenem portu
                listener.Start();
                //pockame na pripojeni nejakeho klienta
                client = listener.AcceptTcpClient();
                //po pripojeni si vyzvedneme proud a nacteme z nej data
                Stream clientStream = client.GetStream();
               
                StreamReader reader = new StreamReader(clientStream);
                string content = reader.ReadToEnd();
                Console.WriteLine("TCP posluchac prijal data : {0}", content);
                clientStream.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                if (client != null)
                {
                    client.Close();
                }
                //zastavime naslouchani
                listener.Stop();
            }
        }

        static void SendData()
        {
            TcpClient client = null ;
            try
            {
                //pripojime se k serveru na port 2000
                client = new TcpClient("localhost", 2000);
                //ziskame proud pripojeni a zapiseme do nej data
                Stream strm = client.GetStream();
                StreamWriter writer = new StreamWriter(strm);
                writer.Write("Ahooj");
                writer.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                if (client != null)
                {
                    client.Close();
                }
            }
           
        }
    }

I když řádek kódu je trochu více, je příklad poměrně jednoduchý. Metoda DoListening obsahuje kód představující jednoduchý TCP server, který pomocí instance třídy TcpListener čeká na připojení klienta. To na kterém portu bude posluchač naslouchat určíme při vytváření jeho instance. Po té je potřeba začít naslouchání zavoláním metody Start. Další důležitou metodou je metoda AcceptTcpClient, která čeká na připojení nějakého klienta.

Do doby než se nějaký klient na daný port připojí, tato metoda blokuje provádění daného vlákna (proto je také metoda DoListening spuštěna ve vláknu vlastním). Po připojení klienta průběh metody pokračuje získáním datového proudu, který představuje spojení s ním (využití toho, že protokol TCP je spojový) a přečtení zaslaných dat. Pro ukončení naslouchání je potřeba zavolat metodu Stop na instanci posluchače.

Klientská část příkladu je jednodušší. Ve chvíli vytvoření instance třídy TcpClient je již vytvořeno spojení s cílovým uzlem. Po té je již jen získán datový proud spojení a do něj jsou zapsána data.

Poznámka: Od tohoto dílu začínám psát ukázkové příklady ve vývojovém prostředí Visual Studio 2005, tudíž jsme se posunuli o krůček dále k využití frameworku .NET 2.0.

Příklady ke článku jsou jako vždy ke stažení zde.

Diskuze (9) Další článek: Český Telecom: finanční výsledky za první čtvrtletí

Témata článku: , , , , , , , , , , , , , , , , , , , , ,