Trying to connect to second Laptop. - Page 2-VBForums
Page 2 of 3 FirstFirst 123 LastLast
Results 41 to 80 of 95

Thread: Trying to connect to second Laptop.

  1. #41
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Poppa Mintin View Post
    I've been trying to find step 2 but must've missed it.

    (Reply #2 to post #38)
    Oh! Huh. For some reason about 4 different networking posts showed up and I think I got some wires crossed.

    To make it sound like it won't take me a month to finish:

    If you got 1 byte working, for the sake of getting you to WinForms faster I think we can blow through 2, 3, and 4 at the same time. They're not much different. 5 is where I really want to talk about the Async/Await pattern, so that should really get its own post. From there, it's debatable if 6 and 7 should be separate posts but I'm a pessimist so I'm assuming they will be. I'm not going to be able to pull it all together tonight, but I'll be looking at it in the morning. (My recent work is with coworkers in a different time zone so they don't show up until 3PM my time, which means if I'm waiting on them... I end up waiting a long time! I just double-checked my email and they didn't answer my questions yet so... I have time to poke around!)

    The stinky reason 2/3/4 are so entangled is it looks so easy to use StreamReader for them, but it tends to get you in trouble in real-life situations. If you dig around in recent forum posts you'll find me talking to someone about the "whys" here, but also if you hold tight I'll explain it again. Forgetting this lesson wrecks my code about twice a year.

    5 deserves to be its own lesson. If you master 5, 6 and 7 are kind of approachable at the same time. It doesn't help that there's 4 "mainstream" ways to do asynchronous code in .NET. I'm going to focus on the one I think is the best going forward.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  2. #42

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Hi,

    I've been 'playing' again !

    I've (finally) got the hang of sending more than one byte, specifically five.
    It wasn't only the sending of the five that's taken this long, I think I've also figured out some of ' Console.WriteLine', which took longer.

    I'm not going to attempt an unknown number of bytes just yet.




    Poppa.

    PS.
    Quote Originally Posted by Sitten Spynne View Post
    My recent work is with coworkers in a different time zone so they don't show up until 3PM my time
    Actually I wondered about that, sadly your 'profile' doesn't give any hint as to which time zone you are in...
    (I'm currently in BST, i.e. GMT + Daylight Saving)


    Pop.
    Last edited by Poppa Mintin; Aug 22nd, 2017 at 07:15 AM. Reason: PS Added.
    Along with the sunshine there has to be a little rain sometime.

  3. #43

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    If you got 1 byte working, for the sake of getting you to WinForms faster I think we can blow through 2, 3, and 4 at the same time. They're not much different. 5 is where I really want to talk about the Async/Await pattern, so that should really get its own post.
    Hi again,

    Been playing again.

    I have messages passing between the two laptops, just modifying your code, nothing solely my own yet, still in two Console Applications,
    but it's reliable and I'm happy with that. I expect you'd've done it more efficiently:
    Code:
    Imports System.Net
    Imports System.Net.Sockets
    Imports System.Threading.Tasks
    Imports System.IO
    
    Module Client
    
        Sub Main()
            Console.WriteLine("I'm the client. I know I need port 3000, but...")
            Console.WriteLine("I need you to tell me the connection IP.")
    
            Dim connectionEndpoint As IPEndPoint = Nothing
            Dim Num As Int32
            Dim Txt As String
    
            ' You have to input the right IP for the client.
            While True
                Console.Write("IP?> ")
                Dim ipInput As String = Console.ReadLine()
                Dim ip As IPAddress
                If Not IPAddress.TryParse(ipInput, ip) Then
                    Console.WriteLine("I don't understand that. Try again.")
                    Continue While
                End If
                connectionEndpoint = New IPEndPoint(ip, 3000)
                Exit While
            End While
    
            ' Ask for the message to send.
            While True
                Console.Write("Message ?> ")
                Dim TxtInput As String = Console.ReadLine()
                Txt = TxtInput
                Num = Len(Txt)
                If Num < 1 Then
                    Console.WriteLine("I need more than that!. Try again.")
                    Continue While
                End If
                Exit While
            End While
    
            Console.WriteLine("   OK, now I'm going to send the message.")
            Using client As New TcpClient()
                client.Connect(connectionEndpoint)
                Console.WriteLine("   I connected!")
    
                Using stream = client.GetStream()
                    Dim data(Num) As Byte
                    For i = 0 To Num - 1
                        data(i) = Asc(Txt.Substring(i, 1))
                    Next
                    Console.WriteLine("  I am going to send '{0}' bytes.", data(Num))
                    stream.Write(data, 0, Num)
                    Console.WriteLine("   Message sent! Disconnecting.")
                End Using
            End Using
            Console.WriteLine("           That's all, folks!")
            Task.Delay(2500).Wait()
        End Sub
    
    End Module
    
    ' Note to self:  Must find out how those  '{0}' work.
    I notice that your listener code knows how many bytes it has to read, I can't see where it gets that.
    Code:
    Imports System.IO
    Imports System.Net
    Imports System.Net.Sockets
    
    Module Server
    
        Private TCP_Ears As TcpListener
        Dim prev As String = ""
        Dim msg As String = ""
    
        Sub Main()
            Dim listenEndPoint As New IPEndPoint(IPAddress.Any, 3000)
            TCP_Ears = New TcpListener(listenEndPoint)
    
            TCP_Ears.Start()
            TCP_Ears.AcceptTcpClientAsync().ContinueWith(AddressOf WhenAConnectionHappens)
            Console.WriteLine("I am listening for traffic on port 3000.")
    
            ' A tricky part of console applications is they end if we reach the end of Sub Main.
            ' So loop until we decide to quit.
    
            msg = "I'm in the loop! If you press Enter I'll stop."
            While True
                If msg <> prev Then
                    Console.WriteLine(msg)
                    prev = msg
                End If
                If Console.KeyAvailable Then
                    Dim theKey = Console.ReadKey()
                    If theKey.Key = ConsoleKey.Enter Then
                        Console.WriteLine("   You pressed enter, I'm shutting down!")
                        prev = ""
                        Exit While
                    End If
                End If
            End While
                Task.Delay(2000).Wait()
            TCP_Ears.Stop()
        End Sub
    
        Private Sub WhenAConnectionHappens(ByVal parentTask As Task(Of TcpClient))
            Console.WriteLine()
            Console.WriteLine("I got a connection!")
            prev = ""
            If parentTask.Exception IsNot Nothing Then
                Console.WriteLine("But there was a problem:")
                Console.WriteLine(parentTask.Exception)
                Console.WriteLine("(This may be expected if you are closing the application!)")
            Else
                Using remoteClient = parentTask.Result
                    Console.WriteLine("It came from IP: {0}", remoteClient.Client.LocalEndPoint)
                    Console.WriteLine("I'm going to try to read data from it.")
    
                    Using remoteStream = remoteClient.GetStream()
                        Dim data(1024) As Byte
                        Dim bytesRead As Integer = 0
                        bytesRead = remoteStream.Read(data, 0, 1024)
    
                        Console.WriteLine("Number of bytes expected {0},  {0} were read.", bytesRead)
                        If bytesRead > 0 Then
                            Dim txt As String = "Message:  "
                            For i = 0 To bytesRead - 1
                                txt += Chr(data(i))
                            Next
                            Console.WriteLine(vbCrLf & txt & vbCrLf)
                        Else
                            Console.WriteLine("Strange. I did not expect 0 bytes.")
                        End If
                    End Using
                End Using
            End If
    
            prev = ""
            Console.WriteLine("I've finished with that connection, now I'm waiting for another." & vbCrLf)
            TCP_Ears.AcceptTcpClientAsync().ContinueWith(AddressOf WhenAConnectionHappens)
        End Sub
    
    End Module
    I still have a long way to go I fear...
    I still have to make both these two app.s work in a single app.
    I still have to get them working together in a Windows Forms app.

    Along with others, you've mentioned firewalls a few times, my Win.10 machine seems not to bother with these app.s but the Win.7 machine asks if it's to allow the client to run, it asks once every time I modify the code, when the crc changes I guess.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  4. #44
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    What you wrote looks good! I skimmed over it, and I think you did a pretty good job. I think you'll be happy to see that some of your "long way to go" things are addressed in this next iteration. In particular, I designed this one to work as a single app! I'm sorry it took so long, but it's hard to describe just how busy I've been at work lately. This paragraph I just wrote, the rest of the tutorial I pasted under, so sorry if it clashes a little!

    The purpose of this post is to make a little chat application. Both "server" and "client" will be in the same console application this time, and I've attached it as a file. Each "side" of the application will take turns sending a String to each other. This is interesting because we have to think about:
    • Sending and receiving data more complicated than "just bytes".
    • Dealing with not knowing how many bytes we expect to receive.

    Why it's not so easy...
    Normally, if you're working with String data from a file, it's convenient to use StreamReader to read the data. Unfortunately you have to think very hard before using StreamReader safely with network data (at least, not synchronously.) Why?

    Both ReadLine() and ReadToEnd() will block their calling thread until they find the thing that means "the end" to them. For many reasons, this may never happen in a networked scenario:
    • "The end of the file" in this case is "the connection closes". But if the other side wants to leave the connection open, that never happens, so ReadToEnd() waits forever.
    • "The end of the line" only happens if the other side sends some kind of line ending characters. If the other side doesn't send those, ReadLine() waits forever.
    • Something may go wrong and you may not get all of the data/it may be taking a long time. This can cause ReadToEnd() and ReadLine() to take much longer than you want.

    A lot of those problems can be mitigated with timeouts and asynchronous code, but for now we're going to deal with them by "avoiding them".

    That means to read a String, we have to read some number of bytes, then use an Encoding to convert those bytes to String. A lot of people use AsciiEncoding for this, but it's more forward-looking to use UTF8Encoding.

    The next problem...
    If users are typing strings at us, we don't know how big the strings are going to be! If we're going to use Read(), we have to know how many bytes are going to be arriving. This is a problem! There are a few ways to solve it.

    We could say we always send some fixed amount of bytes, maybe 512. That's wasteful and sloppy, but gets the job done. We could say we always end the String with some sequence of characters like CRLF. That makes us have to behave a little bit like ReadLine(). What I'm going to do in this example is a third alternative: I will design a protocol.

    In this protocol, the data will always start with 1 byte that indicates the length, in bytes, of the string to follow. So "hello" would come through as "5 ?? ?? ?? ?? ??", where the ?? are the bytes for each letter.

    Finally!

    Since both "sides" of the application have to act like a client, we'll write the least code if we make ONE console application that can decide whether it will be a client or a server. So the workflow for this application is:

    You start the side that you want to be the server. It will ask if you want to be client or server, and you should tell it "server". It will start listening for a connection, and when it gets a connection it will enter "client mode" which I will detail later.

    Next, you start the side you want to be the client. It will ask what you want, and you will tell it to be a client. It will ask for a server IP, then connect to that server. Once it connects, it will enter "client mode".

    (The following is sort of stupid in terms of a chat app, but we can fix how janky this is in the next version, when we work with asynchronous code!)

    In "client mode", the program is either waiting for a message, or waiting for you to type a message to send. The "server" will always start as the one that wants to send first, and the client will always start as the one that wants to receive first.

    When you are in "client mode, sending", the application waits for a Console.ReadLine() to finish, then sends the message to the other side, then enters "client mode, receiving".

    When you are in "client mode, receiving', the application waits for a Read() call to finish, interprets the bytes as a String according to our protocol, then displays the string and switches to "client mode, sending".

    So yes, you can't interrupt someone. You have to wait for them to send you a message before you can send another one. Also: let's say the application stops when one side or the other sends the message "/quit". Got it? Let's get to work. I'm going to decompose into more subs and methods this time because it will make it easier to see how things work individually.

    Remember, there is only one program this time! We're going to choose between "server" and "client" at startup! I'm going to attach the code this time, so I can talk about the code in bits and pieces more easily.

    The Main() method doesn't really have anything interesting in it. It decides between calling StartAsServer() or StartAsClient(). If you don't type a 1 or a 2, it quits. Error handling's hard, and this is an example. Honestly neither StartAsServer() or StartAsClient() are interesting either. The Server starts a TcpListener and waits for a connection, the Client connects to the IP you give it. If you type something that's not an IP, the program probably crashes. Don't do that. If you type the wrong IP it might take a long time to tell. Don't do that.

    Everything that Main() calls eventually enters a loop. When that loop is finished, the user has asked to quit. So at the end of Main(), the TcpClient in _remoteConnection is closed. Where did it get created? Further down. The passage of time is weird in programming files, isn't it? Technically these lines are at the "end" of the program, even though they are "higher" than the rest of the file!

    Both StartAsServer() and StartAsClient() stash their TcpClient in the _remoteConnection field. When you want to keep a connection open and use it for a long time, you need to store it somewhere. Do not Close() or Dispose() any stream you get from GetStream()! If you close that stream, you are technically closing the connection! We can't close or dispose that Stream or the TcpClient until we are finished with it!

    Life isn't even very interesting in ConversationLoop(), which takes a String to tell it if it's starting in "send" or "listen" mode. All it does is call either SendAMessage() or ReceiveAMessage(), then change modes until something changes the _shouldQuit flag. It's not the most elegant way to build the application, but EXAMPLE.

    Both SendAMessage() and ReceiveAMessage() have to look for the string "/quit". At first, I only had SendAMessage() do that. But it turned out things got ugly for the other side if I abruptly disconnected with no warning. So I decided to update our "chat protocol" such that if the program receives the exact string "/quit", it takes that as a sign the other person is disconnecting.

    It's not until the very bottom, 100 lines or so in, that we FINALLY get to the parts of our program that do things! This is why I hate UI, even console UI: it takes a dreadful amount of work to talk to the user!

    Let's look at SendAString() without its debugging statements:
    Code:
    Private Sub SendAString(ByVal message As String)
        ' First, convert the string to bytes. The length is "number of bytes", not "number of characters".
        Dim rawData() As Byte = Encoding.UTF8.GetBytes(message)
        Dim dataLength As Byte = CByte(rawData.Length)
    
        ' Do not dispose the stream since we are holding the connection open!
        Dim outputStream As Stream = _remoteConnection.GetStream()
        outputStream.WriteByte(dataLength)
        outputStream.Write(rawData, 0, dataLength)
    End Sub
    When a string is sent in our protocol, it starts with a length byte. That length has to be the "number of bytes in the string", not the "length of the string in characters". Some might argue, "Well that doesn't matter in a console application!" No, it always matters. The truth is there's no good reason to ever assume 1 Char = 1 Byte, and we have to ask an encoding to tell us how many bytes there are. ASCII is the original encoding, but the way it handles things like accented characters or more modern developments like Emoji is very difficult to deal with. When you need an encoding, you should be using UTF-8 whenever you can. Some people disagree, but those people are going to have a hard time supporting important things like "What if my Japanese friend wants to send me a message in Japanese?" and less important things like, "I want to send an Emoji".

    So I ask Encoding.UTF8 to give me the bytes for the string. Incidentally, that lets me know how many bytes there are. If you ever want JUST the number of bytes, you can use GetByteCount(), but generally you want the bytes.

    I could've packed everything into one big array, or written two separate arrays. It turned out to be easier to call WriteByte() for the length byte, then Write() for the String data. Life would've been more complicated if I had a "length Integer", since that's 4 bytes. Odds are you aren't typing strings longer than 255 characters in this example, so I stuck with 'easy'. If you type a really long string, it will probably crash. So don't do that.

    Writing arbitrary-sized data is easy. Reading's always the scary part. So let's look at ReceiveAString():
    Code:
    Private Function ReceiveAString() As String
        Dim inputStream As Stream = _remoteConnection.GetStream()
        Dim dataLength As Byte = inputStream.ReadByte()
    
        If dataLength = 0 Then
            Return ""
        End If
    
        Dim rawData(dataLength - 1) As Byte
        inputStream.Read(rawData, 0, dataLength)
    
        Dim message As String = Encoding.UTF8.GetString(rawData)
        Return message
    End Function
    Since we know a length byte always comes first, I call ReadByte() first. I want to dispel something that seems like a connection but isn't: I didn't choose this implementation because SendAString() calls WriteByte(). The Stream doesn't really care whether I sent data in pieces or all at once. It's possible if I send 1 byte of length plus 30 bytes of String, I'll receive 31 bytes all at the same time. Or 5, then 15, then another 10. Or 30-different 1-byte packets. Part of TcpClient's job is to look at how many bytes we ask for in Read() and keep receiving data until it has as much as we asked for. It doesn't care if it comes all at once, or later.

    Anyway, armed with the Length, I know how many bytes I want to ask for. But sometimes the length is 0. This happened by accident when I didn't change windows between messages: the console tends to "remember" keypresses between ReadLine() calls. This caused my program to get stuck, because apparently if you try to Write() 0 bytes, the computer doesn't actually write any data. That makes sense. But when I tried to Read() 0 bytes, that didn't go so well. You'd think it'd return immediately, but for some reason my program got stuck. I could've debugged it, but I decided to do something easier.

    In terms of the example, it was easier to find a way to receive 0-length messages safely. If the length comes in as 0, I don't bother with the second Read(). There's some messages in the actual code about it, but they weren't important here. So if we do get a length that isn't 0, we try to read the number of bytes we asked for. When we get those bytes, we convert them to a String and return it so it can be displayed. If it was '/quit', both programs will exit their "ConversationLoop()" and close their connection.

    There's still some things that could go wrong. While poking around at some fun "homework", I discovered some really strange behaviors you can consistently cause if you make the "sender" lie about the size. I think it's confusing to try and talk about it at this point, and it's been a long time since I posted some code.

    Next post: I learned a good bit yesterday that feels like worth mentioning, at some point. I think I'll commit to this order: "asynchronous" -> "Windows Forms" -> "Error Handling". Error handling always clutters the code when I'm trying to teach things, so I think it's appropriate to wait.
    Attached Files Attached Files
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  5. #45

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Hi,

    Thanks for this code... I think I've got my head round most of it.
    I've been 'playing' but haven't made anything worth writing home about.

    Being very new to console stuff, I find it difficult to write code which doesn't fail for one reason or another.
    The most usual reason being 'Can't do that here... that control isn't in here'.

    On a more positive side, I'd not come across this type of coding before:
    Code:
    Dim test As String = String.Format(" {0} and {1} went up the hill...", "Jack", "Jill")
            test += vbCrLf
            test += String.Format("... to fetch a {1} of {0}.", "water", "pail")
            Label1.Text = test
    which isn't console stuff I know, but I'm now confident that I understand how it works there too.
    I've seen it before of course but it always looked to me like some flavour of C.
    In 'console' mode it's also handy to be able to use:
    Code:
     Dim test As String = Console.ReadLine()
    to retrieve data, I think it's a shame I can't use that in a Win. Form app. (Well, not easily).



    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  6. #46

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Hi,

    For my own app. I've been playing with finding which other computers are on my router's LAN, and with finding my own IP address, in readiness maybe for when I've got a complete understanding of what's involved with passing data between two laptops.

    My Form1 has 1 Button, 1 Label and 1 ListBox.
    Code:
    Imports System.IO
    Imports System.Net
    Imports System.Net.Sockets
    
    Public Class Form1
    
        Public myID, player As IPAddress
        Public HostName, MyName, palName As String
        Public MyIDnum As Int32
    
        Private Sub Form1_Load() Handles MyBase.Load
    
            Label1.Text = "Seeking connections"
            Button1.Text = "Exit"
            Me.Show()
            Me.TopMost = True
            Me.Refresh()
            WhoAmI()
            Seek()
        End Sub
    
        Private Sub Button1_Click() Handles Button1.Click
            Application.Exit()
        End Sub ' Exit.
    
        Private Sub Lbx1_Changed() Handles ListBox1.SelectedValueChanged
            Dim LbxS As String = ListBox1.SelectedItem
            Dim Splt() As String = Split(LbxS)
            Dim Lnth As Int32 = Len(Splt(0)) - 1
            Dim Adr As String = Splt(0).Substring(0, Lnth)
    
            ListBox1.Dispose() ' Won't need this again.
            player = IPAddress.Parse(Adr)
            palName = Spt(1)
            Label1.Text = palName & " selected"
    
        End Sub ' Selection made.
    
        Private Sub Seek()
        ' NOTE:  Increase the number of channels to try in Seek !
        '              5 is enough for now,  searching an empty channel takes a long while.
        '	  Only channels 2 have my laptops. Increase to 10 ought to be enough.
        '	  Channel 0 doesn't exist, and Channel 1 is the router.
        
            Dim ipa As String
    
            ListBox1.Items.Clear()
            For i = 2 To 5
                ipa = "192.168.0." & i.ToString
                If i <> MyIDnum Then
                    Try
                        MyName = System.Net.Dns.GetHostEntry(ipa).HostName.ToString
                        ListBox1.Items.Add(ipa & ". " & MyName)
                    Catch ex As Exception
                    End Try
                End If
                Label1.Text = "Seeking connection " & i + 1.ToString
                Me.Refresh()
            Next
            If ListBox1.Items.Count > 0 Then
                Label1.Text = "Please select"
            Else
                Label1.Text = "Sorry..." & vbCrLf & "No other computers found."
            End If
    
        End Sub ' Find other computers.
    
        Private Sub WhoAmI()
            Dim ad As New List(Of String)
            ad.Clear()
            HostName = Dns.GetHostName()
            Dim host As IPHostEntry = Dns.GetHostEntry(HostName)
            Dim ip As IPAddress() = host.AddressList
            Dim i As Int32
            For i = 0 To ip.Length - 1
                myID = ip(i)
            Next
            Dim ID As String = myID.ToString
            Dim spt() As String = Split(ID, ".")
            MyIDnum = Val(spt(3))
        End Sub ' Get my name and address.
    
    End Class

    Poppa.


    PS: See post #69.
    Last edited by Poppa Mintin; Sep 10th, 2017 at 05:43 PM.
    Along with the sunshine there has to be a little rain sometime.

  7. #47
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    ((Sorry it's been a while. I've been under a hurricane, my apartment wall is leaking water, and work is terribly busy. It'd be a lie if I said I hadn't drained an entire bottle of vodka, and this is Texas so we sell it in "multiple Liters".))
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  8. #48

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    ((Sorry it's been a while. I've been under a hurricane, my apartment wall is leaking water, and work is terribly busy. It'd be a lie if I said I hadn't drained an entire bottle of vodka, and this is Texas so we sell it in "multiple Liters".))
    Oh dear that doesn't sound too good. Not sure I'd swap a hurricane for the earthquake we survived on Kos a few weeks ago.

    So... Texas: GMT -5 hours, -6 at the moment due to daylight saving, if Texas too is in daylight saving then I guess it's (more or less) permanently UK time -6.
    You can imagine I've been checking my emails fairly regularly since your last installment.



    Poppa.
    Last edited by Poppa Mintin; Aug 31st, 2017 at 10:53 AM.
    Along with the sunshine there has to be a little rain sometime.

  9. #49

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    I've been under a hurricane
    Hey! That'a a bit of an understatement ain't it!
    The news and the pictures have arrived on British TV and it looks horrific. The Houston area is a disaster if ever I saw one.

    I hope it all gets sorted out in double quick time. In the meantime let's hope it gets no worse, especially in your neck of the woods.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  10. #50

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    ((Sorry it's been a while. I've been under a hurricane, my apartment wall is leaking water, and work is terribly busy. It'd be a lie if I said I hadn't drained an entire bottle of vodka, and this is Texas so we sell it in "multiple Liters".))
    Hi,

    I wonder if there was supposed to have been something else with this post.
    It reads as though you've posted the next installment.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  11. #51
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    No, that was just an "I'm still alive". I'm actually really struggling with the next post. Here's why.

    Asynchronous programming is easy to do in Windows Forms, but you need to understand a lot of little things to get there.

    I can explain "asynchronous programming" in general in 4-5 paragraphs just fine. I'd really like to use the Task-based API to show you examples in Windows Forms. But that means I start writing a "quick" tutorial about the Task API. Every dang time I've done this, it ends up sprawling over pages, because I end up going into deep detail, because you often need a lot of the 'corners' of this API. That means before I even get to writing code examples, I feel like the post is too big to "fit" in your head.

    So help me out here. Have you ever messed with the Task API? Or Async/Await? Would you be too lost if I just used the task-based asynchronous APIs without much explanation? I haven't really tried "explaining as I go along" yet.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  12. #52

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    So help me out here. Have you ever messed with the Task API? Or Async/Await? Would you be too lost if I just used the task-based asynchronous APIs without much explanation? I haven't really tried "explaining as I go along" yet.
    Thanks for the rapid reply.

    I understand the problem, and the snowballing effect of trying to write textbook examples.

    I fear I'm going to have to burn a lot of midnight oil before I can fully get to grips with this question. To make matters worse, I've barely touched Threading, let alone Task, although I've seen Task in code, I thought it might be part of the Wait method. I don't believe I've seen, or at least not taken notice of 'Async/Await'.

    By all means let's see if I can get by without much explanation, if I get stuck I'll say so only after I've researched any problem first. I've been trying to do that anyway.
    You've already given me a load of research to do, so a bit more won't kill me, and if it keeps your input time down that's got to be a good thing.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  13. #53
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    See, the trick with the Task API is it's extremely powerful, and much easier to use than every other approach to asynchronous code... after you've studied a lot of its surface area. Async/Await is a really good language feature, but it's about on par with default instances in terms of "can shoot your foot off if you don't know a lot about what happens behind the scenes".

    It's probably easier for me to explain it in terms of the older IAsyncResult pattern, ugly as it is. I don't feel *as* obligated to go into as much detail because, well, you don't really have a lot of freedom with it. It works the way it works and if you don't like it, tough. That's part of why MS is moving towards Tasks, they give YOU a lot more control over how the code operates. That makes them more complex.

    Anyway, for reference, too, how comfy with lambdas are you? Does this line confuse the heck out of you? Be honest, it's no big deal if it does, but it saves me some effort to know:
    Code:
    Task.Run(Sub()
                DoSomething()
             End Sub)
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  14. #54

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    Anyway, for reference, too, how comfy with lambdas are you? Does this line confuse the heck out of you? Be honest, it's no big deal if it does, but it saves me some effort to know:
    Code:
    Task.Run(Sub()
                DoSomething()
             End Sub)
    I've not come across lambda before (Except as the SI unit for Wavelength), there is however a very clear explanation in MSDN.

    There is also a comprehensive (but less clear) description of Task.Run.

    I shall be re-reading them tomorrow when I'm more awake. It's midnight here, and I'm going to have an early night for a change.


    Poppa.


    PS, I trust the water's receding !

    Pop.
    Along with the sunshine there has to be a little rain sometime.

  15. #55
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    I got the code mostly done, then QA filed me a lot of bug tickets. It turns out you don't need to know much about lambdas, and it turns out I didn't know as much as I thought I did about the "old" asynchronous patterns. Don't wear yourself out reading about Tasks tonight, there's always another day for that! Lambdas though... they're pretty dang useful.

    The quick take is "they are a shortcut for when you need a delegate but are too lazy to go write a full-fledged method". So you can either:

    Code:
    Sub DoSomething()
        Task.Run(New Action(AddressOf DoWork))
    End Sub
    
    Sub DoWork()
        ...
    End Sub
    Or you can:
    Code:
    Sub DoSomething()
        Task.Run(Sub()
            ...
        End Sub)
    End Sub
    You see it a lot in LINQ but it's spreading through many APIs.

    Also: here in Austin we only got something between 4-10" of rain depending on locality. There's some people who are now aware they had roof issues, and there was some minor flooding, but we've had it far worse in the past. Houston took a beating, and lots of the coastal towns got wiped off the map. It's going to take them years to recover.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  16. #56

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Hi, Pleased to hear you're not actually in the thick of it.

    I'm impressed with what I've learned concerning Lambda Expressions, despite having to turn off Option Strict.
    I've been playing a little...
    Code:
    Option Strict Off
    
    Public Class Form1
    
        Private Sub Form1_Load() Handles MyBase.Load
            Button1.Text = "Exit"
            Button2.Text = "Test 1"
            Button3.Text = "Test 2"
            ListBox1.Items.Clear()
    
            AddHandler Button2.Click,
                Async Sub(sender2, e2)
                    Button3.Enabled = False
                    ' ExampleMethodAsync returns a Task.  
                    Await ExampleMethodAsync()
                    Button3.Enabled = True
                    ListBox1.Items.Add("Control returned to Test 2 button.")
                End Sub
        End Sub
    
        Private Sub Button1_Click() Handles Button1.Click
            Me.Close()
        End Sub
    
        Private Sub Button2_Click() Handles Button2.Click
            Dim increment1 = Function(x) x + 1
            Dim increment2 = Function(x)
                                 Return x + 2
                             End Function
            ' Write the value 2.
            ListBox1.Items.Add(increment1(1))
            ' Write the value 4.
            ListBox1.Items.Add(increment2(2))
            ' Write "Hello".
            ListBox1.Items.Add("Hello")
            ' Write "World"
            ListBox1.Items.Add("World")
            ' Write the value 6.
            ListBox1.Items.Add((Function(num As Integer) num + 1)(5))
            ' The following line will print Success, because 4 is even.
            testResult(4, Function(num) num Mod 2 = 0)
            ' The following line will print Failure, because 5 is not > 10.
            testResult(5, Function(num) num > 10)
        End Sub
    
        Async Sub Button3_Click() Handles Button3.Click
            ' ExampleMethodAsync returns a Task.  
            Button2.Enabled = False
            Await ExampleMethodAsync()
            Button2.Enabled = True
            ListBox1.Items.Add("Control returned to Test 1 button.")
        End Sub
    
        Async Function ExampleMethodAsync() As Task
            ' The following line simulates a task-returning asynchronous process.  
            Await Task.Delay(5000)
        End Function
    
        Sub testResult(ByVal value As Integer, ByVal fun As Func(Of Integer, Boolean))
            If fun(value) Then
                ListBox1.Items.Add("Success")
            Else
                ListBox1.Items.Add("Failure")
            End If
        End Sub
    
    End Class
    Only a simple little test, 3 Buttons and 1 ListBox1, mostly more or less direct copies of MSDN examples but I think it demonstrates to me how Lambda Expressions and an Async function work.

    I reckon it's looking promising for the little project which started this thread in the first place.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  17. #57
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    Ha, good to see you're picking it up. (But you don't have to turn off Option Strict to use them. See the other thread!) I went ahead and wrote a version without lambdas already. It might be fun to take this version later and talk about how it looks when you use the Task API instead. Let's just get moving again though.

    "Asynchronous programming" is the fashionable word for what a lot of people still call "multithreading". Today, we don't like to think too hard about the "threading" side of it, and following certain practices means we don't have to think as hard as we used to when working with many threads. The less we have to think, the more successful we tend to be.

    Most asynchronous patterns make you split your code into 2 parts. The "start" and the "when that finishes". So if you think about doing laundry, we might express it in code this way:
    Code:
    Public Sub StartLaundry()
        MyWasher.Load()
        MyWasher.Start()
    End Sub
    
    Private Sub WhenWasherFinishes() Handles MyWasher.Finished
        MyWasher.Unload()
        MyDryer.Load()
        MyDryer.Start()
    End Sub
    
    ...
    The Start() method, in this case, does the laundry on another thread. While it's busy, we're free to do whatever we want in the UI. When it finishes, it will raise an event and execute the code we've set aside for it.

    There are three patterns for asynchronous code in .NET, and unfortunately the networking APIs support the most complicated two. One is complex because it's old and clunky, the other is complex because it's deep and powerful. The one I wish it supported looks like the code sample above. But alas. We have one more thing to think about.

    Windows Forms and Threads
    Windows Forms applications start with one very special thread. We call it "the main thread" or "the UI thread". It is only safe to manipulate controls from THIS thread. If you do so from any other thread, sometimes the program crashes. Other times nothing happens. It's best to just do the right thing.

    Part of the reason I like the other 2 asynchronous patterns is they have ways for you to say, "Please make sure the event always happens on the UI thread". In the pattern we have to use, there's no way to say that. So we know our "when finished" code is always on the wrong thread and we need to do a ritual to fix that.

    You can tell if you're on the right thread by checking the control's InvokeRequired property. This is one of two safe things to do from any thread. If it returns True, you need to "marshal" the code to the UI thread.

    That ritual involves the control's Invoke() method, the other safe thing to call from any thread. This method accepts a delegate and will schedule that delegate to be executed on the UI thread.

    It's a pain in the butt to check every time, so people usually write helper methods so they don't have to think about it. I like these methods because they also help me understand where my "boundaries" between my threads are. The idea behind these methods is best expressed in pseudocode:
    Code:
    * If I'm not on the correct thread:
        * Call myself on the correct thread.
    * If I'm on the correct thread:
        * Do the thing.
    For example, here is a method that safely updates a Form's Text property from any thread:
    Code:
    Private Sub UpdateFormText(ByVal newText As String)
        If Me.InvokeRequired Then
            Me.Invoke(Sub() UpdateFormText(newText))
        Else
            Me.Text = newText
        End If
    End Sub
    The IAsyncResult Pattern
    This is the oldest asynchronous pattern in .NET, but relatively easy to talk about. Types that use this pattern follow a few rules.

    The "Start" method is always named "Begin????()". It is always a function and it always returns an object that implements IAsyncResult. That object can be used to get some information about the stuff that's happening on another thread, but often you don't mess with it.

    USUALLY (it's not required) that method takes some delegate. This delegate is usually AsyncCallback, a Sub that receives an IAsyncResult parameter. This callback will be called from the other thread, so once we get past here we have to be careful not to update controls.

    The callback recieves the same IAsyncResult the 'Begin' method returns. There will be a method named "End????()" that corresponds to the 'Begin' method you called, and it wants to receive that IAsyncResult and possibly other parameters. It might return a value. After that finishes, you'll have the results you need and can move on.

    So if our laundry example used this pattern, it might look something like this:
    Code:
    Public Sub StartLaundry()
        MyWasher.Load()
        MyWasher.BeginWashing(AddressOf WhenWasherFinishes, Nothing)
        UpdateFormText("The washer has started...")
    End Sub
    
    Private Sub WhenWasherFinishes(ByVal token As IAsyncResult)
        Dim wetClothes = MyWasher.EndWashing(token)
        UpdateFormText("The washer has finished!")
    
        ' Maybe start the dryer...
    End Sub
    I find it a little clunky, but it works. I like to call the IAsyncResult a "token" becuase it kind of represents the thread that runs the work. A better name would be "task" but now that's become an actual type!

    Note that the 2nd parameter to the "Begin" method I left as Nothing. You can put any object there, and it will end up as the AsyncState property of the IAsyncResult your callback gets. Remember this. It will be important later.

    Also note that WhenWasherFinishes() isn't called when the work is done, it's called when the worker thread is available. The EndWashing() method here blocks, just like a synchronous method, but since we're on a worker thread that doesn't upset us. We can't officially declare the clothes washed until after EndWashing() returns!

    I think with that teeny bit of introduction, we can do SOMETHING in Windows Forms asynchronously and keep our UI responsive while we also wait for network I/O on other threads. Since it's WinForms, I have to attach a whole dang project. And if you're not using VS 2017... you'll have to figure out how to get the form in a project in your version.

    The Example
    The UI is simple, and it's easy to screw up in a way that busts things.

    The big text box is where chat messages will appear. Beneath it is the input field, if you click "Send" with a message there while you're connected the text will be sent.

    We're not doing error handling yet! If you push the "Connect" button while the "Server" radio button is selected, the form will start as a server. If you push the button while "Client" is selected, the form will expect you to have put an IP address in the bottom text box. This is terrible UI, but it's just an example

    Have a look at StartServer(). It isn't actually very interesting! If it were synchronous, it'd just call AcceptTcpClient(). Now, it calls BeginAcceptTcpClient() and indicates WhenServerGetsAConnection() should be called when that finishes. Note how I passed the listener from StartServer() to WhenServerGetsAConnection() via the state parameter! When it's done, it heads to the ListenToClient() method, which we'll discuss later.

    ConnectToServer() is very similar: instead of synchronous Connect(), we have a BeginConnect() -> callback -> EndConnect() chain. It also ends up in ListenToClient().

    Let's ignore ListenToClient() right now and talk about what happens when you send a message. Asynchronous writes have a slightly easier API to work with.

    Writing

    When you click the "send" button, it first checks to make sure you typed some text. This is the easiest way to stop problems that might happen if we send zero-length strings (though it's not as big a deal this time.) It makes a call to WriteString(), which is waaaaaaay down at the bottom of the file.

    The best way to start is to talk about how the NetworkStream.BeginWrite() function works. It has a long definition, but should look familiar:
    Code:
    Function BeginWrite(
        data As Byte(),
        offset As Integer,
        length As Integer,
        callback As AsyncCallback,
        state As Object) As IAsyncResult
    The first three arguments are the same we sent to the synchronous Write(): the buffer we want to write, and an offset/length in case we don't want to send the whole thing. The new 'callback' parameter will be called later, and the 'state' parameter will be stored in the IAsyncResult that is returned as the AsyncState.

    BeginWrite() has a LOT of text in its documentation. The most important thing it points out is you're going to have to call EndWrite() in the callback, so you should generally pass AT LEAST the stream along as the state. I did a little bit more, because I have some requirements in my callback:

    After the write is finished, I want to display the string on the sender's side. So I want that string to come along for the ride, too! That's why I made the class AsyncWriteState. It will store the NetworkStream and the Message associated with a particular call to BeginWrite() so that I can use it in the callback.

    With all that in mind, let's look at the pair of methods:
    Code:
    Private Sub WriteString(ByVal clientStream As NetworkStream, ByVal message As String
        Dim data() As Byte = System.Text.Encoding.UTF8.GetBytes(message)
        Dim writeState As New AsyncWriteState() With {
            .Message = message,
            .Stream = clientStream
        }
    
        Dim buffer() As Byte = {lengthByte}.Concat(data).ToArray()
    
        clientStream.BeginWrite(buffer, 0, buffer.Length, AddressOf WhenWriteFinishes, writeState)
    End Sub
    
    Private Sub WhenWriteFinishes(ByVal token As IAsyncResult)
        Dim state As AsyncWriteState = token.AsyncState
        state.Stream.EndWrite(token)
    
        AppendMessage("< " & state.Message)
    End Sub
    WriteString() sets up the data to write and the state it needs, then calls BeginWrite(). That causes WhenWriteFinishes() to get called on a worker thread. The first thing WhenWriteFinishes() has to do is check the AsyncState property to get its AsyncWriteState. Next, it calls EndWrite() with the token it got.

    (If you're confused by the part that creates buffer(), here's a quick explanation
    Code:
    Dim buffer() As Byte = {lengthByte}.Concat(data).ToArray()
                                ^          ^            ^ A LINQ method that converts
                                |          |            | IEnumerable to an array
                                |          |
                                |          | "glues" two sequences together, for example:
                                |          | { 1, 2 }.Concat({ 3, 4 }) = { 1, 2, 3, 4 }
                                |
                                | "Please make a new array containing just lengthByte."
    
    So, read left to right:
    "Create an array that has lengthByte in it. Put it at the front of a collection with
     the array 'data' at the end. Convert that collection to an array."
    It's actually important to note this call will block if the write isn't finished! I used to think "the callback is called when BeginWrite() finishes." It turns out it's called immediately, on a worker thread. So it's OK that it blocks, but if you're logging a "finished!" message, it's too early to do so before you call EndWrite()!

    Since a write doesn't return until it writes ALL data, EndWrite() doesn't have to tell us how much data it wrote. So it's a Sub. When it finishes, AppendMessage() puts the message in the chat control. If you have a peek at AppendMessage(), you'll see it does the work to make sure this happens on the right thread. Convenient!

    Reading
    Now that you've seen writing, reading is easier to discuss. Let's start by looking at the BeginRead() method:
    Code:
    Function BeginRead(
        buffer() As Data,
        offset As Integer,
        length As Integer,
        callback As AsyncCallback,
        state As Object) As IAsyncResult
    Just like BeginWrite(), it's the same as Read() with the callback and state parameters glued on the end. But there's something slightly different going on here if we look at EndRead():
    Code:
    Function EndRead(token As IAsyncResult As Integer)
    Just like Read(), it returns the number of bytes it read. Just like Read(), it'll sit there forever while there's 0 bytes to read. And just like Read(), as soon as it gets any data, it goes ahead and returns. That might not be ALL of the data, so we have to rely on our protocol to tell us when it has all of it. So just like Read(), we might have to call several cycles of BeginRead() and EndRead() before we get all of our string.

    That means the state class has to keep track of our offset and how many bytes we expect:
    Code:
    Private Class AsyncReadState
        Public Property Buffer() As Byte()
        Public Property LastOffset As Integer
        Public Property Stream As NetworkStream
    
        Public Property ProtocolLength As Byte
    
        Public Sub New(ByVal bufferSize As Integer, ByVal stream As NetworkStream)
            ReDim Buffer(bufferSize - 1)
            Me.Stream = stream
            ProtocolLength = 0
        End Sub
    
    End Class
    ProtocolLength starts as 0, if it's 0 then our Read() code expects to be able to read 1 byte to determine a length.

    So reading is a bit tricky. To pull it off, we have to:
    • Set up an AsyncReadState with a new buffer, an offset of 0, and a protocol length of 0.
    • Call BeginRead() and pass along that state.
    • When the callback is called, wait for EndRead(), then check for a protocol length if we don't already have one.
    • If we got all the bytes already, great! We're done.
    • If we didn't get all the bytes yet, we need to call BeginRead() again with the CURRENT state, which will send us back to (3).


    Now we can talk about ListenToClient(). All it does is set up our very first AsyncReadState and start the read. We don't know when the client will send us data, but we have to call BeginRead() if we want to be ready. I picked a buffer size of 1024 bytes because in the example, if you go and try to write more than 1,000 characters you get what you deserve.
    Code:
    Private Sub ListenToClient(ByVal remoteClient As TcpClient)
        Dim readState As New AsyncReadState(1024, remoteClient.GetStream())
        StartRead(readState)
    End Sub
    StartRead() takes some AsyncState and calls BeginRead() using that information. It's more convenient than typing all of that stuff out everywhere.

    Let's take WhenSomeDataArrives() bit by bit.

    Code:
    Private Sub WhenSomeDataArrives(ByVal token As IAsyncResult)
        Dim readState As AsyncReadState = CType(token.AsyncState, AsyncReadState)
        Dim bytesRead As Integer = readState.Stream.EndRead(token)
    This is how the start of almost all callbacks goes. Grab the state, call EndRead().
    Code:
        If readState.ProtocolLength = 0 Then
            readState.ProtocolLength = readState.Buffer(0)
        End If
    If the state's ProtocolLength is still 0, we either got a bad message or we haven't read the length yet. I think this could crash if EndRead() returned 0 bytes, let's forget about that case for the sake of examples. If we have any bytes at all, we know the first one SHOULD be the length of the rest of the bytes in the total data set.

    Keep that in mind! If we are getting 50 bytes of String data, we will have to read a 51 byte array since the length is in front.

    In most cases, we'll probably already have that, but just in case we're on the 2nd or later iteration, we can't assume the array offset is 0:
    Code:
        Dim totalLength As Integer = readState.LastOffset + bytesRead
    On the first iteration, this is always kind of silly. "We read 50 bytes, the offset was 0, so we have 50 total bytes." But let's say we got 25 bytes last time, and we got 25 bytes this time. Now we're saying, "We read 25 bytes, but we were starting at 25, so we have 50 total bytes now." Got it?

    The best-case is we've got all the bytes we need right now:
    Code:
        If totalLength = readState.ProtocolLength + 1 Then
            Dim message As String = System.Text.Encoding.UTF8.GetString(readState.Buffer, 1, readState.ProtocolLength)
            AppendMessage(">" & message)
    REMEMBER if we expected 50 bytes of Strings, we have 51 total bytes! So this 'totalLength' should be ProtocolLength + 1 if we have everything. You are going to make this mistake ten million times when writing networking code, I promise. There are fancy, complex ways to help with this, but that's for later.

    Since we got all of the data, we convert bytes to a String. Note that we skip the length byte by providing an offset of 1! That thing keeps popping up. AppendMessage() will safely update the control on the UI thread for us.
    Code:
            ' This read operation is finished, so start a new one.
            Dim nextReadState As New AsyncReadState(1024, readState.Stream)
            StartRead(nextReadState)
    Having received and displayed the incoming message, the current AsyncReadState is finished. So we make a new one, giving it the same Stream, and start a new BeginRead() loop. THE END.

    OR, if we DIDN'T have all of the data yet:
    Code:
        Else
            ' There are more bytes to read, so reuse the same state.
            readState.LastOffset += bytesRead
            StartRead(readState)
        End If
    
    End Sub
    We update the offset so the state reflects how many bytes we've read so far, then pass it along to the next BeginRead() loop. THE END.

    So we can have a lot of things happening on worker threads at any given time in this application:

    • When a server is waiting for a connection, the "waiting" part happens on a worker thread.
    • When a client is connecting to the server, the "connecting" part happens on a worker thread.
    • When you send a message, the "sending" part happens on a worker thread.
    • After connecting, the program is ALWAYS waiting on a message on a worker thread, and when a message comes in it's processed on that thread.


    The only time we end up doing work in the program is in our "up top" methods: AppendMessage() and SetStatus(). Their ONLY job is to safely update the UI. Since all the "waiting" work is done on worker threads the UI is free to update itself the whole time!

    But boy, is this program crash-prone. If you close one or the other window, the other crashes. If something goes wrong with the network, it crashes. If you paste in this post, it's bigger than 1,024 bytes so the program crashes. How the heck do we deal with all the things that can make it crash?

    I'm putting that off until the next post, because WE JUST WROTE AN ASYNCHRONOUS CHAT CLIENT and there's a lot to process here. Once you see how the error handling goes, we can THROW IT ALL AWAY AND REWRITE IT WITH TASKS! Woooo! That will be interesting, becuase we'll find out if it's any easier. I think, if you understand this, you'll get a grip on tasks a lot better.
    Attached Files Attached Files
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  18. #58

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    Code:
    Public Sub StartLaundry()
        MyWasher.Load()
        MyWasher.Start()
    End Sub
    
    Private Sub WhenWasherFinishes() Handles MyWasher.Finished
        MyWasher.Unload()
        MyDryer.Load()
        MyDryer.Start()
    End Sub
    Hi,

    My! This is a Big 'un !

    I'm making a start but before I go any further, Is there a reason that Sub StartLaundry is Public, whereas Sub WhenWasherFinishes is Private ?

    Probably quite a basic answer (No pun intended) but I don't know the answer.
    If I had to guess, I'd say you want Sub StartLaundry to be visible to maybe a second Form.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  19. #59
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    When you see someone else's class from a library, you can only see its Public things (and sometimes Protected).

    Right now we're looking at the source code for "you". Anyone can say, "Will you please start washing clothes?", and you'll put clothes in the washer and start it. That's why that one's Public.

    But who, besides the washer, should tell you, "The washer is finished?" No one. Only the washer knows when it's finished, and it should be the one to tell you. So it's Private, and it's set up as an event handler.

    If it was public, someone could write code like this and cause you to pull sudsy, still-dirty clothes out of the washer:
    Code:
    you.StartWasher()
    Thread.Sleep(100)
    you.WhenWasherIsFinished() " Ha ha!
    Or maybe they could just waste your time and make you run an empty washer and dryer:
    Code:
    you.WhenDryerIsFinished() ' Put dirty clothes in the closet
    you.WhenWasherIsFinished() ' The washer is empty, move 'nothing' into the dryer and start it
    you.StartWasher() ' The hamper is empty, move 'nothing into the washer and start it
    Generally you want "the things that make sense to call from anywhere" Public, and "things that only get called in certain circumstances/by certain things" Private. Even if you just have one Module. It helps you keep track of which things are 'starts' and which are 'ends'.

    But I also recommend using names with words like "Start" and "WhenXFinishes". That also makes it obvious. In fact, I've started naming ALL of my event handlers things like "WhenOkButtonIsClicked". I find that much easier to locate than "btnOK_Click", and it tends to put all my event handlers in the same place, alphabetically.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  20. #60

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Struggling with this one, not so much the actual exercise itself, more a question of finding time whilst I'm still awake enough to comprehend what you're telling me after SWMBO has finished with me... I bet I've read down to:
    The Example
    The UI is simple, and it's easy to screw up in a way that busts things.
    at least ten times by now.

    And of course the soccer World Cup Qualifiers for 2018 are being played !

    Yes, I'm using VS2017. & Win.10. Your example works as described between the (this) Win.10 laptop and SWMBO's Win.7 laptop.


    Poppa.
    Last edited by Poppa Mintin; Sep 4th, 2017 at 06:22 AM.
    Along with the sunshine there has to be a little rain sometime.

  21. #61
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    Haha, SWMBO is a new one for me. Good luck with that! Double-edged sword, that one.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  22. #62

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    The IAsyncResult Pattern
    This is the oldest asynchronous pattern in .NET, but relatively easy to talk about. Types that use this pattern follow a few rules.

    The "Start" method is always named "Begin????()". It is always a function and it always returns an object that implements IAsyncResult. That object can be used to get some information about the stuff that's happening on another thread, but often you don't mess with it.

    USUALLY (it's not required) that method takes some delegate. This delegate is usually AsyncCallback, a Sub that receives an IAsyncResult parameter. This callback will be called from the other thread, so once we get past here we have to be careful not to update controls.

    The callback receives the same IAsyncResult the 'Begin' method returns. There will be a method named "End????()" that corresponds to the 'Begin' method you called, and it wants to receive that IAsyncResult and possibly other parameters. It might return a value. After that finishes, you'll have the results you need and can move on.
    Situation report:

    I'm really struggling, can't get much past here. The text seems to talk about things I can't find in the example. (Which works properly using the Win.10 and Win.7 machines BTW)

    I'm just confused. I think a lot of it may just be the inability to get a good run at it without interruption at a sensible hour.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  23. #63

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Hi,

    I'm too slow for this stuff... I'd like to go back a step or two... My washing machine is overworked and is now sulking!
    Let's talk about (say) a game of tennis:

    Two guys walk onto the court, shake hands: Connected.
    Guy 1 serves the first ball: Send.
    Guy 2 receives the ball: Received.
    Guy 2 returns the ball: Send.
    Guy 1 receives the ball: Received.
    Guy 1 returns the ball: Send.

    This will continue until one of the rules is broken... (Out of bounds, .Net, Double bounce, etc.)
    I guess this would be synchronous rather than async. because the one guy is waiting for the other each time, They're not playing with two balls.

    I think I can see where the Connection occurrs, but can't see how it's maintained, nor how Received differs from Connected.
    I'm struggling with Send too.

    Also...
    I know you've explained what these two subroutines are doing, but I fail to see how they're doing it:
    vb.net Code:
    1. Private Sub AppendMessage(ByVal message As String)
    2.         If txtChat.InvokeRequired Then
    3.             txtChat.Invoke(Sub() AppendMessage(message))
    4.         Else
    5.             txtChat.AppendText(message & Environment.NewLine)
    6.         End If
    7.     End Sub
    8.  
    9.     Private Sub SetStatus(ByVal status As String)
    10.         If Me.InvokeRequired Then
    11.             Me.Invoke(Sub() SetStatus(status))
    12.         Else
    13.             Me.Text = String.Format("Chat - {0}", status)
    14.         End If
    15.     End Sub
    As this appears in the code above, it's lines 3 and 11 which are the problem.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  24. #64
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    Let's talk about the Subs first. They're easier to explain. I can't tell what is confusing to you, Invoke() or the lambda, so I'll explain both.

    Invoke() takes a delegate as a parameter. A delegate is a variable that "stores" a Sub or Function. Invoke() will transfer control to the UI thread, then execute the delegate. Normally you might call Invoke() like this:

    Code:
    Public Sub FromWorkerThread()
        txtBox.Invoke(AddressOf UpdateTextBox)
    End Sub
    
    Private Sub UpdateTextBox()
        txtBox.Text = DateTime.Now.ToString()
    End Sub
    I'm not 100% sure that syntax works, because the rules about what delegate conversions will VB perform confuse me. The .NET 1.0 long-as-heck way would be:
    Code:
    Private Delegate Sub UpdateTextBoxDelegate()
    
    Public Sub FromWorkerThread()
        Dim workToDo As New UpdateTextBoxDelegate()
        txtBox.Invoke(workToDo)
    End Sub
    
    Private Sub UpdateTextBox()
        txtBox.Text = DateTime.Now.ToString()
    End Sub
    If you call UpdateTextBox() from a worker thread, it won't work. You can't update a TextBox from a worker thread. So you need to find a way to run some code on the UI thread. Invoke() is how you tell VB you want to run some code on the UI thread. The delegate you pass is the "some code". You could write your code in this pattern and ALWAYS use Invoke() if you wanted to.

    But people don't like to do that, and we especially don't like having to make a delegate, an "unsafe" method, and a "safe" method for every kind of UI update we write. To do that, we need to know if we're on the UI thread. So controls have the InvokeRequired property. If it's True, you are NOT on the UI thread, and must use Invoke(). That's the story behind the name. The InvokeRequired property answers the question, "Do I need to call Invoke() to safely update this control?"

    Now, I don't want to do the work of making a Delegate type and all of the different methods, so I'm going to use lambdas to save myself some time. Delegates are a strange kind of type in .NET, because we logically understand that even though their names are different, if their parameter lists and return values are the same they are the same thing. When you see this line:
    Code:
    Delegate Sub UpdateTextBoxDelegate()
    It means "any Sub that takes zero parameters". So let's look at how we can create one out of a lambda:
    Code:
    Dim workToDo As new UpdateTextBoxDelegate(Sub txtBox.Text = "Hello!")
    The constructor to UpdateTextBoxDelegate wants to get "a Sub that takes zero parameters". This lambda has no name, but it's definitely "a Sub that takes zero parameters". But this is an extra step! Look at Control.Invoke():
    Code:
    Public Sub Invoke(ByVal method As Delegate)
    This means "I will take any Sub or Function at all!". Technically, since there's no way to pass parameters, it wants a zero-parameter one. But still, it will take ANY zero-parameter delegate. A lambda can be a zero-parameter delegate:
    Code:
    txtBox.Invoke(Sub txtBox.Text = "Hello!")
    Now let's put it all together. I'm going to reverse the order of the Sub to make the logic flow more like how we tend to think about it.

    We want to write a Sub that will safely update a TextBox with a message from any thread. So we need to write a Sub that takes a parameter:
    Code:
    Sub AppendMessage(ByVal message As String)
    If it is already on the UI thread, it should update the TextBox:
    Code:
        If Not txtBox.InvokeRequired Then
            txtBox.AppendText(message & Environment.NewLine)
    If it is NOT already on the UI thread, we want to call this method again on the UI thread:
    Code:
        Else
            txtBox.Invoke(Sub() AppendMessage(message))
        End If
    End Sub
    The Sub calls AppendMessage() again, and relays the original 'message' parameter that was given. So let's doctor it up with some logging statements and talk about how it works. Console.WriteLine() works in Windows Forms applications, and you can see it in the "Output" window of Visual Studio while the application runs.
    Code:
    Sub AppendMessage(ByVal message As String)
        Console.WriteLine("AppendMessage() was called with '{0}'", message")
    
        If Not txtBox.InvokeRequired Then
            Console.WriteLine("It is on the UI thread! Updating.")
            txtBox.AppendText(message & Environment.NewLine)
        Else
            Console.WriteLine("It is NOT on the UI thread! Invoking.")
            txtBox.Invoke(Sub() AppendMessage(message))
        End If
        
        Console.WriteLine("This is the end of one call.")
    End Sub
    If you call that from the UI thread, you will see:
    Code:
    AppendMessage() was called with 'Hello'
    It is on the UI thread! Updating.
    This is the end of one call.
    If you call that from a worker thread, you will see something like:
    Code:
    AppendMessage() was called with 'Hello'
    It is NOT on the UI thread! Invoking.
    It is on the UI thread! Updating.
    AppendMessage() was called with 'Hello'
    It is on the UI thread! Updating.
    This is the end of one call.
    This is the end of one call.
    First, it's called. Then, InvokeRequired returns True, so the Else branch is executed. So Invoke() is called. Invoke() moves to the UI thread, then calls the lambda. The lambda calls AppendMessage() again. This time it's on the UI thread, so InvokeRequired returns False. That causes the main branch of the If to be taken, and the control is updated. Then, the UI thread AppendMessage() ends. After that, the worker thread AppendMessage() ends.

    If it's still unclear, I need to know more about why. Try to explain to me what you do and don't understand.

    In terms of the networking stuff, I think it's best for me to do that in a different post. Digest this one, it'll be a bit before I can write again.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  25. #65
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    OK. Networking.

    Your tennis court analogy is fine for our original, synchronous example, where people take turns. The answers are similar for async. I don't have the synchronous code in front of me and I'm too lazy to dig it up, so I'll speak conceptually.

    The "connection" is represented by the TcpClient. You could also technically say the NetworkStream is the "connection", but we can get the stream from the client. When you close the stream/client, the connection is broken. If you leave it open, the connection is maintained.

    The "client" programs are the easiest to describe, because they do the connecting. They have to create a TcpClient, tell it which IP and port to connect to, then connect to it. The TcpClientNetworkStream remain the representations of that connection.

    The "server" program has to do a little more work. You start with a TcpListener. When you call AcceptTcpClient() you are saying, "Please wait for a client to try to connect, then give me a TcpClient to represent that connection". From that point on, the server behaves just like the client.

    This remains true when we write asynchronous code. We still have to use a TcpListener for the "Server" to get its TcpClient. The "Client" still directly creates its TcpClient. But now we can both Read() and Write() at the same time.

    In the console, ReadLine() is our easiest way to get input from the user. It blocks until they push enter. That means unless we use more than one thread, we can't receive a message while we're asking the user to give us input. Likewise, we have to call Read() when we want to receive data, but since it blocks we can't take input or send data. We could address these with some crazy tricks, but that's a whole lesson in and of itself.

    So it has to be async, or we're stuck "taking turns" forever. And if your buddy takes a break while it's his turn, you're not going to be able to send a message until they get back.

    Windows Forms makes this a little easier to support because you've got a text box they can type in and take their sweet time to push a button. But if we're using synchronous Read(), we'll have the same problem: while we're waiting for a response, we can't let them type becuase the Read() call blocks. Bummer.

    You could make a Thread that has a loop that calls Read() over and over again forever. That does move it to another thread. But for a lot of reasons that would bloat this post even bigger, it's just not the best idea to do it. We need to call it asynchronously.

    I think maybe the thing you got hung up on is how it loops. You're looking for something like "For" or "While" and not finding it. So how does it read more than once? Let's step back and use an imaginary example. For real. I'm going to facepalm if you say this code doesn't work, because I'm making it up.

    Let's say we're writing code that downloads some files from a list. If we're not worried about threading, we'd find this very easy:
    Code:
    Public Sub DownloadAll(ByVal files() As String)
        Dim downloader As New FileDownloader()
        For Each file In files
            downloader.DownloadFile(file)
        Next
    End Sub
    Eventually, you decide you don't like that the UI sits there for 20 minutes without changing. You'd like to use the asynchronous API of FileDownloader to free up your UI to do other things, like display rad ASCII art of skeletons. But you're perplexed. You can't just write a For Each loop here. How do you pull it off?

    To get there, first let's deconstruct how For Each works. Let's not even use a For loop. It will be most enlightening if we pretend we only have a While loop.
    Code:
    Public Sub DownloadAll(ByVal files() As String)
        Dim downloader As New FileDownloader()
        Dim currentIndex As Integer = 0
    
        While currentIndex < files.Length
            Dim currentFile As String = files(currentIndex)
            downloader.DownloadFile(currentFile)
    
            currentIndex += 1
        Next
    End Sub
    It's the same thing, just more work. Now we have to maintain the index, get the right string from the array, then call the synchronous method. Now let's make a challenge. How do we do this WITHOUT a While loop?

    Well, we could use recursion. This is when a method calls itself. You give it some state, and it quits when something about the state indicates it's done. Think about how this works:
    Code:
    Public Sub DownloadAll(ByVal files() As String)
        Dim downloader As New FileDownloader()
        Dim currentIndex As Integer = 0
    
        DownloadRecursive(downloader, files, 0)
    End Sub
    
    Private Sub DownloadRecursive(downloader As FileDownloader, files() As String, index As Integer)
        ' Stop when we reach the end of the array
        If index >= files.Length Then
            Return
        End If
    
        Dim file As String = files(index)
        downloader.DownloadFile(file)
    
        DownloadRecursive(downloader, files, index + 1)
    End Sub
    Think about how this works. You start in the DownloadAll() method. It gets the list of files. It sets up an index and a file downloader. Then it calls DownloadRecursive(downloader, files, 0). What's that do?

    It decides it has work to do, gets the string at index in the array, then downloads it. Next, it calls DownloadRecursive(downloader, files, 1). That one downloads a file and calls D...e(downloader, files, 2). Do you see where this is going?

    It turns out all we cared about in the loop is that it calls DownloadFile() for files 0, 1, 2, ..., whatever. We want to call that method for each file in the array. We don't need a While loop, we just need some code construct that will make all the right calls.

    Problem: for technical reasons, this recursive "unraveling" of the loop will eventually throw a StackOverflowException. And it's still synchronous, so it's still freezing the UI. So it's trash for our downloader program. But it is going to provide valuable insight.

    Small steps. Always take small steps.

    So we know how to download all the files in an array many differnet ways. We want to learn how to do it async. The right "baby step" is to learn how to do it one way. This will be the second tutorial for the kind of async model that uses Begin/End methods. MS calls this the "Asynchronous Programming Model" or APM.

    Every API that uses the APM has a method that starts with the word "Begin". If the method would be named "Foo", it's named "BeginFoo". It has this name because its job is to find a worker thread and start the work that Foo() would do on that thread, then call a delegate you give it so you can know when Foo() finishes on that thread. It returns a value of type IAsyncResult that can be useful in some situations, but generally you don't care about it yet. It might also take a "state" parameter, if it does then whatever you give it ends up in the AsyncState property of the IAsyncResult. Code speaks more concisely than words. If we have:
    Code:
    Function Add(left As Integer, right As Integer) As Integer
    The Asynchronous version should look like:
    Code:
    Function BeginAdd(left As Integer, right As Integer, cb As AsyncCallback) As IAsyncResult
    See how it takes the same arguments, then a callback, and it returns IAsyncResult? If you call BeginAdd(), it's going to find a worker thread, start doing the Add() work on it, then call the callback on that worker thread. The callback will receive the same IAsyncResult, which becomes important.

    The callback doesn't have fancy requirements. It just needs to take one parameter: the IAsyncResult. But it has another job.

    Every beginning has an end. So every BeginFoo() has an EndFoo(). This method MUST be called. It will block until the work is done, but you don't care if you call that from a worker thread. When the EndFoo() method is finished, you know Foo() is done. Generally, EndFoo() only takes the IAsyncResult as a parameter, and returns whatever Foo() returned. So the EndAdd() method should look like this:
    Code:
    Function EndAdd(iar As IAsyncResult) As Integer
    Put that all together, and a cycle of calling asynchronous Add() should look like:
    Code:
    Public Sub StartAdding()
        BeginAdd(2, 2, AddressOf FinishAdding)
    End Sub
    
    Private Sub FinishAdding(ByVal iar As IAsyncResult)
        ' We're on the worker thread, but Add() may not be finished yet!
        Dim result = EndAdd(iar)
    
        ' Now Add() is finished!
        UpdateUi(result)
    End Sub
    Basically we "cut" Add() into two pieces: one that happens on the thread that calls BeginAdd(), and one that happens on the thread where it did its work. In between those two calls is some magic code that will schedule Add() on a worker thread and call your callback for you.

    So, let's talk about downloading a file again. We're going to start with just one.

    Code:
    Public Sub DownloadFirst(ByVal files() As String)
        Dim downloader As New FileDownloader()
    
        Dim firstFile As String = files(0)
        downloader.BeginDownload(firstFile, AddressOf FinishDownload, downloader)
    End Sub
    
    Private Sub FinishDownload(ByVal iar As IAsyncResult)
        ' The downloader is stored in the IAsyncResult.
        Dim downloader = CType(iar.AsyncState, FileDownloader)
    
        ' We have to call the 'end' method
        downloader.EndDownload(iar)
    End Sub
    This "begin" method takes a state parameter. We know the FinishDownload() method needs to call EndDownload() on it, so that's how we pass it between methods. If we wanted to, we could use a variable to pass it along:
    Code:
    Private _downloader As FileDownloader
    
    Public Sub DownloadFirst(ByVal files() As String)
        _downloader = New FileDownloader()
    
        Dim firstFile As String = files(0)
        downloader.BeginDownload(firstFile, AddressOf FinishDownload, Nothing)
    End Sub
    
    Private Sub FinishDownload(ByVal iar As IAsyncResult)
        ' We have to call the 'end' method
        _downloader.EndDownload(iar)
        _downloader = Nothing
    End Sub
    I don't recommend this. The state parameter helps us keep everything tidy when we have multiple operations running. Doing the same thing with variables tends to get messy and require more work. Think of that state parameter like a little secure locker that only this thread gets to see. The less things you touch outside of the locker, the less you have to think about thread safety.

    Now OK. That's one file downloaded. Remember, to download them all recursively, we wrote a method that took the array, an index, and kept calling itself with a bigger index until it ran out of files to download. The problem with that approach was a stack overflow. But async doesn't work like that.

    Every time you call BeginFoo(), a NEW worker thread might be allocated. And because BeginFoo() returns immediately, the thread it's on keeps going. If that happens to be a callback on a worker thread, and that callback completes... that worker thread is done. In this way, asynchronous recursive loops don't use the stack and can't cause a stack overflow.

    But it's easier to see it than describe it. There's not really an API I can use, so you still can't copy/paste this. Just trust me. Suppose I ran this code:
    Code:
    Public Sub StartLooping()
        Console.WriteLine("StartLooping() from thread {0}", Thread.ManagedThreadId)
    
        BeginJustLoop(AddressOf FinishLoop)
    End Sub
    
    Sub FinishLoop(ByVal iar As IAsyncResult)
        Console.WriteLine("In FinishLoop() on thread {0}.", Thread.ManagedThreadId)
        EndJustLoop(iar)
    
        BeginJustLoop(AddressOf FinishLoop)
        Console.WriteLine("End FinishLoop() on thread {0}.", Thread.ManagedThreadId)
    End Sub
    The output would look something like this:
    Code:
    StartLooping() from thread 1.
    In FinishLoop() on thread 3.
    In FinishLoop() on thread 2.
    End FinishLoop() on thread 3.
    End FinishLoop() on thread 2.
    In FinishLoop() on thread 5.
    ...
    Your thread IDs would be different, and the order of things could be different. Threads do their own thing. What's important to see here is each Begin call ends up on a different thread. So if you make your callback call Begin again, you have a loop.

    Back to our example. We've reasoned out we want it to look like the recursive example, but with async stuff. So let's try it:
    Code:
    Public Sub DownloadAsync(ByVal files() As String)
        Dim downloader As New FileDownloader()
        DownloadRecursive(downloader, files, 0)
    End Sub
    
    Private Sub DownloadRecursive(downloader As FileDownloader, files() As String, index As Integer)
        If index >= files.Length Then
            Return
        End If
    
        Dim currentFile = files(index)
        downloader.BeginDownload(currentFile, AddressOf FinishDownload, downloader)
    
        DownloadRecursive(downloader, files, index + 1)
    End Sub
    
    Private Sub FinishDownload(ByVal iar As IAsyncResult)
        ' The downloader is stored in the IAsyncResult.
        Dim downloader = CType(iar.AsyncState, FileDownloader)
    
        ' We have to call the 'end' method
        downloader.EndDownload(iar)
    End Sub
    This will chew through the whole files array and start a download for every file at the same time. That's a bad enough idea, but if you look at it and think hard you'll see another problem. Since we didn't call DownloadRecursive() from the worker thread, we're still risking a StackOverflow on the UI thread. But here's a problem. Let's think about how that FinishDownload might look:
    Code:
    Private Sub FinishDownload(ByVal iar As IAsyncResult)
        ' The downloader is stored in the IAsyncResult.
        Dim downloader = CType(iar.AsyncState, FileDownloader)
    
        ' We have to call the 'end' method
        downloader.EndDownload(iar)
    
        ' Get the next index... uh... oh.
        Dim nextIndex = ' Uh... I don't have the index...
    
        DownloadRecursive(downloader, ' Oh, or the files() array...
    End Sub
    The comments say it all. I've got to find a way to get files() and the current index into FinishDownload(), or I can't call DownloadRecursive(). I'm already putting something in AsyncState, I can't put 3 things in it... or can I?

    I could use variables. I told you not to do that. Don't suggest it.

    For any reasonably complex async call, you tend to have to make a special object for the things you want to pass to the "finish" method. In this case, it needs the downloader, the array, and the index that was downloaded:
    Code:
    Public Class FileDownloadState
        Public Property Downloader As FileDownloader
        Public Property Files() As String()
        Public Property Index As Integer
    End Class
    Now all the pieces are in place and we can write the full asynchronous download loop:
    Code:
    Public Sub DownloadAsync(ByVal files() As String)
        Dim state As New FileDownloadState() With {
            .Downloader = New FileDownloader(),
            .Files = files,
            .Index = 0
        }
    
        StartDownload(state)
    End Sub
    
    Private Sub StartDownload(state As FileDownloadState)
        If state.Index >= state.Files.Length Then
            Return
        End If
    
        Dim currentFile = state.Files(index)
        downloader.BeginDownload(currentFile, AddressOf FinishDownload, state)
    End Sub
    
    Private Sub FinishDownload(ByVal iar As IAsyncResult)
        ' The downloader is stored in the IAsyncResult.
        Dim state = CType(iar.AsyncState, FileDownloadState)
    
        ' We have to call the 'end' method
        downloader.EndDownload(iar)
    
        ' Now start the next download
        state.Index += 1
        StartDownload(state)
    End Sub
    This will download file 0, then file 1, then file 2, and keep going until it has downloaded the last file. Each file will be downloaded one at a time. The UI is free to do its thing while this is happening.

    That is basically how the "read" loops work in the chat program. The state object has everything needed to start the next reed: the stream, the buffer, the current offset, and the length we expect (if we expect one yet). If not all of the data is read, it starts another read with the same buffer, but a different offset. (There is a bug: it should be recalculating the length passed to BeginRead()! I'll fix that as I continue.) If all of the data is read, it starts a new read with a new buffer.

    Why does it start a new read? Because we don't know when the other side will send us data. The only way to know is to call Read(). So when we start a read with BeginRead(), we end up on a worker thread and call EndRead(). If there's no data, we wait. When data arrives, we wake up and do work on it. After that, we're going to call BeginRead() again. That means we're ALWAYS listening for data.

    And because that's on its own thread, we're free to use BeginWrite() whenever to write some data too.

    And because reading and writing happen on worker threads, the UI never freezes.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  26. #66

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Thanks again Sitten Spynne for your time and patience.

    In the console, ReadLine() is our easiest way to get input from the user. It blocks until they push enter. That means unless we use more than one thread, we can't receive a message while we're asking the user to give us input. Likewise, we have to call Read() when we want to receive data, but since it blocks we can't take input or send data. We could address these with some crazy tricks, but that's a whole lesson in and of itself.
    I think these are where I got stuck before:
    "we can't receive a message while we're asking the user to give us input."
    This is because we're still in send mode and haven't called Read() yet ?
    If we've not yet called Read() we're not listening for a message anyway.

    Ah... See it helps to write the question... do you mean we can't receive a message that we're not expecting "while we're asking the user to give us input." ?

    But I still can't see what you're getting at when you say "we have to call Read() when we want to receive data, but since it blocks we can't take input".


    As you can see, I'm not very far down your last post, I was going to save all the questions till I'd got further down in case there were more, but it may be better to send 'em as I see 'em instead of a large number all arriving at once.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  27. #67
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    You're asking good questions, a lot of this is confusing because you have to remember so many things at the same time to arrive at the answer.

    Technically your network interface is always listening (if it's on). When you open a connection and get your TcpClient, there is some memory set aside for incoming data. Even if you aren't calling Read(), it will receive data and store it in that buffer until you call Read(). So if we had to implement Read() ourselves, it probably looks like this pseudocode:
    Code:
    If my private buffer is empty:
        Wait until some data arrives.
    Return data from my private buffer.
    This means you could actually connect and receive 5 or 6 messages before you even start to call Read(). That's why it's important to have a protocol that helps you figure out where each message starts and ends. If you get 5 different messages at the same time, you want to know it's 5 different messages and not 1 giant message.

    Now, why did I say you can't Read() while writing, then?

    The console application I wrote has one thread, and one thread can only do one thing at a time.

    So when it's the application's "turn" to speak, it calls Console.ReadLine(). Once that happens, the thread is stuck waiting for the user input. Data MIGHT arrive for some reason, but we can't "jump" away from the Console.ReadLine() to deal with it. If we had more threads, we could use the other thread to call Read() and get the data. But we don't, so we're "stuck" even though we won't miss the incoming data.

    When it's the application's turn to receive, we call Read(). If data's already there, great, it gets it! If data is not already there, it has to wait. This is just like Console.ReadLine(): our thread is now stuck on the Read() and can't break away to do something else.

    Or, much shorter. You can't call two different methods at the same time with one thread. If we wanted to read and write at the same time like the Windows application I wrote, we'd have to put Console.ReadLine() and NetworkStream.Read() on different threads so they could both be waiting at the same time.

    But weirdo stuff happens in the console when you do this. Think about it. Let's say we rigged it so we have multiple threads, and you're typing a message into Console.ReadLine():
    Code:
    > Hey, I have something importan
    At that moment, the other person sends a message. The Read() thread receives it, parses it, and prints it to the screen. Now it looks like:
    Code:
    > Hey, I have something importan< Haha I interrupted you
    t to say
    You were in the middle of typing, then the Read() thread printed something to the console, so now the stuff you typed is mixed up with the stuff you received. It's a mess. There ARE ways to handle it in the console, but I would write an entire tutorial JUST about that. I think we both agree you're more interested in Windows Forms.

    In the Windows Forms application, there are at least 3 threads active at any time:
    • The UI thread is waiting for mouse/keyboard input and occasionally redrawing the screen.
    • The "read" thread is in an eternal loop of calling BeginRead() then EndRead() and waiting to receive data, then process it. It tells the UI thread to update textboxes sometimes.
    • The "write" thread wakes up when you try to write some data. It tells the UI thread to update textboxes sometimes.

    This means it's possible to be BOTH reading and writing at the same time, but since "get user input", "read", and "write are all on different threads, they don't get in each others' way for more than a few moments at a time. (For example, if both "write" and "read" try to update the TextBox at the same time, the UI thread responds to the first one and tells the second one "wait in line". After it finishes with the first one, it does what the second one asked.)

    So think of threads like timelines, or days in a calendar planner. You can't attend two parties at the same time. So if you're trying to make a "read party" but there's a "write party" on the same day, you either need to make a clone (another thread) so you can attend both parties or schedule the "read party" for a different day.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  28. #68

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    You're asking good questions, a lot of this is confusing because you have to remember so many things at the same time to arrive at the answer.
    Poppa smiles... I had to give up playing contract bridge because of my short-term memory, it was becoming embarrassing, and unfair to my partner.
    I forget which I miss more, bridge or my memory !


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  29. #69

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Hi,
    I'm making this post just in case someone decides to use my subroutine for finding other computers on a LAN. (Post #46)

    The major problem with this subroutine is the time it takes to check each IPAddress, you can see that I've only called 192.168.0.2 to 192.168.0.5, this is because I know that 192.168.0.1 is the router itself and therefore we can save time by starting at 192.168.0.2.
    I only went as far as 192.168.0.5, because it's the highest that I need to go to find the other computer on my LAN, looking any further would impose a large time delay when running.

    However... Problem: How to vastly reduce the time taken to check-out IPAddresses.
    Answer: Ping the IPAddress before checking, if these's no Ping, there's no need to check the address.

    In the updated code below you can see I've increased the addresses to check from 2-5 up to 2-50, but it executes almost faster than the previous code.
    Line 7 is where we ping the address.
    Line 8 adds the result of the ping to the argument which previously only excluded the address of the host computer.
    Line 13 is there because some connections to the LAN don't have a 'Name', but I still show it in the list.

    vb.net Code:
    1. Private Sub Seek()
    2.         Dim active, ipa As String, Yes As Boolean
    3.  
    4.         ListBox1.Items.Clear()
    5.         For i = 2 To 50
    6.             ipa = "192.168.0." & i.ToString
    7.             Yes = My.Computer.Network.Ping(ipa)
    8.             If i <> MyIDnum And Yes Then
    9.                 Try
    10.                     active = System.Net.Dns.GetHostEntry(ipa).HostName.ToString
    11.                     ListBox1.Items.Add(ipa & ".   " & active)
    12.                 Catch ex As Exception
    13.                     ListBox1.Items.Add(ipa & ".   Active: No name.")
    14.                 End Try
    15.             End If
    16.             Label1.Text = "Seeking connection " & i + 1.ToString
    17.             ProgressBar1.PerformStep()
    18.             Me.Refresh()
    19.         Next
    20.         GameOn = True
    21.         If ListBox1.Items.Count > 0 Then
    22.             GameOn = True
    23.             Label1.Text = "Please select"
    24.         Else
    25.             GameOn = False
    26.             Label1.Text = "Sorry..." & vbCrLf & "No other computers found."
    27.         End If
    28.         Me.Refresh()
    29.         Threading.Thread.Sleep(1000) 'Allow view of full progressbar.
    30.         ProgressBar1.Dispose()
    31.  
    32.     End Sub ' Find other computers.

    NOTE: This subroutine uses global variables which have been initialised prior to this subroutine being called. 'MyIDnum' for instance is the IPAddress for the computer running the code.


    Poppa.
    Last edited by Poppa Mintin; Sep 11th, 2017 at 05:31 AM. Reason: Correction to Post number.
    Along with the sunshine there has to be a little rain sometime.

  30. #70

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    Let's talk about the Subs first....

    ...If it's still unclear, I need to know more about why.
    Thanks Sitten Spynne, I've grasped that part.


    Pop.
    Along with the sunshine there has to be a little rain sometime.

  31. #71
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    Also, here's one reason why your "network scanner" might be slow.

    Think about what happens when you ring someone's doorbell. You expect three results.
    1. The person you are trying to meet answers the door.
    2. A different person answers the door and informs you the person you want isn't there.
    3. No one answers.

    The first two cases tend to be fast, and are "strong" results: you know whether you met the person or not.

    The third case is bothersome. How long is "long enough" to wait at a door for the person? Twenty seconds? One minute? Five minutes? If you know you're at the right house, they'll EVENTUALLY show up, so you might sit on their porch and wait even longer. If you give up, you have a "weak" result: you know you rang the doorbell and no one answered, but you aren't sure if "they aren't there" or "they didn't hear the doorbell". You have to assume "they aren't there" even if it might not be true.

    This is now the ping protocol works. Your computer sends a small message to an IP address and port. The other end receives the request and responds. Your computer interprets the respone as "it worked" and prints some timing information. But if it doesn't get a response, it's not quite sure how to handle that. Ping can be used to diagnose slow networks, so it will generally wait forever unless you tell it how long to wait. If it takes longer than that time period, you can't always be sure if "no machine is there" or "something went wrong and it didn't answer".

    In fact, there's good reasons why computers might not respond to the ping protocol. An IP scan is a ham-fisted precursor to most network attacks. Any interested attacker needs to know which IP addresses represent active machines so later port scans will be more successful. Any attentive administrator can detect a dumb IP scan, but it's better to foil them by telling your firewalls to ignore the ping protocol. When they do this, any attempt to send pings is met by the network hardware as if it's trying to route to the IP address and it just can't find it. That generally means "it takes until timeout", which means scanning hundreds of addresses will take much longer.

    .NET's default timeouts are all over the place, but usually at least 30 seconds. So if you try to ping a machine that doesn't exist, or if you ping one that is passively refusing pings, you have to wait that long before .NET decides "the ping broke, the machine isn't there".

    There's not a good way to "speed this up" because the uncertainty of "I want a response, but don't have it yet, and don't know if it means the other side isn't there" is something we can't overcome in networking. Other than deciding for yourself the timeouts should be shorter. For a local network scan latency of more than 500ms would be strange. So if you can figure out how to change the timeout Ping uses, you can make the timeout shorter and see some increased speed. I don't see a clear way to do it, but one of the advantages of asynchronous APIs is you can always add timeouts to ones that don't have them.

    The reason the snippet you posted is much faster is it doesn't use Ping. It asks your DNS server if it knows the name of the IP address. For systems that have a name, it will. For systems that don't, I don't think that means "this IP is active". The DNS server is always going to respond (hopefully) quickly because DNS is a protocol vital to the internet. But like I said, I'm not 100% certain you can use DNS to determine if a local IP is active.

    When people want their program to find "local" servers, they tend to use an entirely different kind of protocol. I hesitate to talk about it because I feel like you'll go off and try to implement that instead of finishing the important, but boring task of learning how to use asynchronous APIs. So a somewhat obscured explanation is:

    There is a way to send some data to "every machine on my subnet". So some client/server protocols include two messages. One is the "Servers, reveal thyself" message sent by clients to all subnet machines. The other is "I'm a server" and servers will respond with it. That's the fastest way to go about it because you can assume anything that didn't respond isn't a running server.

    But it's sort of silly to try to implement it at this point, because it uses all the same tricks you need to "write a client/server chat program" and you're still working through that.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  32. #72

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    But it's sort of silly to try to implement it at this point, because it uses all the same tricks you need to "write a client/server chat program" and you're still working through that.
    This is true... Sadly it's taking it's time to get through too. I'll get there in the end, it seems to me that the best way for me to 'get to grips' with it is to try to implement what you're telling me in little test pieces, which is what I'm doing. Sadly it's taking me much longer that I thought it would.
    The excuse I give myself is that I can only test my code when the other laptop is free at the same time as I am.

    Nil desperandum.


    Poppa.

    PS:

    Had a decent run at it today, first time for a few days.
    Actually got somewhere this time, the mud is settling.xxxxxxxxxxxxxx

    Pop.
    Last edited by Poppa Mintin; Sep 11th, 2017 at 07:59 PM. Reason: PS: Added 2.00 AM.
    Along with the sunshine there has to be a little rain sometime.

  33. #73

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Sitrep:

    Not had a chance to do anything for a few days... (Not SWMBO) been incapacitated. xxxxxxx

    Still struggling... I'm beginning to think I'm not going to get this.
    I feel bad about that after all the work and time you've spent, I've not stopped trying but I'm getting dispirited. It's not much fun any more.

    Oddly enough, right back at the beginning, when we were only passing one byte, I thought it was going to be easy, one byte is all my application requires, it's not a chat, but I figure if I'm going to get to the bottom of coms I have to stick to it, especially after all your hard work, it'd be ungrateful not to.
    I've tried to grasp what you're telling me but it's not going in. I've had some minor bits working but don't seem to get a long enough run at it and I'm sorry to say I loose patience when things continually don't work.

    I suspect one problem is the disparity between the two operating systems. In a few trials the Win.7 machine would 'connect' but the Win.10 one wouldn't. That's copying the .exe file onto a 'stick' and running the Win.7 from that and the Win.10 either in debug mode or just directly from the .exe.
    Trying to connect from 7 to 10, the 7 connects, trying from 10 to 7 the 10 doesn't, despite exactly the same .exe being used. when the 7 connects I can't get any further than 'Connected', other than a 'Connected' text in the Console and in a Label. (These are just little test projects, mostly now deleted. If they don't work I start from scratch with the next trial, else trials become a patchwork of corrections.)

    Incidentally, I don't see any messages in my 'Output' page, I have it selected to 'Show output from: Debug' I've also tried 'Build Order' and 'Build'. However, I do see all the Console.WriteLine("text") texts in my 'Immediate' page. As far as I'm aware this has always been the case, although I've not really used the Console before embarking on this exercise. (Come to think of it... This may be because folk have told me to look on the output page and I've not seen anything.) Maybe there's a switch somewhere that I don't know about ?


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  34. #74
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    3,924

    Re: Trying to connect to second Laptop.

    I think when you start Visual Studio for the first time it asks you what language you primarily use and sets some options based on your selection.
    I think if you choose something like "General" programing, or one of the other languages (not Visual Basic), then debug output will go to the output window. If you choose Visual Basic then output will go to the immediate window since that was what Visual Basic has done for since VB1 days. They didn't have an "output" window in the early Visual Basic IDE. But you can change that option at any time.

    There shouldn't be any Operating System reason for the software to not work between Win7 and Win10, other than you may have firewall settings different between the two.

    Also, if you had one byte transfers working, then there is no reason an operating system difference would fail just because you're using multiple bytes.

    Back in post 14 you said this when running my example.
    Played with it for a while, then copied the lot to a second laptop, did the same again(except IP address 192.168.0.4) ... Everything's lovely.
    Then I opened the two app.s on the first computer, changed the IP addresses to the other machine on both and tried again...
    Brilliant ! Goes from the second laptop to the first, no problem. Tried from the first (Windows 10) to the second (Windows 7), no joy.
    The bold text concerns me. If you're using two computers you shouldn't be running the "two app.s" on either machine. You would run one on one machine, and the other on the other.
    The BlockDisplay application is the Server in the TCP connection and the BlockCapture is the Client. You only want to run the Server on one machine and the Client on the other.

    I didn't mention it earlier because Sitten Spynne has really been working through this with you and too many cooks in the kitchen is not a good thing and my example is more complicated than you need and was more for verifying that the communication could work between the two computers to eliminate a system level thing, e.g. a firewall, being the culprit hanging up your program.

  35. #75

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Situation Report:

    Sadly I've not done anything with this project for several weeks now, not by choice, (was in sick bay for a while).

    Just trying to get back into it, hope to have some progress to report soon.


    Pop.
    Along with the sunshine there has to be a little rain sometime.

  36. #76

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Hi,

    Well I'm not doing very well, I think there's too much here for me to remember all in one go, so I decided I'd just take it step by step to try to get to the result I was originally looking for.

    So, I went back to the start and using Sitten Spynne's examples I'm trying to pick out the parts which fit in with my original project.

    Starting with just connecting the two laptops via TCP, I have what I think is a working application so far as it goes. Sitten Spynne's example which I'm mainly using requires the user to decide whether to be the Server or the Client, I'm trying to get my project to do that at run time.

    I have that working well enough, with both computers running the app. either can initiate the connection, and the other (remote) does connect, but I've spent a couple of days wading through all the items in the 'Watch' window via a breakpoint in the remote laptop, looking for something which will actually identify the initiating computer.

    I'm using this bit of code in the remote computer to accept the initiating computer:
    Code:
        Private Sub StartServer()
                Dim listenEndpoint As New IPEndPoint(IPAddress.Any, 3000)
                Dim Ears As New TcpListener(listenEndpoint)
    
                Ears.Start()
                S_Token = Ears.BeginAcceptTcpClient(AddressOf SvrGot, Ears)
        End Sub 
    
        Private Sub SvrGot(ByVal token As IAsyncResult)
            Dim Ears As TcpListener = CType(token.AsyncState, TcpListener)
    
            UpDate_T("Connected !")
            _remoteClient = Ears.EndAcceptTcpClient(S_Token)
            S_Token = Nothing
            Ears.Stop()
        End Sub
    With a breakpoint at... 'S_Token = Nothing' ...I was hoping that I'd find something to identify the initiating computer in 'token', 'S_Token' or 'Ears'.


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  37. #77

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Poppa Mintin View Post
    With a breakpoint at... 'S_Token = Nothing' ...I was hoping that I'd find something to identify the initiating computer in 'token', 'S_Token' or 'Ears'.
    Hi again,

    Using Debug Watch1:

    Navigating to... token > Non-Public members > Result > RemoteEndPoint > Address ...I find the LAN address of the remote computer, but of course I can't read anything beyond 'Non-Public members' with code...

    ...unless someone knows better !


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  38. #78
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    3,848

    Re: Trying to connect to second Laptop.

    What do you think '_remoteClient' is?

    Did you consider that since the point of EndGetTcpClient() is "get an object that represents the remote connection", the information about that connection might be in that object?
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  39. #79

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Near Barnsley South Yorkshire, England.
    Posts
    1,355

    Re: Trying to connect to second Laptop.

    Quote Originally Posted by Sitten Spynne View Post
    What do you think '_remoteClient' is?

    Did you consider that since the point of EndGetTcpClient() is "get an object that represents the remote connection", the information about that connection might be in that object?
    I looked in there first, I don't know how I missed it ! (Dog tired at the time possibly.)
    However... Using Debug Watch1:

    Navigating to... _remoteClient > Client > RemoteEndPoint ...I find the LAN address and Port of the remote computer.
    Navigating to... _remoteClient > Client > RemoteEndPoint > Address ...I find the LAN address of the remote computer.

    Sadly, I still can't get hold of it. Using 'z' as a quick temporary variable I've tried to grab the address as an IPAddress and as a String but can't find how to get it.
    Code:
    Dim z As IPAddress = _remoteClient.Client.RemoteEndPoint.Address
    Results in 'Address is not a member of EndPoint'. It suggests using:
    Code:
    Dim z As IPAddress = _remoteClient.Client.RemoteEndPoint.AddressFamily
    Which produces 'Value type AddressFamily cannot be converted to IPAddress'.

    Trying:
    Code:
    Dim z As String = _remoteClient.Client.RemoteEndPoint
    '    OR
    Dim z As String = _remoteClient.Client.RemoteEndPoint.Address
    Again results in 'Address is not a member of EndPoint'. and again it suggests using:
    Code:
    Dim z As String = _remoteClient.Client.RemoteEndPoint.AddressFamily
    For the latter, which returns '2'.

    The 'Type' column of Watch1 gives: 'System.Net.IPAddress' for _remoteClient > Client > RemoteEndPoint > Address
    and
    'System.Net.EndPoint(System.Net.IPEndPoint)' for _remoteClient > Client > RemoteEndPoint,
    but I can't find how to use that either.



    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  40. #80
    Fanatic Member PlausiblyDamp's Avatar
    Join Date
    Dec 2016
    Location
    Newport, UK
    Posts
    524

    Re: Trying to connect to second Laptop.

    You might need to either declare it as an IpEndPoint or if it is being returned from a method try casting it to an IpEndPoint, EndPoint is a base class which doesn't have an Address property.

    https://msdn.microsoft.com/en-us/lib...v=vs.110).aspx

Page 2 of 3 FirstFirst 123 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width

Survey posted by VBForums.