rss

Corona SDK: How to communicate between lua and javascript

Wednesday, May 2, 2012

This tutorial is meant to provide users who have already some understanding in programming with the knowledge necessary to use the LUA SocketLib and the Coroutines in order to set up a TCP/IP server in a host application.

Introduction

TCP/IP is a large collection of different communication protocols based upon the two original protocols: TCP and IP. TCP is at the fourth level of the OSI model (transport layer) and IP at the third level (network layer). The TCP protocol is used for the data transmission from an application to the network and can handle a byte flow (stream) between a source and an addressee in a total reliable way. A TCP connection is represented by a quintuplet {protocol, IP local_address, local_port, IP remote_address, remote_port} and is unique all over a given network. A connection is totally defined by two half-sockets: the source (client) half socket and the destination (server) half socket [2].

When listening to a port, the TCP server creates a half-socket (server socket). When receiving a connection the server duplicates this half-socket and connects it with the distant half-socket (client socket) in order to build the communication socket that will insure the information data flow. Therefore, at each moment, the server has an available half-socket.

1 - Setting up a TCP/IP server

The usual procedure to create a TCP server can directly be applied with SocketLib functions. This schema is the following one:
  1. Create a server half-socket
  2. Attach the half-socket to a port
  3. Listen on this port
The SocketLib can be used like any other standard library provided with LUA (io, math, string, os, etc.) simply by calling its name, e.g. socket.tcp(). The following code shows how to create the TCP server.
local function createTCPServer( port )
   local socket = require("socket")
        
   -- Create Socket
   local tcpServerSocket , err = socket.tcp()
   local backlog = 5
        
   -- Check Socket
   if tcpServerSocket == nil then 
      return nil , err
   end
        
   -- Allow Address Reuse
   tcpServerSocket:setoption( "reuseaddr" , true )
        
   -- Bind Socket
   local res, err = tcpServerSocket:bind( "*" , port )
   if res == nil then
      return nil , err
   end
        
   -- Check Connection
   res , err = tcpServerSocket:listen( backlog )
   if res == nil then 
      return nil , err
   end
    
   -- Return Server
   return tcpServerSocket     
end

The function socket.tcp() allow us to create a TCP server object. Then one can access the server functions. All the functions used after the creation of the server object are really well known for this kind of job: setoption(), bind() and listen().

Most of the SocketLib functions return one parameter if successful and two parameters if not. If a failure occurs, the first returned value is nil and the second value is the error message.

The function createTCPServer() must be called once during the initialization of the host program. Moreover, if you set it with "localhost", the TCP server will accept only localhost incoming connections (i.e the client is localized on the same computer than the server). So if you wish to be able to connect to your server from a client placed physically in another computer, you have to set host as follow: host = "*"

Creating a TCP server is the easiest part. Now, let us tackle the hardcore one: the acceptation and the management of the in going connections.

2 - Lua: Incoming and outgoing connections management

The guiding principle of managing incoming connections is to set the TCP server in a non-ending loop and to wait for the connections at the beginning of the loop with the server:accept() function. But we immediately see the problem: if we enter a non-ending loop, all the host program will freeze at that point. This is absolutely unacceptable!

The answer to this problem is to transfer this non-ending loop into an execution path separated from the main execution path of the host program. The first function we have to write is a function that parses an URL into a key-value list. Then commands or values sent by the javascript client can be handled in a easier way. Note that we will receive data in a URL fashion.
function getArgs( query )
   local parsed = {}
   local pos = 0

   query = string.gsub(query, "&", "&")
   query = string.gsub(query, "<", "<")
   query = string.gsub(query, ">", ">")

   local function ginsert(qstr)
      local first, last = string.find(qstr, "=")
      if first then
         local pos = string.sub(qstr, 0, first-1)
         parsed[pos] = string.sub(qstr, first+1)
      end
   end

   while true do
      local first, last = string.find(query, "&", pos)
      if first then
         ginsert(string.sub(query, pos, first-1));
         pos = last+1
      else
         ginsert(string.sub(query, pos));
         break;
      end
   end
   return parsed
end

Due to some bugs related to how Android handles URLs in Honeycomb and ICS, I decided to pass parameters using JASON, eg. we can't simply do
native.showWebPopup("index.html?action=init")

Hence, we can also send parameters without using a TCP server. Initially, I was using this function together with the next piece of code and a web popup URL listener to handle the communication between lua and javascript. But the approach doesn't work with all devices.
-- Write values to be used by webpopup
function setArgs( args )
   local path = system.pathForFile( "args.json", 
                              system.DocumentsDirectory  )
   local file, errStr = io.open( path, "w+b" )
   if file then
      local newstr = ""
      for k,v in pairs(args) do
         if k ~= nil and v ~= nil then
            if newstr ~= "" then
               newstr = newstr .. ", "
            end
            local val = ""
            if type(v) == "boolean" or 
               tonumber(v) ~= nil then
                  val = tostring(v) 
            else
                val = "\"" .. v .. "\""
            end
            newstr = newstr .. "\"" .. k .. "\": " .. val 
         end
      end
      local content = "{ " .. newstr .. " }"
      file:write( content )
      file:close() 
      return true
   end
end

Now we write a function to initialize the TCP server, create a web popup and intercept client requests. The next piece of code illustrates how to do it.
local function doStuff( args )

   -- We set the initial parameters so
   -- they can immediately be used in our webpage. 
   setArgs( args ) 

   if runTCPServer ~= nil then
      Runtime:removeEventListener( "enterFrame", 
                                    runTCPServer )
   end

   -- We define a function to intercept 
   -- and process client requests.     
   runTCPServer = function()
      tcpServer:settimeout( 0 )
      local tcpClient , _ = tcpServer:accept()
      if tcpClient ~= nil then
         local tcpClientMessage , _=tcpClient:receive('*l')
         if tcpClient ~= nil then 
            tcpClient:send(20)                             
            tcpClient:close()
         end
         if ( tcpClientMessage ~= nil ) then
            local myMessage =  tcpClientMessage
            local event = {}
            local xArgPos = string.find( myMessage, "?" )
            if xArgPos then
               local m = string.sub( myMessage, xArgPos+1 )
               local newargs = getArgs(m)     
               if newargs.shouldLoad == nil or 
                      newargs.shouldLoad == "false" then
                  native.cancelWebPopup()
               else
                  -- do some stuff ...
                  newargs.arg = tostring(os.date( "*t" ))
                  setArgs(newargs) 
                  -- or you can use send  
                  -- tcpClient:send( "some stuff" .. "\n")
               end             
            end
         end
      end
   end
        
   if tcpServer == nil then 
      tcpServer, _ = createTCPServer( "8087" )
   end
   Runtime:addEventListener( "enterFrame" , runTCPServer )

   -- We set the base url to point to our 
   -- documents directory. To make things 
   -- easier you can pack all your resources 
   -- into a tar file and then uncompress it
   -- there. A routine to do such this is available.       
   local options = { 
      --hasBackground = false, 
      baseUrl = system.DocumentsDirectory, 
      --urlRequest = function( event ) return true end
      }

   -- We open the HTML page.
   native.showWebPopup("index.html", options )
        
end

3 - Creating a Javascript client

Finally, we have to write a javascript client to communicate with our lua TCP server. Hence, we need two javascript methods: getArgs, to read JSON values from a file called "args.json", and getVariable( name, defaultValue ) to later on access these values.
   var urlVars = {}

   function getArgs(){
      try {
         var xobj = new XMLHttpRequest();
         xobj.overrideMimeType("application/json");
         xobj.open('GET', "args.json", false);
         xobj.send(null);
         return eval("(" + xobj.responseText + ")");
      }
      catch(err){}
      return null;
   }

   function getVariable( name, defaultValue ){
      try {
         if (urlVars[name] == null) return defaultValue;
         return urlVars[name];
      }
      catch(err){}
      return defaultValue;
   }

Then we simply use an AJAX call to request or send data. After sending the data through the TCP server, we call the function update to make values available.
   function update( ){
      urlVars = getArgs();
      var arg = getVariable( 'arg', 1 );
      var tag = document.getElementById("method")
      tag.style.display = 'block';
      tag.innerHTML = arg;
      return true
   }
                                
   function send( params ){
      var xobj = new XMLHttpRequest();
      var server_ip = "http://127.0.0.1:8087"
      xobj.open('GET', server_ip + "?" + params, true);
      xobj.onreadystatechange = update( );
      xobj.send(null);
      return true
   }

   try{
      window.onload = function () {
            update();
      };
   } 
   catch(err){}

One can obtain the full source code here

References

[1] Corona SDK. anscamobile.com. 2012.
[2] Jerome Guinot. The LUA SocketLib and the Coroutines. ozone3d.net, 2006.

5 comments:


Tokyo Dan said...

Great tutorial. I'd also like to see a reverse version...communicating with a JavaScript server (nodeJS) via a lua client done in Corona or Gideros.



Bruno Simões said...

Interesting suggestion! Thank you for your feedback Dan.



Anonymous said...

Thank you for sharing this post!



SaC said...

Great post! Works perfetc, but I'm not able to send response to js client. I'm using jquery to make an ajax GET request. The response is always empty even when I send a string with tcpclient:send. What am I doing wrong?



Anonymous said...

A TCP/IP Server ... We used to say TCP/UDP or UDP ... TCP/IP is like saying, "I have an Internet/Ip network..."