圖三、1 網路通訊基本架構 : 客戶端(Client)--伺服器(Server)示意圖
在網路通訊的世界裡,如【圖三、1】所示,最基本就是兩個角色,一個是提出請求(Rquest)的客戶端(Client),另一邊則是回應需求的伺服器(Server)端,這兩種裝置透過網路(包括網際網路(Internet)、本地網路(Local Net)與無線WiFi網路等)連接在一起;至於其他的網路設備,如路由器(Router)、集線器(Hub)、通訊閘道(Gateway)、中繼器(Repeater)...等,都只是為了要讓這兩種裝置能順利地連接在一起,達成網路通訊的目的而已。ESP32這個晶片模組可分別執行客戶端(Client)與伺服器(Server)端這兩個腳色,可以連上網際網路,亦可自行組成一個區域性的無線WiFi網路,如果要在這個無線WiFi區域網路擔任伺服器(Server)個腳色時,就必須扮演無線WiFi網路中的存取點(AP:Access Point)功能了!
所謂的網路伺服器(WebServer),是指一種連接在網路上的設備,這種設備主要用於儲存和提供數據/文件,而客戶端(Client)這種裝置可以請求(Request)這些文件或其他數據,網路伺服器將此請求(Request)解譯之後,再將正確的數據/文件發送回客戶端。一般來說這兩種設備之間使用所謂的HTTP通訊協定來進行需求與資料的雙向傳輸,所謂的HTTP(Hypertext Transfer potocal)或超文本傳輸協定,是用於與(Web)伺服器通信的基於文本的協定,HTTP請求方法有多種,但由於ESP32只是一顆內部資源有限的單晶片微電腦而已,所以我們只會介紹兩種最常用的方法也就是:GET和POST。
在HTTP通訊協定中,GET這種請求用於從伺服器檢索數據/文件,例如瀏覽器連接網頁;它只是單純從伺服器獲取數據/文件,不會要求或試著更改伺服器內部的資料,因此沒有其他的副作用。當我們使用瀏覽器中打開某一個網頁時,它將獲取URL並將其放入HTTP GET請求中,這只是純文本(plain text)資料,然後它將使用TCP通訊協定將請求發送到正確的伺服器;伺服器讀取請求後,檢查URL,並將該URL的正確HTTP響應發送回瀏覽器。
圖三、2 電腦瀏覽器連接http://www.example.com網站後的內容
在HTTP 的GET請求中最重要的部分是請求行(request Line)和主機檔頭(host header),接下來我們以一個範例來說明:
請啟動你個人電腦的瀏覽器(在此使用的是Google Chrome,其他的瀏覽器也可以,當然你的電腦必須已經連上網際網路),接著在URL列輸入以下鏈接網址:
這時您的瀏覽器將發出以下HTTP請求:
在【圖三、2】中標記1是我們要前往網頁的URL網址,這是一個網路上有名的樣本網站,的內容就是左邊標記2簡單的文字說明;不過要看到右邊相關的資訊,請在連上這個網站之後按下電腦鍵盤的[F12]功能鍵,然後依序點擊的標記3重新載入按鈕再次的載入網頁,接著是標記4的[ Network ]標籤、標記5的網站名稱[www.example.com]、標記7的解析(parsed)選項後,便可以完整的看到標記6與8的內容了。
所謂的請求行主要包括兩個部份,分別是URI(Uniform Resource Identifier)也就是請求的內容,再來就是請求的方式「GET」;至於主機檔頭,則是要連接的主機伺服器(host server)的網域名稱。在標記6的區域中我們可看到前兩行的內容為:
Request URL:http://www.example.com/
Request Method:GET
第二行的內容就是請求的方式「GET」,而第一行的內容其實是由兩個部分所構成,分別是代表主機檔頭的主機伺服器(host server)網域名稱:[http://www.example.com],及請求的內容:「’/’」,這個內容也就是一般網站主網頁的意思。
接下來我們就以一個範例程式來看看,當我們用手機或平板電腦的瀏覽器去連接ESP32的AP存取點伺服器網站時,會送出那些資訊出來:
◎範例程式功能與動作說明:
1、以ESP32建立一無線WiFi存取點,SSID名稱為『ES32_softAP01』,此AP點不使用密碼。
2、此AP存取點內建伺服器的IP位址為:[ 192.168.4.1 ]。
3、當有客戶端裝置連線上ESP32時,系統會讀取客戶端(手機、平板或筆電上的瀏覽器)所傳送來的完整請求訊息,接著除了會顯示在電腦上的Arduino IDE序列監控視窗中之外,還會將此訊息回傳給客戶端,也就是會顯示在連線裝置的瀏覽器螢幕上。
【圖三、3】是本範例在行動通訊裝置連線上ESP32,並開啟瀏覽器後預期的顯示畫面,上方標記1是本範例的提示訊息,而下方的一系列文字都是客戶端瀏覽器所送出的網頁首頁請求訊息內容,本範例程式只是將此訊息原封不動的回傳給使用者而已。
圖三、3 客戶端瀏覽器螢幕預期畫面
◎程式說明與列表:
以下是這個範例程式的全文:
/*
* ESP32 soft AP 範例二: 分析及顯示瀏覽器傳送的GET請求訊息內容
*/
#include <WiFi.h>
#include "index.h"
const char* ssid = "ESP32_softAP01";
const char* password = "12345678";
String yourGet="";
WiFiServer server(80);
// 初始化設定程式開始:
void setup()
{
Serial.begin(115200);
delay(10);
// We start by connecting to a WiFi network
WiFi.softAP(ssid); // 本範例暫時不使用WiFi連線密碼
Serial.println("Setting softAP ...");
Serial.println();
Serial.print("Your softAP is : ");
Serial.println(ssid); // 顯示ESP32 soft AP名稱
Serial.println("IP address: ");
Serial.println(WiFi.softAPIP()); // 顯示ESP32內建伺服器IP位址
server.begin(); // 啟動伺服器功能
} // 初始化設定程式結束.
// 主迴圈程式開始:
void loop(){
WiFiClient client = server.available(); // listen for incoming clients
// wait for a client (web browser) to connect
if (client)
{
Serial.println("\n[Client connected]"); // 在序列監控視窗輸出連線提示訊息
while (client.connected())
{
// read line by line what the client (web browser) is requesting
if (client.available())
{
String line = client.readStringUntil('\r'); // 逐行讀取客戶端送來的訊息
Serial.print(line);
yourGet+=line; // 將客戶端送來的訊息整合成一個字串變數
yourGet+="<br>"; // 替HTTP網頁程式上換行的指令
// wait for end of client's request, that is marked with an empty line
if (line.length() == 1 && line[0] == '\n') // 測試HTTP請求是否結束
{
String tmpString = MAIN_page; // 取出html網頁回應程式
tmpString.replace("%yourGet%", yourGet ); // 帶入客戶端HTTP請求內容至回應網頁
client.print( tmpString );
break;
}
}
}
delay(1); // give the web browser time to receive the data
// close the connection:
client.stop();
yourGet=""; // 清除回應客戶端訊息字串變數
Serial.println("[Client disonnected]");
}
} // 主迴圈程式結束.
// 以下為”index.h” 標籤頁面的內容:
const char MAIN_page[] PROGMEM = R"=====(
HTTP/1.1 200 OK
Content-Type:text/html
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
<meta charset='utf-8'>
<meta http-equiv='refresh' content='%rTime%'>
<style>
body {font-size:100%;}
#main {display: table; margin: auto; padding: 0 10px 0 10px; }
</style>
<title>Soft AP模式-Station連線內容讀取實習</title>
</head>
<body>
<div id='main'>
<h2><center>[Soft AP模式] <br>
Station連線內容讀取實習<br></center>
</h2>
<h3><br> %yourGet% </h3>
</div>
</body>
</html>
)=====";
程式名稱:softAP1_ShowGet.ino
圖三、4 Arduino IDE 中建構index.h程式頁面畫面
我們的程式中會有一段回應客戶端的HTML網頁程式,但是在Arduino IDE上面並不適合直接用來撰寫HTML網頁程式語法,為了讓程式容易設計,而且版面看起來比較乾淨俐落,因此建議把相關的HTML網頁程式另外開一個標籤頁面來撰寫,然後用引入的方式來使用,這樣就可以把兩者分得比較清楚。【圖三、4】的畫面即為本範例程式實際在Arduino IDE的寫法,在前面的程式列表中,70~96行便是「index.h」這個頁面的內容。
程式的13行在宣告一個網路伺服器物件物件變數:
13. WiFiServer server(80); // 宣告網路伺服器物件
在初始化程式(setup())部分和上一個範例幾乎完全一樣,只是多了30行程式啟動伺服器的功能:
30. server.begin(); // 啟動伺服器功能
在主迴圈(loop())開始的第36行程式呼叫「server.available()」這個方法函式去監測是否有客戶端裝置連線到本機的伺服器,若無則回頭繼續執行主迴圈:
36. WiFiClient client = server.available(); // 測試是否有客戶端連接本機伺服器
如果有客戶端裝置連線到伺服器,則主迴圈程式剩下的部分便會開始接收與回應客戶端的請求訊息。41~59行程式是以while()迴圈的方式來持續的接收客戶端傳來的請求訊息,直到接收到代表請求結束的空行字串為止。46~49行程式會逐行讀取客戶端送來的訊息,並把它們合併為「yourGet」這個字串變數,因為一般文字的換行碼”\n”在html網頁程式中並不會讓瀏覽器執行換行的動作,因此在每一行的後面必須加上<br>這個指令標籤才能產生換行的效果。
46. String line = client.readStringUntil('\r'); // 逐行讀取客戶端送來的訊息
47. Serial.print(line);
48. yourGet+=line; // 將客戶端送來的訊息整合成一個字串變數
49. yourGet+="<br>"; // 替HTTP網頁程式上換行的指令
一般的瀏覽器在結束請求傳送時,會送出一個空白行,也就是一整行只有一個代表換行的字元”\n”,因此當ESP32接收到這樣的字串時(51~58行),便會把網頁首頁程式MAIN_page[]傳送到tmpString這個字串變數中,同時也把整的客戶端的請求訊息(即「yourGet」字串變數)帶入到tmpString這個網頁html程式內,在送出回應網頁程式至客戶端後,以”break”的整令結束while()迴圈。
51. if (line.length() == 1 && line[0] == '\n') // 測試HTTP請求是否結束
52. {
53. String tmpString = MAIN_page; // 取出html網頁回應程式
54. tmpString.replace("%yourGet%", yourGet ); // 帶入客戶端HTTP請求55. 內容至回應網頁
56. client.print( tmpString ); // 送出回應網頁程式至客戶端
57. break;
58. }
由於我們的html網頁程式是放在ESP32的程式記(ROM)憶體中,是無法更改的,可是我們的網頁內容又常是動態的,因此在54行程使用了一個Arduino的內建字串指令「tmpString.replace("%yourGet%", yourGet )」,它原來的格式如下:
String.replace(string1, string2 )
也就是說用第二個字串參數去取代第一個,用這種程式設計法可以讓程式寫起來更方便簡單,也更有彈性!
接著的70~96行是是「index.h」這個頁面的內容,是一個完整的伺服器網頁首頁程式,在此是使用了下面這種在C++語言中稱為原始字串 (Raw String) 的語法,也就是將整個網頁程式字串包在後面的左右括弧「R”=====(……..)=====”」中間”…”的部分,而左、右括弧的兩邊一共用了5個等號(“=”),缺一個都不行!這種語法可以保持html程式原來的樣貌,增加程式設計的方便性與可看性,而這點是Arduino IDE這個軟體所無法提供的。
const char MAIN_page[] PROGMEM = R"=====( ……….. )=====";
程式在此是定義了一個名稱為「MAIN_page[]」的字元常數陣列,而「PROGMEM」的意思是這字元常數陣列最後會放在程式記憶體中,這樣就不會占用到ESP32內部的RAM主記憶體,以免程式太大時造成記憶體溢位的問題!
這個html網頁程式的主體(即87~94行的<body>…</body>中間)部分如下:
87. <body>
88. <div id='main'>
89. <h2><center>[Soft AP模式] <br>
90. Station連線內容讀取實習<br></center>
91. </h2>
92. <h3><br> %yourGet% </h3>
93. </div>
94. </body>
其中的89、90行是用來實現【圖三、3】中標記1的提示訊息,而92行則會將前面54行所傳來的「yourGet」字串變數帶入程式中,這樣的寫法可讓我們的網頁程式部分更有彈性。
◎ 執行結果:
在程式執行之後接著開啟手機/平板/筆電的WiFi功能,如【圖三、5】所示,在看到”ESP32_softAP01”這個AP存取點後點選它,接著啟動瀏覽器,並在網址輸入欄中輸入[ 192.168.4.1 ]這個IP位址並前往。
圖三、5 手機啟動WiFi功能後連接ESP32的AP存取點
這時我們可以看到如【圖三、6】所示的手機瀏覽器畫面,這也就是實現了前面【圖三、3】的功能。而在Arduino IDE串列監控視窗中則會看到【圖三、7】的內容,其中標記1的部分是在程式初始化時前設定的SSID名稱(在此為”ESP32_softAP01”),和而標記2的ESP32內定的本地IP位址(192.168.4.1),在本例中為了方便連線測試,所以沒有加上連線密碼。
圖三、6 客戶端手機瀏覽器連線後畫面
圖三、7 客戶端瀏覽器連線後Arduino IDE串列監控視窗之內容
如果我們在手機瀏覽器的網址輸入欄輸入URL:「 192.168.4.1/test 」這樣的內容送出之後,可看到【圖三、8】的內容,其中紅色框線中的部分就是此次客戶端送來的請求行(request Line):
GET /test HTTP/1.1
跟前面【圖三、6】的差別就是請求的內容由「’/’」 變成「’/test’」,,也就是說只要在網域名稱或是網路IP後面加上一參數,便可將這些訊息當成請求的一部分傳送給目的伺服器,以作為一些延伸應用。
圖三、8 瀏覽器網址列輸入”192.168.4.1/test”後回傳送資料內容
沒有留言:
張貼留言