如何使用新的 SDK (NodeMCU) 发送多个数据 (conn:send())

Posted

技术标签:

【中文标题】如何使用新的 SDK (NodeMCU) 发送多个数据 (conn:send())【英文标题】:How to send multiple data (conn:send()) with the new SDK (NodeMCU) 【发布时间】:2016-07-04 21:34:07 【问题描述】:

我一直在阅读 NodeMCU 文档和几个关于 SDK 更改的已解决问题,以前允许发送多个数据流(就像排队的 net.socket:send)。

似乎在这里 (#730) 和那里 (#993) 甚至这里 (#999) 引发了一场巨大的争论。但是,我没有找到任何令人信服的网络服务器代码示例,可以让我读取多个 html 文件(例如 head.htmlbody.html)以显示页面。以下是我尝试改编但没有成功的 TerryE 示例:

srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
    conn:on ("receive", function(sck, req)
        local response = 

        local f = file.open("head.html","r")
        if f ~= nil then
            response[#response+1] = file.read()
            file.close()
        end

        local f = file.open("body.html","r")
        if f ~= nil then
            response[#response+1] = file.read()
            file.close()
        end

        local function sender (sck)
            if #response>0 then sck:send(table.remove(response,1))
            else sck:close()
            end
        end
        sck:on("sent", sender)
        sender(sck)
    end )
end )

连接到 ESP8266 时,没有加载任何内容,并且我从 lua 终端没有收到任何错误。

供您参考,head.html 包含:

<html>
<head>
</head>

body.html 包含:

<body>
<h1>Hello World</h1>
</body>
</html>

我是NodeMCU的新手,请多多包涵。

【问题讨论】:

【参考方案1】:

这是我的解决方案,不使用表格,节省一些内存:

function Sendfile(sck, filename, sentCallback)
    if not file.open(filename, "r") then
        sck:close()
        return
    end
    local function sendChunk()
        local line = file.read(512)
        if line then 
            sck:send(line, sendChunk) 
        else
            file.close()
            collectgarbage()
            if sentCallback then
                sentCallback()
            else
                sck:close()
            end
        end
    end
    sendChunk()
end


srv = net.createServer(net.TCP)
srv:listen(80, function(conn)
    conn:on("receive", function(sck, req)
        sck:send("HTTP/1.1 200 OK\r\n" ..
            "Server: NodeMCU on ESP8266\r\n" ..
            "Content-Type: text/html; charset=UTF-8\r\n\r\n", 
            function()
                Sendfile(sck, "head.html", function() Sendfile(sck, "body.html") end)
            end)        
    end)
end)

这是用于提供单个文件:

function Sendfile(client, filename)
    if file.open(filename, "r") then
        local function sendChunk()
            local line = file.read(512)
            if line then 
                client:send(line, sendChunk) 
            else
                file.close()
                client:close()
                collectgarbage()
            end
        end
        client:send("HTTP/1.1 200 OK\r\n" ..
            "Server: NodeMCU on ESP8266\r\n" ..
            "Content-Type: text/html; charset=UTF-8\r\n\r\n", sendChunk)
    else
        client:send("HTTP/1.0 404 Not Found\r\n\r\nPage not found")
        client:close()
    end
end


srv = net.createServer(net.TCP)
srv:listen(80, function(conn)
    conn:on ("receive", function(client, request)
        local path = string.match(request, "GET /(.+) HTTP")
        if path == "" then path = "index.htm" end
        Sendfile(client, path)
    end)
end)

【讨论】:

【参考方案2】:

感谢您的回复。我实际上添加了您提到的标题,我不知道这是必要的,我还删除了 sender 函数中的 sck 参数。我的第一个代码实际上正在运行,我不知道上次出了什么问题。

无论如何,它帮助我理解了发生了什么:以下代码似乎连接了 response 数组,因为套接字的事件 sent 回调了 sender 函数 (sck:on("sent", sender))

sck:send(table.remove(response,1))

其实table.remove(array, 1)返回数组的第一项,并移除数组的该项。多次调用此行具有逐项阅读的效果。

为简单起见,这里是一个简单的网络服务器能够提供多个文件的代码:

header = "HTTP/1.0 200 OK\r\nServer: NodeMCU on ESP8266\r\nContent-Type: text/html\r\n\r\n"
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
    conn:on ("receive", function(sck, req)
        local response = header

        tgtfile = string.sub(req,string.find(req,"GET /") +5,string.find(req,"HTTP/") -2 )
        if tgtfile == "" then tgtfile = "index.htm" end
        local f = file.open(tgtfile,"r")
        if f ~= nil then
            response[#response+1] = file.read()
            file.close()
        else
            response[#response+1] = "<html>"
            response[#response+1] = tgtfile.." not Found - 404 error."
            response[#response+1] = "<a href='index.htm'>Home</a>"
        end
        collectgarbage()
        f = nil
        tgtfile = nil

        local function sender ()
            if #response>0 then sck:send(table.remove(response,1))
            else sck:close()
            end
        end
        sck:on("sent", sender)
        sender()
    end)
end)

此示例取自instructables,并已修复为与新 SDK 一起使用(不再允许多个 :send)。如果这段代码有问题,请告诉我。

我不知道文件的大小限制是多少。尽管如此,我还是设法将超过 2Ko 附加到 response 变量并立即发送它而没有任何问题。

【讨论】:

Terry 最初示例的美妙之处在于,它可以与带有回调的隐式循环一起使用,正如您所指出的那样。它从表中取出一块(请不要称它为数组),发送它,并在发送回调时再次调用发送者以发送下一块,直到表为空。您的代码的唯一主要问题是无论实际内容如何,​​您都发送相同的 HTTP 标头。在file == nil 的情况下,您的标头应报告 HTTP 404 而不是 HTTP 200。因此,与其在第 5 行添加 header,不如在 if/else 中有条件地执行此操作。 另外,lua-users.org/wiki/LuaStyleGuide -> Lua Idioms,你可以使用if f then 代替if f ~= nil then。就个人而言,我永远不会将变量称为“f”,因为我更喜欢富有表现力的名称,但这是风格问题。 我的主要 codicil 是值得额外的十几行 Lua 在响应数组中向前看并计算有多少适合最大 espconn 缓冲区大小,然后将前 N 个缓冲区编组为单次发送,例如sck:send(table.concat(unpack(response,1,n)))。这可能看起来很笨拙,但大部分笨拙都是在直接从 C 编译的代码中执行的,因此运行效率很高,并且提供了良好的数据包大小。 @TerryE 最大 espconn 缓冲区大小是多少,即多少字节?

以上是关于如何使用新的 SDK (NodeMCU) 发送多个数据 (conn:send())的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 NodeMCU 通过 TCP 发送/接收二进制数据?

ESP8266 NodeMCU 内存不足

使用 nodeMCU 的 Wifi 网状网络

使用 SerialTransfer 库通过 UART 接收从 nodemcu 发送到 Arduino UNO 的有效负载中的所有零

Android & NodeMCU,从服务器接收响应不能正常工作?

NodeMCU Lua 整数最大值为 2^31