Základy TCP spojení, implementace funkcí Připojit a Založit server

2. díl - Základy TCP spojení, implementace funkcí Připojit a Založit server

Tomáš Jecha, MVP, MCSD       26.04.2007       C#, VB.NET, Threading, I/O operace, .NET       21915 zobrazení

V tomto díle si zjednodušeně vysvětlíme na jakém principu vlastně TCP přenos funguje a napíšeme funkce pro připojení na server a založení serveru.

Základní princip TCP

K čemu slouží?

TCP je komunikační protokol sloužící k přenosu dat přes počítačovou síť mezi dvěma počítači. Jde o spolehlivý protokol, který nám zaručuje, že data přijdou přijemci ve správném pořadí a žádné jeho části se neztratí - to zní možná jako samozřejmost, ale například UDP prokol takto nefunguje.

Průběh TCP spojení

  1. V první fázi se stává jedna aplikace serverem a začíná poslouchat na libovolně zvoleném portu (16bitové bezznaménkové číslo - existuje tedy 65535 portů). Žádné spojení není zatím vytvořeno.
  2. Klient se rozhodne, že se na server připojí a proto se pokusí navázat spojení. Musí znát cílovou IP adresa, kde server poslouchá, a jeho port.
  3. Pokud vše proběhne hladce, vytvoří se rovnocenné spojení, kterým si mohou obě strany posílat libovolná data.
  4. Až jedna ze stran spojení zavře, nebo pokud je spojení násilně přerušeno, konexe je považována za ukončenou.

Implementace

Budeme používat namespace System.Net.Sockets a v něm převážně 3 objekty:

  • TcpListener dokáže poslouchat na libovolném portu TCP žádosti o vytvoření spojení (tedy až na případy, kde již poslouchá jiná aplikace na stejném portu). Pokud nějaké takové přijde, můžeme ho přijmout a odvodit z něj TcpClient.
  • TcpClient reprezentuje vytvořené spojení. Dovoluje nám se připojit na server (v případě, že jsme klientská aplikace), kontrolovat stav připojení a spojení uzavřít.
  • NetworkStream je objekt vytvořený funkcí TcpClient.GetStream, který dovolí přenášet už samotná data.

Proměnné a funkce

Myslím, že se můžeme vrhnout konečně na kód. Začneme s deklaracemi přímo ve formuláři. Nejdřív budeme potřebovat objekty, které jsme si popsali před chvilkou:

 

  
         Dim tcp As Net.Sockets.TcpClient ' TCP klient používaný při připojení 
         Dim tcpListener As Net.Sockets.TcpListener ' dokáže poslouchat příchozí TCP připojení 
         Dim networkStream As Net.Sockets.NetworkStream ' zprostředkuje komunikaci přes TcpClient 
   

Pak nějaké informace o tom, zda jsme klient nebo server, na jakém portu bude probíhat komunikace a kam se budeme připojovat, pokud jsme klient (localhost je zástupné jméno pro aktálně používaný počítač, takže můžete zkoušet naši aplikaci na jednom počítači):

 

  
         Dim hostName As String = "localhost" ' počítač, na který se připojíme 
         Const Port As Integer = 3556 ' budeme používat tento port 
  
         Enum StatusPripojeni 
                 Server 
                 Klient 
         End Enum 
         Dim Status As StatusPripojeni ' určuje, zda jsme klient nebo server 
   

Teď trošku odbočím - zkuste si vzpomenout na položky v hlavním menu: Založit spojení, Připojit se a Odpojit se. Bylo by hloupé nechat uživateli například možnost klepnout na Připojit se, když už je připojen. Nejen, že to dokáže zmást, ale také vyvolat nepříjemné chyby. Proto si vytvoříme pár funkcí na zamykání/odemykání funkčních tlačítek:

 

  
     Sub ZamkniPolozky() 
         If Me.InvokeRequired Then 
             Me.Invoke(New MethodInvoker(AddressOf ZamkniPolozky)) 
         Else 
             ' zamknutí položek v menu 
             ToolStripMenuItemCreate.Enabled = False 
             ToolStripMenuItemConnect.Enabled = False 
         End If 
     End Sub 
     Sub OdemkniPolozky() 
         If Me.InvokeRequired Then 
             Me.Invoke(New MethodInvoker(AddressOf OdemkniPolozky)) 
         Else 
             ToolStripMenuItemCreate.Enabled = True 
             ToolStripMenuItemConnect.Enabled = True 
             ' pokud odemikáme i například "Připojit se", je jisté, že nejsme připojeni a tlačítko "Odpojit" můžeme tedy zamknout 
             ToolStripMenuItemDisconnect.Enabled = False 
         End If 
     End Sub 
     Sub OdemkniOdpojeniPolozky() 
         If Me.InvokeRequired Then 
             Me.Invoke(New MethodInvoker(AddressOf OdemkniOdpojeniPolozky)) 
         Else 
             ' odemknutí položky "Odpojit" 
             ToolStripMenuItemDisconnect.Enabled = True 
         End If 
     End Sub 

Použití je jednoduché:

  • ZamkniPolozky zamkne tlačítka Připojit se a Založit spojení - vhodné, pokud čekáme na klienta nebo se přávě připojujeme
  • OdemkniPolozky dělá opak předchozí funkce a navíc zamkne tlačítko Odpojit - když ukončíme spojení, máme možnost se znovu připojit
  • OdemkniOdpojeniPolozky odemkne tlačítko Odpojit se - vhodné použít při čekání na klienta, dává nám možnost poslouchání zrušit

Určitě jste si všimli, že používáme funkci Invoke a vlastnost InvokeRequired. Má to co dočinění s vlákny o kterých se dočtete v dalším odstavci.

Asynchronní připojování a čekání

Všichni známe programy, kde při vykonávání nějaké akce aplikace kompletně vytuhne a nereaguje na nic. Přesně tak by mohla vypadat i naše kreslící tabule. Protože čekání na klienta nebo připojování může trvat i delší dobu, izolujeme je a spustíme jako samostatné vlákno, které nebude ovlivňovat uživatelské prostředí programu.

Vlákna

Teď se dostáváme k již použité funkci Me.Invoke(...). Používáme ji pro bezpečné volání mezi vlánky. V našem případě jsou to funkce na zamykání a odemykání položek v menu. To můžeme udělat jen z hlavního vlákna formuláře a proto použijeme volání přes Invoke. Za předpokladu, že bychom volali funkci přímo, vyvolala by se vyjímka při prvním pokusu změnit něco na formuláři.

Připojení na server

Jako první se budeme zabývat tlačítkem Připojit se. Vyžádáme si adresu serveru a pomocí tcp.BeginConnect začne připojování. To bude probíhat v jiném vlákně a nakonec se zavolá procedura Pripojit.

 

  
     Private Sub ToolStripMenuItemConnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripMenuItemConnect.Click 
         Status = StatusPripojeni.Klient    ' stáváme se klientem 
         ZamkniPolozky()    ' uzamkneme menu, probíha připojování, až skončí, menu zase odemkneme 
         hostName = InputBox("Zadejte adresu serveru:", "Připojit se", hostName)    ' vložení jména serveru přes dialogové okno 
         If String.IsNullOrEmpty(hostName) = True Then Exit Sub ' stisknuto Storno 
         StripInfo.Text = "Přípojuji se k " + hostName + ":" + Port.ToString + "..."    ' informace o připojování na spodní liště 
         tcp = New Net.Sockets.TcpClient 
         tcp.BeginConnect(hostName, Port, AddressOf Pripojit, Nothing) ' spustíme připojování do jiného vlákna 
     End Sub 

Procedura Pripojit poběží jako první ve svém vlastním vlákně. Pokouší se inicializovat TCP spojení.

     Sub Pripojit(ByVal at As System.IAsyncResult) 
         Try 
             tcp.EndConnect(at) ' dokončíme připojování
             networkStream = tcp.GetStream()    ' stream pro přenos dat
             StripInfo.Text = "Připojeno" ' informace o připojení
             grp.Clear(Color.Black) ' smažeme tabuli
             Kresleni = False ' zrušíme případné aktivní kreslení
             PicTabule.Invalidate() ' a překreslíme tabuli
             OdemkniOdpojeniPolozky() ' jsme připojeni - odblokujeme tlačítko "Odpojit"
         Catch e As ObjectDisposedException 
             ' proběhlo stornování připojení, ne chyba
         Catch 
             ' proběhla neočekávaná chyba
             MsgBox("Připojení se nezdařilo!", MsgBoxStyle.Information) 
             StripInfo.Text = "Připojení se nezdařilo!" ' informace o nezdařilém připojení
             OdemkniPolozky() ' nakonec odemkneme znovu položky v menu na připojení a vytvoření spojení
         End Try 
     End Sub 

Vytvoření serveru

