Články

Tisk článku Tisk článku

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

[Zpět na kategorii]

Datum: 26. 4. 2007 14:16       Autor: Tomáš Jecha       Zobrazeno: 9838x

Kategorie: Sítě

Seriál: Kreslící tabule pro více uživatelů přes TCP/IP - Díl 2.

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: 


> Na začátek

 

Hodnocení:

Hlasů: 11
Zvolte své hodnocení

Tomáš Jecha

Tomáš Jecha již několikátým rokem získal ocenění Microsoft MVP. V současné době pracuje ve společnosti AVAST jako architekt a vývojář interních systémů. Působí také jako lektor a konzultant v počítačové škole Gopas. V současné době se zajímá především o SQL Server a technologie nad .NET Frameworkem 4. Společně s Tomášem Hercegem napsal tento web a stará se o jeho administraci.

Podpořte vznik dalších článků

Související články

DílNázev článku 
Díl 1. Úvod, uživatelské prostředí 26. 4. 2007
Díl 2. Základy TCP spojení, implementace funkcí Připojit a Založit server 26. 4. 2007
Díl 3. Přenos dat, protokol a dokončení 26. 4. 2007

RSS Feed RSS Feed

Diskuse

Připojit se

Datum: 3.2.2008 16:20
Autor: Petr Slezák
Hodnocení autora: 6
Příspěvků: 60
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 ...
 
           [Odpovědět]
 
Hodnocení: 6 Čekejte, prosím...

Re: Připojit se

Datum: 3.2.2008 16:32
Autor: Tomáš Jecha
Hodnocení autora: 704
Příspěvků: 1287
Děkuji za opravdu, opravdu tyto dva řádky mají opravdu být prohozené.
 
           [Odpovědět]
 
Hodnocení: 2 Čekejte, prosím...

Re: Připojit se

Datum: 3.3.2008 21:40
Autor: neregistrovaný (158.196.50.178)
Hodnocení autora: není
Příspěvků: 0
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
 
           [Odpovědět]
 
Hodnocení: 2 Čekejte, prosím...

Re: Připojit se

Datum: 26.3.2008 20:57
Autor: Tomáš Jecha
Hodnocení autora: 704
Příspěvků: 1287
Můžu poradit s konkrétními kusy kódu. Celý program bohužel nestíhám přepsat.
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Re: Připojit se

Datum: 20.11.2010 15:11
Autor: d d8
Hodnocení autora: 0
Příspěvků: 5
Je možné pripojiť sa na server kde je viac užívatelov než dvaja?
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Názvy položek Menu

Datum: 5.2.2008 20:34
Autor: neregistrovaný (213.192.11.3)
Hodnocení autora: není
Příspěvků: 0
Snažím se dojít na to jak v programu zjistit všechny položky Menu (MenuStrip). Mohl by jste poradit?
Děkuji
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: Názvy položek Menu

Datum: 6.2.2008 10:25
Autor: neregistrovaný (194.228.81.178)
Hodnocení autora: není
Příspěvků: 0
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
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Re: Názvy položek Menu

Datum: 6.2.2008 13:53
Autor: Lubor Brabec
Hodnocení autora: 2
Příspěvků: 29
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.
lubor.brabec@seznam.cz
Brabec L.
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...

Re: Názvy položek Menu

Datum: 26.3.2008 17:00
Autor: neregistrovaný (217.112.164.100)
Hodnocení autora: není
Příspěvků: 0
Co

Dim pMenu as Object
 
?

:David
 
           [Odpovědět]
 
Hodnocení: 0 Čekejte, prosím...

Re: Názvy položek Menu

Datum: 26.3.2008 18:03
Autor: Petr Slezák
Hodnocení autora: 6
Příspěvků: 60
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 :-(
 
           [Odpovědět]
 
Hodnocení: 3 Čekejte, prosím...

IPv6

Datum: 19.9.2010 8:49
Autor: neregistrovaný (88.212.37.229)
Hodnocení autora: není
Příspěvků: 0
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...
 
           [Odpovědět]
 
Hodnocení: 1 Čekejte, prosím...
 

VBNET.CZ | © 2007 Tomáš Herceg, Tomáš Jecha | Kopírování a přejímání jakéhokoliv obsahu z tohoto webu je bez písemného svolení autorů zakázáno.