love2d教程20--UDP網絡連接


此文簡單翻譯自官方教程,由於涉及了網絡編程,我也不熟,可以先看這篇socket的文章

love2d已經把lua的網絡庫luasocket編譯進去了,所以只需要簡單的require "socket"就可。

下面我們實現一個love2d的客戶端和一個純lua的服務端(都可以直接用love運行,先運行

服務端再運行客戶端,如果服務端假死不用管。開啟多個客戶端后,可以在客戶端上看到

一些數字,使用方向鍵可以移動當前客戶端的數字,其它客戶端上相應的數字也跟着運動)

love2d的wiki上沒有socket的文檔,需要自行查看,這里是luasocke的文檔

 

客戶端

導入socket,設置一些變量。

local socket = require "socket" --調用socket庫

-- 服務端的ip地址和端口,localhost=127.0.0.1(本機)
local address, port = "localhost", 12345 

local entity --一個隨機數,標示每個客戶端
local updaterate = 0.1 -- 更新速率0.1s一次 

local world = {} --里面存放的是鍵值對 world[實體]={x,y}
local t --計時

 

首先,在load里我們和服務端連接上,並產生一個隨機數來作為客戶端的id(entity ),

之后發送一條消息給服務器。

function love.load()

 -- 創建一個沒有連接的udp對象,有了它我們就可以使用網絡了,若失敗則返回nil和錯誤消息

    udp = socket.udp()
   
-- socket按塊來讀取數據,會產生阻塞直到數據里有信息為止,或者等待一段時間
-- 這顯然不符合游戲的實時的要求,所以把等待時間設為0
    udp:settimeout(0)
   
--不像服務端,客戶端只需要連接服務端就可,使用setpeername來連接服務端
--address是地址,port端口
    udp:setpeername(address, port)
   
  --取隨機數種子
    math.randomseed(os.time())
   
  --通過剛才的隨機數種子生成0---99999之間的隨機數
  --entity實際就是一個字符串
    entity = tostring(math.random(99999))

   --現在開始使用網絡,這里我們僅是產生一個字符串dg,並把它用send發送出去 
   --此處發送的是 “entity at 320240”
    local dg = string.format("%s %s %d %d", entity, 'at', 320, 240)
    udp:send(dg) 
   
    -- 初始化t為0,t用來在love.update里計時
    t = 0 
end

在update里檢測鍵盤的按下,並每隔一段時間把鍵盤狀態發送到服務端,然后

接收來自服務端的消息,解析后放到world表里。

function love.update(deltatime)

    t = t + deltatime 
   
    --為了防止網絡堵塞,我們需要限制更新速率,對大多數游戲來說每秒10次已經足夠
    --(包括很多大型在線網絡游戲),更新速率不要超過每秒30次
    if t > updaterate then
       --可以每次更新都發送數據包,但為了減少帶寬,我們把更新整合到一個數據包里,在
       --最后的更新里發送出去
        local x, y = 0, 0
        if love.keyboard.isDown('up') then  y=y-(20*t) end
        if love.keyboard.isDown('down') then    y=y+(20*t) end
        if love.keyboard.isDown('left') then    x=x-(20*t) end
        if love.keyboard.isDown('right') then   x=x+(20*t) end


        --把消息打包到dg,發送出去,這里發送的是 entity,move和坐標拼接的字符串
        local dg = string.format("%s %s %f %f", entity, 'move', x, y)
        udp:send(dg)   

     --服務器發送給我們世界更新請求
 
        --[[
        注意:大多數設計不需要更新世界狀態,而是讓服務器定期發送。
       這樣做有很多原因,你需要仔細注意的一個是anti-griefing(反擾亂)。
       世界更新是游戲服務器最大的事,服務器會定期更新,使用整合的數據將會更有效。
        ]]
        local dg = string.format("%s %s $", entity, 'update')
        udp:send(dg)

        t=t-updaterate -- 復位t
    end

   
   --很可能有許多消息,因此循環來等待消息
    repeat
        --[[這里期望另一端的udp:send!
        udp:receive將返回等待數據包 (或為nil,或錯誤消息)。
        數據是一個字符串,承載遠端udp:send的內容。我們可以使用lua的string庫處理
        ]]
        data, msg = udp:receive()
        
        if data then 
  
           --這里的match是string.match,它使用參數中的模式來匹配
           --下面匹配以空格分隔的字符串
            ent, cmd, parms = data:match("^(%S*) (%S*) (.*)")
            if cmd == 'at' then
                --匹配如下形式的"111 222"的數字
                local x, y = parms:match("^(%-?[%d.e]*) (%-?[%d.e]*)$")
                assert(x and y) -- 使用assert驗證x,y是否都不為nil
           
                --不要忘記x,y還是字符串類型
                x, y = tonumber(x), tonumber(y)
                --把x,y存入world表里
                world[ent] = {x=x, y=y}
            else
                --[[
                打印日志,防止有人黑服務器,永遠不要信任客戶端
                ]]
                print("unrecognised command:", cmd)
            end
       
        --[[
        打印錯誤,一般情況下錯誤是timeout,由於我們把timeout設為0了,
        ]]
        elseif msg ~= 'timeout' then
            error("Network error: "..tostring(msg))
        end
    until not data

end

draw則比較簡單,只是在屏幕上x,y處打印所有的客戶端entity

function love.draw()
--打印world里的信息
    for k, v in pairs(world) do
        love.graphics.print(k, v.x, v.y)
        print(k)
    end
end

 

服務端

服務端只是一個純lua文件,並不在love里運行(其實也可以,如果使用lua運行,在win下安裝lua for windows后即可

linux下需要自己編譯)。下面這幾行和客戶端類似。

local socket = require "socket"
local udp = socket.udp()
udp:settimeout(0)

-- 和客戶端不同,服務器必須知道它綁定的端口,否則客戶端將永遠找不到它。
--綁定主機地址和端口。
--“×”則表示所有地址;端口為數字(0----65535)。
--由於0----1024是某些系統保留端口,請使用大於1024的端口。

udp:setsockname('*', 12345)

因為我們並不知道客戶端來自哪里,所以需要監聽相應端口來自所有ip地址的消息。

下面這些參數和客戶端相同

local world = {}

local data, msg_or_ip, port_or_nil

local entity, cmd, parms

服務端當然得始終運行,所以我們使用無限循環,其實love也是一個無限循環。

local running = true

print "Beginning server loop."

while running do

udp:receivefrom() 和udp:receive()類似但它返回數據、發送者的ip地址、發送者的端口

(我們需要這些信息來回復)。我們在客戶端里沒這么做,主要原因是已經把端口綁定到服

務端。(必須成對使用receivefrom/sendto、receive/send)

data, msg_or_ip, port_or_nil = udp:receivefrom()

下面進行數據檢測,按照客服端發過來的指令進行處理

if data then
       
        entity, cmd, parms = data:match("^(%S*) (%S*) (.*)")
        if cmd == 'move' then
            local x, y = parms:match("^(%-?[%d.e]*) (%-?[%d.e]*)$")
            assert(x and y) -- 驗證x,y是否都不為nil
            --記得x,y還是字符串,要轉換為數字
            x, y = tonumber(x), tonumber(y)
            -- 
            local ent = world[entity] or {x=0, y=0}
            world[entity] = {x=ent.x+x, y=ent.y+y}
        elseif cmd == 'at' then
            local x, y = parms:match("^(%-?[%d.e]*) (%-?[%d.e]*)$")
            assert(x and y) 
            x, y = tonumber(x), tonumber(y)
            world[entity] = {x=x, y=y}
        elseif cmd == 'update' then
            for k, v in pairs(world) do
            --發送給客戶端
                udp:sendto(string.format("%s %s %d %d", k, 'at', v.x, v.y), msg_or_ip,  port_or_nil)
            end
        elseif cmd == 'quit' then
            running = false;
        else
            print("unrecognised command:", cmd)
        end
    elseif msg_or_ip ~= 'timeout' then
        error("Unknown network error: "..tostring(msg))
    end

讓cpu休息,減少負載

socket.sleep(0.01)

end

print "Thank you."

 

這里使用了很多lua模式匹配可以參考文章1文章2

 

這篇教程不易理解,可以會個圖把客戶端和服務端receivefrom/sendto、receive/send對應起來。

對於socket我知道的也不多,暫時也不想深究,希望高手多多指點。

接下來是角色在地圖上的移動。

代碼下載(已clone的直接git pull)
git clone git://gitcafe.com/dwdcth/love2d-tutor.git
或git clone https://github.com/dwdcth/mylove2d-tutor-in-chinese.git


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM