LuaSocket questions

edited March 2015 in Questions Posts: 192

Hello all,
Not sure much of us would know about this topic as it has yet to be implemented in to Codea(unless you are a beta tester, and even then it's not documented), but I have been taking a look at the LuaSocket docs and have a few questions I was hoping I could get answered:

  1. Will the Codea implementation of LuaSocket be changed at all from the normal library? Just wondering, and I assume not.

  2. In most multiplayer games, you can scan for local games. Is there a way to do this, to check for IP's on the local network hosting games and then connect to the sockets from there with any of the LuaSocket libraries?

  3. Can you send anything other than strings over the sockets? I don't really care, but if it is just strings then I see a lot of loadstring()() in my future.

  4. Will TCP be the only implementation of LuaSocket, or will there be any others such as FTP, etc?

Thanks for taking the time to read my stupid questions if anyone knows, and answering this brings me one step closer to building a simple multiplayer library for you all! Thanks!

-TheSolderKing

«13

Comments

  • edited March 2015 Posts: 1,976

  • Posts: 2,042

    @SkyTheCoder, can he set the category to beta even though he isn't a tester?

  • Posts: 1,976

    @JakAttak I didn't realize he wasn't a beta tester. How does he know about sockets, though?

  • IgnatzIgnatz Mod
    edited March 2015 Posts: 5,396

    Category is reset.

    Let's talk sockets.

  • Posts: 418

    @SkyTheCoder Simeon mentioned it here

  • IgnatzIgnatz Mod
    edited March 2015 Posts: 5,396

    Does anybody actually know how to write socket code?

  • edited March 2015 Posts: 1,976

    @Goatboy76 Sorry, I missed that.

    @Ignatz I think @toffer does, he posted this code that works:

    function setup()
        socket=require("socket") --EDIT added by ignatz  
        server = socket.tcp()
        -- Replace with your local IPad ip
        server:bind("ip goes here, example: 123.456.7.89",52096)
        server:listen()
        server:settimeout(0.0)
        local ip,port = server:getsockname()
        print("please telnet to", ip, "on port", port)
        client = nil
        buf = {}
    end
    
    local _twu = tween.update
    tween.update = function(...)
        if not client then
            client = server:accept()
            if client then
                client:settimeout(0.0)
                local ip,po = client:getsockname()
                client:send("Welcome to the REPL mr(s) "..ip.." "..po.."\n")
                client:send("Type bye to quit\n")
            end
        end
        if client then
            local lin, err = client:receive()
            if not err then
                if lin == "bye" then
                    client:close()
                    client = nil
                else
                    table.insert(buf,lin)
                    local f,e = load(table.concat(buf,"\n"))
                    if not e then
                        f()
                        client:send("> eval\n")
                        buf = {}
                    end
              end
            end
        end
        _twu(...)
    end
    
    
  • IgnatzIgnatz Mod
    edited March 2015 Posts: 5,396

    The ip address referred to in the code above is your internal network ip. You get this from Settings, tap your (ticked) Wifi provider and you should see an ip address.

    toffer has used a clever hack to keep listening for socket messages. He replaces the normal tween update function (which runs each time a frame is drawn) with the function above, so it checks for socket messages instead. He has, however, stored the name of the original tween update function in _twu, and runs this at the end of the function above, so that the tweens do get updated. So he has basically just added some code into the tween update function.

    This means that Codea will run normally while the socket is listening.

    EDIT - it worked fine to start with, now Codea knows when I send messages from my PC, but doesn't recognise "bye" and is unable to print what I type. Hmmm..
    :-?

  • edited March 2015 Posts: 2,042

    I think I understand the sockets somewhat...

    For example, if I wanted to send data between two iPads I believe it would be something like this:

    function setup()
        socket = require("socket")
    
        local myip, myport = getLocalIP(), 5400
        print("Connect to " .. myip .. ":" .. myport)
    
        server = socket.udp()
        server:setsockname(myip, myport)
        server:settimeout(0)
    
        client = socket.udp()
        client:setpeername(myip, myport)
        client:settimeout(0)
    
        parameter.text("msg_to_send", "")
        parameter.action("send message", sendMessage)
    end
    
    function getLocalIP()
        local randomIP = "192.167.188.122"
        local randomPort = "3102" 
        local randomSocket = socket.udp() 
        randomSocket:setpeername(randomIP,randomPort) 
    
        local localIP, somePort = randomSocket:getsockname()
    
        randomSocket:close()
        randomSocket = nil
    
        return localIP
    end
    
    function sendMessage()
        client:send(msg_to_send)
    
        print("Client sent to server: '" .. msg_to_send .. "' at", os.date("%x, %X", os.time()))
    end
    
    function draw()
        background(255, 255, 255, 255)
    
        local data, msg_or_ip, port_or_nil = server:receivefrom()
        if data then
            print("Server received from client: '" .. data .. "' at", os.date("%x, %X", os.time()))
        end
    end
    

    Though obviously the client code would run on one and the server code on the other.

    EDIT: updated with method to get local ip (so the server iPad could tell you it's ip, which you could then tell the client iPad to connect to)

  • Posts: 289

    remote control?

  • IgnatzIgnatz Mod
    Posts: 5,396

    @JakAttak - thanks! =D>

  • toffertoffer Mod
    Posts: 151

    @JakAttak - nice shot for the ip retrieval!

  • dave1707dave1707 Mod
    Posts: 7,522

    @JakAttak Is the code working correctly. If I put in an ip of 0.0.0.0 , the ip stays at 0's, but it still says the message was sent and received. I don't know anything about sockets, so I can't say what's happening.

  • Posts: 2,042

    @dave1707, I believe it will work as long as the ip given to the client and server is the same. Of course, the true test will be running this on two iPads, which I will try once 2.3 comes out.

  • Thanks everyone for your help and example code! You answered most of my questions, but still, is there any way I can make a game where both players do not need to know the other's ip, ie scanning for nearby games? Thanks!

  • edited March 2015 Posts: 154

    @TheSolderKing, well, the programs will still heed to know each other IPs, in order to communicate efficiently. Several ways that can be done.

    One approach could be as simple as periodically broadcasting your ip via a specially addressed (255.255.255.255) udp packet, which gets delivered to all devices on your local network. The packet can contain a simple message like: "my ip is 192.168.1.5" or whatever it is. Another client does the same, and this way they can discover each other.

    Another approach could be writing a discovery service, which could be running on your desktop computer, and then each client will be able to register with it, and also ask for all the others who had registered with it too. (that could be a simple HTTP service, you don't necessarily need to use raw sockets)

  • IgnatzIgnatz Mod
    Posts: 5,396

    @TheSolderKing, @juce - for home networks, local ip addresses are usually sequential and only the last digit changes, perhaps making it easy to scan for a player who sets up as the server.

  • edited March 2015 Posts: 154

    @Ignatz, that's true. Just scanning the IPs sequentially would sort of work too, although then you have to guess what valid range is and such. I guess my main point (which i failed to mention :) ) was that the whole process consists of two steps:
    1. Discover the peer to play with
    2. Use discovered IP addr of the peer to communicate directly

  • IgnatzIgnatz Mod
    Posts: 5,396

    I haven't tried the latest code above, but using toffers' code, I was able to send messages but not read them at the other end, eg pritning them showed nothing, and "bye" wasn't recognised

  • toffertoffer Mod
    Posts: 151

    @Ignatz - strange, I'll have to check. from the telnet connection you'll have to type lua code followed by carriage return, the server wait for "valid code" to execute. Even with a simple print("Hello") it does not work ? Sorry for that.

  • IgnatzIgnatz Mod
    Posts: 5,396

    Not your fault, @toffer

    I changed your code so it just prints the variable "lin"

    But even the test for lin="bye" doesn't recognise the word bye, for some reason. It gets the message but doesn't treat it as text.

  • toffertoffer Mod
    Posts: 151

    @Ignatz - odd can you paste your code ?

  • IgnatzIgnatz Mod
    Posts: 5,396

    I am using your exact code with a statement that prints "lin" when it is received. It just prints blank, and the program doesn't recognise "bye" as it should.

    Funnily, when I first tried it, it did recognise bye. Maybe it's something on the PC side, but I can't think what.

  • edited March 2015 Posts: 2,042

    Just popping back in to say I got the opportunity to test my above code on two devices, (apple testflight is linked to id not device, so I signed in on a friend's), and it worked great.

    I plan to put together a Multiplayer class of sorts to make connecting to, and sending data between, instances of a game easier.

  • Posts: 835

    Can't wait @JakAttak

  • IgnatzIgnatz Mod
    Posts: 5,396

    That would be great, thanks!

  • edited March 2015 Posts: 2,042

    As promised, here is the first second version, along with a main tab that shows how to use it.



    --# Main function setup() local connectionMade = function() output.clear() parameter.clear() print("Connected!") end multihandler = Multiplayer(receiveData, connectionMade) parameter.action("Host Game", function() multihandler:hostGame() end) parameter.action("Search for and Join Game", function() multihandler:findGame() end) parameter.action("Join Game", function() if other_ip then multihandler:joinGame(other_ip, other_port) else parameter.text("other_ip", "") parameter.text("other_port", "") print("Fill in the host's ip and port, then click join game again") end end) opos, cpos = vec2(WIDTH / 2, HEIGHT / 2), vec2(WIDTH / 2, HEIGHT / 2) end function receiveData(d) cpos = loadstring("return " .. d)() end function draw() background(255, 255, 255, 255) multihandler:update() if multihandler.connected then local p1, p2 = opos, cpos if not multihandler.is_host then p1, p2 = cpos, opos end fill(255, 0, 0) ellipse(p1.x, p1.y, WIDTH / 4) fill(0, 0, 255) ellipse(p2.x, p2.y, WIDTH / 4) else fill(0) text("Waiting for connection...", WIDTH / 2, HEIGHT / 2) end end function touched(t) if multihandler.connected then opos = vec2(t.x, t.y) multihandler:sendData("vec2(" .. t.x .. ", " .. t.y .. ")") end end --# Multiplayer local socket = require("socket") Multiplayer = class() function Multiplayer:init(dcb, ccb) self.my_ip, self.my_port = self:getLocalIP(), 5400 self.peer_ip, self.peer_port = nil, self.my_port self.client = socket.udp() self.client:settimeout(0) self.connected = false self.is_host = false self.searching = false self.dataCallback = dcb or function() end self.connectedCallback = ccb or function() end end -- Returns this iPad's local ip function Multiplayer:getLocalIP() local randomIP = "192.167.188.122" local randomPort = "3102" local randomSocket = socket.udp() randomSocket:setpeername(randomIP,randomPort) local localIP, somePort = randomSocket:getsockname() randomSocket:close() randomSocket = nil return localIP end -- Set the connected status and call the connection callback if needed function Multiplayer:setConnectedVal(bool) self.connected = bool if self.connected then self.connectedCallback() end end function Multiplayer:setHostVal(bool) self.is_host = bool end -- Prepare to be the host function Multiplayer:hostGame() print("Connect to " .. self.my_ip .. ":" .. self.my_port) self.client:setsockname(self.my_ip, self.my_port) self:setConnectedVal(false) self.is_host = true self.searching = false end -- Find a host function Multiplayer:findGame() print("Searching for games...") self.searching = true local ip_start, ip_end = self.my_ip:match("(%d+.%d+.%d+.)(%d+)") for i = 1, 255 do if i ~= tonumber(ip_end) then tween.delay(0.01 * i, function() self.client:setsockname(ip_start .. i, self.my_port) self.client:sendto("connection_confirmation", ip_start .. i, self.my_port) end) end end end -- Prepare to join a host function Multiplayer:joinGame(ip, port) self.peer_ip, self.peer_port = ip, port self.client:setsockname(ip, port) self.is_host = false self.searching = false self:sendData("connection_confirmation") end -- Send data to the other client function Multiplayer:sendData(msg_to_send) if self.peer_ip then self.client:sendto(msg_to_send, self.peer_ip, self.peer_port) end end -- Check for data received from the other client function Multiplayer:checkForReceivedData() local data, msg_or_ip, port_or_nil = self.client:receivefrom() if data then -- Store the ip of this new client so you can send data back self.peer_ip, self.peer_port = msg_or_ip, port_or_nil if not self.connected and data == "connection_confirmation" then self:sendData("connection_confirmation") self:setConnectedVal(true) end -- Call callback with received data if data ~= "connection_confirmation" then self.dataCallback(data) end end end function Multiplayer:update() self:checkForReceivedData() end

    enjoy!

    EDIT: updated to allow one to automatically find another that is hosting

    A quick description of the class: it simply handles all of the connections between two iPad's and allows you to send and receive data. That's all. It's up to your project to tell it when to start, what to connect to, what to send, and to handle the received data.

  • Posts: 835

    Nice work @JakAttak =D>

  • IgnatzIgnatz Mod
    edited March 2015 Posts: 5,396

    B-)

  • Posts: 2,042

    I've updated the above code to add the ability to auto find and connect to a broadcasting client, though currently it just joins the first one it finds. I think I may work on it more to end up with a list of games you could join.

  • Posts: 1,595

    @JakAttak Oh lordie! I'm a long way away from multiplayer but I think a good test would be using the physics engine, from the look of the code though it seems very plausible.

  • edited March 2015 Posts: 2,042

    @Luatee, physics certainly seems possible, but it might be a bit difficult to implement for a couple of reasons.

    1) If you simply send the positions of the bodies, collisions may not work right

    2) If you send the movement data (ex. anything you'd pass to applyForce), the two instances aren't guaranteed to keep in sync (positions could get off)

    3) If you send the movement data and a packet gets dropped, things get off

    So, a combination of 1 and 2 might be possible, and I will certainly play around with it, but it might be best if I implement TCP for this kind of thing, which I will also look into doing.

    EDIT: After playing with it, I found that having one client do all the physics and another simply draw objects using data it gets from the 'server' client is the most accurate physics, with only a slight lag on the 'client' client.

  • Posts: 1,595

    @JakAttak what if the you have a host player and a connecting player. Use the whole physics engine from the host's iPad and allow the connecting player to manipulate it? Send the positions, angles and velocities to physics bodies local to the connecting player, this does mean the connecting player and the host will have different experiences. It shouldn't be buggy at all, maybe a bit slower to react for the connecting player.

  • edited March 2015 Posts: 2,042

    @Luatee, great idea. yep that is the most reliable, with only a slight lag between a click and a result on the client player.

  • Posts: 1,595

    @JakAttak how slow is 'slight lag'? What's the average refresh rate? Thanks for the tips.

  • Posts: 2,042

    @Luatee, I would say it takes about a hundredth of a second for the action to make it to the host, then back.

    I will try to take a short video.

  • Posts: 2,042

    @Luatee, here is the code:

    function setup()
        physics.continuous = true
    
        multihandler = Multiplayer(receiveData, gameSetup)
    
        parameter.action("Host Game", function() multihandler:hostGame() end)
        parameter.action("Find Game", function() multihandler:findGame() end)
        parameter.action("Join Game", function()
            if other_ip then
                multihandler:joinGame(other_ip, other_port)
            else
                parameter.text("other_ip", "")
                parameter.text("other_port", "")
    
                print("Fill in the host's ip and port, then click join game again")
            end
        end)
    end
    
    function gameSetup()
        parameter.clear()
        output.clear()
        print("Connected!")
    
        -- Game walls to keep balls on screen
        walls = {}
        walls[1] = physics.body(EDGE, vec2(0,0), vec2(WIDTH, 0))
        walls[2] = physics.body(EDGE, vec2(WIDTH,0), vec2(WIDTH, HEIGHT))
        walls[3] = physics.body(EDGE, vec2(WIDTH,HEIGHT), vec2(0, HEIGHT))
        walls[4] = physics.body(EDGE, vec2(0,HEIGHT), vec2(0, 0))
    
    
        local pos1, pos2 = vec2(WIDTH / 4, HEIGHT / 2), vec2(WIDTH * 3/4, HEIGHT / 2)
        local rad = WIDTH / 16
    
        -- Variables
        balls = {}
    
        if multihandler.is_host then
            -- Create physics bodies
            balls[1] = physics.body(CIRCLE, rad)
            balls[2] = physics.body(CIRCLE, rad)
            balls[1].position, balls[2].position = pos1, pos2
    
            controlling, other = balls[1], balls[2]
        else
            balls[1] = { position = pos1, radius = rad }
            balls[2] = { position = pos2, radius = rad }
            controlling, other = balls[2], balls[1]
        end
    
        moveForce = 100
        target = vec2(0, 0)
        syncdata = true
    end
    
    function receiveData(d)
        local tb = loadstring("return " .. d)()
    
        if syncdata then
            if multihandler.is_host then
                other:applyForce(tb.move)
            else
                balls[1].position, balls[2].position = tb.pos, tb.pos2
            end
        end
    end
    
    function draw()
        background(255)
    
        multihandler:update()
    
        if multihandler.connected then
            fill(255, 0, 0)
            ellipse(balls[1].position.x, balls[1].position.y, balls[1].radius * 2)
    
            fill(0, 0, 255)
            ellipse(balls[2].position.x, balls[2].position.y, balls[2].radius * 2)
    
            fill(0)
            text("Tap the screen to move", WIDTH / 2, HEIGHT / 2)
    
            if multihandler.is_host then
                multihandler:sendData("{ pos = " .. vec2ToStr(balls[1].position) .. ", pos2 = " .. vec2ToStr(balls[2].position) .. " }")
            end
        else
            fill(0)
            text("Waiting for connection...", WIDTH / 2, HEIGHT / 2)
        end
    end
    
    function vec2ToStr(vec)
        return "vec2" .. tostring(vec)
    end
    
    function touched(t)
        if multihandler.connected then
            if t.state == ENDED then
                local target = vec2(t.x, t.y)
                local move = (target - controlling.position) * moveForce
                if multihandler.is_host then
                    controlling:applyForce(move)
                else
                    multihandler:sendData("{ move = " .. vec2ToStr(move) .. " }")
                end
            end
        end
    end
    

    and here is a short video of it in action.

    the left iPad is the 'client', so you hopefully can get an idea of how minimal the lag is.

  • edited March 2015 Posts: 192

    Man, I can't wait for this update!! @Luatee I see your upcoming game may have multiplayer, eh? I can help with that, I was quite good with Java sockets and I can try to help you once the update comes out. As always, super interested in Aedifico, seems like the game I've always wanted to play/make. Good job so far. Thanks everyone for the examples as well!

  • dave1707dave1707 Mod
    Posts: 7,522

    @JakAttak Nice code. Too bad my second iPad is an iPad 1 and I can't try your code with it. It almost works if I try it just on my iPad Air. I get as far as it showing the 2 balls.

  • Posts: 384

    @JakAttak tried the multiplayer demo on two ipads-works like a dream! Thanks for sharing.

  • Posts: 2,042

    @piinthesky, glad to hear it!

  • @JackAttak this is an awesome piece of code you've made here, just one thing-even when my friend and I are on the same wifi, we still can't connect via the "search and join" feature. Perhaps there are too many peope on the wifi, as it was school wifi? I figured that the problem was that because the 3rd number in our ips did not match, the code was not able to work correctly. I looked at your code, and I believe it only searches 1-255 in the 4th number, am I correct? Anyways, great code, and I will definitely be using this in future projects.

  • Posts: 547

    @TheSolderKing this will probably be because schools block certain traffics and use proxies etc, the problem will probably be the latter one as your ips aren't 'almost identical'

  • Posts: 2,042

    @TheSolderKing, yes, 255 is the max I believe. Anyways, the best way to do a find and join would really be to use broadcast packets but that's a little complex.. I'll certainly look into it when I have some time.

  • @JakAttak sorry to dig up this old thread again, but in your code if a peer is connecting to a host, why do you use self.client:setsockname instead of self.client:setpeername? Is self.client:setpeername only for host sockets? Thanks for answering, and did you ever find out how to use broadcast packets? I looked but I couldn't find anything on the topic. Thanks again!

  • Posts: 2,042

    @TheSolderKing, I used setsockname because it worked and it allows the code to be essentially the same for the host and client.

    Broadcast packets are (from what I can tell, I'm certainly no expert) pretty much just packets sent to the broadcast address, see here: http://en.wikipedia.org/wiki/Broadcast_address

  • Posts: 154

    Broadcast packets would work well within a subnet, but there appears to be some restriction: when i try to send a udp packet to a broadcast addr, i get "permission denied"


    --# Main -- Sock -- Use this function to perform your initial setup function setup() local socket = require("socket") local s = socket.udp() s:setsockname("*",0) local ip,port,typ = s:getsockname() print(s:sendto("hello","255.255.255.255",port)) s:settimeout(3) print(s:receive()) end
  • edited March 2015 Posts: 2,042

    @juce, you need to give it the broadcast permission.

    s:setoption('broadcast', true)

  • Posts: 154

    @JakAttak, thank you! That works great.

    There is probably an easier way, but i was trying to do with a broadcast - determine my own IP on the local network.

    Code, if anyone needs it:


    --# Main -- MyIp function setup() local socket = require("socket") local s = socket.udp() s:setsockname("*",0) s:setoption("broadcast", true) local ip,port,typ = s:getsockname() local bytes,err = s:sendto("hello","255.255.255.255",port) if not err then local msg s:settimeout(3) msg,ip,port = s:receivefrom() print(ip) else print("PROBLEM: " .. err) end end
  • Jmv38Jmv38 Mod
    Posts: 3,295

    thanks you @juce.
    what about making a bigger program showing sokets usage?
    Like a chat between my 2 ipads?
    I am totally noob at sokets.

Sign In or Register to comment.