Tutorial: Local multiplayer with UDP/TCP

Tutorial: Local multiplayer with UDP/TCP

Today’s guest tutorial comes to you courtesy of Mark Steelman, founder of Steelman Games LLC. Mark is currently working on a turn-based RPG incorporating local multiplayer called Legend of Us Roleplaying Game. Before becoming an indie developer, Mark worked for four years at Electronic Arts as a game designer. You can follow the progress of his projects on Facebook, Google+, or by subscribing to his newsletter.


Make devices find each other with UDP

As noted in the introduction, I’m building a turn-based role-playing game using Corona SDK. As part of the design, I wanted local multiplayer and I also wanted it to be cross-platform. After a lot of research, I figured out how to accomplish this and, in this tutorial, I will share some of what I learned. Even better: this architecture will work for both turn-based and action games, so stick around if you’re making an action game!

This tutorial assumes that you have a basic understanding of Corona SDK, Lua, and peer-to-peer networking. We are going to use LuaSocket which is included in Corona SDK under the socket library. Because this design is intended for cross-platform multiplayer, it’s not going to use any native systems. While it does assume that the player has a Local Area Network (LAN), that network doesn’t need to be connected to the internet.

Including LuaSocket

In order to use LuaSocket, you must first include it:

That’s the easy part — let’s continue…

UDP and TCP

First, let me give a brief explanation of UDP and TCP. Both of these are protocols which allow computers to talk to each other and each one has certain advantages and disadvantages. However, I’m only going to cover features that are relevant to the tutorial.

UDP has the ability to send messages to an address without knowing if anything is there. It doesn’t check to see if the message made it anywhere — it simply transmits the message. UDP also allows you to listen to an address without being connected to a computer at that address.

TCP is a reliable protocol. It sends messages to another computer that it’s connected to via a TCP socket. If the other computer responds that there was a problem with the message, it sends the message again.

Knowing this, you might ask “Why would anyone use UDP when TCP is so reliable?” Well, there are several reasons, but the reason most relevant to this tutorial is the fact that you must know the IP address of the server in order to use TCP. Any network which you can join without visiting the LAN administrator is assigning you a random IP address using DHCP. This means that we’ll need to discover the IP address of the server on our own. Fortunately, UDP can help with that.

Advertise the server

For local multiplayer to work, one of the devices must act as the server/host. The host doesn’t need to have special privileges in your game, but the primary record of the game in progress will be stored on this device. In the language of role-playing games, let’s call this player the game master. This game master is going to announce his/her presence to the local network via some method. I utilize an “Invite” button which calls the following function upon being pressed.

Here are some notes about this code:

  • Multicast is a device feature which allows one device to communicate with several. iPhones and iPads have it, but I’ve been told that iPods do not. I haven’t tried it on any Android devices, so maybe somebody can test it and report their results in the comments section. As a result of this inconsistency, we also use broadcast. “Why don’t we just use broadcast?” you might ask. Well, the catch with broadcast is that the LAN has to allow broadcasts. By using both, we are maximizing the chance of finding each other.
  • The “pulse” of the timer is ten times per second. I don’t recommend setting your timer pulse faster than that unless you have a good reason — after all, your game needs time to do other things. This is a standard pulse speed for most action games including MMOs.
  • The port you choose can be anything between 1 and 65535, however, applications almost always block the port that they use and you’ll get an error if you try to bind to a port that is currently in use. Likewise, if you bind to a port, you need to unbind/close the port when you end the game so you don’t block it indefinitely on the players device. Lower number ports are used by commonly run applications, so it’s best to use a port between 1024 and 65535.
  • The settimeout() function allows you to tell the socket how long to wait for a message before moving on. Default is to wait indefinitely, meaning that your game freezes until it gets a message. Setting it to 0 tells it to just check and if there’s nothing to receive and move on to the next task.

Finding the server

The client will need to know its own IP address for the next step. Fortunately, UDP in LuaSocket can help with that:

The IP address in the above function is arbitrary — I used the Google address because I know it. You don’t even need to be connected to the internet for this function to return your IP address, but you must at least be on a local network.

Listening for the server

Now we are prepared to listen for the server. We will recognize the server because of the message AwesomeGameServer. Obviously, this could be any string; we are just going to match strings.

I put a lot of inline comments above, but I’ll elaborate on a few things:

  • Notice that we account for the fact that not all devices have Multicast.
  • The receivefrom() function is going to just pull in anything that’s at that address, so we need to filter it. This is why we have the string message to compare with.
  • When two devices find each other, it can get painful if they both have a short duration. I like to make the server wait much longer than the clients. If the server is advertising, the client finds them pretty quick. Basically, I just want to avoid “Can you try that again? I missed it.”
  • In this example, I’m passing in a reference to the button that the player pressed to activate the function. I do this because so the player can push it again and stop broadcasting. If you don’t want to do that, you don’t need the button reference.

So, at this point, we know how to let the game master be discovered by the players. The essential IP address required to use TCP is attached to the UDP message. Now that we have the game master’s IP address, we can connect to their device using TCP.

Swapping strings

Now we’ll discuss how to create a TCP server, connect to it, and send messages back and forth.

First, let’s discuss what TCP will provide and what it won’t. Like I said above, once we have a connection between devices, they’ll be able to send messages back and forth. These messages will just be strings. Imagine it like a text message app — in this case, the app on one device sends texts to the app on another device. These messages are then interpreted by the apps on each device and some action occurs.

Security

This tutorial will not go in depth about security, but a couple points should be covered:

  • The server and client can only control each other as far as you allow it. As I’ve iterated several times now, TCP just sends and receives text strings. For a Pac-Man clone that could be controlled by a second device, about the only information the server would need is “BEGIN”, “UP”, “DOWN”, “LEFT”, and “RIGHT” — all else could simply be ignored.
  • You should never try to make your app accept functions that have been turned into a string. Let the client and the server have their own functions and just use the transmitted text to call the functions. If your app accepts functions, you open up a very serious security vulnerability, so don’t do it! Instead, just pass commands with parameters.

In any case, don’t lie awake at night worrying about this. Neither iOS nor Android will let you damage someone’s device with such foolishness, but it may ruin the install of your game!

Starting the server

The server runs in a periodic loop. On each iteration of the loop, it checks to see if any clients want to join and whether connected clients sent a message. If the buffer has any messages to send out, it sends them. Here’s a basic TCP server module with further explanation following:

And that is a basic server. Let’s start at the top with some explanation:

  • socket.bind() creates a server object which you bind to the port of your choice. I used 11111, but you can use any that we listed in the earlier section. Remember to close the TCP object when you shut down the server via the stopServer() function!
  • settimeout( 0 ) tells LuaSocket to move on if there’s no information waiting at the socket.
  • accept() returns a client object which represents the connection to the other device. Each client will get their own object and each one will need to be closed when the game is done. We do this in the function at the bottom called stopServer().
  • socket.select() goes through our list of client connections to see which are available. Any that are not available are ignored but not closed.
  • receive() receives one line of data. You can designate a line of data in a string by putting \n at the end. It’s simple and you’ll be able to create bite-sized pieces of data. This function is structured so that you end up with a numbered table of string lines. They are numbered in the order that they were received, but you can’t rely on the lines you send being received in the order you sent them. If this is important, and it often is, you’ll need to create a way for the server to know if a line is in the right order.
  • Next we go through the list of lines and interpret them. This is usually just a series of ifthen statements with a liberal use of the string library.
  • Finally, we send whatever is in the buffer. The buffer is another list of strings. Again, you can’t absolutely control the order in which they are received. You don’t have to use a buffer, but when you are using a multi-use device like a phone as a server, it’s a good idea. You may just :send() to a client socket at any time but the only way the device knows that the message didn’t go through is if the other device responds. If the other device is taking a call, it will ignore your message and the message will be lost. If you implement a buffer, it sends the message every pulse until something happens that removes the message from the buffer, however you’ll need to implement a way of knowing when to remove items from the buffer.

Connecting to the server

Connecting to the server is much simpler:

To elaborate on this slightly:

  • socket.connect is pretty self explanatory: attempt to connect to the server at that address.
  • settimeout( 0 ) again lets the socket know that you want it to just check the socket and move on if there’s no incoming message.
  • Nagle’s algorithm is a standard function that causes the socket to aggregate data until the data is of a certain size, then send it. If you are just going to send “UP” and you want it sent right away, you’ll want this off.

What’s not included in this example is a method to determine if the client is connecting for the first time or reconnecting (return session). This is outside the scope of this tutorial, but one option is to use a session ID which the client gets the first time it connects to the server. In this case, both the client and the server save the ID. Then, if the client loses the connection, this ID is sent upon reconnection and the server can update the client’s data with the new client socket.

Client loop

The final piece of the puzzle is the client loop. This will look very much like the server loop, but it never tries to accept connections.

Note that the client is always responsible for making the connection to the server. The server never tries to reach the client — it has enough to handle already. Beyond that, there isn’t anything clarify that wasn’t already covered in the server loop section.

Conclusion

I hope this tutorial helps you achieve your multiplayer dreams! While it’s not intended to be a comprehensive tutorial on networking, what I accomplished should help you get two devices talking with each other using TCP.

Thank you for reading and remember to check out Legend of Us Roleplaying Game which inspired me to figure all of this out.


Rob Miracle
rob@coronalabs.com

Rob Miracle creates mobile apps for his own enjoyment and the amusement of others. He serves the Corona Community in the forums, on the blog, and at local events.

25 Comments
  • Lerg
    Posted at 16:41h, 23 September

    Wow. Great topic!

  • egruttner
    Posted at 19:01h, 23 September

    A-M-A-Z-I-N-G !!!
    Thank you Rob!

  • Anton
    Posted at 05:05h, 24 September

    It would be great to see working example

    • simpleex
      Posted at 01:01h, 26 September

      +1

  • Bob
    Posted at 05:39h, 25 September

    Mark,

    Thanks for sharing your knowledge and experience. This should be helpful to many developers.

    Bob

  • CK
    Posted at 09:14h, 25 September

    Thank you for the tutorial.
    Does this also work with 2 devices connected via bluetooth?

    • Mark Steelman
      Posted at 18:21h, 26 September

      Bluetooth is a different protocol, it’s like TCP or UDP except it doesn’t need a LAN. As of right now, I don’t know how to get that to work with Corona but I’ll let you know if I figure it out.

      As for demonstrating this code, just take all the code above and put it in a “main.lua” file. Then make two buttons, one to call the function to advertise the server and one to call the function to look for the server. Set up the server loop to kick off when you start the app. Now load the app on a device and run it. Also run it in the emulator. Push the server button in the emulator and the client button on the device (or vis versa) and you will see print messages in the console of each that will confirm it is working. This is assuming that both the emulator and the device are on the same network.

      I didn’t explain that in detail in the tutorial because there are other tutorials on how to make a button and this tutorial is long enough as it is. You don’t have to use buttons of course, that is just an easy way to demonstrate the code.

  • Rob Miracle
    Posted at 17:00h, 25 September

    All thanks go to Mark for this!

    CK: I doubt this will work over bluetooth unless you’re doing TCP/IP networking over Bluetooth.

    Rob

  • Mark Steelman
    Posted at 06:36h, 08 October

    I looked at Anton’s example linked above and I need to make a couple of corrections to my tutorial. Rob is going to fix them inline but I will point them out here because it might help you not make the same mistake.

    First, you need to use a different port for your server and for finding other devices. The server will block the port it is using. We are changing the port that the server uses in the tutorial to 22222 but any legal port is fine. Be sure in your code that you don’t try to start the server more than once or you are going to get a port blocked error.

    Second, when you send a string over TCP you need to put “n” after it. The receive function in its default state reads a line. You need to tell it when the line has ended or it will keep waiting for the rest of the line before it prints.

  • Jv
    Posted at 13:20h, 13 May

    Does this work for people connecting to your server outside your LAN?

  • Anish Krishna
    Posted at 06:17h, 22 July

    i was making an app needing to send and receive just data bytes instead of string…
    how can i do it
    strings are going ok on UDP in my program…

  • AK
    Posted at 07:07h, 22 July

    how can i send a byte value (specifically from 0-255) in UDP and decode the same
    I am able to send strings successfully in UDP

  • stevon8ter
    Posted at 12:34h, 06 August

    I looked over this tutorial, and it turned out to be really useful.

    I’m coding in CODEA, but this also has LuaSocket and thus most part could be reused, thanks for the great tutorial 🙂

  • stevon8ter
    Posted at 10:19h, 10 August

    One small thing tho, in “advertising the server”, there’s this:

    send:sendto( msg, “228.192.1.1”, 11111 )

    but the IP should be 226. …

    since the client is listening to the 226. … as well 🙂

  • Adi
    Posted at 00:17h, 19 September

    Very useful. Thanks for sharing.

  • Wesley
    Posted at 14:14h, 03 November

    I had to use the following code to get the local broadcast working. It basically replaces the last octet of your IP address with 255. A more correct solution would probably be to use the correct broadcast address based on the subnet mask but I was unsure of how to do this in CoronaSDK and I believe that this will work on most home networks (hopefully other networks support multicast or 255.255.255.255 broadcast).

    --http://stackoverflow.com/questions/20459943/find-the-last-index-of-a-character-in-a-string
    function findLast(haystack, needle)
    local i=haystack:match(".*"..needle.."()")
    if i==nil then return nil else return i-1 end
    end

    -- replace last octet of our ip address with '255'
    function get_local_broadcast_address()
    local ip = getIP()
    local index = findLast(ip, "%.")
    local substr = string.sub(ip,1,index) .. "255"
    return substr
    end

  • Adi
    Posted at 23:31h, 05 November

    Great tutorial. Its been very helpful in our current game.

  • awais
    Posted at 21:32h, 16 November

    when i run on device Runtime error occurs ( attempt to index local “UDPBroadcast”(a nil value).)

    • Mohammad Fakhreddin
      Posted at 14:07h, 21 January

      I checked your code change this part of your code to work correctly:
      Your Main.lua
      —————–
      sendbF = function(event)
      — Get a session ID (“sock”) from server
      if(server_ip~=0) then
      local sock = connectToServer(server_ip,1235)
      createClientLoop(sock,server_ip,1235)
      else
      findServer(sendbF)
      end
      end

  • Mohammad Fakhreddin
    Posted at 14:05h, 21 January

    Thanks for your tutorial
    I have to say it needs a few changes for working correctly on android devices :
    I changed your client listener to 0.0.0.0
    ——————————————
    Client:
    ———————————————————
    listen = socket.udp() –make a new socket
    listen:setsockname( “0.0.0.0”, 1119 ) –set the socket name to the real IP address
    ———————————————————
    Server
    ————————————————-
    print(“Advertise server”)
    assert(send:setoption( “broadcast”, true )) –turn on broadcast
    assert(send:sendto( msg, “255.255.255.255”, 1119 ))
    assert(send:setoption( “broadcast”, false )) –turn off broadcast

  • Tapas
    Posted at 11:21h, 22 February

    Its a great help. But in my case in the following code in createServer method
    local ready, writeReady, err = socket.select( clientList, clientList, 0 )
    Table ready is empty and writeReady contains the client data. Can you let me why is that.

  • Adi
    Posted at 22:59h, 26 June

    Seems this code is no longer working in latest build 2016.2907. Perhaps it is due to the update of LuaSocket lib in build 2016.2883. Appreciate any advise on how to fix it.