用單片機實現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語言數組”,用這個小工具,把圖片先轉換成十六進制數組。


(剩下部分還在探索,文章中有一些小問題,需要驗證後再做更改,待續...如有疑問或建議,請直接回復)
(原創文章,轉載請註明出處)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章