Velmi podobným způsobem implementujeme poslouchání. Vytvoříme také vlastní vlákno, které bude čekat až se klient připojí a zavolá se KlientSePripojuje.

  
     Private Sub ToolStripMenuItemCreate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripMenuItemCreate.Click 
         Status = StatusPripojeni.Server    ' stáváme se klientem 
         ZamkniPolozky()    ' uzamkneme menu, probíha čekání na klienta 
         ToolStripMenuItemDisconnect.Enabled = True ' odemkneme tlačítko "Odpojit", tím bude možné zrušit čekání 
         tcpListener = New Net.Sockets.TcpListener(System.Net.IPAddress.Any, Port)  ' vytvoříme TCP Listener 
  
         Try 
             tcpListener.Start()    ' začneme poslouchat 
         Catch 
             ' nelze začít poslouchat 
             MsgBox("Nelze poslouchat na portu " + Format(Port) + "!", MsgBoxStyle.Information) 
             StripInfo.Text = "Připojení se nezdařilo!" ' informace o nezdařilém připojení 
             OdemkniPolozky() ' nakonec odemkneme znovu položky v menu na připojení a vytvoření spojení 
             Exit Sub 
         End Try 
  
         ' zjistíme lokální IP adresu 
         Dim h As System.Net.IPHostEntry = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName) 
         Dim lokalniIP As String = CType(h.AddressList.GetValue(0), Net.IPAddress).ToString 
  
         StripInfo.Text = "Čekám na klienta. Server: " + lokalniIP + ":" + Port.ToString + "..."     ' informace o čekání na spodní liště 
         tcpListener.BeginAcceptTcpClient(AddressOf KlientSePripojuje, Nothing) ' pokud se nyní někdo připojí, zavolá se funkce KlientSePripojuje 
     End Sub 
     Sub KlientSePripojuje(ByVal at As System.IAsyncResult) 
         Try 
             tcp = tcpListener.EndAcceptTcpClient(at) ' přijmeme klienta 
             tcpListener.Stop() ' zastavíme poslouchání 
             networkStream = tcp.GetStream()    ' stream pro přenos dat 
             StripInfo.Text = "Připojeno" ' informace o připojení 
             grp.Clear(Color.Black) ' smažeme tabuli 
             Kresleni = False ' zrušíme případné aktivní kreslení 
             PicTabule.Invalidate() ' a překreslíme tabuli 
             OdemkniOdpojeniPolozky() ' jsme připojeni - odblokujeme tlačítko "Odpojit" 
         Catch e As ObjectDisposedException 
             ' proběhlo zastavení poslouchání, ne chyba 
         Catch 
             ' proběhla neočekávaná chyba 
             MsgBox("Připojení se nezdařilo!", MsgBoxStyle.Information) 
             StripInfo.Text = "Připojení se nezdařilo!" ' informace o nezdařilém připojení 
             OdemkniPolozky() ' nakonec odemkneme znovu položky v menu na připojení a vytvoření spojení 
         End Try 
     End Sub  

Odpojit se

Poslední co si v tomto díle probereme je příkaz na odpojení. Musíme rozlišit mezi 3 stavy:

  1. jsme již spojeni - uzavřeme spojení
  2. posloucháme, ale nejsme ještě spojeni - ukončíme poslouchání
  3. spojení už bylo ukončeno druhou stranou - nemusíme dělat nic
  
     Private Sub ToolStripMenuItemDisconnect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripMenuItemDisconnect.Click 
         ' pokud se chceme odpojit, musime zjistit zda jsme server nebo klient 
         If Status = StatusPripojeni.Klient Then    ' pokud jsme připojený klient 
             tcp.Close()    ' uzavřeme spojení 
             StripInfo.Text = "Spojení ukončeno"    ' informujeme uživatele 
          ElseIf Status = StatusPripojeni.Server Then 
             ' zjistíme, zda objekt tcp existuje a zda je připojen 
             Dim Connected As Boolean = False 
             If Not tcp Is Nothing Then 
                 If Not tcp.Client Is Nothing Then 
                     Connected = tcp.Connected 
                 End If 
             End If 
             If Connected = False Then  ' pokud server zatím jen poslouchá a klient není připojen 
                 tcpListener.Stop() ' zastavíme poslouchání 
                 StripInfo.Text = "Poslouchání ukončeno"    ' informujeme uživatele 
             Else ' pokud jsme server a klient se už připojil 
                 tcp.Close()    ' uzavřeme spojení 
                 StripInfo.Text = "Spojení ukončeno"    ' informujeme uživatele 
             End If 
             End If 
  
             OdemkniPolozky() ' nakonec odemkneme znovu položky v menu na připojení a vytvoření spojení 
     End Sub 

