Valhalla Legends Forums Archive | .NET Platform | VB8 - System.Net.Sockets.Socket help (I'm definately beginning to annoy you)

AuthorMessageTime
JoeTheOdd
I'm creating an instance of clsSocketWrapper and connecting it to uswest.battle.net:6112. It's telling me that the connection timed out, when it shouldn't have. It makes me cry.

clsSocketWrapper:
[code]Imports System.Text
Imports System.Net
Imports System.Net.Sockets

Public Class clsSocketWrapper

    Private MySocket As Socket

    '/**
    '  Connects this socket
    '  @param server Server to connect to
    '  @param port Port to connect on
    '  @return True if connected, false if not.
    '*/
    Public Function Connect(ByVal server As String, ByVal port As Integer) As Boolean
        Randomize()

        Dim hostEntry As IPHostEntry = Dns.GetHostEntry(server)

        Dim address As IPAddress = hostEntry.AddressList(Int(Rnd() * hostEntry.AddressList.Length))

        Dim endPoint As New IPEndPoint(address, port)
        Dim tempSocket As New Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)

        Try
            tempSocket.Connect(endPoint)
        Catch ex As Exception
            modFunctions.AddChat(Color.Red, "Error: " & ex.ToString)
        End Try

        If tempSocket.Connected Then
            MySocket = tempSocket
            Return True
        End If

        Return False
    End Function

    '/**
    '  Disconnects the socket
    '*/
    Public Sub Disconnect()
        MySocket.Disconnect(True)
    End Sub

    '/**
    '  Checks if the socket is connected.
    '  @return True if connected, false if not.
    '*/
    Public Function IsConnected() As Boolean
        Return MySocket.Connected
    End Function


    '/**
    '  Receives data
    '  @return All the incomming data on the socket
    '*/
    Public Function ReceiveData() As String
        Dim bytesReceived(255) As Byte
        Dim bytes As Integer
        Dim data As String = ""
        Do
            bytes = MySocket.Receive(bytesReceived, bytesReceived.Length, 0)
            data = data & Encoding.ASCII.GetString(bytesReceived, 0, bytes)
        Loop While bytes > 0
        ReceiveData = data
    End Function

    '/**
    '  Sends data
    '  @param b Data to send
    '  @return True if sent, false otherwise.
    '*/
    Public Function SendData(ByVal b As Byte()) As Boolean
        Return MySocket.Send(b, b.Length, 0)
    End Function

    '/**
    '  Overload of SendData(byte())
    '*/
    Public Function SendData(ByVal s As String) As Boolean
        Dim b(Len(s) - 1) As Byte, I As Integer
        For I = 1 To Len(s)
            b(I - 1) = CType(Mid(s, I, 1), Byte)
        Next
        Return SendData(b)
    End Function

End Class[/code]

Usage:
[code]Public Class clsStarcraftPacketThread

    Private MySocket As New clsSocketWrapper
    Private PacketBuffer As New clsPacketBuffer
    Private Active As Boolean

    Public Sub StartThread()
        modFunctions.AddChat(Color.Yellow, "Connecting to Battle.net server " & My.Settings.Server & ":6112..")

        If MySocket.Connect(My.Settings.Server, 6116) Then
            modFunctions.AddChat(Color.GreenYellow, "Connected to Battle.net!")
            Active = True
        Else
            modFunctions.AddChat(Color.Red, "Failed to connect to Battle.net!")
            Active = False
        End If

        If Active Then
            SendPacket_0x50()
            modFunctions.AddChat(Color.Yellow, "Requesting authorization..")
        End If
    End Sub[/code]

The bot yelling at me:
[quote][3:49:43 AM] Welcome to Magnicient Bot by Joe[e2]!
[3:49:46 AM] Connecting to Battle.net server uswest.battle.net:6112..
[3:50:07 AM] Error: System.Net.Sockets.SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
  at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)
  at System.Net.Sockets.Socket.Connect(EndPoint remoteEP)
  at MagnificentBot.clsSocketWrapper.Connect(String server, Int32 port) in C:\Users\Joe\Documents\Visual Studio 2005\Projects\MagnificentBot\MagnificentBot\clsSocketWrapper.vb:line 27
