关于网络协议 HTTP 2
上一节,我们用nodemcu服务器向客户端发送nodemcu的内存信息。这一节反过来,我们介绍如何用客户端控制nodemcu。
先介绍一个简单的例子,用客户端控制nodemcu的GPIO4,实现nodemcu的蓝色LED远程开关。
Step 3
这里仍然参考了 ckuehnel
的代码,gpio.lua
- 下面是我的代码,取名叫My_gpio.lua
-- SSID = " "
-- password = " "
pin = 4
-- wifi.setmode(wifi.STATION)
-- wifi.sta.config(SSID,password)
print(wifi.sta.getip())
gpio.mode(pin, gpio.OUTPUT)
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive", function(conn,payload)
print(payload)
local _, _, method, path, vars = string.find(payload, "([A-Z]+) (.+)?(.+) HTTP");
if(method == nil)then
_, _, method, path = string.find(payload, "([A-Z]+) (.+) HTTP");
end
local _GET = {}
if (vars ~= nil)then
for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
_GET[k] = v
end
end
-- HTML
local buf = "";
buf = buf.."<h1> Web Server</h1>";
buf = buf.."<p>LIGHT 1 "
buf = buf.."<a href=\"?pin=ON4\"><button>LED OFF</button></a> "
buf = buf.."<a href=\"?pin=OFF4\"><button>LED ON</button></a></p>";
if(_GET.pin == "ON4")then
gpio.write(pin, gpio.HIGH);
elseif(_GET.pin == "OFF4")then
gpio.write(pin, gpio.LOW);
end
conn:send(buf);
conn:close();
collectgarbage();
end)
end)
逐段来看代码
- 前两行,定义路由器的账号与密码,因为我的nodemcu已经默认连上路由,所有这里就屏蔽了。
- 注意,当你连上路由,获得IP地址后,这个信息就保存在flash里了。所以就算掉电重启,还会自动连接
- 第三行,
pin = 4
定义GPIO,4号对应的是nodemcu上控制LED的管脚,注意高电平时LED关,低电平开。 - 第四行和第五行,设置esp8266为station模式,连接路由器。同样之前连接过,信息就保存了,所以就屏蔽了。
-
print()
打印IP地址信息,这个是客户端浏览器要访问nodemcu,就要输它的IP地址。- 每次上电时的IP有可能不一样
gpio.mode()
设置gpio为输出模式,也就是控制模式srv=..
建立TCP服务器-
srv.listen()
监听80端口的信息,80也就是http协议对应的端口 -
conn.on("recieve"..)
这个与上一节的例程一样,也是接收客户端发出的信息payload
。不同之处在于:- 上节的例程只接收客户端连接到服务器的信息。
- 本节的例程除了接收连接信息之外,还接收客户端发送的gpio控制指令
-
print()
打印客户端发来的payload信息,会有四五行,具体内容可以见上一节关于网络协议 HTTP
,最重要的是第一行,里面包含的信息需要提取出来,一般是这样GET / HTTP/1.1
或者这样
GET /?pin=ON4 HTTP/1.1
-
string.find()函数用来提取控制指令,它会返回符合规则的字符串,這里的目标信息是客户端发送的控制指令,一般为如下形式:
GET /?pin=ON4 HTTP/1.1
这里定义了3个字符串
method
,path
,vars
.-
method
代表客户端的方法,有GET
,POST
等等。GET
用来向服务器发送请求的信息,POST
用来向服务器提交网页上输入的信息。 -
path
代表路径,这里只得到一个/
vars
代表控制按钮button
返回的控制信息前面的两个
_
,_
是模糊变量,这两个变量是返回find函数找到字符串的第一个和最后一个的index(没什么用)
find()
的第一个变量是要检索的字符串,第二个变量是检索的规则-
method
,path
,vars
对应的检索规则分别是([A-Z]+)
,(.+)
,(.+)
-
([A-Z]+)
,[A-Z]
代表检索的是从A到Z的大写字母,+
代表不是单个字母,而是多个字符构成的字符串 -
(.+)
,.
代表检索所有字符,+
同样代表是字符串 -
?
和HTTP
是格式,代表path
和vars
之间有?
隔开,vars
后以HTTP
结束,相当于path
和var
的读取终止符
假如是客户端刚连接时发送的信息,paylaod第一行是这样
GET / HTTP/1.1
不符合刚才带?的格式,
method
,path
,vars
都得到空值nil
-
if..
用来判断method
是否为nil
,如果是,说明是客户端的连接信息,find()
的检索模式改为([A-Z]+) (.+) HTTP
,只接收两个字符串method
和path
定义局域变量
_GET
,接收vars
中的gpio
控制信息- 如果
vars
不为空,其代表了控制信息 - 用for循环提取
pin=ON4
里的信息 -
string.gmatch()
是专用在迭代forx循环中的字符串函数,-
(%w+)=(%w+)&*
,%w
代表字母和数字,+代表是多个字符。 -
&
应该是和=
一样,是格式, -
*
的作用和+
类似,区别是*
代表该字符可以不出现。&*
的意思是vars字符串的提取格式最后可以没有&
,也可以有一个或者多个&
。
-
更多字符串函数和模式的语法,请点击这里和这里
接下来是HTML代码
- 首先定义一个空字符串
- 接下来直接写HTML的body部分,前面没有head,也没有
<html>
,<head>
,<body>
关键字,直接写标题1:<h1>..</h1>
- 然后写第一端的文字部分,
<p1>
.. -
接下来的代码定义
button
和它的返回值。关键字<a>
通常是链接的关键字,href=
后是链接的地址。但是
这里,用来建立一个button
对象,-
href = \"?pin=ON4\"
:\
是转义符,当按钮被按下,客户端就返回?pin=ON4
这一信息, -
<button>LED OFF</button>
:LED OFF
是button对象上的文字标签 -
</a>
:结束<a>
开头的代码, 
代表一个空格 - 这行代码的含义是点击LED ON开启LED,gpio4处于低电位
-
- 下一段代码的含义是:点击LED OFF关闭LED。点击后,返回
?pin=OFF4
- 文字,按钮on和按钮off都属於段落p1,处在同一行上
接下来,对vars
代表的字符串pin=ON4
进行判断,
-
pin
作为关键字,ON4
或者OFF4
是关键字对应的元素。 - 如果
_GET.pin
对应的元素是ON4
,拉高GPIO电平,反之,则拉低
最后发送buf
包,并关闭conn
连接,清理内存垃圾
-
注意,这里并没有像
Step 1
的例程,没有加入HTTP的头部信息,可能是传送buff量比较小的原因,而传送的HTML
文档本身就是一个很简略的版本。如果传送buff比较长,或者对传送正确率要求比较高,还是采用标准的HTML格式和HTML协议头部信息。
这里直接用
conn:close()
关闭连接,没有像上一个例程,采用conn:on("sent"..)
事件发生函数。
Step 4
代码解释完后,我们上传到nodemcu。如果你的nodemcu没有连上路由,或者刚刷了固件,就取消My_gpio.lua
的注释,输入你的wifi账号和密码
- Save to ESP
后,会打印你的IP信息,像这样
172.21.100.79 255.255.255.0 172.21.100.254
第一个就是nodemcu的IP,后面分别是子网掩码,和路由器的的IP
- 在浏览器里输入nodemcu的IP,出现界面,可以选择关闭和开启nodemcu的LED,像这样
- 因为HTML没有Head信息,所以标签上显示的和地址栏信息是一样的
- 点击LED OFF,原先的IP地址就会附加信息:
/?pin=ON4
,作为客户端的GET信息
本来还打算讲一个提交表单的例程,不过已经写得很长了,就放到下一节好了。顺带会讲一讲CSS,让没有图片的服务器也能exciting。