Moi.
Onko kenelläkään hajua kuinka voi toteuttaa TCP/IP Client/Server ohjelman "vahingossa" katkenneen yhteyden ilmaisemisen...???
Eli jos Serveri ohjelma kaatuu, miten Client ohjelma voisi saada siitä tiedon ja sama homma toisin päin että jos Client kaatuu, ni kuinka Serveri saisi tiedon siitä.
Happy.
Ei se tieto voi mistään maagisesti ilmestyä samalla hetkellä, vaan tieto perustuu siihen, että viesteihin ei tule vastausta määrätyn ajan kuluessa ja TCP-yhteys sen vuoksi katkeaa.
Niin, ei tietenkään ilmesty itsellään. Juuri siitä tässä onkin kysymys että kuinka se tieto saataisiin. Esim. Lähettämällä jotain "kysymystä" edestakas servun ja clientin välillä. Ja jos toinen ei saa vastausta takaisin se olisi merkki että ohjelma on kaatunut..??
Ja tuo mainitsemasi aika jolloin yhteys katkeaa automaattisesti on on määritelty jossain Windowsin asetuksissa ja se on 2 tuntia.
Juuri noin. Lähetät jotain sopivin väliajoin ja jos ei mene perille, siitä tulee sitten tieto.
Ja ei TCP-yhteys kahta tuntia odota jos paketti ei mene perille. Sen aikakatkaisun voi itse määritellä. Jos taas ei lähetä yhtään mitään, yhteys pidetään auki "ikuisesti."
Esimerkiksi netistä selviää, että TcpClient.ReceiveTimeout määrää, kauanko Read-metodi odottaa dataa. Jos aikaraja umpeutuu, Read heittää IOException-tyyppisen poikkeuksen.
No niin, nyt mä oon aivan sekaisin, ja täysin tietämätönkin näköjään.
Aloitetaapa aivan alusta.
Kuinka lähetetään Client koneelta tieto Serveri koneelle että Client kone on poistunut, siis niin että se on yhteys katkaistaan halutusti, eikä vahingossa.
Oisko jotain koodin pätkää tähän?
Voihan se client-sovellus lähettää vaikka jonkun tietyn tavun, jonka avulla serveri päässä päätellään, että nyt client on poistunut. Ei siihen minun käsittääkseni oikein muuta keinoa voi olla, kun yhteys kahden koneen välillä on käytännössä ip-pakettien lähettelyä. TCP:ssä ideana on aina lähettää vastaus, että paketti on tullut perille. Jos siis vastausta ei saada voidaan päätellä, että yhteys on katkennut.
Riippuu ihan täysin mitä siellä putkessa liikutetaan. Kai siellä joku protokolla on, siihen voinee määritellä yhteyden katkaisuun oman komennon? Tai jos vain katkaisee yhteyden niin siitäkin toki tulee tieto, mutta ei tiedetä katkaistiinko tarkoituksella vai ei.
Niin siis sitä yritin juuri sanoa, että voidaan lähettää vaikka tavu 123, joka ilmaisee, että nyt client on päättynyt ottaa hatkat ja serverissä sitten vaikka if-lauseessa tsekataan, että mitä tuo tavu 123 tarkoittaa ja toimii sen mukaisesti ja lähettää vaikka tavun 200 clientille ilmoittaakseen, että on saanut viestin perille ja sen jälkeen client voi lopulta katkaista yhteyden. Varmasti protokollat ovat voineet toteuttaa tämän jo itsekin, en itse ole esim. TCP:n sisäiseen toimintaan perehtynyt. Jokatapauksessa sen toiminta on perustuttava johonkin tällaiseen ratkaisuun.
TCP sisältää yhteyden katkaisun, joten putken "sisällä" ei tarvitse lähettää mitään.
Jos käytettäisiin vaikka UDP:tä, niin sitten täytyisi itse kertoa toiselle puolelle että "haluan nyt katkaista yhteyden".
GFFSFTW!
Yhteys päätetään nelitiekättelyllä. Yhteyden molemmat osapuolet katkaisevat yhteyden erikseen. Molemmat lähettävät FIN-paketin ja molemmat kuittaavat sen ACK-paketilla.
Yhteys voidaan päättää myös kolmitiekättelyllä. Toinen osapuoli lähettää FIN-paketin, jonka toinen osapuoli kuittaa FIN-ACK paketilla. Tämän jälkeen ensimmäinen osapuoli lähettää ACK-paketin.
Yhteys voidaan myös keskeyttää suoraan jommankumman osapuolen toimesta lähettämällä RESET-valitsimella varustettu paketti. Tämä toimenpide on ilmaisu katkaista yhteys heti eikä vastauspaketteja lähetetä.
qeijo kirjoitti:
GFFSFTW!
Yhteys päätetään nelitiekättelyllä. Yhteyden molemmat osapuolet katkaisevat yhteyden erikseen. Molemmat lähettävät FIN-paketin ja molemmat kuittaavat sen ACK-paketilla.
Yhteys voidaan päättää myös kolmitiekättelyllä. Toinen osapuoli lähettää FIN-paketin, jonka toinen osapuoli kuittaa FIN-ACK paketilla. Tämän jälkeen ensimmäinen osapuoli lähettää ACK-paketin.
Yhteys voidaan myös keskeyttää suoraan jommankumman osapuolen toimesta lähettämällä RESET-valitsimella varustettu paketti. Tämä toimenpide on ilmaisu katkaista yhteys heti eikä vastauspaketteja lähetetä.
Joo noinhan tuo Wikipedia sanoo :P
Ja saiskohan tosta vielä jonkinlaisen koodi esimerkin?
Happy kirjoitti:
Ja saiskohan tosta vielä jonkinlaisen koodi esimerkin?
Miten olisi vaikka metodi TcpClient.Close?
No nii, tossa on nyt tollanen räkä viritys...
Serveri puoli:
'' SERVERI '' 2 x Timer Imports System.Net Imports System.Net.Sockets Imports System.Text.UTF7Encoding Public Class Form1 Dim server As TcpListener Dim client As TcpClient Dim Yhteys As NetworkStream Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load server = New TcpListener(New IPEndPoint(IPAddress.Any, 8000)) server.Start() Me.Text = " Odotetaan yhteyksiä.... " Timer1.Start() End Sub Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick If server.Pending Then client = server.AcceptTcpClient Me.Text = " Yhteys Clienttiin avattu." Yhteys = client.GetStream Timer1.Stop() Timer2.Start() End If End Sub Private Sub Timer2_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer2.Tick If client.Available > 0 Then Dim t(client.Available - 1) As Byte Yhteys.Read(t, 0, t.Length) Dim Viesti As String = System.Text.UTF7Encoding.UTF7.GetString(t) Select Case Viesti Case "exit" Me.Text = " Odotetaan yhteyksiä.... " Timer2.Stop() Timer1.Start() client.Close() End Select End If End Sub End Class
Client puoli:
'' CLIENT '' 2 x Button Imports System.Net Imports System.Net.Sockets Imports System.Text.UTF7Encoding Public Class Form1 Dim server As TcpListener Dim client As TcpClient Dim Yhteys As NetworkStream Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try client = New TcpClient client.Connect("127.0.0.1", 8000) Yhteys = client.GetStream Catch ex As Exception MsgBox(ex.Message) End Try End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click Dim t() As Byte = UTF7.GetBytes("exit") Yhteys.Write(t, 0, t.Length) End Sub End Class
Tuntuishan toi noin toimivan....
Mutta tuo ei tiedä sitä jos yhteys katkeaa vahingossa....
Ja tuohon serveriin ei voi liittyä kuin yksi client, eli se pitäis vissiinkin vääntää johonkin multithread hommaan....
Mod. korjasi kooditagisekoilut!
TCP ei itsessään tosiaan tiedä jos yhteys katkeaa, ellei tietoa yritetä lähettää. Siksi protokollissa on monesti ping-toiminto, jolla katsotaan onko yhteys auki. TCP:ssä itsessäänkin on keepalive-ominaisuuksia, mutta itse tekisin tuon mieluummin ylemmällä tasolla.
Noita streameja en ole käyttänyt, mutta ainakin jos käytät suoraan socketeja ja niiden BeginAccept/BeginReceive-funktioita et tarvitse yhtään säiettä. Nämä menevät taustalle odottelemaan yhteyksiä tai dataa ja saat eventin kun jotain tapahtuu. Kun laitat oman dataolion BeginReceiven mukaan, tiedät mikä yhteys on kyseessä ja voit käsitellä sen tiedot halutulla tavalla. Huomattavasti vähemmän virhemahdollisuuksia ja tehokkaampaakin kuin säikeiden kanssa touhuaminen.
Metabolix kirjoitti:
Miten olisi vaikka metodi TcpClient.Close?
Tosi kiva esimerkki kysymykseen...
Happy kirjoitti:
Kuinka lähetetään Client koneelta tieto Serveri koneelle että Client kone on poistunut, siis niin että se on yhteys katkaistaan halutusti, eikä vahingossa.
Oisko jotain koodin pätkää tähän?
Jee. tuollahan se selviää.
En mä täällä kyselisi tuollaisia kysymyksiä jos tietäisin niihin vastauksen itse. Ja en pyydä teitä tietenkään tekemään mulle valmista koodia, vaan antamaan suuntaa johon lähteä. Mutta on turha tulla naljailemaan ja heittämään tuon laista Pa**a koodi esimerkkiä jotka ei johda mihinkään. Lähes kaikki tietää että yhteys suljetaan noin...
Happy kirjoitti:
Mutta tuo ei tiedä sitä jos yhteys katkeaa vahingossa....
Tarvitset tietenkin ajastimen, jonka avulla tarkistat tarkistat sopivin väliajoin pois pudonneet asiakkaat. MSDN ohjeissa sanotaan:
If you need to determine the current state of the connection, make a nonblocking, zero-byte Send call. If the call returns successfully or throws a WAEWOULDBLOCK error code (10035), then the socket is still connected; otherwise, the socket is no longer connected.
Happy kirjoitti:
Metabolix kirjoitti:
Miten olisi vaikka metodi TcpClient.Close?
Tosi kiva esimerkki kysymykseen... – – Jee. tuollahan se selviää.
No todellakin selviää. Jos kuitenkin yksinkertaisen metodin kutsuminen on noin mahdottoman vaikeaa, tässä on vielä huolellisesti kommentoitu koodiesimerkki:
' Lähetetään clientiltä serverille tieto, että client katkaisee yhteyden. client.Close();
Tätä kysyit, tähän vastasin.
Metabolix kirjoitti:
No todellakin selviää. Jos on yksi metodi, joka ei ota edes yhtä ainoaa parametria, niin ei voi jumalauta olla noin vaikeaa käyttää sitä. Tässä on vielä kokonainen koodiesimerkki:
' Lähetetään clientiltä serverille tieto, että client katkaisee yhteyden. client.Close();
Jaa kaunistelit sitten tuota edellistä vastaustasi. :D
No eipä tuo sinun huolellisesti kommentoitu koodiesimerkki LÄHETÄ MITÄÄN TIETOA SERVERILLE. Sehän vain katkaisee client koneen yhteyden, mutta serveri ei saa siitä tietoa että client on katkaissut yhteyden.
Kyllä palvelin saa siitä tiedon. Tuo koodi tekee kaiken, mitä qeijo hienosti aiemmin selitti TCP-yhteyden katkaisemisesta. Palvelimella tieto näkyy sillä tavalla, että tietoa luettaessa ohjelma ei pysähdy odottamaan vaan lukeminen epäonnistuu saman tien.
Happy kirjoitti:
Jaa kaunistelit sitten tuota edellistä vastaustasi. :D
Moderaattori varmaan sensuroi.
Kerroppas nyt että kuinka se palvelin saa siitä tiedon.
Imports System.Net.Sockets Public Class Form1 Dim client As TcpClient Dim Yhteys As NetworkStream Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click client = New TcpClient client.Connect("127.0.0.1", 8000) Yhteys = client.GetStream End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click client.Close() End Sub End Class
Tuossa on hyvin yksinkertainen client ohjelma.
Button1 -
Yhdistää Serveriin...
Button2 -
Katkaisee yhteyden Clientiltä, mutta ei lähetä Serverille mitään, vaan jättää
Socketin "roikkumaan" Serverin päähän.
EI se serveri tiedä että Client on poistunut.
Paitsi sitten kun se yrittää lähettää jotain Clientille ja huomaa että "eihän siellä enää ketää ollukkaan."
Mod. korjasi kooditagisekoilut!
Happy kirjoitti:
Kerroppas nyt että kuinka se palvelin saa siitä tiedon.
Aika outo kysymys siihen nähden, että juuri äsken jo vastasin siihen:
Metabolix kirjoitti:
Palvelimella tieto näkyy sillä tavalla, että tietoa luettaessa ohjelma ei pysähdy odottamaan vaan lukeminen epäonnistuu saman tien.
Toki sama koskee lähetystä. Tämän tavan käyttäminen suoraan edellyttää tietenkin, että dataa luetaan erillisessä säikeessä, jotta ylipäänsä voidaan kutsua Read-metodia ja katsoa, tuleeko dataa vai ei. Tekemäsi ”räkä viritys” näköjään toimii kuitenkin ajastimella, joten joudut tarkistamaan tilan erikseen Poll-metodilla:
Dim valmisLuettavaksi As Boolean = Yhteys.Socket.Poll(0, SelectMode.SelectRead) If client.Available > 0 Then ' Lue dataa. ElseIf valmisLuettavaksi ' Valmis luettavaksi mutta ei dataa => yhteys on poikki. End If
Toisin sanoen yhteys on suljettu silloin, kun Socket.Poll(...) = True mutta Available = 0.
Ei ole olemassa mitään maagista temppua, jolla palvelimella automaattisesti ajettaisiin koodia yhteyden katketessa, koska tieto ei yleensä siirry käyttöjärjestelmän verkkoajureista ohjelmalle itsestään vaan vain ohjelman pyynnöstä (eli silloin, kun ohjelma lähettää tai vastaanottaa dataa tai muuten käsittelee kyseistä TCP-yhteyttä). Pitää siis ihan itse ohjelmoida koodi, joka tarkistaa, onnistuuko lukeminen vai onko yhteys suljettu.
Jos taas päädyt käyttämään Socket.BeginReceive()-metodia ja kumppaneita, siellä yhteyden katketessa eventti laukeaa poikkeuksella ja voit sen napata kiinni. Ei tarvitse lähetellä tyhjiä, ei pollata, ei mitään.
No niin, oon nyt Googlettanu 12 tuntia, ja silti en saa selville että mitä mun pitäs tehdä...
Voipi olla että oon puhunut täyttä pa**aa koska en nyt itsekkään ymmärrä missä mennään tai mitä teen.
Siis jos nyt sitten vaikka ensin yritä kertoa mitä mä haluan tehdä tällä ohjelmalla.
Mulla on tekeillä lentokone simulaattorin "Replika ohjaamo" DCS A-10c peliin.
Eli ohjaamosta löytyy kaikki nappulat ja näytöt kuten oikeassa lentokoneessakin on.
Mutta, kun kyseisestä koneesta löytyy 9 kpl eri näyttöä jotka pitäisi siirtää siitä itse pelistä "Replika ohjaamon" näyttöihin, niin yksinkertaisesti loppuu VGA, HDMI portit kesken.
Siis siksi tarvitsen ohjelman joka peri aatteessa on ns. REMOTE DESKTOP ohjelma, joka voi siirtää "kuvaa" useampiin koneisiin netin välityksellä.
Eli tossa on mun koodit sekä Serveri ja Client Koneelle...(yhdelle koneelle)
Serveri puoli...
Imports System.Net.Sockets Imports System.Threading Imports System.Drawing Imports System.Runtime.Serialization.Formatters.Binary Public Class Form1 Dim Client As New System.Net.Sockets.TcpClient Dim portti As Integer Dim Server As System.Net.Sockets.TcpListener Dim NS As NetworkStream Dim IP As System.Net.IPAddress = System.Net.IPAddress.Any Dim BF As New BinaryFormatter Dim Listening As New Thread(AddressOf Listen) Dim GetImage As New Thread(AddressOf Receive) Private Sub Receive() Try While Client.Connected = True NS = Client.GetStream PictureBox1.Image = BF.Deserialize(NS) End While Catch ex As Exception ' MITÄ TÄHÄN KOHTAAN PITÄISI LAITTAA....????? MsgBox(ex.Message) End Try End Sub Private Sub Listen() While Client.Connected = False Server.Start() Client = Server.AcceptTcpClient End While GetImage.Start() End Sub Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load portti = Integer.Parse("8000") Server = New System.Net.Sockets.TcpListener(IP, portti) Listening.Start() End Sub End Class
Ja Sitten Client puoli.
Imports System.Net.Sockets Imports System.Threading Imports System.Drawing Imports System.Runtime.Serialization.Formatters.Binary Public Class Form1 Dim client As New TcpClient Dim NS As NetworkStream Dim port As Integer Public Function Stream() As Image Dim Area As Rectangle = Nothing Dim Capture As System.Drawing.Bitmap = Nothing Dim graph As Graphics = Nothing Area = Screen.PrimaryScreen.Bounds Capture = New Bitmap(Area.Width, Area.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb) graph = Graphics.FromImage(Capture) graph.CopyFromScreen(Area.X, Area.Y, 0, 0, Area.Size, CopyPixelOperation.SourceCopy) Return Capture End Function Private Sub SendDesktop() Try Dim bf As New BinaryFormatter ns = client.GetStream bf.Serialize(ns, Stream()) Catch ex As Exception ' KUIN MYÖS, MITÄ TÄHÄN PITÄISI LAITTAA...??? Timer1.Stop() Client.close() MsgBox(ex.Message) End Try End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Try client = New TcpClient client.Connect("127.0.0.1", 8000) Timer1.Start() Catch ex As Exception MsgBox(ex.Message) End Try End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click Timer1.Stop() client.Close() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick SendDesktop() End Sub End Class
Jos joku viittii kokeilla tota koodia.... niin kaikkihan toimii kuten pitääkin. Siihen asti kun jompikumpi yhteys katkeaa. :(
Serveri puoli huomaa ja Client puoli huomaa että yhteys on katkennut.
Mutta silti uutta "toimivaa" yhteyttä ei voida "luoda".
Client puoli muka yhdistää "johonkin" mutta koko ohjelma Client puolelta jäätyy ja Serveri ei vastaanota mitään.
Oon yrittänyt tehdä kaiken noihin
' MITÄ TÄHÄN KOHTAAN PITÄISI LAITTAA....?????
Mutta mikään auta....
Mikä on väärin, tai mitä MÄ teen väärin?
Kuten sanottua en tiedä mitä teen, joten jos joku tietää kuinka tästä selvitään, niin olisin kiitollinen....
WTF!
Jospa vaikka koittaisit asettaa serverin takaisin kuuntelemaan yhteyden katketessa.
Formin alustuksessa, asetat Listener säikeen kautta kuuntelun päälle.
Listen metodi sisältää:
While Client.Connected = False Server.Start() Client = Server.AcceptTcpClient End While GetImage.Start()
Mitä kuvittelet että while loopille tapahtuu, kun yhteys on ensimmäisen kerran luotu?
Eli ratkaisu ongelmaasi on, että yhteyden katkettua "alustat" tcp:n samaan tilaan, kuin se on serverisi käynnistyessä. (mm. käynnistät listener säikeen uudestaan). Yksi vaihtoehto olisi luonnollisesti dynaaminen monisäikeisyys, jolloin uutta yhteyttä muodostettaessa luotaisiin omat säikeet uudelle yhteydelle (eikä näin rajoitettaisi hommaa yhteen yhteyteen, ja kiinteisiin säikeistyksiin, esim: VB.NET Multithreaded server socket ).
groovyb kirjoitti:
Jospa vaikka koittaisit asettaa serverin takaisin kuuntelemaan yhteyden katketessa.
While Client.Connected = False Server.Start() Client = Server.AcceptTcpClient End While GetImage.Start()Mitä kuvittelet että while loopille tapahtuu, kun yhteys on ensimmäisen kerran luotu?
Eli ratkaisu ongelmaasi on, että yhteyden katkettua "alustat" tcp:n samaan tilaan, kuin se on serverisi käynnistyessä. (mm. käynnistät listener säikeen uudestaan).
Joo, tiedän että Serveri lopettaa ton loopin jälkeen yhteyden otot Clienteiltä.
Mutta millä, Ja ANTEEKSI jo etukäteen kielenkäyttöni... Himpsutilla toi Serverin voi "alustaa" uudelleen.?
Oon yrittänyt näin:
Try While Client.Connected = True NS = Client.GetStream PictureBox1.Image = BF.Deserialize(NS) End While Catch ex As Exception Listening.Abort() GetImage.Abort() NS.Flush() Client.Close() Server.Close() MsgBox(ex.Message) portti = Integer.Parse("8000") Server = New System.Net.Sockets.TcpListener(IP, portti) Listening.Start() End Try
Eli tonhan pitäs "tappaa" kaikki Threadit ja Streamit ja hyvä että ei vedä vielä töpseliäkin seinästä :D , mutta ei toi clientpuoli siltikään pysty yhdistämään Serveriin. Tai Siis yhdistää, mutta Ei ainakaan jatka siitä mihin jäätiin. (Eli jatkaisi sitä kuvan siirtämistä Serverille).
Eli onko nyt niin että tossa Client puolessa on joku häikkä?
Millähän tavalla clientissä yrität yhdistää uudestaan, ja mitä sitten tapahtuu?
Eli vaikka Suljen koko Client ohjelman(Serveri ohjelma on koko ajan päällä)
Näitä oon yrittäny servu puolelle yhteyden katkettua.
Try While Client.Connected = True NS = Client.GetStream PictureBox1.Image = BF.Deserialize(NS) End While Catch ex As Exception Listening.Abort() GetImage.Abort() NS.Flush() Client.Close() Server.Close() MsgBox(ex.Message) portti = Integer.Parse("8000") Server = New System.Net.Sockets.TcpListener(IP, portti) Listening.Start() End Try
Eli nyt siis Client ohjelma uudestaa käyntiin ja yritän yhdistää serveriin, niin se kyllä luulee yhdistävänsä Serveriin, mutta serveri ei saa "uudestaan" siitä lähetettävästä kuvasta kiinni.
Eli Client luulee olevansa yhdistetty Serveriin mutta Serveri ei ota mitään vastaan. Ja Samalla myös toi Clien ohjelmä "jäätyy"
Olisiko kyse siitä, että jätät Client-muuttujaan roikkumaan tuon vanhan Clientin? Kokeile Closen jälkeen alustaa sekin muuttuja uudestaan. Kaikkein selvintä olisi palauttaa ohjelma täsmälleen samaan tilaan kuin ensimmäisellä yhdistyskerralla. Helppo tapa tähän on, että laitat kaiken yhteyteen liittyvän eri luokkaan ja luot uuden instanssin siitä luokasta.
No niin nyt ohjelmat toimii lähes kuten pitääkin... :D
Client.Close() Server.Stop() Listening = New Thread(AddressOf Listen) GetImage = New Thread(AddressOf Receive) portti = Integer.Parse("8000") Server = New System.Net.Sockets.TcpListener(IP, portti) Listening.Start()
Tuolla sain sen serverin takaisin peliin...
Nyt sitten vain pitäisi keksiä kuinka tuon Client koneen voisi käynnistää ennen serveriä. Eli että sekin "haistelisi" että koska serveri on Online ja sitten vasta rupeaisi puskemaan kuvaa..
Kokeilin tollasta, mutta se odottelee nyt n.20 sekunttia ja heittää sitten virheen...(The operation is not allowed on non-connected sockets.)
Try Do while Client.Connected = False client = New TcpClient client.Connect("127.0.0.1", 8000) Loop Timer1.Start() Catch ex As Exception MsgBox(ex.Message) End Try
Aihe on jo aika vanha, joten et voi enää vastata siihen.