授人以魚不如授人以漁,目的不是爲了教會你具體項目開發,而是學會學習的能力。但願你們分享給你周邊須要的朋友或者同窗,說不定大神成長之路有博哥的奠定石。。。html
QQ技術互動交流羣:ESP8266&32 物聯網開發 羣號622368884,不喜勿噴node
1、基礎篇git
2、網絡篇github
- ESP8266開發之旅 網絡篇① 認識一下Arduino Core For ESP8266
- ESP8266開發之旅 網絡篇② ESP8266 工做模式與ESP8266WiFi庫
- ESP8266開發之旅 網絡篇③ Soft-AP——ESP8266WiFiAP庫的使用
- ESP8266開發之旅 網絡篇④ Station——ESP8266WiFiSTA庫的使用
- ESP8266開發之旅 網絡篇⑤ Scan WiFi——ESP8266WiFiScan庫的使用
- ESP8266開發之旅 網絡篇⑥ ESP8266WiFiGeneric——基礎庫
- ESP8266開發之旅 網絡篇⑦ TCP Server & TCP Client
- ESP8266開發之旅 網絡篇⑧ SmartConfig——一鍵配網
- ESP8266開發之旅 網絡篇⑨ HttpClient——ESP8266HTTPClient庫的使用
- ESP8266開發之旅 網絡篇⑩ UDP服務
- ESP8266開發之旅 網絡篇⑪ WebServer——ESP8266WebServer庫的使用
- ESP8266開發之旅 網絡篇⑫ 域名服務——ESP8266mDNS庫
- ESP8266開發之旅 網絡篇⑬ SPIFFS——ESP8266 Flash文件系統
- ESP8266開發之旅 網絡篇⑭ web配網
- ESP8266開發之旅 網絡篇⑮ 真正的域名服務——DNSServer
- ESP8266開發之旅 網絡篇⑯ 無線更新——OTA固件更新
3、應用篇web
4、高級篇算法
一般,爲了讓手機連上一個WiFi熱點,基本上都是打開手機設置裏面的WiFi設置功能,而後會看到裏面有個WiFi熱點列表,而後選擇你要的鏈接上去。
基本上你只要打開手機鏈接WiFi功能,都會發現附近有超級多的各類來路不明的WiFi熱點(鏈接有風險需謹慎),那麼手機是怎麼知道附近的WiFi的呢?
一般,無線網絡提供的WiFi熱點,大部分都開放了SSID廣播(記得以前樓主講過WiFi熱點也能夠隱藏的),Scan WiFi的功能就是掃描出全部附近的WiFi熱點的SSID信息,這樣一來,客戶端就能夠根據須要選擇不一樣的SSID連入對應的無線網絡中。
在前面章節裏面,博主講解了ESP8266WiFi庫裏面的一些重要內容。這裏回顧一下博主講了哪些重要內容:json
注意點:後端
#include <ESP8266WiFi.h>
固然,ESP8266WiFi庫裏面還有其餘重要內容,好比跟http相關的 WiFiClient、WiFiServer,跟https相關的 WiFiClientSecure、WiFiServerSecure。
終於,到這篇,能夠看到跟網絡請求有關的東西了。
那確定就會有不少人會問:到底何時用到哪一個呢?
在這裏,博主給你們歸納瞭如下幾點,但願深刻理解核心:api
這一章節,咱們講講解兩大模塊:數組
至於什麼是TCP傳輸協議,你們執行查資料吧。
概念圖:
client,又名客戶端,也就是須要經過獲取server提供的服務數據來展現本身。Tcp client,只是架構在tcp協議之上的客戶端。上圖中,ESP8266做爲client端,經過路由,訪問局域網內的Pc server或者廣域網下的網絡服務器信息,server收到請求後會處理請求而且把響應數據返回以供ESP8266使用。
博主總結了 WiFiClient 百度腦圖:
總體上來講,方法能夠分爲4類:
函數說明:
/** * 創建一個tcp鏈接 * @param ip IPAddress of tcpserver * @param port port of tcpserver * @return result of tcp connect * 1 --- success * 0 --- fail */ int connect(IPAddress ip, uint16_t port); /** * 創建一個tcp鏈接 * @param host host of tcpserver (192.xx.xx.xx) * @param port port of tcpserver * @return result of tcp connect * 1 --- success * 0 --- fail */ int connect(const char *host, uint16_t port) /** * 創建一個tcp鏈接 * @param host host of tcpserver (192.xx.xx.xx) * @param port port of tcpserver * @return result of tcp connect * 1 --- success * 0 --- fail */ int connect(const String host, uint16_t port);
函數說明:
/** * 判斷tcp鏈接是否創建起來(ESTABLISHED) * @return result of tcp connect * 1 --- success * 0 --- fail */ uint8_t connected();
函數說明:
/** * 關閉tcp鏈接 */ void stop();
函數說明:
/** * 獲取tcp鏈接狀態 * @return result of tcp connect * CLOSED = 0, * LISTEN = 1, * SYN_SENT = 2, * SYN_RCVD = 3, * ESTABLISHED = 4, * FIN_WAIT_1 = 5, * FIN_WAIT_2 = 6, * CLOSE_WAIT = 7, * CLOSING = 8, * LAST_ACK = 9, * TIME_WAIT = 10 */ uint8_t status();
發送操做的源碼能夠查閱 Print.cpp
函數說明:
/** * 發送數據 * @param str 須要單個字節 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t write(uint8_t); /** * 發送數據 * @param str 須要發送字符串或者字符數組 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t write(const char *str); /** * 發送數據 * @param buffer 須要發送字符串或者字符數組 * @param size 數據字節數 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t write(const char *buffer, size_t size) /** * 發送數據 * @param stream 數據流,好比文件流 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t write(Stream& stream);
注意點:
size_t write(const char *str) { if(str == NULL) return 0; return write((const uint8_t *) str, strlen(str)); }
函數說明:
/** * 發送數據 * @param FlashStringHelper 須要發送的字符串,字符串存在flash中(PROGMEM) * @return size_t 成功寫入發送緩衝區的字節數 */ size_t print(const __FlashStringHelper *); /** * 發送數據 * @param String 須要發送的字符串,字符串存在內存中 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t print(const String &); /** * 發送數據 * @param String 須要發送的字符數組,字符數組存在內存中 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t print(const char[]); /** * 發送數據 * @param String 須要發送的字符 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t print(char); /** * 發送數據 * @param String 須要發送的數據,可能是數字,轉成對應的進制,通常都是傳輸數字型數據 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t print(unsigned char, int = DEC); size_t print(int, int = DEC); size_t print(unsigned int, int = DEC); size_t print(long, int = DEC); size_t print(unsigned long, int = DEC); size_t print(double, int = 2);
注意點:
//實例代碼 非完整代碼 不可直接使用 理解便可 WiFiClient client; client.print( F("This is an flash string")); //字符串「This is an flash string」存在於flash
函數說明:
/** * 發送數據,而且加上換行符 "\r\n" * @param FlashStringHelper 須要發送的字符串,字符串存在flash中(PROGMEM) * @return size_t 成功寫入發送緩衝區的字節數 */ size_t println(const __FlashStringHelper *); /** * 發送數據,而且加上換行符 "\r\n" * @param String 須要發送的字符串,字符串存在內存中 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t println(const String &s); /** * 發送數據,而且加上換行符 "\r\n" * @param String 須要發送的字符數組,字符數組存在內存中 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t println(const char[]); /** * 發送數據,而且加上換行符 "\r\n" * @param String 須要發送的字符 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t println(char); /** * 發送數據,而且加上換行符 "\r\n" * @param String 須要發送的數據,可能是數字,轉成對應的進制,通常都是傳輸數字型數據 * @return size_t 成功寫入發送緩衝區的字節數 */ size_t println(unsigned char, int = DEC); size_t println(int, int = DEC); size_t println(unsigned int, int = DEC); size_t println(long, int = DEC); size_t println(unsigned long, int = DEC); size_t println(double, int = 2); /** * 發送換行符 "\r\n" * @return size_t 成功寫入發送緩衝區的字節數 */ size_t println(void);
注意點:
函數說明:
/** * 返回接收緩存區可讀取字節數 * @return int 接收緩衝區可讀取字節數 */ int available();
注意點:
函數說明:
/** * 返回發送緩衝區剩餘可寫字節數 * @return int 發送緩衝區剩餘可寫字節數 */ size_t availableForWrite();
注意點:
函數說明:
/** * 讀取接收緩衝區一個字節 * @return int 一字節數據 */ int read();
注意點:
函數說明:
/** * 讀取接收緩衝區size大小的字節數據 * @param buf 數據存儲到該buf * @param size 讀取大小 * @return int 成功讀取的大小 */ int read(uint8_t *buf, size_t size);
注意點:
函數說明:
/** * 讀取接收緩衝區一個字節 * @return int 一字節數據 */ int peek();
注意點:
函數說明:
/** * 讀取接收緩衝區length大小的字節數據 * @param buffer 數據存儲到該 buffer * @param length 讀取大小 * @return size_t 成功讀取的大小 */ size_t peekBytes(uint8_t *buffer, size_t length); size_t peekBytes(char *buffer, size_t length);
注意點:
函數說明:
/** * 讀取響應數據直到某個字符串爲止 * @param end 結束字符 * @return String 讀取成功的字符串 */ String readStringUntil(char end);
函數說明:
/** * 判斷是否存在某個目標字符串 * @param buffer 目標字符串 * @return bool 存在返回true */ bool find(char *buffer);
注意點:
函數說明:
/** * 清除緩衝區 */ void flush(void);
注意點:
while(client.read()>0);
函數說明:
/** * 是否禁用 Nagle 算法。 * @param nodelay true表示禁用 Nagle 算法 */ void setNoDelay(bool nodelay);
底層源碼:
void setNoDelay(bool nodelay) { if(!_pcb) { return; } if(nodelay) { tcp_nagle_disable(_pcb); } else { tcp_nagle_enable(_pcb); } }
注意點:
前面講了這麼多理論內容,接下來用幾個例子來講明一下。
例子介紹:
本實驗演示 WiFiClient 與 TCP server 之間的通訊功能,須要使用到TCP調試助手,請在TCP調試助手上創建一個Tcp server,ip地址是192.168.1.102,端口號是8234。
源碼:
/** * Demo: * STA模式下,演示WiFiClient與TCP server之間的通訊功能 * 本實驗須要跟TCP調試助手一塊兒使用。 * @author 單片機菜鳥 * @date 2019/1/25 */ #include <ESP8266WiFi.h> //如下三個定義爲調試定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) #define AP_SSID "TP-LINK_5344" //這裏改爲你的wifi名字 #define AP_PSW "xxxxxxx"//這裏改爲你的wifi密碼 const uint16_t port = 8234; const char * host = "192.168.1.102"; // ip or dns WiFiClient client;//建立一個tcp client鏈接 void setup() { //設置串口波特率,以便打印信息 DebugBegin(115200); //延時5s 爲了演示效果 delay(5000); // 我不想別人鏈接我,只想作個站點 WiFi.mode(WIFI_STA); WiFi.begin(AP_SSID,AP_PSW); DebugPrint("Wait for WiFi... "); //等待wifi鏈接成功 while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } DebugPrintln(""); DebugPrintln("WiFi connected"); DebugPrint("IP address: "); DebugPrintln(WiFi.localIP()); delay(500); } void loop() { DebugPrint("connecting to "); DebugPrintln(host); if (!client.connect(host, port)) { DebugPrintln("connection failed"); DebugPrintln("wait 5 sec..."); delay(5000); return; } // 發送數據到Tcp server DebugPrintln("Send this data to server"); client.println(String("Send this data to server")); //讀取從server返回到響應數據 String line = client.readStringUntil('\r'); DebugPrintln(line); DebugPrintln("closing connection"); client.stop(); DebugPrintln("wait 5 sec..."); delay(5000); }
測試結果:
例子介紹:
經過TCP client包裝Http請求協議去調用天氣接口獲取天氣信息
源碼:
/** * Demo: * 演示Http請求天氣接口信息 * @author 單片機菜鳥 * @date 2019/09/04 */ #include <ESP8266WiFi.h> #include <ArduinoJson.h> //如下三個定義爲調試定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) const char* ssid = "TP-LINK_5344"; // XXXXXX -- 使用時請修改成當前你的 wifi ssid const char* password = "6206908you11011010"; // XXXXXX -- 使用時請修改成當前你的 wifi 密碼 const char* host = "api.seniverse.com"; const char* APIKEY = "wcmquevztdy1jpca"; //API KEY const char* city = "guangzhou"; const char* language = "zh-Hans";//zh-Hans 簡體中文 會顯示亂碼 const unsigned long BAUD_RATE = 115200; // serial connection speed const unsigned long HTTP_TIMEOUT = 5000; // max respone time from server const size_t MAX_CONTENT_SIZE = 1000; // max size of the HTTP response // 咱們要今後網頁中提取的數據的類型 struct WeatherData { char city[16];//城市名稱 char weather[32];//天氣介紹(多雲...) char temp[16];//溫度 char udate[32];//更新時間 }; WiFiClient client; char response[MAX_CONTENT_SIZE]; char endOfHeaders[] = "\r\n\r\n"; void setup() { // put your setup code here, to run once: WiFi.mode(WIFI_STA); //設置esp8266 工做模式 DebugBegin(BAUD_RATE); DebugPrint("Connecting to ");//寫幾句提示,哈哈 DebugPrintln(ssid); WiFi.begin(ssid, password); //鏈接wifi WiFi.setAutoConnect(true); while (WiFi.status() != WL_CONNECTED) { //這個函數是wifi鏈接狀態,返回wifi連接狀態 delay(500); DebugPrint("."); } DebugPrintln(""); DebugPrintln("WiFi connected"); delay(500); DebugPrintln("IP address: "); DebugPrintln(WiFi.localIP());//WiFi.localIP()返回8266得到的ip地址 client.setTimeout(HTTP_TIMEOUT); } void loop() { // put your main code here, to run repeatedly: //判斷tcp client是否處於鏈接狀態,不是就創建鏈接 while (!client.connected()){ if (!client.connect(host, 80)){ DebugPrintln("connection...."); delay(500); } } //發送http請求 而且跳過響應頭 直接獲取響應body if (sendRequest(host, city, APIKEY) && skipResponseHeaders()) { //清除緩衝 clrEsp8266ResponseBuffer(); //讀取響應數據 readReponseContent(response, sizeof(response)); WeatherData weatherData; if (parseUserData(response, &weatherData)) { printUserData(&weatherData); } } delay(5000);//每5s調用一次 } /** * @發送http請求指令 */ bool sendRequest(const char* host, const char* cityid, const char* apiKey) { // We now create a URI for the request //心知天氣 發送http請求 String GetUrl = "/v3/weather/now.json?key="; GetUrl += apiKey; GetUrl += "&location="; GetUrl += city; GetUrl += "&language="; GetUrl += language; // This will send the request to the server client.print(String("GET ") + GetUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); DebugPrintln("create a request:"); DebugPrintln(String("GET ") + GetUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n"); delay(1000); return true; } /** * @Desc 跳過 HTTP 頭,使咱們在響應正文的開頭 */ bool skipResponseHeaders() { // HTTP headers end with an empty line bool ok = client.find(endOfHeaders); if (!ok) { DebugPrintln("No response or invalid response!"); } return ok; } /** * @Desc 從HTTP服務器響應中讀取正文 */ void readReponseContent(char* content, size_t maxSize) { size_t length = client.peekBytes(content, maxSize); delay(100); DebugPrintln("Get the data from Internet!"); content[length] = 0; DebugPrintln(content); DebugPrintln("Read data Over!"); client.flush();//清除一下緩衝 } /** * @Desc 解析數據 Json解析 * 數據格式以下: * { * "results": [ * { * "location": { * "id": "WX4FBXXFKE4F", * "name": "北京", * "country": "CN", * "path": "北京,北京,中國", * "timezone": "Asia/Shanghai", * "timezone_offset": "+08:00" * }, * "now": { * "text": "多雲", * "code": "4", * "temperature": "23" * }, * "last_update": "2017-09-13T09:51:00+08:00" * } * ] *} */ bool parseUserData(char* content, struct WeatherData* weatherData) { // -- 根據咱們須要解析的數據來計算JSON緩衝區最佳大小 // 若是你使用StaticJsonBuffer時才須要 // const size_t BUFFER_SIZE = 1024; // 在堆棧上分配一個臨時內存池 // StaticJsonBuffer<BUFFER_SIZE> jsonBuffer; // -- 若是堆棧的內存池太大,使用 DynamicJsonBuffer jsonBuffer 代替 DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.parseObject(content); if (!root.success()) { DebugPrintln("JSON parsing failed!"); return false; } //複製咱們感興趣的字符串 strcpy(weatherData->city, root["results"][0]["location"]["name"]); strcpy(weatherData->weather, root["results"][0]["now"]["text"]); strcpy(weatherData->temp, root["results"][0]["now"]["temperature"]); strcpy(weatherData->udate, root["results"][0]["last_update"]); // -- 這不是強制複製,你可使用指針,由於他們是指向「內容」緩衝區內,因此你須要確保 // 當你讀取字符串時它仍在內存中 return true; } // 打印從JSON中提取的數據 void printUserData(const struct WeatherData* weatherData) { DebugPrintln("Print parsed data :"); DebugPrint("City : "); DebugPrint(weatherData->city); DebugPrint(", \t"); DebugPrint("Weather : "); DebugPrint(weatherData->weather); DebugPrint(",\t"); DebugPrint("Temp : "); DebugPrint(weatherData->temp); DebugPrint(" C"); DebugPrint(",\t"); DebugPrint("Last Updata : "); DebugPrint(weatherData->udate); DebugPrintln("\r\n"); } // 關閉與HTTP服務器鏈接 void stopConnect() { DebugPrintln("Disconnect"); client.stop(); } void clrEsp8266ResponseBuffer(void){ memset(response, 0, MAX_CONTENT_SIZE); //清空 }
注意點:
測試結果:
注意點:
Tcpclient就介紹到這裏,博主只是帶領你們作簡單學習,深刻的理解還請自行查閱源碼;
接下來,博主開始介紹TCP Client的重要夥伴 —— Tcp Server。
如今,手機上網已是人們天天必不可少的事情。好比刷微博,刷朋友圈,刷新聞等等; 那麼這些朋友圈、微博、新聞內容都是從哪裏來的呢?作個App開發的同窗都應該知道,手機App屬於client端,屬於UI端,展現UI內容,而顯示什麼UI內容基本上都是發送一些http請求到後端服務(server),服務器根據具體的請求內容返回對應的響應內容。
所謂server,能夠簡單理解爲提供服務,提供數據的一個地方。
先來理解一下概念圖:
mobile phone做爲client端,經過路由熱點,向Server端的ESP8266請求數據,8266獲取到請求後解析請求而後返回響應數據。
可是,請開發者注意:ESP8266上創建一個server是比較簡單的,不過是屬於局域網內的server,由於真正意義上的server並非這樣的,大夥瞭解一個這樣的概念就好。
在ESP8266上創建TCP Server須要用到WiFiServer庫,WiFiServer庫也是屬於ESP8266WiFi庫裏面的一部分,主要是負責跟server有關的操做。
先來了解一下總體函數結構,博主總結了一波百度腦圖:
方法整體上能夠分爲三部分:
函數說明:
/** * 函數功能:建立TCP server * @param addr server的ip地址 * @param port server的端口 */ WiFiServer(IPAddress addr, uint16_t port); /** * 函數功能:建立TCP server * @param port server的端口 */ WiFiServer(uint16_t port);
函數說明:
/** * 函數功能:啓動TCP server */ void begin(); /** * 函數功能:啓動TCP server * @param port server端口號 */ void begin(uint16_t port);
注意點:
函數說明:
/** * 是否禁用 Nagle 算法。 * @param nodelay true表示禁用 Nagle 算法 */ void setNoDelay(bool nodelay);
注意點:
函數說明:
/** * 關閉TCP server */ void close();
函數說明:
/** * 中止TCP server */ void stop();
注意點:
void WiFiServer::stop() { close(); }
函數說明:
/** * 返回TCP server狀態 * @return wl_tcp_state tcp狀態 */ uint8_t status();
wl_tcp_state 包括:
//博主暫時沒理解具體每個怎麼用 enum wl_tcp_state { CLOSED = 0,// 關閉 LISTEN = 1,// 監聽中 SYN_SENT = 2, SYN_RCVD = 3, ESTABLISHED = 4,// 創建鏈接 FIN_WAIT_1 = 5, FIN_WAIT_2 = 6, CLOSE_WAIT = 7, CLOSING = 8, LAST_ACK = 9, TIME_WAIT = 10 };
函數說明:
/** * 獲取有效的wificlient鏈接 * @return 若是存在有效的wificlient鏈接,就返回WiFilient對象,若是沒有那就返回一個無效的wificlient(connected等於false,開發者能夠經過判斷connected() */ WiFiClient available(uint8_t* status = NULL);
函數源碼:
WiFiClient WiFiServer::available(byte* status) { (void) status; //判斷是否有非空的鏈接對象 if (_unclaimed) { WiFiClient result(_unclaimed); _unclaimed = _unclaimed->next(); result.setNoDelay(_noDelay); DEBUGV("WS:av\r\n"); return result; } optimistic_yield(1000); //沒有鏈接對象就返回無用的wificlient對象 return WiFiClient(); }
函數說明:
/** * 判斷是否有client鏈接 * @return bool 若是有client鏈接就返回true */ bool hasClient();
注意點:
前面講了這麼多理論內容,接下來用幾個例子來講明一下。
例子介紹:
8266做爲WiFiServer端,打開TCP調試助手,模擬TCP Client的請求。
例子源碼:
/** * Demo: * 演示WiFiServer功能 * 打開TCP調試助手 模擬TCP client請求 * @author 單片機菜鳥 * @date 2019/09/04 */ #include <ESP8266WiFi.h> //定義最多多少個client能夠鏈接本server(通常不要超過4個) #define MAX_SRV_CLIENTS 1 //如下三個定義爲調試定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) const char* ssid = "TP-LINK_5344"; const char* password = "6206908you11011010"; //建立server 端口號是23 WiFiServer server(23); //管理clients WiFiClient serverClients[MAX_SRV_CLIENTS]; void setup() { DebugBegin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); DebugPrint("\nConnecting to "); DebugPrintln(ssid); uint8_t i = 0; while (WiFi.status() != WL_CONNECTED && i++ < 20) { delay(500); } if (i == 21) { DebugPrint("Could not connect to"); DebugPrintln(ssid); while (1) { delay(500); } } //啓動server server.begin(); //關閉小包合併包功能,不會延時發送數據 server.setNoDelay(true); DebugPrint("Ready! Use 'telnet "); DebugPrint(WiFi.localIP()); DebugPrintln(" 23' to connect"); } void loop() { uint8_t i; //檢測是否有新的client請求進來 if (server.hasClient()) { for (i = 0; i < MAX_SRV_CLIENTS; i++) { //釋放舊無效或者斷開的client if (!serverClients[i] || !serverClients[i].connected()) { if (serverClients[i]) { serverClients[i].stop(); } //分配最新的client serverClients[i] = server.available(); DebugPrint("New client: "); DebugPrint(i); break; } } //當達到最大鏈接數 沒法釋放無效的client,須要拒絕鏈接 if (i == MAX_SRV_CLIENTS) { WiFiClient serverClient = server.available(); serverClient.stop(); DebugPrintln("Connection rejected "); } } //檢測client發過來的數據 for (i = 0; i < MAX_SRV_CLIENTS; i++) { if (serverClients[i] && serverClients[i].connected()) { if (serverClients[i].available()) { //get data from the telnet client and push it to the UART while (serverClients[i].available()) { //發送到串口調試器 Serial.write(serverClients[i].read()); } } } } if (Serial.available()) { //把串口調試器發過來的數據 發送給client size_t len = Serial.available(); uint8_t sbuf[len]; Serial.readBytes(sbuf, len); //push UART data to all connected telnet clients for (i = 0; i < MAX_SRV_CLIENTS; i++) { if (serverClients[i] && serverClients[i].connected()) { serverClients[i].write(sbuf, len); delay(1); } } } }
測試結果:
例子介紹:
8266做爲web server端,打開PC瀏覽器輸入IP地址,請求web server。
例子源碼:
/** * Demo: * 演示web Server功能 * 打開PC瀏覽器 輸入IP地址。請求web server * @author 單片機菜鳥 * @date 2019/09/05 */ #include <ESP8266WiFi.h> const char* ssid = "TP-LINK_5344";//wifi帳號 這裏須要修改 const char* password = "xxxx";//wifi密碼 這裏須要修改 //建立 tcp server 端口號是80 WiFiServer server(80); void setup(){ Serial.begin(115200); Serial.println(); Serial.printf("Connecting to %s ", ssid); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED){ delay(500); Serial.print("."); } Serial.println(" connected"); //啓動TCP 鏈接 server.begin(); //打印TCP server IP地址 Serial.printf("Web server started, open %s in a web browser\n", WiFi.localIP().toString().c_str()); } /** * 模擬web server 返回http web響應內容 * 這裏是手動拼接HTTP響應內容 * 後面樓主會繼續講解另外兩個專用於http請求的庫 */ String prepareHtmlPage(){ String htmlPage = String("HTTP/1.1 200 OK\r\n") + "Content-Type: text/html\r\n" + "Connection: close\r\n" + // the connection will be closed after completion of the response "Refresh: 5\r\n" + // refresh the page automatically every 5 sec "\r\n" + "<!DOCTYPE HTML>" + "<html>" + "Analog input: " + String(analogRead(A0)) + "</html>" + "\r\n"; return htmlPage; } void loop(){ WiFiClient client = server.available(); // wait for a client (web browser) to connect if (client){ Serial.println("\n[Client connected]"); while (client.connected()){ // 不斷讀取請求內容 if (client.available()){ String line = client.readStringUntil('\r'); Serial.print(line); // wait for end of client's request, that is marked with an empty line if (line.length() == 1 && line[0] == '\n'){ //返回響應內容 client.println(prepareHtmlPage()); break; } } //因爲咱們設置了 Connection: close 當咱們響應數據以後就會自動斷開鏈接 } delay(100); // give the web browser time to receive the data // close the connection: client.stop(); Serial.println("[Client disonnected]"); } }
測試結果:
例子介紹:
8266做爲WiFiServer端,演示簡單web Server功能,webserver會根據請求來作不一樣的操做。
例子源碼:
/* * Demo: * 演示簡單web Server功能 * web server會根據請求來作不一樣的操做 * http://server_ip/gpio/0 打印 /gpio0 * http://server_ip/gpio/1 打印 /gpio1 * server_ip就是ESP8266的Ip地址 * @author 單片機菜鳥 * @date 2019/09/05 */ #include <ESP8266WiFi.h> //如下三個定義爲調試定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) const char* ssid = "TP-LINK_5344";//wifi帳號 這裏須要修改 const char* password = "xxxx";//wifi密碼 這裏須要修改 // 建立tcp server WiFiServer server(80); void setup() { DebugBegin(115200); delay(10); // Connect to WiFi network DebugPrintln(""); DebugPrintln(String("Connecting to ") + ssid); //我只想作個安靜的美男子 STA WiFi.mode(WIFI_STA); //我想鏈接路由wifi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); DebugPrint("."); } DebugPrintln(""); DebugPrintln("WiFi connected"); // 啓動server server.begin(); DebugPrintln("Server started"); // 打印IP地址 DebugPrintln(WiFi.localIP().toString()); } void loop() { // 等待有效的tcp鏈接 WiFiClient client = server.available(); if (!client) { return; } DebugPrintln("new client"); //等待client數據過來 while (!client.available()) { delay(1); } // 讀取請求的第一行 會包括一個url,這裏只處理url String req = client.readStringUntil('\r'); DebugPrintln(req); //清掉緩衝區數據 聽說這個方法沒什麼用 能夠換種實現方式 client.flush(); // 開始匹配 int val; if (req.indexOf("/gpio/0") != -1) { DebugPrintln("/gpio0"); val = 0; } else if (req.indexOf("/gpio/1") != -1) { DebugPrintln("/gpio1"); val = 1; } else { DebugPrintln("invalid request"); //關閉這個client請求 client.stop(); return; } //清掉緩衝區數據 client.flush(); // 準備響應數據 String s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nGPIO is now "; s += (val) ? "high" : "low"; s += "</html>\n"; // 發送響應數據給client client.print(s); delay(1); DebugPrintln("Client disonnected"); // The client will actually be disconnected // when the function returns and 'client' object is detroyed }
測試結果:
這一篇章,博主主要講了TCP通訊的兩大角色——client和server。你們須要區分tcp http。而且也要區分工做模式和client server不是一個概念,二者沒有必然的聯繫。這篇算是入門http請求的重點內容,但願讀者能夠仔細研讀,並結合源碼去理解。