Local Multiplayer with UDP/TCP

It is impossible to make that work, as described in the tutorial http://coronalabs.com/blog/2014/09/23/tutorial-local-multiplayer-with-udptcp/

The compound is, but sock: send (“we are connected”) does not transmit.

My code:

main.lua

socket = require( "socket" ) local socket = require( "socket" ) local advertiseServer = function( button ) print("start") local send = socket.udp() send:settimeout( 0 ) --this is important (see notes below) local stop local counter = 0 --using this, we can advertise our IP address for a limited time local function broadcast() local msg = "AwesomeGameServer" --multicast IP range from 224.0.0.0 to 239.255.255.255 send:sendto( msg, "226.192.1.1", 11111 ) --not all devices can multicast so it's a good idea to broadcast too --however, for broadcast to work, the network has to allow it send:setoption( "broadcast", true ) --turn on broadcast send:sendto( msg, "255.255.255.255", 11111 ) send:setoption( "broadcast", false ) --turn off broadcast counter = counter + 1 if ( counter == 80 ) then --stop after 8 seconds stop() end end --pulse 10 times per second local serverBroadcast = timer.performWithDelay( 100, broadcast, 0 ) button.stopLooking = function() timer.cancel( serverBroadcast ) --cancel timer button.stopLooking = nil print("stop") end stop = button.stopLooking end local getIP = function() local s = socket.udp() --creates a UDP object s:setpeername( "74.125.115.104", 80 ) --Google website local ip, sock = s:getsockname() print( "myIP:", ip, sock ) return ip end local function findServer( button ) local newServers = {} local msg = "AwesomeGameServer" local listen = socket.udp() listen:setsockname( "226.192.1.1", 11111 ) --this only works if the device supports multicast local name = listen:getsockname() if ( name ) then --test to see if device supports multicast listen:setoption( "ip-add-membership", { multiaddr="226.192.1.1", interface = getIP() } ) else --the device doesn't support multicast so we'll listen for broadcast print("name") listen:close() --first we close the old socket; this is important listen = socket.udp() --make a new socket listen:setsockname( getIP(), 11111 ) --set the socket name to the real IP address end listen:settimeout( 0 ) --move along if there is nothing to hear local stop local counter = 0 --pulse counter local function look() repeat local data, ip, port = listen:receivefrom() --print( "data: ", data, "IP: ", ip, "port: ", port ) if data and data == msg then if not newServers[ip] then print( "I hear a server:", ip, port ) local params = { ["ip"]=ip, ["port"]=11111 } newServers[ip] = params con = connectToServer(getIP(),11111) print(con) end end until not data counter = counter + 1 if counter == 20 then --stop after 2 seconds stop() end end --pulse 10 times per second local beginLooking = timer.performWithDelay( 100, look, 0 ) function stop() timer.cancel( beginLooking ) button.stopLooking = nil --con = connectToServer(getIP(),11111) --print(con) --evaluateServerList( newServers ) --do something with your found servers listen:close() --never forget to close the socket! end button.stopLooking = stopLooking end function connectToServer( ip, port ) print("connect...") local sock, err = socket.connect( ip, port ) if sock == nil then return false end sock:settimeout( 0 ) sock:setoption( "tcp-nodelay", true ) --disable Nagle's algorithm sock:send("we are connected") --sock:send( ms ) return sock end button = {} local server = require "server" -- server\_b local serverb = display.newText("server", 400, 150, "pixelDR", 36) serverb:setTextColor(255, 0, 0) serverbF = function(event) advertiseServer(button) server.createServer() end serverb:addEventListener("tap",serverbF) -- client\_b local clientb = display.newText("client", 400, 200, "pixelDR", 36) clientb:setTextColor(0, 255, 0) clientbF = function(event) findServer( button ) end clientb:addEventListener("tap",clientbF) -- send\_b local sendb = display.newText("send", 400, 290, "pixelDR", 36) sendb:setTextColor(255, 0, 255) sendbF = function(event) send = connectToServer(getIP(),11111) print(send) end sendb:addEventListener("tap",sendbF) 

server.lua

local S = {} local socket = require( "socket" ) local clientList = {} local clientBuffer = {} S.getIP = function() local s = socket.udp() s:setpeername( "74.125.115.104", 80 ) local ip, sock = s:getsockname() print( "myIP:", ip, sock ) return ip end S.createServer = function() local tcp, err = socket.bind( S.getIP(), 11111 ) --create a server object tcp:settimeout( 0 ) local function sPulse() repeat local client = tcp:accept() --allow a new client to connect if client then print( "found client" ) client:settimeout( 0 ) --just check the socket and keep going --TO DO: implement a way to check to see if the client has connected previously --consider assigning the client a session ID and use it on reconnect. clientList[#clientList+1] = client clientBuffer[client] = { "hello\_client" } --just including something to send below end until not client local ready, writeReady, err = socket.select( clientList, clientList, 0 ) if err == nil then for i = 1, #ready do --list of clients who are available local client = ready[i] local allData = {} --this holds all lines from a given client repeat local data, err = client:receive() --get a line of data from the client, if any print(data ) if data then allData[#allData+1] = data end until not data if ( #allData \> 0 ) then --figure out what the client said to the server for i, thisData in ipairs( allData ) do print( "thisData: ", thisData ) --do stuff with data end end end for sock, buffer in pairs( clientBuffer ) do for \_, msg in pairs( buffer ) do --might be empty local data, err = sock:send( msg ) --send the message to the client end end end end --pulse 10 times per second local serverPulse = timer.performWithDelay( 100, sPulse, 0 ) local function stopServer() timer.cancel( serverPulse ) --cancel timer tcp:close() for i, v in pairs( clientList ) do v:close() end end return stopServer end return S

Mark Steelman Well what do you say?

For starters don’t start your server with a button, just start it when you run the program. Otherwise, if you push the server button more than once you will get an error because the port will be blocked the second time.

Second, in the function look(), the first parameter in the call to function connectToServer should be ip. As written the device is trying to connect to itself.

Third, you are getting blockage by having your server use the port 11111 and then trying to advertise the server on the same port.  The port can only be used by oneTCP or one UDP object. It doesn’t really matter what port you use for either, as long as it is in range and they are different. For the sake of this discussion lets use 22222 for the server. This means you are going to need to plug that number into createServer when you bind your TCP object. Also, you will need to plug that number into findServer when you make the params table as well as when you call connectToServer. Because it’s your program on both devices, you can be confident that the server is listening at that port.

Finally, you are going to want to change “We are connected” to “We are connected\n”. The receive function pulls in a line of data, “\n” designates end of line. You will notice that \n is trimmed off the line before it is printed, this it automatic. As it was, the server is waiting for the rest of the line before printing and there is no more line.

With those changes it will work.

I will contact Rob and get him to update the tutorial with the changes to the print statements and the port numbers.

Thank you for taking time to do the example. Best of luck to you.

Thank, so works:

main.lua

socket = require( "socket" ) local socket = require( "socket" ) server\_ip = 0 local advertiseServer = function( button ) print("start") local send = socket.udp() send:settimeout( 0 ) --this is important (see notes below) local stop local counter = 0 --using this, we can advertise our IP address for a limited time local function broadcast() local msg = "AwesomeGameServer" --multicast IP range from 224.0.0.0 to 239.255.255.255 send:sendto( msg, "226.192.1.1", 1234 ) --not all devices can multicast so it's a good idea to broadcast too --however, for broadcast to work, the network has to allow it send:setoption( "broadcast", true ) --turn on broadcast send:sendto( msg, "255.255.255.255", 1234 ) send:setoption( "broadcast", false ) --turn off broadcast counter = counter + 1 if ( counter == 80 ) then --stop after 8 seconds stop() end end --pulse 10 times per second local serverBroadcast = timer.performWithDelay( 100, broadcast, 0 ) button.stopLooking = function() timer.cancel( serverBroadcast ) --cancel timer button.stopLooking = nil print("stop") end stop = button.stopLooking end local getIP = function() local s = socket.udp() --creates a UDP object s:setpeername( "74.125.115.104", 80 ) --Google website local ip, sock = s:getsockname() print( "myIP:", ip, sock ) return ip end local function findServer( button ) local newServers = {} local msg = "AwesomeGameServer" local listen = socket.udp() listen:setsockname( "226.192.1.1", 1234 ) --this only works if the device supports multicast print("find ip ".. getIP()) local name = listen:getsockname() if ( name ) then --test to see if device supports multicast listen:setoption( "ip-add-membership", { multiaddr="226.192.1.1", interface = getIP() } ) else --the device doesn't support multicast so we'll listen for broadcast listen:close() --first we close the old socket; this is important listen = socket.udp() --make a new socket listen:setsockname( getIP(), 1234 ) --set the socket name to the real IP address end listen:settimeout( 0 ) --move along if there is nothing to hear local stop local counter = 0 --pulse counter local function look() repeat local data, ip, port = listen:receivefrom() --print( "data: ", data, "IP: ", ip, "port: ", port ) if data and data == msg then if not newServers[ip] then print( "I hear a server:", ip, port ) local params = { ["ip"]=ip, ["port"]=1235 } newServers[ip] = params con = connectToServer(ip,1235) server\_ip = ip --print(con) end end until not data counter = counter + 1 if counter == 20 then --stop after 2 seconds stop() end end --pulse 10 times per second local beginLooking = timer.performWithDelay( 100, look, 0 ) function stop() timer.cancel( beginLooking ) button.stopLooking = nil --evaluateServerList( newServers ) --do something with your found servers listen:close() --never forget to close the socket! end button.stopLooking = stopLooking end function connectToServer( ip, port ) print("connect...") local sock, err = socket.connect( ip, port ) if sock == nil then return false end sock:settimeout( 0 ) sock:setoption( "tcp-nodelay", true ) --disable Nagle's algorithm sock:send("we are connected!!!\n") --sock:send( ms ) return sock end button = {} local server = require "server" server.createServer() -- server\_b local serverb = display.newText("server", 400, 150, "pixelDR", 36) serverb:setTextColor(255, 0, 0) serverbF = function(event) advertiseServer(button) --server.createServer() end serverb:addEventListener("tap",serverbF) -- client\_b local clientb = display.newText("client", 400, 200, "pixelDR", 36) clientb:setTextColor(0, 255, 0) clientbF = function(event) findServer( button ) end clientb:addEventListener("tap",clientbF) -- send\_b local sendb = display.newText("send", 400, 290, "pixelDR", 36) sendb:setTextColor(255, 0, 255) sendbF = function(event) send = connectToServer(server\_ip,1235) print(send) end sendb:addEventListener("tap",sendbF)

server.lua

local S = {} local socket = require( "socket" ) local clientList = {} local clientBuffer = {} S.getIP = function() local s = socket.udp() s:setpeername( "74.125.115.104", 80 ) local ip, sock = s:getsockname() print( "myIP:", ip, sock ) return ip end S.createServer = function() local tcp, err = socket.bind( S.getIP(), 1235 ) --create a server object tcp:settimeout( 0 ) local function sPulse() repeat local client = tcp:accept() --allow a new client to connect if client then print( "found client" ) client:settimeout( 0 ) --just check the socket and keep going --TO DO: implement a way to check to see if the client has connected previously --consider assigning the client a session ID and use it on reconnect. clientList[#clientList+1] = client clientBuffer[client] = { "hello\_client" } --just including something to send below end until not client local ready, writeReady, err = socket.select( clientList, clientList, 0 ) if err == nil then for i = 1, #ready do --list of clients who are available local client = ready[i] local allData = {} --this holds all lines from a given client repeat local data, err = client:receive() --get a line of data from the client, if any --print(data ) if data then allData[#allData+1] = data end until not data if ( #allData \> 0 ) then --figure out what the client said to the server for i, thisData in ipairs( allData ) do print( "thisData: ", thisData ) --do stuff with data end end end for sock, buffer in pairs( clientBuffer ) do for \_, msg in pairs( buffer ) do --might be empty local data, err = sock:send( msg ) --send the message to the client end end end end --pulse 10 times per second local serverPulse = timer.performWithDelay( 100, sPulse, 0 ) local function stopServer() timer.cancel( serverPulse ) --cancel timer tcp:close() for i, v in pairs( clientList ) do v:close() end end return stopServer end return S

It is a pity that you can not do via bluetooth, for Wi-Fi Lan need an access point.

I agree, but at least we have a way to make multiplayer games that are cross platform and don’t require a remote server.

Great work. Many thanks to both of you guys!

Thanks to GamingStudio17 and mark_steelman for providing a working set of example code.  I am sure there are 10x more people who appreciate the work done, but don’t bother leaving a message of thanks.

Mark Steelman Well what do you say?

For starters don’t start your server with a button, just start it when you run the program. Otherwise, if you push the server button more than once you will get an error because the port will be blocked the second time.

Second, in the function look(), the first parameter in the call to function connectToServer should be ip. As written the device is trying to connect to itself.

Third, you are getting blockage by having your server use the port 11111 and then trying to advertise the server on the same port.  The port can only be used by oneTCP or one UDP object. It doesn’t really matter what port you use for either, as long as it is in range and they are different. For the sake of this discussion lets use 22222 for the server. This means you are going to need to plug that number into createServer when you bind your TCP object. Also, you will need to plug that number into findServer when you make the params table as well as when you call connectToServer. Because it’s your program on both devices, you can be confident that the server is listening at that port.

Finally, you are going to want to change “We are connected” to “We are connected\n”. The receive function pulls in a line of data, “\n” designates end of line. You will notice that \n is trimmed off the line before it is printed, this it automatic. As it was, the server is waiting for the rest of the line before printing and there is no more line.

With those changes it will work.

I will contact Rob and get him to update the tutorial with the changes to the print statements and the port numbers.

Thank you for taking time to do the example. Best of luck to you.

Thank, so works:

main.lua

socket = require( "socket" ) local socket = require( "socket" ) server\_ip = 0 local advertiseServer = function( button ) print("start") local send = socket.udp() send:settimeout( 0 ) --this is important (see notes below) local stop local counter = 0 --using this, we can advertise our IP address for a limited time local function broadcast() local msg = "AwesomeGameServer" --multicast IP range from 224.0.0.0 to 239.255.255.255 send:sendto( msg, "226.192.1.1", 1234 ) --not all devices can multicast so it's a good idea to broadcast too --however, for broadcast to work, the network has to allow it send:setoption( "broadcast", true ) --turn on broadcast send:sendto( msg, "255.255.255.255", 1234 ) send:setoption( "broadcast", false ) --turn off broadcast counter = counter + 1 if ( counter == 80 ) then --stop after 8 seconds stop() end end --pulse 10 times per second local serverBroadcast = timer.performWithDelay( 100, broadcast, 0 ) button.stopLooking = function() timer.cancel( serverBroadcast ) --cancel timer button.stopLooking = nil print("stop") end stop = button.stopLooking end local getIP = function() local s = socket.udp() --creates a UDP object s:setpeername( "74.125.115.104", 80 ) --Google website local ip, sock = s:getsockname() print( "myIP:", ip, sock ) return ip end local function findServer( button ) local newServers = {} local msg = "AwesomeGameServer" local listen = socket.udp() listen:setsockname( "226.192.1.1", 1234 ) --this only works if the device supports multicast print("find ip ".. getIP()) local name = listen:getsockname() if ( name ) then --test to see if device supports multicast listen:setoption( "ip-add-membership", { multiaddr="226.192.1.1", interface = getIP() } ) else --the device doesn't support multicast so we'll listen for broadcast listen:close() --first we close the old socket; this is important listen = socket.udp() --make a new socket listen:setsockname( getIP(), 1234 ) --set the socket name to the real IP address end listen:settimeout( 0 ) --move along if there is nothing to hear local stop local counter = 0 --pulse counter local function look() repeat local data, ip, port = listen:receivefrom() --print( "data: ", data, "IP: ", ip, "port: ", port ) if data and data == msg then if not newServers[ip] then print( "I hear a server:", ip, port ) local params = { ["ip"]=ip, ["port"]=1235 } newServers[ip] = params con = connectToServer(ip,1235) server\_ip = ip --print(con) end end until not data counter = counter + 1 if counter == 20 then --stop after 2 seconds stop() end end --pulse 10 times per second local beginLooking = timer.performWithDelay( 100, look, 0 ) function stop() timer.cancel( beginLooking ) button.stopLooking = nil --evaluateServerList( newServers ) --do something with your found servers listen:close() --never forget to close the socket! end button.stopLooking = stopLooking end function connectToServer( ip, port ) print("connect...") local sock, err = socket.connect( ip, port ) if sock == nil then return false end sock:settimeout( 0 ) sock:setoption( "tcp-nodelay", true ) --disable Nagle's algorithm sock:send("we are connected!!!\n") --sock:send( ms ) return sock end button = {} local server = require "server" server.createServer() -- server\_b local serverb = display.newText("server", 400, 150, "pixelDR", 36) serverb:setTextColor(255, 0, 0) serverbF = function(event) advertiseServer(button) --server.createServer() end serverb:addEventListener("tap",serverbF) -- client\_b local clientb = display.newText("client", 400, 200, "pixelDR", 36) clientb:setTextColor(0, 255, 0) clientbF = function(event) findServer( button ) end clientb:addEventListener("tap",clientbF) -- send\_b local sendb = display.newText("send", 400, 290, "pixelDR", 36) sendb:setTextColor(255, 0, 255) sendbF = function(event) send = connectToServer(server\_ip,1235) print(send) end sendb:addEventListener("tap",sendbF)

server.lua

local S = {} local socket = require( "socket" ) local clientList = {} local clientBuffer = {} S.getIP = function() local s = socket.udp() s:setpeername( "74.125.115.104", 80 ) local ip, sock = s:getsockname() print( "myIP:", ip, sock ) return ip end S.createServer = function() local tcp, err = socket.bind( S.getIP(), 1235 ) --create a server object tcp:settimeout( 0 ) local function sPulse() repeat local client = tcp:accept() --allow a new client to connect if client then print( "found client" ) client:settimeout( 0 ) --just check the socket and keep going --TO DO: implement a way to check to see if the client has connected previously --consider assigning the client a session ID and use it on reconnect. clientList[#clientList+1] = client clientBuffer[client] = { "hello\_client" } --just including something to send below end until not client local ready, writeReady, err = socket.select( clientList, clientList, 0 ) if err == nil then for i = 1, #ready do --list of clients who are available local client = ready[i] local allData = {} --this holds all lines from a given client repeat local data, err = client:receive() --get a line of data from the client, if any --print(data ) if data then allData[#allData+1] = data end until not data if ( #allData \> 0 ) then --figure out what the client said to the server for i, thisData in ipairs( allData ) do print( "thisData: ", thisData ) --do stuff with data end end end for sock, buffer in pairs( clientBuffer ) do for \_, msg in pairs( buffer ) do --might be empty local data, err = sock:send( msg ) --send the message to the client end end end end --pulse 10 times per second local serverPulse = timer.performWithDelay( 100, sPulse, 0 ) local function stopServer() timer.cancel( serverPulse ) --cancel timer tcp:close() for i, v in pairs( clientList ) do v:close() end end return stopServer end return S

It is a pity that you can not do via bluetooth, for Wi-Fi Lan need an access point.

I agree, but at least we have a way to make multiplayer games that are cross platform and don’t require a remote server.

Great work. Many thanks to both of you guys!

Thanks to GamingStudio17 and mark_steelman for providing a working set of example code.  I am sure there are 10x more people who appreciate the work done, but don’t bother leaving a message of thanks.

Thanks Mark for the amazing tutorial on creating a multiplayer connection.  Sorry for bumping this old thread, but it’s very useful and I have a question.

I’m following the example shown by GamingStudio17 and trying to figure out where in the code I would add createClientLoop from the tutorial, to allow the client to receive messages from the server.

For example, currently the server is continuously sending “hello_client” to every client.  So I added the following to the example at the top, but my client does not appear to receive anything:

sendbF = function(event) local sock = connectToServer(server\_ip,1235) createClientLoop(sock,server\_ip,1235) end

That looks ok. It must be something else that is causing the problem. Let me see your socket.send statement.

It’s the same send() operation as in the example:

 for sock, buffer in pairs( clientBuffer ) do for \_, msg in pairs( buffer ) do --might be empty print("Server sends to clients:", msg) local data, err = sock:send( msg ) --send the message to the clients end end

If I check the value of “err” from the sock:send() command, it is “nil”.

The clientBuffer array is in the form:  clientBuffer[client] = { “hello_client” }, where a client is the value returned by tcp:accept().

I’m using this for the createClientLoop():

createClientLoop = function( sock, ip, port ) local buffer = {} local clientPulse local function cPulse() local allData = {} local data, err -- Get line by line of data and store in allData repeat data, err = sock:receive() if data then allData[#allData+1] = data end if ( err == "closed" and clientPulse ) then --try again if connection closed print("Client connection closed. Re-connect and try to receive again.") connectToServer( ip, port ) data, err = sock:receive() if data then allData[#allData+1] = data end end until not data if ( #allData \> 0 ) then for i, thisData in ipairs( allData ) do print( "Client received from server:", thisData ) --------------------------- --react to incoming data --------------------------- end end -- TO DO: Set buffer for sending for i, msg in pairs( buffer ) do local data, err = sock:send(msg) if ( err == "closed" and clientPulse ) then --try to reconnect and resend print("Client connection closed. Re-connect and try to send again.") connectToServer( ip, port ) data, err = sock:send( msg ) end end end --pulse 10 times per second clientPulse = timer.performWithDelay( 100, cPulse, 0 ) local function stopClient() timer.cancel( clientPulse ) --cancel timer clientPulse = nil sock:close() end return stopClient end

As you can see from the above code (which is pretty much the same as your example), I never see the line “Client received from server”.

It appears nothing is in your buffer. For a quick and dirty experiment change local buffer = {} to local buffer = {“message to server\n”}

I already did send a message to the server when I called connectToServer, I had verified that it worked. The problem I’m having is that the client is not *receiving* from the server after the server had done its send() with the “hello_client” message.