Networking problems (bNet)

BlitzMax Forums/BlitzMax Programming/Networking problems (bNet)

Ghost Dancer(Posted 2007) [#1]
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.


Dreamora(Posted 2007) [#2]
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


Ghost Dancer(Posted 2007) [#3]
Ah, thanks :)