Networking problems (bNet)
BlitzMax Forums/BlitzMax Programming/Networking problems (bNet)
| ||
| I am trying to adapt the bNet code that Eikon used for his tutorial (http://www.eiksoft.com/multi/multi.htm). BTW, thanks for that Eikon - it has been most helpful. I have been trying to make it a little more modular and also aimed more at turn based games. Most of it seems to be working except for 1 bug (which is also in Eikons example) which I can not work out why it's happening. Basically, you can make the connection just fine, but if the connection is broken for any reason, it will not connect again. I have made a quick example which emulates how it works in my game: main code: Framework BRL.GLMax2D Import BRL.StandardIO Import PUB.BNet Strict AppTitle = "Multiplayer test" Include "multiplayer.bmx" Global gameSetup, gameStarted, turnData:TTurnData = New TTurnData Graphics 640, 480 Repeat Cls DrawText "Connected: " + bConn, 500, 10 If gameStarted And bConn Then DrawText "Game started", 10, 10 DrawText "Ping: " + myPing + "ms", 10, 30 DrawText "Press D to Disconnect", 10, 60 If KeyHit(KEY_D) Then NetDisconnect Else Select bConType Case netcon_none DrawText "Press H to Host, or J to Join", 10, 10 If KeyHit(KEY_H) Then setConnection(netcon_host) If KeyHit(KEY_J) Then setConnection(netcon_client) Case netcon_client If bConn = False ' Not connected DrawText "Press C to Connect to " + ServerIP$, 10, 10 If KeyHit(KEY_C) Then NetConnect Else DrawText "Ping: " + myPing + "ms", 10, 10 DrawText "Waiting for host to start...", 10, 30 'check for game start If gameSetup Then gameStarted = True End If Case netcon_host If bConn = False ' Not connected DrawText "Waiting for opponent to join...", 10, 10 Else DrawText "Ping: " + myPing + "ms", 10, 10 DrawText "Press S to Start game", 10, 30 If KeyHit(KEY_S) Then 'send game info to client WriteInt Stream, SendID WriteByte Stream, NET_SETUP WriteByte Stream, True SendUDP gameStarted = True End If End If End Select End If UpdateUDP Flip Until AppTerminate() End '------------------------------------------------------------------------------ Function netSetup() 'game setup info sent by host to client '------------------------------------------------------------------------------ gameSetup = ReadByte(Stream) End Function '------------------------------------------------------------------------------ Function netReset() 'game setup info sent by host to client '------------------------------------------------------------------------------ gameSetup = False gameStarted = False End Function '------------------------------------------------------------------------------ Function netData() 'called by multiplayer.UpdateUDP for turn data '------------------------------------------------------------------------------ turnData.data1 = ReadByte(Stream) turnData.data2 = ReadByte(Stream) End Function '------------------------------------------------------------------------------ Function netClear() 'clear network data '------------------------------------------------------------------------------ turnData.data1 = 0 turnData.data2 = 0 End Function '============================================================================== Type TTurnData 'turn data for network games '============================================================================== Field data1, data2 End Type include file:
Const netcon_none = 0, netcon_host = 1, netcon_client = -1
Const netTimeout = 2500 'timeout time for connect/disconnect attempt
Const gameTimeout = 5000 'timeout period when game ends if no ping is received
' // Network Specific Declares
Global bConType = netcon_none ' Are you hosting or joining?
Global bConn% = False ' Are you connected?
Global ServerIP$ = "127.0.0.1" ' Client should set to server's IP (localhost for testing)
Global intServerIP% = IntIP(ServerIP$) ' Int of server IP
Global Stream:TUDPStream ' UDP Stream
Global myPing%, PingTime ' Ping and Ping Timer
Global UpdateTime ' Main Update Timer
Global SendID%, RecvID%, lastRecv[32] ' Packet IDs and Duplicate checking Array
Global ClientIP%, ClientPort:Short ' Client IP and Port
Global DebugMode = False ' Toggles the display of debug messages
Global bSentFull ' Full update boolean
' // Networking Constants
Const HOSTPORT = 41219 ' * IMPORTANT * This UDP port must not be blocked by a router
' or firewall, or CreateUDPStream will fail
Const NET_ACK = 1 ' ACKnowledge
Const NET_JOIN = 2 ' Join Attempt
Const NET_PING = 3 ' Ping
Const NET_PONG = 4 ' Pong
Const NET_QUIT = 5 ' Quit
Const NET_SETUP = 11 ' set up data
Const NET_DATA = 12 ' game turn data
Global n:NetObj, netList:TList = New TList ' Packet Object and List
Function setConnection(conType)
'network setup
Select conType
Case netcon_host ' Hosting preperations
InitNetwork ' Initialize Network
Stream = CreateUDPStream(HOSTPORT) ' Attempt to open port
Case netcon_client ' Client preperations
Stream = CreateUDPStream() ' Attempt to open port
Default
'no connection type - kill connection
bConn = False
Stream = Null
If bConType = netcon_host Then CloseNetwork
netReset 'custom function to reset network connections
End Select
bConType = conType
If Stream <> Null Then
If Not Stream Then Notify "CreateUDPStream Failed!"; End ' Failed to open stream
'just show friendly error - no quit
'*** code here ***
' Clear Duplicate Array
ClearLastRecv
End If
End Function
' NetObj Type (Makes UDP Packets reliable)
Type NetObj
Field ID, SID, T, Retry ' Packet ID, SendID, Timer, Retry
Function Create:NetObj(ID)
If Stream = Null Then Return
n:NetObj = New NetObj ' Create Packet
n.ID = ID ' Packet ID
n.SID = SendID ' Send ID
WriteInt Stream, n.SID
WriteByte Stream, ID
SendUDP ' Send UDP Message
n.T = MilliSecs() ' Set Timer
Return n
End Function
' Resends Packets
Method Handle()
If Stream = Null Then Return
Select ID
Case NET_JOIN ' Join Attempt
If MilliSecs() >= T + 100 Then ' Resend every 100ms
WriteInt Stream, SID
WriteByte Stream, NET_JOIN
SendUDP
T = MilliSecs()
If Retry < 20 Then ' Retry (20 times every 100ms = 2 seconds)
Retry:+1
If DebugMode = True Then Print "*** Resent join packet to host, attempt " + Retry + " ***"
Else
If DebugMode = True Then Print "*** Dropped join packet ***"
NetList.Remove n ' Drop Packet
End If
End If
Case NET_QUIT ' Quit Attempt (Never Drops)
If MilliSecs() >= T + 100 Then ' Resend every 100ms
WriteInt Stream, SID
WriteByte Stream, NET_QUIT
SendUDP
T = MilliSecs()
If DebugMode = True Then Print "*** Resent quit packet ***"
End If
End Select
End Method
End Type
Function UpdateUDP()
If bConn = True Then ' Updates
If MilliSecs() >= PingTime + 1000 ' Ping once every second (unreliable)
WriteInt Stream, SendID
WriteByte Stream, NET_PING
SendUDP
PingTime = MilliSecs()
End If
'check for dropped connection (no ping in 5 seconds)
If MilliSecs() - PingTime > netTimeout Then netDisconnect
End If
Local bMsgIsNew
If RecvUDPMsg(Stream) Then ' A UDP packet has been received
RecvID = ReadInt(Stream)
Local Data = ReadByte(Stream)
Local IP = UDPMsgIP(Stream)
Local Port = UDPMsgPort(Stream)
bMsgIsNew = IsMsgNew(RecvID, Data) ' Make sure packet is not a duplicate
If bMsgIsNew = True Then
Select Data
Case NET_JOIN ' JOIN PACKET
If bConn = False And bConType = netcon_host Then
ClientIP = IP
ClientPort = Port
NetAck RecvID ' Send ACK
bConn = True ' Connected
End If
Case NET_PING ' Received Ping, Send Pong
WriteInt Stream, SendID
WriteByte Stream, NET_PONG
SendUDP
Case NET_PONG ' Ping / Pong - Calculate Ping
myPing = (MilliSecs() - pingTime) / 2
Case NET_ACK ' ACK PACKET
RemoveNetObj ReadInt(Stream) ' Remove Reliable Packet (ACK has been received)
Case NET_SETUP ' game setup
netSetup
Case NET_DATA ' end of turn
netData
Case NET_QUIT ' Player Quits
NetAck RecvID ' Send ACK
'bConn = False ' Quit (EDIT - used to be set to 2, but changed to False)
netDisconnect
End Select
End If
End If
End Function
' // SendUDP sends the current packet to either host or client
Function SendUDP()
If bConType = netcon_host Then ' Send to Client
SendUDPMsg Stream, ClientIP, ClientPort
Else ' Send to Host
SendUDPMsg Stream, IntServerIP, HOSTPORT
End If
SendID:+1 ' Increment send counter
End Function
' // IsMsgNew determines whether the current UDP packet is old or a duplicate
Function IsMsgNew(ID, Data = 0)
For Local i = 30 To 0 Step -1
lastRecv[i + 1] = lastRecv[i]
Next
lastRecv[0] = ID
For Local i = 1 To 31
If lastRecv[i] = lastRecv[0] Then ' Duplicate
'Print "ID: " + lastRecv[0]
Select Data ' ACK Anyway (Reliable packets only)
Case NET_JOIN, NET_QUIT
NetAck ID
If DebugMode = True Then Print "*** Received duplicate join packet, resending ACK ***"
End Select
Return False
End If
Next
Return True
End Function
' // NetConnect attempts to connect client to host
Function NetConnect()
Local timeOutTime = MilliSecs()
NetList.AddLast netObj.Create(NET_JOIN) ' Send Reliable Join Attempt
Local tmpConn = 0
ClearLastRecv ' Clear Duplicate Array
Repeat
DrawText "Connecting...", 5, 5
'*** need better output - use a message q? ***
'*** code here ***
If RecvUDPMsg(Stream) Then ' A UDP packet has been received
RecvID = ReadInt(Stream)
Local Data = ReadByte(Stream)
Local IP = UDPMsgIP(Stream)
Local Port = UDPMsgPort(Stream)
Local bMsgIsNew = True ' ACK is always new
If bMsgIsNew = True Then
Select Data
Case NET_ACK ' ACK PACKET
RemoveNetObj ReadInt(Stream) ' Remove Reliable Packet (ACK has been received)
tmpConn = 1 ' We have connected!
End Select
End If
End If
' Handle reliable packets
For n:NetObj = EachIn netList
n.Handle
Next
If MilliSecs() >= timeOutTime + netTimeout Then tmpConn = 2 ' No response received in 2.5 seconds, bail
Flip; Cls
Until tmpConn <> 0
For n:NetObj = EachIn netList; netList.Remove n ; Next ' Clear packets
If tmpConn = 1 Then bConn = True Else bConn = False
End Function
' // NetDisconnect closes net connections
Function NetDisconnect()
NetList.AddLast netObj.Create(NET_QUIT) ' Send Reliable Quit Attempt
Local timeOutTime = MilliSecs()
Local tmpConn = 0
SetClsColor 0, 0, 0
Repeat
DrawText "Disconnecting...", 5, 5
If RecvUDPMsg(Stream) Then ' A UDP packet has been received
RecvID = ReadInt(Stream)
Local Data = ReadByte(Stream)
Local IP = UDPMsgIP(Stream)
Local Port = UDPMsgPort(Stream)
Local bMsgIsNew = True ' ACK is always new
If bMsgIsNew = True Then
Select Data
Case NET_ACK ' ACK PACKET
RemoveNetObj ReadInt(Stream) ' Remove Reliable Packet (ACK has been received)
tmpConn = 1 ' We have disconnected
End Select
End If
End If
For n:NetObj = EachIn netList; n.Handle; Next ' Handle reliable packets
If MilliSecs() >= timeOutTime + netTimeout Then tmpConn = 2 ' No response received in 2.5 seconds, bail
Flip; Cls
Until tmpConn <> 0
' Clear packets
For n:NetObj = EachIn netList
netList.Remove n
Next
setConnection(netcon_none)
End Function
' // RemoveNetObj removes reliable UDP packets from the queue once an ACK has been received
Function RemoveNetObj(AckID)
For n:NetObj = EachIn NetList
If n.SID = AckID Then
Local tmpID = n.ID
NetList.Remove n
'Remove old entries
For n:NetObj = EachIn NetList
If n.ID = tmpID And n.SID <= AckID Then NetList.Remove n
Next
Exit
End If
Next
End Function
' // NetAck sends ACKnowledge packet to client
Function NetAck(RecvID)
WriteInt Stream, SendID
WriteByte Stream, NET_ACK
WriteInt Stream, RecvID
SendUDP
End Function
Function ClearLastRecv()
' Clear duplicate array
For Local i = 0 To 31
lastRecv[i] = -1
Next
End Function
Note you also need bNet which can be downloaded from the link above. If you compile and run 2 instances of the above code (1 host & 1 client), then disconnect, then try to reconnect - you will see the problem. I'm guessing there is soemthing not being reset but after several days I have not found what is causing it. The client does seem to be sending the join request, but the host does not seem to be receiving it. As always, any help would be appreciated. Edit: I think I may just scrap this and try doing it in GNet instead - it looks a lot simpler. |
| ||
| That and many other bugs were fixed long ago. But only for BNetEx, BNet was dropped long ago. You can find it here: http://vertex.dreamfall.at/bnet/bnetex165.zip |
| ||
| Ah, thanks :) |