用单片机实现HTTP网页服务器功能(详细教程)

在阅读本教程时,假设你已经

1、能够用单片机驱动网络模块(如W5100、ENC28J60),实现最基本的socket连接和数据收发。如果你没有驱动代码,请在百度中搜索下载。
2、了解TCP/IP协议。如果不了解,请查看谢希仁《计算机网络(第五版)》或其他相关书籍。
3、会用html语言编写(简单或复杂)网页。如果不会编写,请点击->  http://www.w3school.com.cn 

一、通过浏览器向单片机发送请求

我们在浏览器地址栏输入地址并确定,实际上就是发送一个网页请求,现在,我们把单片机作为网页的服务器,那么如何接收并响应这个请求呢?

首先,将网络模块的某个socket设置为sever模式,并将此socket的端口设置为80(HTTP默认端口)。如果不想把端口设置为80行不行呢?当然可以,但是需要注意:
假设socket的地址为192.168.1.199,端口号80,在浏览器输入HTTP://192.168.1.199后,就可以直接向此socket发起连接,因为HTTP的默认端口会自动加到IP后面。
若此socket的端口为30000,那么输入地址的时候,就需要手动输入端口号,即:HTTP://192.168.1.199:30000。如果懒得输端口号,那就直接将端口设置为80吧。

二、浏览器请求解析

下面进行下一个准备工作:将网络模块 socket接收到的数据通过串口转发出来,并用串口助手观察。

在浏览器输入socket地址后,可以在串口助手上看到以下数据:(以下数据均为字符)


//------------------------------------------------

GET / HTTP/1.1
Host: 192.168.1.199
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 SE 2.X MetaSr 1.0
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8

//------------------------------------------------


这就是浏览器向服务器发送的请求数据(不同浏览器的数据稍有不同),关于这些数据的含义,请参看以下两篇文章:

->(文章1) http://www.cnblogs.com/loveyakamoz/archive/2011/07/22/2113614.html
->(文章2) http://canrry.iteye.com/blog/1331292

在这里我们只关心第一行,这行数据表明,浏览器向单片机请求网页。这是浏览器第一次请求的命令,若需要请求其他网页,第一行可能会变成:
GET /xxxx.HTML HTTP/1.1,这时,只要判断此处的信息,就可以知道该发送哪个网页给浏览器。

三、单片机返回相应网页数据包

假设收到浏览器第一次发送的请求“GET / HTTP/1.1”,此时浏览器和网络模块的socket已经建立连接,并等待数据返回。

我们通过相应socket向浏览器返回一个登陆界面 Login.HTML的代码:(下文相同代码均用(网页代码1)等方式表示)


(网页代码1)

//------------------------------------------------

<!DOCTYPE html>
<html>
<title>LOGIN</title> 
<body>
<form method='get' action='Login.cgi'> 
用户名:<input type='text' name='username'><br> 
密 码:<input type='password' name='password'><br> 
<input type='submit' value='登 录'>
</form>
</body>
</html>

//------------------------------------------------


因为单片机的储存空间有限,一般将网页以const的形式存放在flash中,而且尽量精简,除了必要的效果和兼容性语句外,

其他都可以删掉(如对齐格式时使用的大量tab、空格等),以减轻单片机负担。
但是为了编写网页方便,建议将网页源文件复制粘贴到别处,删除多余的内容后再烧写到单片机中。

好了,接着上面的说,真的只发一个网页过去就行了吗?

当然了,肯定不行。因为浏览器无法知道单片机要发送什么类型、多少长度的数据。所以,我们还需要一个返回数据的格式。


//------------------------------------------------

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: xx

//------------------------------------------------


文章1`2中也已经讲到了。所以,我们发送到浏览器的数据应该是这样的:


//------------------------------------------------

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length:282

(网页代码1)


//------------------------------------------------

 
 


注意,这些数据中,有些换行是不能省的(下文\r\n起强调作用,并不是接收到的字符):


//------------------------------------------------

HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Content-Length:xx\r\n
\r\n
(网页代码1)\r\n
\r\n

//------------------------------------------------


否则浏览器无法识别数据。

其中的Content-Length:282是网页文件的大小(字节),需要提前算好。
这样,浏览器收到了第一个网页,显示效果如下。



不管其他的,我们先登陆一下试试,输入用户名:hello,密码:world,点击登录。然后我们发现单片机收到了如下数据:


//------------------------------------------------

GET /Login.cgi?Username=hello&Password=world HTTP/1.1
Host: 192.168.1.199
Connection: Keep-Alive
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; InfoPath.2; SE 2.X MetaSr 1.0)
Referer: http://192.168.1.199/
Accept-Encoding: gzip, deflate

//------------------------------------------------


好了,我们需要的数据应该全在第一行里面了,有用户名和密码,但是看一下浏览器的地址栏,用户名和密码同样以URL的形式出现了:


//------------------------------------------------

HTTP://192.168.1.199/Login.cgi?Username=hello&Password=world

//------------------------------------------------


逗我呢这是,密码全看见了,有没有更为安全的方式呢,当然是有。首先看一下下面一篇文章:
->(文章3) http://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html

知道了get 和 post 的区别,我们将Login.html中 method='get' 改为 method='post'重新操作一次,结果,单片机收到的数据变成了这样:


//------------------------------------------------

POST /Login.cgi HTTP/1.1
Host: 192.168.1.199
Connection: Keep-Alive
Content-Length: 29
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Accept-Language: zh-CN
Content-Type: application/x-www-form-urlencoded
Origin: http://192.168.1.199
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; InfoPath.2; SE 2.X MetaSr 1.0)
Referer: http://192.168.1.199/
Accept-Encoding: gzip, deflate\r\n
\r\n
Username=hello&Password=world

//------------------------------------------------


用户名和密码跑到了最后,浏览器地址栏也没有显示用户名和密码了,甚好!
以此方法,其他网页也可以发送到浏览器并返回单片机所需要的数据。

四、通过JS让网页活起来

现在让我们先做一个设置单片机串口的网页,并通过网页更新单片机串口设置。网页代码如下   

(网页代码2)

//------------------------------------------------

<!DOCTYPE html>
<html>
<title>COM SETTING</title> 
<body>
<form method='post' action='IP_Set.cgi'>
串口号:<input type='text' id='COM' name='COM_NUM'/><br>
波特率:<input type='text' id='BDR' name='BD_RATE'/><br>
<input type='submit' value='确定'/>
</form>
</body>
</html>

//------------------------------------------------


 先看一下效果:

串口的设置是在变的,不能写死到网页里,该怎么动态显示呢?
当然,这种时候少不了Javascript。在网页的最后追加几句话:

(网页代码3)

//------------------------------------------------

<script>
document.getElementById('COM').value='1';
document.getElementById('BDR').value='9600';
</script>

//------------------------------------------------


value里的值是通过单片机实时生成的,我们姑且这样写。发送一下试试先:


//------------------------------------------------

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length:410

(网页代码2)
(网页代码3)


//------------------------------------------------


现在,显示效果是这样的:


这样,我们完成了单片机内部参数在网页上显示的效果。

得陇望蜀,人之常情,做完串口配置,又想把所有的单片机配置全部用网页完成,结果做出来一个比较大的网页。

这个网页上需要显示很多的值,需要动态生成很多的JS语句,如果采用Content-Length这种固定长度的方式发送,该怎么计算网页长度呢?

当然,可以一句一句用strlen计算加上去,再更改Content-Length的值。
然而这种方式很麻烦,不好操作(因为网页是const类型),有时候网页的长度根本没办法预知,会导致网页发送失败,浏览器打不开网页。
实际上,这种问题当然是可以完全解决的。


五、灵活的网页传输方式


我们在上网的时候,实际上大部分的网页是动态的,不能预知长度,那么这些网页是怎么正确传输到浏览器上的呢?
如果你有TCP/IP的抓包软件,在抓到的数据包中,可能会看到这样一句:
Transfer-Encoding: chunked
这句话的意思是:不定长发送,即每个数据包中规定此包数据的大小,等待发送完成后,再发送一个结束符。
这样浏览器就不必知道整个网页有多大,只要等结束符就行。

具体操作方式是这样的:


//------------------------------------------------

HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Transfer-Encoding: chunked\r\n
\r\n
数据长度1(16进制)\r\n
数据1\r\n
数据长度2(16进制)\r\n
数据2\r\n
......
0\r\n
\r\n(结束符:0表示数据长度为0,换行后没有数据内容,再换行)

//------------------------------------------------


结束符前可以有任意多个数据。注意Content-Length方式的长度值是十进制,这种方式的长度值是十六进制。

那么再试一试上面的网页:


//------------------------------------------------

HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Transfer-Encoding: chunked\r\n
\r\n
12C\r\n
(网页代码2)\r\n
6C\r\n
(网页代码3)\r\n
......
0\r\n
\r\n

//------------------------------------------------


显示效果和之前的方法一样,但是这种方式操作起来就很方便了。
如果想向网页中添加数据,只要在结束符之前发送就OK。

现在,最基本的功能都实现了,但是,网页看起来有点丑,还能再给力点吗?

通过CSS格式,可以做出很多炫酷的网页特效,但是有些效果,还是不能代替图片。那么,看下一节:


六、图片,让网页绚丽多彩。


是的,少了图片的网页,看起来枯燥无味。那么,让我们尝试着发送一张图片到浏览器上去。


首先,需要把图片存入单片机,在百度中搜索“任意文件转C语言数组”,用这个小工具,把图片先转换成十六进制数组。


(剩下部分还在探索,文章中有一些小问题,需要验证后再做更改,待续...如有疑问或建议,请直接回复)
(原创文章,转载请注明出处)


  • 76
    点赞
  • 357
    收藏
    觉得还不错? 一键收藏
  • 25
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值