9、nodeMCU學習筆記--net模塊

esp8266??nodemcu??lua??wifi??net??web

閑言碎語

nodemcu的wifi模塊,花了三篇文章被我水完了。內容還是比較淺顯的。不過,nodemcu的開發者確實把wifi模塊設計的很簡單,也和容易使用。配置函數、station類函數、ap類函數、監聽注冊函數,總體來講還是很清晰明了。要用熟這些wifi功能,其實還要配合其他模塊一起來(比如這篇文章要說的net),循序漸進。

這里有個綜合文章這里

模塊函數

net模塊的函數也是比較多的。不過,整體結構也是很清晰的。趕緊來看看這些函數

序號 函數名 參數 返回值
1 net.createConnection() type, secure net.socket 子模塊
2 net.createServer() type, timeout net.server 子模塊
3 net.multicastJoin() if_ip, multicast_ip
4 net.multicastLeave() if_ip, multicast_ip
5 net.server:close()
6 net.server:listen() port,[ip],function(net.socket)
7 net.server:on()
8 net.server:send()
9 net.socket:close() nil
10 net.socket:connect() port, ip / domain nil
11 net.socket:dns() domain, function(net.socket, ip)
12 net.socket:getpeer() ip, port
13 net.socket:hold()
14 net.socket:on() event, function() nil
15 net.socket:send() string[, function(sent)]
16 net.socket:unhold()
17 net.dns.getdnsserver() dns_index(0 / 1) ip
18 net.dns.resolve() host, function(ip) nil
19 net.dns.setdnsserver() dns_ip_addr, dns_index nil
20 net.cert.verify() enable / pemdata true

參數里面有個type的,只有兩種選擇,要么net.TCP,要么net.UDP。有幾個server相關的API,幾個socket相關的API。這里不打算一個一個函數的講了,直接來幾個例子反而更容易理解API的含義。

實踐一下

光說不練假把式,直接來實踐一下。從API中可以知道,net模塊可以創建server和client。實踐前,確保nodemcu已經連入網絡。

wifi的配置后會一直生效。如果你先前配置過,可以不用配置。當然,重新配置一下也可以。

client

這里先來看看如何創建一個client,以及如何進行通信。

cl = net.createConnection(net.TCP, 0)
cl:connect(9999, "192.168.199.101")
cl:on("receive", function(sck, c) print(c) end)
cl:on("disconnection", function(sck, c) print("disconnection!") end)

使用.createConnection創建一個net.TCP客戶端,函數會返回一個socket子模塊,后面要用的都是socket相關的函數,第二個參數,1表示加密,0表示不加密。net.socket:connect用來連接到服務端。參數2既可以是ip地址,也可以是域名。注意connect前面用的是冒號:,不是點。接著,找一個網絡調試工具來創建一個server。這里我找了個名字叫網絡調試的手機APP。net.socket:on函數用來綁定幾個事件回調,函數原型是這樣的 function(net.socket[, string]):

  • "connection" : 連接;
  • "reconnection" : 重連接;
  • "disconnection" : 斷開連接;
  • "receive" : 接收回調,string表示接收到的字符串數據;
  • "sent" : 發送;

這個例子里面,nodemcu連接到app創建的server后,并沒有產生回調事件,具體是什么原因,不清楚。不過,嘗試連接到域名卻可以產生回調事件。比如下面這個域名

cl:connect(80, "www.nodemcu.com")
客戶端

服務端

點擊APP左邊的客戶端列表,斷開nodemcu,得到一個預期的斷開回調。使用.createConnection創建多個客戶端。比如,這樣子:

cl = net.createConnection(net.TCP, 0)
cl2 = net.createConnection(net.TCP, 0)

使用net.socket:send可以向服務端發送數據。比方說在ESPlorer右邊的輸入框里面輸入下面這句語句:

=cl:send("Hello NodeMCU")

這里需要說明的是,send函數發送的數據長度是有限度的,大概是1400多個字節。當要發送大于1400字節的內容的時候,比如說發送一個帶css、js的網頁,就需要分成多次發送。多次發送也不是簡單的把上面的代碼復制幾遍就能解決的。而是要用到"sent"事件來回調。

cnt = 0
cl = net.createConnection(net.TCP, 0)
cl:connect(9999, "192.168.199.101")
cl:on("receive", function(sck, c) print(c) end)
cl:on("disconnection", function(sck, c) print("disconnection!") end)
cl:on("sent", function(c) 
    if cnt ~= 10 then
        cl:send(cnt)
        cnt = cnt + 1
    end 
end)

這個例子可以讓客戶端在發送完第一條消息后,再發10條消息給服務端。激活的方法還是在ESPlorer中輸入一條send語句。

=cl:send("Hello NodeMCU")

當nodemcu發送完第一條語句后,會觸發"sent"事件,進而發送10條消息給服務端。

收到消息了!

server

知道了如何創建并使用一個client后,我們來繼續看如何創建一個server。先上個開胃菜。

ns = net.createServer(net.TCP, 15)
ns:listen(80, function(c)
    c:on("receive", function(c, d)
        print(d)
        c:send(d)
    end)
    c:on("connection", function(c, d) print(d) end)
    c:on("disconnection", function(c, d) print("disconnection") end)
end)

使用.createServer創建一個net.TCP的服務端,第二次參數用于設置不活動連接的超時時間,返回一個net.server模塊。nodemcu只能創建一個server,不像client可以創建多個。需要注意一下。net.server只有4個函數,其中的send和on僅對udp有用。tcp要使用socket的send和on函數。
??接著用net.server:listen創建一個監聽。回調傳入的是一個socket。可以盡情的使用socket的函數了,比如用net.socket:on設置各種事件回調。這個例子里面的"connection"依然沒效果╮(╯_╰)╭。使用APP連接到創建好的server,試著發送信息。

這圖有點大啊 ╮(╯_╰)╭

??接著到主菜上場了。內容有點長。主要是實現上篇文章說的enduser setup。動筷子前記得把wifi模式設置成AP模式或者混合模式。

web = '<!doctype html><html><head><meta charset=\'utf-8\'><meta name=\'viewport\'content=\'width=380\'><title>Connect gadget to you WiFi</title><style media=\'screen\'type=\'text/css\'>*{margin:0;padding:0}html{height:100%;background:linear-gradient(rgba(196,102,0,0.2),rgba(155,89,182,0.2)),url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAA8AgMAAACm+SSwAAAADFBMVEVBR1FFS1VHTlg8Q0zU/YXIAAADVElEQVQ4yy1TTYvTUBQ9GTKiYNoodsCF4MK6U4TZChOhiguFWHyBFzqlLl4hoeNvEBeCrlrhBVKq1EUKLTP+hvi1GyguXqBdiZCBzGqg20K8L3hDQnK55+OeJNguHx6UujYl3dL5ALn4JOIUluAqeAWciyGaSdvngOWzNT+G0UyGUOxVOAdqkjXDCbBiUyjZ5QzYEbGadYAi6kHxth+kthXNVNCDofwhGv1D4QGGiM9iAjbCHgr2iUUpDJbs+VPQ4xAr2fX7KXbkOJMdok965Ksb+6lrjdkem8AshIuHm9Nyu19uTunYlOXDTQqi8VgeH0kBXH2xq/ouiMZPzuMukymutrBmulUTovC6HqNFW2ZOiqlpSXZOTvSUeUPxChjxol8BLbRy4gJuhV7OR4LRVBs3WQ9VVAU7SXgK2HeUrOj7bC8YsUgr3lEV/TXB7hK90EBnxaeg1Ov15bY80M736ekCGesGAaGvG0Ct4WRkVQVHIgIM9xJgvSFfPay8Q6GNv7VpR7xUnkvhnMQCJDYkYOtNLihV70tCU1Sk+BQrpoP+HLHUrJkuta40C6LP5GvBv+Hqo10ATxxFrTPvNdPr7XwgQud6RvQN/sXjBGzqbU27wcj9cgsyvSTrpyXV8gKpXeNJU3aFl7MOdldzV4+HfO19jBa5f2IjWwx1OLHIvFHkqbBj20ro1g7nDfY1DpScvDRUNARgjMMVO0zoMjKxJ6uWCPP+YRAWbGoaN8kXYHmLjB9FXLGOazfFVCvOgqzfnicNPrHtPKlex2ye824gMza0cTZ2sS2Xm7Qst/UfFw8O6vVtmUKxZy9xFgzMys5cJ5fxZw4y37Ufk1Dsfb8MqOjYxE3ZMWxiDcO0PYUaD2ys+8OW1pbB7/e3sfZeGVCL0Q2aMjjPdm2sxADuejZxHJAd8dO9DSUdA0V8/NggRRanDkBrANn8yHlEQOn/MmwoQfQF7xgmKDnv520bS/pgylP67vf3y2V5sCwfoCEMkZClgOfJAFX9eXefR2RpnmRs4CDVPceaRfoFzCkJVJX27vWZnoqyvmtXU3+dW1EIXIu8Qg5Qta4Zlv7drUCoWe8/8MXzaEwux7ESE9h6qnHj3mIO0/D9RvzfxPmjWiQ1vbeSk4rrHwhAre35EEVaAAAAAElFTkSuQmCC)}body{font-family:arial,verdana}div{position:absolute;margin:auto;top:0;right:0;bottom:0;left:0;width:320px;height:274px}form{width:320px;text-align:center;position:relative}form fieldset{background:white;border:0 none;border-radius:5px;box-shadow:0 0 15px 1px rgba(0,0,0,0.4);padding:20px 30px;box-sizing:border-box}form input{padding:15px;border:1px solid#ccc;border-radius:3px;margin-bottom:10px;width:100%;box-sizing:border-box;font-family:montserrat;color:#2C3E50;font-size:13px}form.action-button{width:100px;background:#27AE60;font-weight:bold;color:white;border:0 none;border-radius:3px;cursor:pointer;padding:10px 5px;margin:10px 5px}form.action-button:hover,#msform.action-button:focus{box-shadow:0 0 0 2px white,0 0 0 3px#27AE60}.fs-title{font-size:15px;text-transform:uppercase;color:#2C3E50;margin-bottom:10px}.fs-subtitle{font-weight:normal;font-size:13px;color:#666;margin-bottom:20px}</style></head><body><div><form><fieldset><h2 class=\'fs-title\'>WiFi Login</h2><h3 class=\'fs-subtitle\'>Connect gadget to your WiFi</h3><input type=\'text\'autocorrect=\'off\'autocapitalize=\'none\'name=\'wifi_ssid\'placeholder=\'WiFi Name\'/><input type=\'password\'name=\'wifi_password\'placeholder=\'Password\'/><input type=\'submit\'name=\'save\'class=\'submit action-button\'value=\'Save\'/></fieldset></form></div></body></html>'    

sendBuf = {}

for i = 1, #web, 1400 do
    local len = #web - i 
    if len > 1400 then
        sendBuf[#sendBuf + 1] = string.sub(web, i, i+1400-1)
    else
        sendBuf[#sendBuf + 1] = string.sub(web, i, i+len)
    end
end

web數組存儲了一個web頁面。當然了,這個web頁面比較大,遠遠超過了1400字節。需要將它分成幾塊,以便后面分批發送。所以,把這個web頁面分塊存儲到一個table中。

function sendWeb(c)
    if #sendBuf > 0 then
        s = table.remove(sendBuf, 1)
        c:send(s)
    else
        c:close()
    end
end

函數sendWeb用來把table里面的內容發送出去,一邊發送,一邊remove表里面的內容,所以用瀏覽器瀏覽只能打開頁面一次 o(╯□╰)o。或許這個地方可以優化一下。

sv = net.createServer(net.TCP, 60)

sv:listen(80, function(c)
    c:on("receive", function(cn, req)
        local _, _, method, path, vars = string.find(req, "([A-Z]+) (.+)?(.+) HTTP")
        if method == nil then
            _, _, method, path = string.find(req, "([A-Z]+) (.+) HTTP")
        end

        local _GET = {}
        if vars ~= nil then
            for k, v in string.gmatch(vars, "(%w+_%w+)=(%w+)&*") do
                _GET[k] = v
                print(k .. ":" .. v)
            end        
            local sendbuf = "<h1>Config Succeed!</h1>"
            sendbuf = sendbuf.."<p>wifi_ssid: ".._GET["wifi_ssid"].."</P>"
            sendbuf = sendbuf.."<p>wifi_password  :".._GET["wifi_password"].."</P>"
            cn:send(sendbuf)
            cn:close()
        else
            cn:on("sent", sendWeb)
            sendWeb(cn)
        end
    end)
end)

最后這一部分,和開胃菜那個例子的效果差不多,只是這回發送的是一個頁面。回調函數中,先解析瀏覽器get過來的內容,之后把類似于這種格式的字符串("wifi_ssid=hello&wifi_password=12345678")存儲到一個table中。最后又把提取到的內容send出來,趕緊用瀏覽器訪問nodemcu看看效果吧。只需要在瀏覽器的地址欄輸入ip地址即可。
??利用net的server,還可以顯示web控制led之類的效果,網上有相關的例子。或者可以配合nodemcu上面的AD完成更多東西來。不過前提是,要能寫出漂亮的web頁面o(╯□╰)o。

一點lua語法

local _, _, method, path, vars = string.find(req, "([A-Z]+) (.+)?(.+) HTTP")

這個地方的 _ 實際上是一個變量,叫虛變量,因為string的find方法會返回子串的起始和結束地址。不需要的話,可以用虛變量來存儲。

簡書評論不能貼圖, 如有需要可以到我的GitHub上提issues

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容