[3:50:07 AM] Failed to connect to Battle.net![/quote]

Also, can anyone think of a better name? Magnificent Bot sounds *really* stupid.
June 29, 2006, 8:51 AM
Myndfyr
OK first of all, dump the "cls" prefix.  Notice that nowhere else in the world of .NET is a class prefixed with "cls".

I'll be back in a bit to answer more!
June 29, 2006, 4:01 PM
JoeTheOdd
Done. Proceed.
June 29, 2006, 9:23 PM
Myndfyr
I was looking at your starcraft packet thread "class".  I think it's a mistake, or poor design, to design a class simply to handle the one specific client, particularly when the implementation on the other clients is not particularly different.

What you should do for your background thread is implement *only* the listening portion of your socket.  Now, I should warn you: I've always found .NET sockets to be a bit odd in behavior for receiving.  For example, they don't always fail if they disconnect, and sometimes tell you that it got data when it really didn't.

Having said so, the following code follows the patterns I used when designing my WoW realm server client.  It has not been tested or even run through the IDE (so I don't know if it's syntactically correct).  But I commented it a bunch, and you should understand what each subroutine does.

[code]
Imports System.Text
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading

Public Class SocketWrapper
    Private sck As Socket
    ' An example of using Hungarian notation to avoid confusing the thread for a list
    Private m_tdList As Thread
    Private m_connected As Boolean

    Public Sub New()
        sck = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

        m_tdList = New Thread(AddressOf Listener)
        m_tdList.IsBackground = True
        m_tdList.Priority = Priority.BelowNormal

        m_connected = False
    End Sub

    Public Function Connect(ByVal ep As IPEndPoint) As Boolean
        Dim connected As Boolean = False
        Try
            sck.Connect(ep)
            m_connected = connected = sck.Connected
        Catch se As SocketException
            ' Inform the user of the error (your code)

            m_connected = connected = False
        End Try
        ' Note: there are other exceptions that could have been caught here, but
        ' it is more appropriate for them to be caught up the food chain.  For example,
        ' the main application should catch a SecurityException, or the class that uses
        ' this one should catch an ObjectDisposedException.

        Return m_connected
    End Function

    Public Sub BeginListening()
        If Not IsConnected Exit Sub
       
        m_tdList.Start()
    End Sub

    Public Sub Close()
        If Not IsConnected Exit Sub

        Try
            If sck <> Nothing Then sck.Close()  :  sck = Nothing
            If m_tdList <> Nothing Then m_tdList.Abort()  :  m_tdList = Nothing

        Catch tae As ThreadAbortException
            ' No code is necessary here.
        Finally
            m_connected = False
        End Try
    End Sub

    Public Readonly Property IsConnected As Boolean
        Get
            Return m_connected
        End Get
    End Property

    Public Readonly Property IsReceiving As Boolean
        Get
            If m_tdList = Nothing Then Return False

            Return m_tdList.IsAlive
        End Get
    End Property

    ' This subroutine implements a general Battle.net protocol listener
    ' It does only header parsing.
    Private Sub Listener()
        Do While sck.Connected
            Dim packetId As Byte
            Dim dataLength As Short
            Dim packetData() As Byte

            Try
                Dim header() as Byte
                header = Receive(4)
                If header(0) <> &Hff Then
                    ' Notify user of protocol violation (your code)

                    ' Break out of the loop which ends the thread
                    sck.Close()
                    Exit Do
                End If

                packetId = data(1)
                dataLength = BitConverter.ToInt16(header, 2)

                packetData = Receive(dataLength)

            Catch se As SocketException
                ' Notify the user of the error (your own code here)

                ' End the loop, which ends the thread
                Exit Do
            End Try

            ' Now we have the variables packetId and dataLength, and packetData filled in.
            ' Call your custom parsing method.
            ' Alternatively, add your packet data to a priority queue (with its ID)
            ' Then, on yet another thread, take the highest-priority packets out first
            ' and process them as you have time.  In this case, I just do a direct parse
            ' on the listener thread.  The problem with this is that if there is an
            ' unhandled exception, it can terminate the listening thread.  So, give it
            ' its own Try/Catch block.

            Try
                Parse(packetId, packetData)
            Catch ex As Exception
                ' Inform user of the error with the packet ID and data, but
                ' this time we don't end the loop.
            End Try
        Loop
    End Sub

    Protected Overridable Function Receive(ByVal num As Integer) As Byte()
        Dim data(num) As New Byte
        Dim curIndex As Integer = 0
        Dim toGo As Integer = num
        While toGo > 0
            Dim tmpLen As Integer
            tmpLen = sck.Receive(data, curIndex, toGo, SocketFlags.None)
            curIndex = curIndex + tmpLen
            toGo = toGo - tmpLen
        Loop

        Return data
    End Function
End Class
[/code]


Some notes:

After calling Close(), your object will be invalid, so you'll need to create a new one for a new connection.

You should call Close() whenever you have an error.  You can do this from an external object (through events), or within your code in places I marked "your code goes here".

Be aware that not all exceptions are caught.

Any questions, feel free to ask.  :)
June 30, 2006, 12:03 AM
JoeTheOdd
Alright. I'll look into that tomorrow.
June 30, 2006, 7:23 AM
Myndfyr
One thing I need to caution you about using this method:

Having the listening happening on the background thread in a loop is great because the Receive() method (Socket.Receive()) blocks thread execution until data is received, which could mean that, had you done it on your main thread, nothing on your window would be redrawn. 

The price you pay, though, is having to marshal your GUI updates to the main thread, because Windows controls can only change their appearance on the main thread.

I would suggest doing this.  Whatever your current AddChat routine is, rename it to AddChatImpl (for add chat implementation).  Create a new AddChat with the same signature.

Let's say your AddChat routine is this:
[code]
Public Sub AddChat(ByVal ParamArray obj() As Object)
...
End Sub
[/code]

Declare this.  It's called a delegate, and it's like a function pointer in C and C++, but you know that it's type safe and that it's definitely a function pointer and not a pointer to somewhere on the heap:

[code]
Public Delegate Sub AddChatCallback(ByVal ParamArray obj() As Object)
[/code]

Note how the delegate's signature matches your AddChat signature.

Now, in your new AddChat:

[code]
Public Sub AddChat(ByVal ParamArray obj() As Object)
    Dim callback As New AddChatCallback(AddressOf AddChatImpl)

    If rtbChat.InvokeRequired Then
        rtbChat.Invoke(callback, obj)
    Else
        callback(obj)
    End If
End Sub
[/code]
There's one other thing.  Your AddChatImpl signature - and the delegate - should not be ParamArray.  .NET gets weird because the runtime isn't sure whether you are passing the array as the array, or as item 0 of the array (since an array is also just another object).

So, all told, your new code should look like this:

[code]
Private Delegate Sub AddChatCallback(ByVal obj() As Object)

Private Sub AddChatImpl(ByVal obj() As Object)
    ' This used to be the AddChat function.
End Sub

Public Sub AddChat(ByVal ParamArray obj() As Object)
    Dim callback As New AddChatCallback(AddressOf AddChatImpl)

    If rtbChat.InvokeRequired Then
        rtbChat.Invoke(callback, obj)
    Else
        callback(obj)
    End If
End Sub
[/code]

You'll need to do this kind of thing for anything that you want to update your GUI for if it will have to do with data you got from Battle.net.  So if you get friends or a clan list, for instance, adding those to ListViews will likely also need to have the Invoke call.
June 30, 2006, 4:55 PM

Search