关于网络协议 HTTP
按照约定,这次要讲非常exciting的内容,网络协议Networking protocols。
这部分并不好写,内容很庞杂,会分成若*分。
网络协议我们在前面已经提及,TCP/IP协议就是最重要的网络协议。在AT指令和lua固件的例程中,我们建立了TCP/IP的客户端和服务器,实现无线通信和远程控制。在电脑端,我们可以用TCP/IP的网络调试助手,也可以用c++或python自己编写客户端和服务器,和nodemcu通信。
但是今天我们就要谈谈更exciting,也是应用最广泛的网络协议:
超文本传输协议HTTP
- 对,就是你上网最先输的那四个字母。
首先我们要把HTTP,TCP和IP的关系理一理,了解什么是网络协议。
- 按惯例,为了保持文档的独立性,先把关键资料放上。
资料篇
Nodemcu
- 硬件
- 固件,nodemcu的固件源文件,包含示例
- 开发环境ESPlorer
- 一键烧写工具
- 在线固件生成器
- 官网英文文档,中文API文档
- nodemcu的参考应用程序,不错的参考应用程序,官网文档中被推荐
Lua
网络协议
这两本书是TCP/IP协议的经典之作,因为版权问题,恕在下不能给出链接
- computer networking, a top-down approach.
Jim Kuross & Keith Ross
不少国外大学的制定教材,通俗易懂,入门必备
- TCP/IP Illustrated, Volume 1, The Protocols.
W. Richard Stevens
被誉为网络圣经的第一卷,经典中的经典,网络工程师人手一本。不过内容组织上和上一本正相反,是从下到上
HTML和CSS
既然要讲超文本传输协议,就不能不提超文本标记语言HTML,相信大家都有了解
- Head First HTML with CSS.
Elisabeth Freeman & Eric Freeman.
本来学语言就很痛苦,还要看学霸白富美和高富帅秀恩爱,实在有点不能忍。
但是,这本书确实经典,在排行榜上一直领先。排版非常活剥,效果也不错,可以很快上手
如果想更深入,可以看下面这两本,主要针对HTML的具体样式:CSS(层叠样式表):
- Css Mastery Advanced Web Standards Solutions.
Andy Budd with Cameron Moll and Simon Collison
- CSS, the Definitive Guide.
Eric A. Meyer
HTTP 与网络协议
HyperText Transfer Protocol, HTTP
即超文本传输协议,是互联网的核心。 我们上网happy刷的网页都是基于HTTP协议。
HTTP和Hypertext出现之前,网络只是用在大学和研究机构中,传个实验数据或资料什么的,并没有什么WWW万维网和花花绿绿的网页。
- 所谓Hypertext超文本,就是一堆超链接组成的文本。比如说本教程前面提供了一堆资料链接,就可以算是超文本。
- 最常见的超文本就是网站网页,通过超链接形成有组织和层级的资料库
HTTP协议位于网络的顶层
,属于应用层协议,凌驾于TCP(传输控制协议)和IP(网络协议)之上。
后两者属于传输层和网络层。传输层位于网络层之上。
网络层的再下一层是连链接层,主要是wifi,光纤灯物理设备的驱动协议,实现与底层硬件的连接和控制。
最下一层就是物理层,也就是wifi,光纤,以太网,路由器等硬件物理设备。
以上就是网络协议的五层模型
,
而TCP/IP协议簇
就是与五层网络模型配套的各层网络协议包。此外还有基于OSI模型的七层网络模型
简单讲, TCP/IP协议簇的运作模式
就是传输网络信息的层层打包。
网络应用基于网络信息的传输,实现客户端与服务器之间的互动。
位于最高层的应用发出书面指示。而最底层的硬件就是快递员,负责把这封信给送出去。
-
为了实现某一功能,某服务器的网络应用层(比如nodemcu)要向客户端(比如手机上的浏览器)发送一串信息,指定信息属于HTTP协议。
信息被应用层打包,信息前加上应用层的标签信息,明确属于哪种包裹(应用层协议除了HTTP,还有FTP,SMTP...)
-
传输层接到应用层指示,在客户端和服务器之间开辟一条传输通道,它把很长的信息打包成一小段小一段,并且有一整套的校验机制,确保信息可靠发送。
数据分割后二次打包,头部附上响应传输层标签,明确快递收件人和寄件人,确定了快递路线,控制寄件时间,并且监督快递安全送达
-
网络层接到传输层指示,负责把数据从发送端传输到接收端。发送端和接收到都有唯一的IP地址,就像快递收件地址和寄件地址。
信息三次打包,加上IP信息,明确收货地址和寄件地址
-
有了收件地址和寄件地址,就需要这两个地址派快递员送信,就是服务器nodemcu的wifi设备和客户端(手机)的wifi设备。
数据四次打包,加上客户端和服务端的wifi硬件信息,明确谁是快递员。
上述手续办完之后,快递员(硬件设备)会忠于职守,把快递送达。
lua固件提供了对各层网络协议的支持,底层协议不用我们关心。我们要关注的是应用层的协议,或者再下一层的传输层协议。
应用层HTTP是基于传输层TCP协议的。与TCP传输一样,HTTP也分为客户端和服务器端,这里特指nodemcu作为HTTP的客户端和服务器。
lua固件中有现成的HTTP客户端模块,对于服务器端,大部分需要自己编写。接下来,我们就看一个HTTP服务器例程。
Step 1
固件文件夹中的HTTP示例程序我没有调试通过,因此我参考的是另一个版本的服务器程序,程序包可以点这里下载,文件名是dht11_webserver.lua
。
为了介绍HTML,HTTP和TCP协议,我对原代码进行了修改
- 示例程序是用TCP/IP协议实现的HTTP服务器,在电脑和手机的浏览器里输入nodemcu的IP地址,就可以进入服务器
- 本服务器网页,也就是HTML文件,包括三个部分:标题,图片和段落。每刷新一次,服务器返回nodemcu当前剩余的内存
- 服务器代码如下,文件名为LUAserver.lua
-- LUA Webserver --
dofile("import_image.lua")
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive",function(conn,payload)
print("Heap = "..node.heap().." Bytes")
print("Print payload:\n"..payload)
-- write HTML
head = "<html><head><title>ESP8266 Webserver</title></head>"
body = "<body><h1>Welcome to Nodemcu</h1>"
para = "<p>The size of the memory available: "..tostring(node.heap()).." Bytes </p>"
image1 = "<img src=\""..png1
image2 = png2.."\" width = \"50\" height = \"50\" />"
ending = "</body></html>"
reply1 = head..body..image1
reply2 = image2..para..ending
payloadLen = string.len(reply1) + string.len(reply2)
conn:send("HTTP/1.1 200 OK\r\n")
conn:send("Content-Length:" .. tostring(payloadLen) .. "\r\n")
conn:send("Connection:close\r\n\r\n")
conn:send(reply1)
conn:send(reply2)
collectgarbage()
end)
conn:on("sent",function(conn)
conn:close()
end)
end)
下面对代码进行解释
首先看HTML部分,
程序第八行(算上注释)-- write HTML
以下的内容,到空白行为止
-
head..
HTML文件以<html>
开始,</html>
结束。文件开头要有<html>
,之后的第一部分是head,以
<head>
开始,</head>
结束。这里用来确定标题titletitle的格式是
<title>...</title>
,title的内容ESP8266 Webserver
会显示在网页的标签上 -
body..
body部分是HTML的主体,同样以<body>
开始,</body>
结束<h1>
代表第一个标题,格式是<h1>...<\h1>
-
para..
标题以下是段落部分,以<p>
开始,</p>结束
这里要显示nodemcu的剩余内存大小,用函数node.heap()得到数值,并用tostring()转换成字符串,再用
..
将前后的字符串连成一体 -
image1..
,image2..
是HTML插入的图像,本来是一句,因为太大分成了两部分。最简单格式为
<img src="XX.XX">
,XX.XX代表图像名和后缀,比如nodmcu.png
,图片文件通常和HTML文件放在一起,或放在专门的文件夹。但是,在nodemcu里不能这样做
nodemcu文件系统中,并没有存放HTML文档。而发送的HTML是作为代码内嵌在程序中。
同样,图像文件也作为代码内嵌到程序中.我们可以将png等图像文件转换成base64编码,图像文件用很长的字符串表示。
在另一个lua文件
import_image.lua
中,我们定义了图像的base64字符串png1和png2.文件有1k多,太大所以分成了两个。当本程序执行时,首先dofile(“import_image.lua”),得到图像字符串结尾处的
width = \"50\" height = \"50\"
定义图像的大小,50两边"
前要加转义符/
,否则编译会报错 -
end..
部分用</body>
和</html>
结束body部分和整个html然后我们从头开始看
dofile()
用来导入html的image数据,前面已经讲过-
net.createServer()
创建TCP服务器,这是建立HTTP服务器的基础 -
srv
是创建的service服务器子模块,下面有方法listen()
,也就是监听函数listen()
监听函数是整个程序的主体,一直到代码最后一个end)
结束listen()
的回调函数function(conn)
体内包含了两个事件触发函数conn:on("receive"...end)
和conn:on("send"...end)
,二者分别在接收到客户端的信息后和服务器发送消息后执行其内部的回调函数
function(conn,payload)
和function(conn)
-
最内层的两个匿名函数
function(conn,payload)
和function(conn)
可以调用最上层封闭函数的变量,conn
和payload
conn
代表TCP协议创建的从服务器到客户端的连接通道payload
是服务器从客户端接收到的消息或请求
conn:on("receive",...end)
中的recieve
,代表事件是接收到客户端的信息
,回调函数function(conn,payload)
的两个变量上面已经解释过了,下面的代码只到第一个end)
,都是它的body- 两行
print()
函数分别显示nodemcu还有多少内存,以及接收到的客户端信息payload
- 从
head..
到ending..
是html代码,上面已经解释过了 - HTML代码比较大,而TCP一次发包的大小不能大于1460byte,所以我们手动把代码打成两个包,
reply1
和reply2
并且统计了两个包的总长度,定义了payloadLen。这个payload是服务器要发给客户端的
payload
,上一个payload
是从客户端发过来的。-
下面有五个连续的
conn:send()
,代表服务器要向客户端通过HTTP协议发送payload信息。也是HTTP协议对paylaod
信息的打包过程。后两条是被打包的reply1和reply2 我们要传输的HTML文档。
前三条是HTTP的头部信息,第一条明确是HTTP,第二条确定打包信息
payload
的长度,第三条关闭HTTP传输- 在确认客户端收到完整的信息后才会关闭TCP连接。
collectgarbage()
l收集垃圾,释放内存-
conn:on("send"..)
当上述payload也就是HTML文件被发送,就把TCP的conn连接关闭。这样从客户端接收到
payload
信息,到向客户端发送payload
信息,就完成了一次对话过程当浏览器(也就是客户端)刷新一次,客户器重新向服务器发送paylaod,上述对话过程会再进行一次。但是服务器不会保存每次对话的相关信息。
Step2
接下来我们开始实际操作。
- 首先请确认nodemcu已经连上了你家路由。请参考关于lua
和关于nodemcu的点点滴滴
相关内容。
- 上传import_image.lua
,操作如下图。这是服务器HTML的内嵌图片base64
代码,我把这个文件和本文档一起上传到论坛上。注意要用Upload
,不要用Save to Esp
你也可以上传自己的base64代码,有网站可以将图片在线转换成base64。
但是这个图片不能太大。最好不要超过2k,否则会报错。因为内存里有lua的解释器运行,空闲的内存也就10k左右。
同时代码要打包放到几个字符串中。因为send一次发送量不能超过1460byte
- 上传LUAserver.lua文档(step1中的代码),如果用
Save to Esp
有时会报错,重启后多试几次 - 执行dofile(“LUAbserver.lua”)命令,(用
Save to Esp
会自动执行) - 打开电脑或手机浏览器,确保已经连接无线。输入nodemcu的IP地址。(connect_wifi.lua程序中会给出,一般是192.168.1.XXX)
在浏览器上回显示如下界面:
-
在esplore界面上回显示nodemcu的可用内存,以及从浏览器客户端收到的payload信息,如下
> Heap = 13608 Bytes
Print payload:
GET / HTTP/1.1
Host: 192.168.1.7
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cache-Control: max-age=0 每次刷新网页,服务器就返回nodemcu当前的可用内存,它是动态变化的。
这部分就到这里,
这一节我们介绍了网络协议及其运作方式,给出一个nodemcu服务器例程,顺便介绍了HTML的基本语法。
nodemcu的可用内存不大,因此HTML中嵌入不了太大的图片。建议最好不嵌入图片,本文只是一个示例。
nodemcu作服务器的最大好处是多个用户端可以访问,比如手机,平板,电脑,只要有浏览器就能访问。
它的缺点就是内存小,因此只能作很简单的服务器,结构太复杂或者嵌入大图片,就会耗尽内存。
本节的例程是用服务器传输nodemcu的状态信息,当然也可以传递从外设的信息,感兴趣的朋友可以自己试一试。
下一节,讲如何在客户端上控制nodemcu的外设,例如控制gpio操纵nodemcu上的LED,实现远程开关功能。