Author | Message | Time |
---|---|---|
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 |