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.
- 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.
- 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.
- Pokud vše proběhne hladce, vytvoří se rovnocenné spojení, kterým si mohou obě strany posílat libovolná data.
- 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
Dim tcpListener As Net.Sockets.TcpListener
Dim networkStream As Net.Sockets.NetworkStream
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"
Const Port As Integer = 3556
Enum StatusPripojeni
Server
Klient
End Enum
Dim Status As StatusPripojeni
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
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
ToolStripMenuItemDisconnect.Enabled = False
End If
End Sub
Sub OdemkniOdpojeniPolozky()
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf OdemkniOdpojeniPolozky))
Else
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.
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
ZamkniPolozky()
hostName = InputBox("Zadejte adresu serveru:", "Připojit se", hostName)
If String.IsNullOrEmpty(hostName) = True Then Exit Sub
StripInfo.Text = "Přípojuji se k " + hostName + ":" + Port.ToString + "..."
tcp = New Net.Sockets.TcpClient
tcp.BeginConnect(hostName, Port, AddressOf Pripojit, Nothing)
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)
networkStream = tcp.GetStream()
StripInfo.Text = "Připojeno"
grp.Clear(Color.Black)
Kresleni = False
PicTabule.Invalidate()
OdemkniOdpojeniPolozky()
Catch e As ObjectDisposedException
Catch
MsgBox("Připojení se nezdařilo!", MsgBoxStyle.Information)
StripInfo.Text = "Připojení se nezdařilo!"
OdemkniPolozky()
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
ZamkniPolozky()
ToolStripMenuItemDisconnect.Enabled = True
tcpListener = New Net.Sockets.TcpListener(System.Net.IPAddress.Any, Port)
Try
tcpListener.Start()
Catch
MsgBox("Nelze poslouchat na portu " + Format(Port) + "!", MsgBoxStyle.Information)
StripInfo.Text = "Připojení se nezdařilo!"
OdemkniPolozky()
Exit Sub
End Try
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 + "..."
tcpListener.BeginAcceptTcpClient(AddressOf KlientSePripojuje, Nothing)
End Sub
Sub KlientSePripojuje(ByVal at As System.IAsyncResult)
Try
tcp = tcpListener.EndAcceptTcpClient(at)
tcpListener.Stop()
networkStream = tcp.GetStream()
StripInfo.Text = "Připojeno"
grp.Clear(Color.Black)
Kresleni = False
PicTabule.Invalidate()
OdemkniOdpojeniPolozky()
Catch e As ObjectDisposedException
Catch
MsgBox("Připojení se nezdařilo!", MsgBoxStyle.Information)
StripInfo.Text = "Připojení se nezdařilo!"
OdemkniPolozky()
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:
- jsme již spojeni - uzavřeme spojení
- posloucháme, ale nejsme ještě spojeni - ukončíme poslouchání
- 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
If Status = StatusPripojeni.Klient Then
tcp.Close()
StripInfo.Text = "Spojení ukončeno"
ElseIf Status = StatusPripojeni.Server Then
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
tcpListener.Stop()
StripInfo.Text = "Poslouchání ukončeno"
Else
tcp.Close()
StripInfo.Text = "Spojení ukončeno"
End If
End If
OdemkniPolozky()
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.
Hotový projekt z druhého dílo tohoto seriálu je ke stažení zde: