A post that Erel made on Facebook about client/server auto-connect reminded me that I had this code & should probably share it because it doesn't require either device to be "hard" configured as either a client or a server.
This is the code I use in my Five Dice game to connect 2 devices on a local network. It will initially look for another device sending a server broadcast & if it doesn't find one, will send one itself.
The first device to receive a server broadcast message sends back a client message & then both devices connect via TCP sockets (one a server & the other a client).
When I first wrote this code, I assumed that all networks that allow UDP broadcast (some block it, so I also have a manual IP configuration option for my users) would use the .255 address - however I see that Erel has recently posted a code snippet that will find the correct broadcast address for the local network, so I might look at implementing that in my code. Having said that, I've only had a handful of users tell me that they can't get the auto-connect to work (which I assumed was because their network was blocking UDP broadcasts) & they have been able to successfully connect using the manual option.
This is the code I use in my Five Dice game to connect 2 devices on a local network. It will initially look for another device sending a server broadcast & if it doesn't find one, will send one itself.
The first device to receive a server broadcast message sends back a client message & then both devices connect via TCP sockets (one a server & the other a client).
When I first wrote this code, I assumed that all networks that allow UDP broadcast (some block it, so I also have a manual IP configuration option for my users) would use the .255 address - however I see that Erel has recently posted a code snippet that will find the correct broadcast address for the local network, so I might look at implementing that in my code. Having said that, I've only had a handful of users tell me that they can't get the auto-connect to work (which I assumed was because their network was blocking UDP broadcasts) & they have been able to successfully connect using the manual option.
B4X:
#Region Service Attributes
#StartAtBoot: False
#End Region
'=========================================================================================================
'This service looks for a UDP broadcast "I'm a server" message & if it doesn't immediately see one
'will start sending its own while simultaneously looking for another device broadcasting the same message via UDP. During the broadcast it keeps a TCP server socket open waiting for a connection.
'
'The first device to receive a server broadcast message sends an "I'm a client" message to the server 'IP address, then stops the UDP broadcast & opens a TCP client socket connected to the other device's 'TCP server socket.
'
'When the other device receives the "I'm a client" message, it closes it's UDP socket & accepts the 'client connection on the TCP socket.
'
'Both devices then open an asynchronous stream to use for sending messages to each other.
Sub Process_Globals
Public sMyIP As String
Public aStream() As AsyncStreams
Public sClientIP() As String
Public iClientsConnected As Int = 0
Private UDPSocket1 As UDPSocket
Private Tablet As Reflector
Private UDPPort As Int = 13130
Private TCPPort As Int = 13131
Private sBroadCastIP As String
Private tmr As Timer
Private tcpClient() As Socket
Private tcpServer As ServerSocket
Private sIP(4) As String
Private bFoundServer As Boolean = False
Private sServerIP As String
Private iTryCount As Int = 0
End Sub
Sub Service_Create
Tablet.Target = Tablet.GetContext
Tablet.Target = Tablet.RunMethod2("getSystemService", "wifi", "java.lang.String")
Tablet.Target = Tablet.RunMethod2("createMulticastLock", "mylock", "java.lang.String")
Tablet.RunMethod2("setReferenceCounted", False, "java.lang.boolean") 'not really necessary but safer
Tablet.RunMethod("acquire") 'acquire the lock
End Sub
Sub Service_Start (StartingIntent As Intent)
Private ws As PhoneWakeState
Private tcpClient(Starter.iNumPlayers) As Socket
Private aStream(Starter.iNumPlayers) As AsyncStreams
Private sClientIP(Starter.iNumPlayers) As String
ws.KeepAlive(True)
GetNetIP
UDPSocket1.Initialize("UDP", UDPPort, 256)
bFoundServer = False
iClientsConnected = 0
CheckForServer
End Sub
'Send "I'm a server" message
Sub SendMYID
UDPSendMsg("Server", sBroadCastIP)
End Sub
Sub UDPSendMsg(msg As String, IPAddr As String)
Private Packet As UDPPacket
Private data() As Byte
data = msg.GetBytes("UTF8")
Packet.Initialize(data, IPAddr, UDPPort)
UDPSocket1.Send(Packet)
End Sub
'Receive UDP broadcast messages
Sub UDP_PacketArrived (Packet As UDPPacket)
Private msg As String
msg = BytesToString(Packet.Data, Packet.Offset, Packet.Length, "UTF8")
Private IP As String = Packet.HostAddress
Private parts() As String = Regex.Split("~", msg)
If IP <> sMyIP Then
Select Case parts(0)
'If the other device is a server
Case "Server"
tmr.Enabled = False
'Log("Server IP: " & IP)
ToastMessageShow("Player found at " & IP, True)
UDPSendMsg("Client", IP)
bFoundServer = True
sServerIP = IP
UDPSocket1.Close
tmr.Interval = 2000
tmr.Enabled = True
'If the other device is a client
Case "Client"
tmr.Enabled = False
ToastMessageShow("Player found at " & IP, True)
'Log("Client IP: " & IP)
UDPSocket1.Close
Case Else
End Select
End If
End Sub
Sub Service_Destroy
Kill_Connections
End Sub
Sub Kill_Connections
Private ws As PhoneWakeState
Private iCnt As Int
UDPSocket1.Close
Tablet.RunMethod("release")
For iCnt = 0 To tcpClient.Length - 1
tcpClient(iCnt).Close
Next
tcpServer.Close
tmr.Enabled = False
ws.ReleaseKeepAlive
End Sub
'Get the network address & create a UDP broadcast address
Sub GetNetIP
Private netS As ServerSocket
netS.Initialize(0, "")
sMyIP = netS.GetMyIP
netS.Close
If Not(Starter.cOpts.ManualNet) Then
sIP = Regex.Split("\.", sMyIP)
'UDP broadcasts are on the .255 address of the network
sBroadCastIP = sIP(0) & "." & sIP(1) & "." & sIP(2) & "." & "255"
Else
sBroadCastIP = Starter.cOpts.OtherIP
End If
End Sub
Sub CheckForServer
If tmr.IsInitialized = False Then tmr.Initialize("tmr", 5000)
tmr.Enabled = True
End Sub
Sub tmr_Tick
'Haven't found the server yet
If bFoundServer = False Then
iTryCount = iTryCount + 1
If tcpServer.IsInitialized = False Then
tcpServer.Initialize(TCPPort, "Server")
tcpServer.Listen
End If
tmr.Enabled = False
tmr.Interval = 2000
tmr.Enabled = True
SendMYID
Else
'Found the server so will TCP connect as a client
tmr.Enabled = False
tcpClient(0).Initialize("Client")
tcpClient(0).Connect(sServerIP, TCPPort, 10000)
End If
End Sub
Sub Client_Connected(Successful As Boolean)
tmr.Enabled = False
If Successful Then
'Connected as a client, so open a stream
ToastMessageShow("Connected", False)
Starter.NetGame.Initialize
Starter.NetGame.PlayerMe = Starter.NetGame.PLAYER_SECOND
aStream(0).InitializePrefix(tcpClient(0).InputStream, False, tcpClient(0).OutputStream, "AStream")
Starter.NetGame.SendMsg("Connected:" & Starter.cOpts.GameMode & ":" & Starter.NetGame.PlayerMe)
Else
'Connection failed
tcpClient(0).Close
ToastMessageShow("Connection Failed", False)
StopService("")
End If
End Sub
Sub Server_NewConnection(Successful As Boolean, NewSocket As Socket)
tmr.Enabled = False
UDPSocket1.Close
If Successful Then
'Connected to a client
iClientsConnected = iClientsConnected + 1
ToastMessageShow("Connected Player " & (iClientsConnected + 1) & " of " & Starter.iNumPlayers, False)
If iClientsConnected = 1 Then
Starter.NetGame.Initialize
Starter.NetGame.PlayerMe = Starter.NetGame.PLAYER_FIRST
End If
tcpClient(iClientsConnected - 1) = NewSocket
aStream(iClientsConnected - 1).InitializePrefix(tcpClient(iClientsConnected - 1).InputStream, False, tcpClient(iClientsConnected - 1).OutputStream, "AStream")
sClientIP(iClientsConnected - 1) = Get_Client_IP(NewSocket)
Starter.NetGame.SendMsg("Connected:" & Starter.cOpts.GameMode & ":" & iClientsConnected)
Else
'Connection to client failed
tcpServer.Close
ToastMessageShow("Connection Failed", False)
StopService("")
End If
End Sub
'Get the client IP address from the socket
Sub Get_Client_IP(ClientSock As Socket) As String
Private r As Reflector
r.Target = ClientSock
r.Target = r.GetField("socket")
r.Target = r.RunMethod("getInetAddress") 'InetAddress
Return r.RunMethod("getHostAddress")
End Sub
'Received a message on the stream
Sub AStream_NewData (Buffer() As Byte)
Private msg As String
msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8")
'Do something with the msg string
End Sub