Shrnutí

Teď už aplikace umí spojení vytvořit a připojit se k serveru. V příštím díle si ukážeme, jak posílat data a synchronizovat obraz na kreslících tabulích.

Připojí již funguje!

Hotový projekt z druhého dílo tohoto seriálu je ke stažení zde: 

 

hodnocení článku

4 bodů / 4 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Všechny díly tohoto seriálu

3. Přenos dat, protokol a dokončení 26.04.2007
2. Základy TCP spojení, implementace funkcí Připojit a Založit server 26.04.2007
1. Úvod, uživatelské prostředí 26.04.2007

 

Mohlo by vás také zajímat

Jednoduchý scheduler v .NETu

Asi to znáte – máte nějaký složitější systém na zpracování velkého objemu dat a čas od času potřebujete vykovat nějakou automatizovanou údržbu – typicky smazat všechny položky starší než několika dní. Možností, jak toho dosáhnout, je hodně. Snažil jsem se vymyslet něco jednoduchého a efektivního.

ASP mvc–from zero to hero (3), vývojový stack a solution

Hledáme .NET vývojáře (Praha, Brno, Frýdek-Místek)

 

 

Nový příspěvek

 

Diskuse: Díl 2. - Základy TCP spojení, implementace funkcí Připojit a Založit server

Velmi dobré jako vše na vbnet

jen připomínka k akci Připojit se

na základě dialogu

hostName = InputBox("Zadejte adresu serveru:", "Připojit se", hostName)	' vložení jména serveru přes dialogové okno

... pokud stisknu Storno předchozí akce

ZamkniPolozky()	' uzamkneme menu, probíha připojování, až skončí, menu zase odemkneme

Zamkla kompletně menu a my nemáme možnost nového připojení se

akce ZamkniPolozky()by měla následovat až pokud je vrácena hodnota hostName = InputBox ...

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Děkuji za opravdu, opravdu tyto dva řádky mají opravdu být prohozené.

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Zdravím, možnost získat kod i v C# asi možný není, že? Basic moc nezvládám, jen základy a tady už jsem docela ztracen - přitom podobné stránky na C jsem nenašel.

Kdyžtak mco díky!!!

Pavel

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Můžu poradit s konkrétními kusy kódu. Celý program bohužel nestíhám přepsat.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Je možné pripojiť sa na server kde je viac užívatelov než dvaja?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Díl 2. - Základy TCP spojení, implementace funkcí Připojit a Založit server

Zdravim , robim program podla vaseho navodu, narazil som vsak na problem.

Pri vypisovani IP adresy mi to stale dava IPv6 adresu namiesto klasickej IPv4. Da sa to nejak zmenit ? Dakujem

http://www.durli.ic.cz/programovanie/ser...

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Diskuse: Díl 2. - Základy TCP spojení, implementace funkcí Připojit a Založit server

Snažím se dojít na to jak v programu zjistit všechny položky Menu (MenuStrip). Mohl by jste poradit?

Děkuji

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Globálně by to mělo být ve smyslu

projít všechny položky Menustrip

záleží na tom jak se menustrip jmenuje

predpokládejme "Menustrip_1"

For Each Ctrl In Me.Menustrip_1.Controls
        Msgbox(Ctrl.Name)
Next Ctrl 

Je to psáno "na sucho" z hlavy

doufám, že jsem tam neudělal systémový překlep

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Předem díky za reakci,

i když mám v hlavním Menu 4 položky,

MenuStrip.Controls.Count = 0 (nulový)

  Dim pMenu As MenuItem
   For Each pMenu In MenuStrip.Controls
        MsgBox(pMenu.Name)
   Next pMenu

Nemělo by tam být spíš:???

For Each pMenu In MenuStrip.Items

Jenže netuším jak by mělo být nadefinováno

Dim pMenu As MenuItem

Díky za námět.

[email protected]

Brabec L.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Co

Dim pMenu as Object

?

:David

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Byl jsem zhruba před měsícem na školení v Microsoftu

které bylo na ASP.NET, AJAX, LINQ

A v rámci dotazů jsem se zeptal přímo na toto

bylo mi řečeno ať pošlu projekt že se na to podívají

Bohužel zatím nic

Stejně jako jsem prosil a nejen za sebe aby uploadovali

projekty prezentované ze školení.

Škoda, týden si pamatuji o čem to bylo, ale za měsíc onové věci

a z hlavy? Jak kdybych tam nebyl. Nedotažená akce :-(

nahlásit spamnahlásit spam 3 / 3 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback