Networking UDP and TCP on the same client.

I have made excellent progress on iPod Touch client that will be connected to a local classroom network (no Internet).  We two servers:

  • main TCP server: passes messages back and forth among clients and a shared display (on a computer). It also sends one way messages to a robot server.
  • robot UDP server: receives messages only from the main TCP server and sends data directly to the clients and shared display over a UDP connection (lossy is ok).

These two servers are already working very well on a laptop (or desktop) computer.

I am struggling to make the Corona SDK iPod Touch client.  Here’s what I need to do:

  • create a UDP listener that processes the incoming data stream from the robot server and updates the graphical display.
  • create a two-way TCP connection that sends and receives messages from the main server.  These received messages can be either responses to a message sent, or initiated by the server. 

At the moment the UDP and TCP connections are on different ports.  I find a great deal on the Corona site about ads and http networking.  Are there any good examples of TCP and UDP networking I can follow?  I have read through the Lua Socket documentation.

I have been able to connect with both the UDP and TCP connections, send a message and receive a reply like:

local socket = require "socket" local tcp = socket.tcp() tcp:connect(address, port) local function handleButton (event) if event.phase == "began" then … else event.phase == "ended" then local response = tcp:send("login USERNAME") print(response) response = tcp:receive() print(response) end end

If I try to click the button again, it hangs.   The server is sending back a response each time.  I don’t have a good idea how to receive messages pushed by the server when the client hasn’t sent anything.

I realize these are probably questions with simple answers.  If anyone could direct me to relevant examples I would much appreciate it.

–Scot

Ok. Discovered one simple problem.  I forgot to append the “\n” character which hung on the server end.  Still need to know how to set up the tcp connection to receive as well as send messages without interrupting the gui and UDP data stream. 

A few more discoveries.  TCP exchanges are going well as long as the client initiates. I learned that native alerts on IOS are nonblocking, which can be useful.

Still not sure how to set up a listener that won’t interrupt the program flow.  Does the performWithDelay() function allow other processes to continue during the delay?  Putting nearly clear touch pads on top of the interface greatly improved performance.

I think I will be able to suspend UI inputs during the robot runs on the client.  I will still need to update the display to graph the motion.

performWithDelay is nonblocking, the function it calls when delay elapses is however. LUA sockets are synchronous (blocking) so while you are receiving data your GUI will be blocked. Is this a lot of data? I would just check in regular intervals say one second if there was something sent. You should implement some connection checking as well in case it breaks. You can check for data by doing read with a short timeout but make sure if you receive something and it’s not all the data that you append what was already read to the next read operation. Also note that default read pattern waits for LF so make sure you have it the end of every packet and if you send more than one line at time repeat the read until you get a timeout. While you wait for time out your app will block so better to make it short and read until you timeout with no result. Make sure to give app a chance to refresh between reads so always call a read in performWithDelay with at least 1 ms.

@prizmoz.  Wow! Many thanks.  A lot of great stuff packed in a small message.  Yeah, I discovered some of the timing issues when checking for the host over the tcp connection.  Wow, very cool to have peformWithDelay to be non-blocking.  I think I can do a reasonable timing loop during the UDP data stream. (updates of robot position 4-5 times a second.  Not worried about UI during the run.  

Thanks.  I’ll just keep putting my questions here as they come up.

I am trying to set up a listener and I can’t seem to get it working…

Here is the code.  It works one time, but will not keep looping…

socket = require "socket" tcp = socket.tcp() tcp:settimeout(0) --networking functions local function tcpmonitor (event) print("monitoring tcp connection port...") local data repeat local status = tcp:getpeername() if status == nil then print("\*\*\*\*\*\*\*\*\* Connecting...") status = tcp:connect(Data.address, Data.port) end data = tcp:receive() if data ~= nil then print("\*\*\*\*\*\*\*"..data) end until data ~= nil end timer.performWithDelay(100, tcpmonitor(),0)

Well the way you wrote it it will stay in repeat loop until it gets some data.

The first problem is you timer.

Also a side note: What is event? you don’t have it defined and you don’t use it in tcpmonitor.

You have to pass a function to the timer not a result of the function. Try this:

timer.performWithDelay(100, function() tcpmonitor(event) end,0)

if you get rid of the event than just do this:

timer.performWithDelay(100, tcpmonitor,0)

Now for your repeat loop, i would keep reading until result is null and err is timeout like this:

socket = require "socket" tcp = socket.tcp() tcp:settimeout(0) --networking functions local part = "" local hTimer local function tcpmonitor () if hTimer then timer.pause(hTimer) end print("monitoring tcp connection port...") local data local status = tcp:getpeername() if status == nil then print("\*\*\*\*\*\*\*\*\* Connecting...") status = tcp:connect(Data.address, Data.port) part = "" end data, err, part = tcp:receive("\*l", part) if not data then if err ~= "timeout" then print("connection failed: ", err) part = "" else if part and part ~= "" then -- give app time to refresh timer.performWithDelay(1, tcpmonitor) return end end else print("\*\*\*\*\*\*\*"..data) part = "" --check for more data -- give app time to refresh timer.performWithDelay(1, tcpmonitor) return end --resume the original timer only when you get a timeout and nothing was read if hTimer then timer.resume(hTimer) end end hTimer = timer.performWithDelay(100, tcpmonitor,0)

Not tested so might have a typo or a bug in it but it will give you an idea.

Wow again!  This is starting to make some sense.  Your code is really helpful.  I have implemented it as an event timer at the moment but I like your approach to pausing the original timer unless it needs to fire again.

Here’s my code so far.  Not robust enough yet.  Doesn’t handle lost connections very well, or restarts of the simulator.  The app won’t restart so I need to figure out how to handle it if the server quits or the connection drops.

tcp = socket.tcp() tcp:settimeout(0) tcp:bind("\*",9999) --networking functions tcpmonitor = {} function tcpmonitor:timer (event) local listening = true local status = tcp:getpeername() if status == nil then status = tcp:connect(Data.address, Data.port) end local data,error = tcp:receive() if data ~= nil then cnet.handleMessage(data) listening = false else -- error handling goes here end socket.sleep(0.01) end listener = timer.performWithDelay(100, tcpmonitor, 0)

Huge thanks.

–Scot

That’s ok but you run the risk of flooding the socket if the server will send a lot of messages rapidly as you are only reading one line every 100ms. I strongly suggest you read until you get nothing (data == nil and error = “timeout”)

Ok.  Added a repeat loop to get all the data.  Works well.   I also start up the listener on “applicationStart” When I restart the simulator the app no longer connects to the server.  I can’t figure out how to make sure the client reconnects every time.  Here’s the code and the output:

The code:

tcp = socket.tcp() listener = {} --networking functions tcpmonitor = {} function tcpmonitor:timer (event) local listening = true local client = tcp:getpeername() if client == nil then local status, sterr = tcp:connect(Data.address, Data.port) end timer.pause(listener) repeat local data,error = tcp:receive() print("=== client", client) print("+++ status", status) print("\>\>\> sterr", sterr) print("\*\*\* data", data) print("--- Error:", error) if data ~= nil then cnet.handleMessage(data) listening = false else -- error handling goes here end until data == nil or error == "closed" timer.resume(listener) socket.sleep(0.01) end local handleRuntimeEvents = function (event) print("\*\*\*\*\*\*\*\* event.type", event.type) if event.type == "applicationStart" then print("\*\*\*\*\*\* application started............") tcp:settimeout(0) tcp:bind("\*",9999) listener = {} listener = timer.performWithDelay(100, tcpmonitor, 0) elseif event.type == "applicationExit" then tcp:close() -- timer.cancel(listener) -- listener = {} end end Runtime:addEventListener("system", handleRuntimeEvents)

The first time I run it in the simulator and stop it:

2014-01-31 19:18:17.879 Corona Simulator[8639:d03] \*\*\*\*\*\*\*\* event.type applicationStart 2014-01-31 19:18:17.879 Corona Simulator[8639:d03] \*\*\*\*\*\* application started............ 2014-01-31 19:18:17.991 Corona Simulator[8639:d03] === client nil 2014-01-31 19:18:17.991 Corona Simulator[8639:d03] +++ status nil 2014-01-31 19:18:17.991 Corona Simulator[8639:d03] \>\>\> sterr nil 2014-01-31 19:18:17.991 Corona Simulator[8639:d03] \*\*\* data nil 2014-01-31 19:18:17.991 Corona Simulator[8639:d03] --- Error: timeout 2014-01-31 19:18:18.107 Corona Simulator[8639:d03] === client 192.168.1.4 2014-01-31 19:18:18.107 Corona Simulator[8639:d03] +++ status nil 2014-01-31 19:18:18.108 Corona Simulator[8639:d03] \>\>\> sterr nil 2014-01-31 19:18:18.108 Corona Simulator[8639:d03] \*\*\* data Welcome to the server. 2014-01-31 19:18:18.108 Corona Simulator[8639:d03] --- Error: nil 2014-01-31 19:18:18.108 Corona Simulator[8639:d03] === client 192.168.1.4 2014-01-31 19:18:18.108 Corona Simulator[8639:d03] +++ status nil 2014-01-31 19:18:18.109 Corona Simulator[8639:d03] \>\>\> sterr nil 2014-01-31 19:18:18.110 Corona Simulator[8639:d03] \*\*\* data nil 2014-01-31 19:18:18.110 Corona Simulator[8639:d03] --- Error: timeout 2014-01-31 19:18:18.239 Corona Simulator[8639:d03] === client 192.168.1.4 2014-01-31 19:18:18.239 Corona Simulator[8639:d03] +++ status nil 2014-01-31 19:18:18.240 Corona Simulator[8639:d03] \>\>\> sterr nil 2014-01-31 19:18:18.240 Corona Simulator[8639:d03] \*\*\* data nil . . . 2014-01-31 19:22:17.598 Corona Simulator[8639:d03] \*\*\*\*\*\*\*\* event.type applicationExit

server side:

New client connected: 192.168.1.51 Client disconnected: 192.169.1.51

The second time I run it in the simulator:

2014-01-31 19:22:14.847 Corona Simulator[8639:d03] \*\*\*\*\*\*\*\* event.type applicationStart 2014-01-31 19:22:14.847 Corona Simulator[8639:d03] \*\*\*\*\*\* application started............ 2014-01-31 19:22:14.963 Corona Simulator[8639:d03] === client nil 2014-01-31 19:22:14.963 Corona Simulator[8639:d03] +++ status nil 2014-01-31 19:22:14.964 Corona Simulator[8639:d03] \>\>\> sterr nil 2014-01-31 19:22:14.964 Corona Simulator[8639:d03] \*\*\* data nil 2014-01-31 19:22:14.964 Corona Simulator[8639:d03] --- Error: closed 2014-01-31 19:22:15.079 Corona Simulator[8639:d03] === client nil 2014-01-31 19:22:15.079 Corona Simulator[8639:d03] +++ status nil 2014-01-31 19:22:15.079 Corona Simulator[8639:d03] \>\>\> sterr nil 2014-01-31 19:22:15.079 Corona Simulator[8639:d03] \*\*\* data nil 2014-01-31 19:22:15.079 Corona Simulator[8639:d03] --- Error: closed . . . 2014-01-31 19:22:17.598 Corona Simulator[8639:d03] \*\*\*\*\*\*\*\* event.type applicationExit

server side does not show any connection.

You might have a problem with bind. I don’t know why you need it as you’re not opening a server socket you are connecting to your server. Try removing that. Also print the result of the connect.

And what do you have to do to get it working again afterwards?

It might be a problem on the server also.

You were soooooo right!  Removing bind did the trick.  I also discovered that I really need to kill and restart the socket for a new login.  The tcp stuff is working really well right now.

I just cancel the listener and close the tcp socket and reinitiate both.  Humming along now.

Thanks for all your help.

There are still the UDP connections to worry about, but I’ll put up another topic if I need help on those.

I really appreciate the help.

–Scot

You’re welcome.

Ok. Discovered one simple problem.  I forgot to append the “\n” character which hung on the server end.  Still need to know how to set up the tcp connection to receive as well as send messages without interrupting the gui and UDP data stream. 

A few more discoveries.  TCP exchanges are going well as long as the client initiates. I learned that native alerts on IOS are nonblocking, which can be useful.

Still not sure how to set up a listener that won’t interrupt the program flow.  Does the performWithDelay() function allow other processes to continue during the delay?  Putting nearly clear touch pads on top of the interface greatly improved performance.

I think I will be able to suspend UI inputs during the robot runs on the client.  I will still need to update the display to graph the motion.

performWithDelay is nonblocking, the function it calls when delay elapses is however. LUA sockets are synchronous (blocking) so while you are receiving data your GUI will be blocked. Is this a lot of data? I would just check in regular intervals say one second if there was something sent. You should implement some connection checking as well in case it breaks. You can check for data by doing read with a short timeout but make sure if you receive something and it’s not all the data that you append what was already read to the next read operation. Also note that default read pattern waits for LF so make sure you have it the end of every packet and if you send more than one line at time repeat the read until you get a timeout. While you wait for time out your app will block so better to make it short and read until you timeout with no result. Make sure to give app a chance to refresh between reads so always call a read in performWithDelay with at least 1 ms.

@prizmoz.  Wow! Many thanks.  A lot of great stuff packed in a small message.  Yeah, I discovered some of the timing issues when checking for the host over the tcp connection.  Wow, very cool to have peformWithDelay to be non-blocking.  I think I can do a reasonable timing loop during the UDP data stream. (updates of robot position 4-5 times a second.  Not worried about UI during the run.  

Thanks.  I’ll just keep putting my questions here as they come up.

I am trying to set up a listener and I can’t seem to get it working…

Here is the code.  It works one time, but will not keep looping…

socket = require "socket" tcp = socket.tcp() tcp:settimeout(0) --networking functions local function tcpmonitor (event) print("monitoring tcp connection port...") local data repeat local status = tcp:getpeername() if status == nil then print("\*\*\*\*\*\*\*\*\* Connecting...") status = tcp:connect(Data.address, Data.port) end data = tcp:receive() if data ~= nil then print("\*\*\*\*\*\*\*"..data) end until data ~= nil end timer.performWithDelay(100, tcpmonitor(),0)

Well the way you wrote it it will stay in repeat loop until it gets some data.

The first problem is you timer.

Also a side note: What is event? you don’t have it defined and you don’t use it in tcpmonitor.

You have to pass a function to the timer not a result of the function. Try this:

timer.performWithDelay(100, function() tcpmonitor(event) end,0)

if you get rid of the event than just do this:

timer.performWithDelay(100, tcpmonitor,0)

Now for your repeat loop, i would keep reading until result is null and err is timeout like this:

socket = require "socket" tcp = socket.tcp() tcp:settimeout(0) --networking functions local part = "" local hTimer local function tcpmonitor () if hTimer then timer.pause(hTimer) end print("monitoring tcp connection port...") local data local status = tcp:getpeername() if status == nil then print("\*\*\*\*\*\*\*\*\* Connecting...") status = tcp:connect(Data.address, Data.port) part = "" end data, err, part = tcp:receive("\*l", part) if not data then if err ~= "timeout" then print("connection failed: ", err) part = "" else if part and part ~= "" then -- give app time to refresh timer.performWithDelay(1, tcpmonitor) return end end else print("\*\*\*\*\*\*\*"..data) part = "" --check for more data -- give app time to refresh timer.performWithDelay(1, tcpmonitor) return end --resume the original timer only when you get a timeout and nothing was read if hTimer then timer.resume(hTimer) end end hTimer = timer.performWithDelay(100, tcpmonitor,0)

Not tested so might have a typo or a bug in it but it will give you an idea.

Wow again!  This is starting to make some sense.  Your code is really helpful.  I have implemented it as an event timer at the moment but I like your approach to pausing the original timer unless it needs to fire again.

Here’s my code so far.  Not robust enough yet.  Doesn’t handle lost connections very well, or restarts of the simulator.  The app won’t restart so I need to figure out how to handle it if the server quits or the connection drops.

tcp = socket.tcp() tcp:settimeout(0) tcp:bind("\*",9999) --networking functions tcpmonitor = {} function tcpmonitor:timer (event) local listening = true local status = tcp:getpeername() if status == nil then status = tcp:connect(Data.address, Data.port) end local data,error = tcp:receive() if data ~= nil then cnet.handleMessage(data) listening = false else -- error handling goes here end socket.sleep(0.01) end listener = timer.performWithDelay(100, tcpmonitor, 0)

Huge thanks.

–Scot