納尼?昨天剛剛打印了個“Hello World!”,今天你就讓我學習TCP通信?有沒有搞錯~哈哈,相信很多讀者會很迷,其實學習這東西嘛,單單學一些比較簡單的,相信沒兩天就沒人看了,所以咱就在基礎篇和網絡篇穿插着去學習一下ESP8266,畢竟興趣才是最好的老師嘛!大家以后遇到問題了,來翻文章建議大家根據[XX篇]去快速定位該去哪一篇文章中去查找問題,當然具體會在哪一篇文章中有講,也不一定了,后面也會穿插着寫一點[項目篇][進階篇][閑扯篇],總的來說就是,本系列文章並沒有固定的路線,大家不如忍忍?當然這里寫的文章也不一定是對的,大家在實際測試中如果遇到了問題,還請私信我,或者在Github上提交issue!
閑話少說,既然要學習TCP通信的知識,那么我們得先要了解一下TCP到底是個什么東西(玩意),正所謂孫子曾經曰過“知彼知己,百戰不殆”,那我們先補一些關於TCP的知識吧!
TCP基礎知識:
- 是什么?
TCP(Transmission Control Protocol傳輸控制協議)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。(百度詞條搜到的~)
說白了就是一種傳輸協議,就像我們串口、232、485等一樣的通訊協議,只不過TCP多用於網絡間的通訊,不過TCP是在IP層之上的,相信你肯定見過或者聽說過TCP/IP協議,沒錯,這哥倆是綁在一塊的,TCP是依賴IP協議的。
- 通訊機制
TCP與我們常使用的串口等通訊協議有什么不同呢?首先是兩台主機建立TCP連接機制是比較麻煩的,要經過三次握手協議,才能建立連接,過程用漢字描述如下:
- 客戶端發送SYN(SEQ=x)報文給服務器端,進入SYN_SEND狀態。
- 服務器端收到SYN報文,回應一個SYN (SEQ=y)ACK(ACK=x+1)報文,進入SYN_RECV狀態。
- 客戶端收到服務器端的SYN報文,回應一個ACK(ACK=y+1)報文,進入Established狀態。
看字比較麻煩,我們還是看圖吧!
既然有建立連接,那么肯定也有斷開連接,但是沒想到斷開連接比建立連接還要麻煩,還有四次揮手協議,納尼?是的,斷開連接需要經過四次揮手協議,直接看圖吧:
- 可靠性
上面說的通訊機制之所以這么麻煩,主要還是為了傳輸的可靠性,因為TCP是在整個網絡中去傳輸,只要你有IP地址,並且在廣域網中訪問,那我們就可以建立TCP連接,說會悄悄話~所以,兩者之間的可靠性就顯得很重要了,客戶端與服務端之間的連接可靠性靠很多策略去實現的比如心跳包機制,這里就不再詳細敘述了,有點跑題了,這是ESP8266學習筆記~
- 數據包格式
TCP的數據包相對來說是比較麻煩的,確認信息占據了整個數據包的很大一部分,設計的這么復雜主要原因還是保證在整個網絡當中每包數據傳輸的正確性,這里我們就看一下數據包到底有多么復雜:
- ESP8266作為TCP Client跟Server(PC)通信
了解了一點點基礎知識后,我們還是要落實到實踐上,下面我們正式開始,首先我們需要先看一下官方SDK編程指導手冊,還沒有?不知道去哪里下?好嘞,直接戳下面卡片下載好了~
英文版戳這里,如果你英語跟小編一樣好的話,建議看英文版,要不斷提升自己的英語~
咳咳咳,那我們先打開這個英文文檔,找到97頁的TCP/UDP接口,我們先看一下接口分了幾類:
咳咳咳,不要懷疑,使用的PDF查看軟件有自動翻譯動能,手動滑稽.jpg,可以看到一共分了4類,本篇文章我們先研究下通用 API和TCP API,關於UDP和mDNS的我們再后面的文章中再繼續學習,你可以先大體看一下,每個API的注釋都講的很明白,我們不妨直接copy點代碼直接跑一下?
1 git clone git@github.com:imliubo/makingfunxyz-esp8266.git 2 3 #如果之前有clong過,可以直接 git pull,不會用?那肯定是沒好好看廖雪峰的git教程
下載完成后,打開ESP_IDE導入在makingfunxyz-esp8266-NONOS文件中的6.TCP_UDP_Server_Client這個工程,然后先修改一點東西,這一步很重要,請不要忽略:
Wi-Fi名稱跟密碼相信你肯定知道,TCP_SERVER_IP應該要修改成什么呢?就是你電腦的IP地址,這里每個人的都不一樣,建議你先去查看一下,方法看視頻~
視頻還在找托管方~
可以看到我這里是192.168.0.109,那我就將它修改成192.168.0.109,這三個地方都修改好了后,我們直接先編譯代碼,先跑一下看看!編譯好代碼后,下載到ESP8266,這里跟上篇文章中是一樣的,就不再詳述,下載地址參考:
- eagle.flash.bin-------->0x00000
- eagle.irom0text.bin---->0x10000
- esp_init_data_default_v08.bin --> 0x3FC000
- blank.bin --> 0x3FE000
下載完成后,我們先不要急着去看結果,因為我們還沒有開啟TCP Server,那我們該怎么開啟TCP Server呢?我這里用Python寫了一個很簡單的Server端程序,大家可以使用我這個,要是使用別的TCP調試助手,注意TCP_SERVER_PORT也要修改一下,運行我們的Server很簡單,但是你需要安裝一下Python,我這里沒有打包成可執行文件,先去安裝一下Python吧,我用的是Python3,建議大家也安裝Python3,因為Python2跟Python3有些語法不一樣,戳卡片下載:
下載安裝完成后,開始搞起來,我們需要在6.TCP_UDP_Server_Client這個文件夾下找到TCP_Server.py這個文件夾,然后打開修改一下IP地址,將IP地址修改成上面我們查看的本機地址,看圖:
最后我們還需要設置一下開放端口,這里步驟有點多,就不再細述了,大家可以直接看這個百度知道,寫的很詳細:
然后打開命令窗口,具體操作辦法,在此文件下按住shift鍵,然后右鍵選擇打開powershell窗口(WIN7 應該是cmd窗口),然后:
1 start cmd 2 #可以直接在powershell里面執行,但是我還是比較喜歡cmd窗口,所以就~win7不用,因為打開的就是cmd窗口 3 #cmd窗口打開后,輸入以下命令 4 python TCP_Server.py
視頻還在找托管方~
- ESP8266作為TCP Server跟Client(PC)通信
上面是ESP8266作為Client去跟Server通信,但是ESP8266不僅可以作為Client還可以作為Server等待Client去建立連接去通信,這里我們修改幾個地方,就可以將ESP8266作為Server去跟Client通信了,我在源碼中已經都寫好了,這里我們將同樣將ESP8266的6666作為PC去連接的端口號:
其中TCP_Client.py文件中的IP地址需要在ESP8266上電打印后修改一下,我們將上面小節中的tcp_client_init()注釋掉,tcp_server_init()取消注釋,然后重新編譯代碼下載就好了,PC上的Client程序跟Server程序運行一樣,這里我們直接看一下視頻吧!
視頻還在找托管方~
這里代碼就不再解釋了,我寫的注釋還算全,大家一看就懂,主要代碼:
1 /**************************** 2 * TCP CLIENT FUNCTIONS * 3 ****************************/ 4 5 /********************************** 6 * TCP CLIENT STATIC PROTOTYPES * 7 **********************************/ 8 static void tcp_client_sent_cb(void *arg); 9 static void tcp_client_recv_cb(void *arg,char *pdata,unsigned short length); 10 static void tcp_client_recon_cb(void *arg,sint8 error); 11 static void tcp_client_discon_cb(void *arg); 12 static void tcp_client_connect_cb(void *arg); 13 14 /********************************** 15 * TCP CLIENT STATIC VARIABLES * 16 **********************************/ 17 os_timer_t tcp_client_send_data_timer; 18 struct espconn tcp_client; 19 uint8 i; 20 21 /********************************** 22 * TCP CLIENT STATIC FUNCTIONS * 23 **********************************/ 24 25 /** 26 * TCP Client數據發送回調函數 27 */ 28 static void ICACHE_FLASH_ATTR 29 tcp_client_sent_cb(void *arg){ 30 os_printf("tcp client send data successful\r\n"); 31 } 32 33 /** 34 * TCP Client數據接收回調函數,可以在這處理收到Server發來的數據 35 */ 36 static void ICACHE_FLASH_ATTR 37 tcp_client_recv_cb(void *arg,char *pdata,unsigned short len){ 38 os_printf("tcp client receive tcp server data\r\n"); 39 os_printf("length: %d \r\ndata: %s\r\n",len,pdata); 40 41 //TO DO 42 43 /** 44 *process the receive data 45 */ 46 } 47 48 /** 49 * TCP Client重連回調函數,可以在此函數里做重連接處理 50 */ 51 static void ICACHE_FLASH_ATTR 52 tcp_client_recon_cb(void *arg,sint8 error){ 53 os_printf("tcp client connect tcp server error %d\r\n",error); 54 os_timer_disarm(&tcp_client_send_data_timer);//取消定時發送數據定時器 55 } 56 57 /** 58 * TCP Client斷開連接回調函數 59 */ 60 static void ICACHE_FLASH_ATTR 61 tcp_client_discon_cb(void *arg){ 62 os_printf("tcp client disconnect tcp server successful\r\n"); 63 os_timer_disarm(&tcp_client_send_data_timer); 64 } 65 66 /** 67 * TCP Client連接成功回調函數 68 */ 69 static void ICACHE_FLASH_ATTR 70 tcp_client_connect_cb(void *arg){ 71 struct espconn *pespconn = arg; 72 73 os_printf("tcp client connect tcp server successful\r\n"); 74 espconn_regist_recvcb(pespconn,tcp_client_recv_cb);//注冊接收數據回調函數 75 espconn_regist_sentcb(pespconn,tcp_client_sent_cb);//注冊數據發送完成回調函數 76 espconn_regist_disconcb(pespconn,tcp_client_discon_cb);//注冊斷開連接回調函數 77 78 os_timer_disarm(&tcp_client_send_data_timer); 79 os_timer_setfn(&tcp_client_send_data_timer, (os_timer_func_t *) tcp_client_send_data,NULL);//注冊Client定時發送數據回調函數 80 os_timer_arm(&tcp_client_send_data_timer, 1000, true);//時間設置為1s 81 } 82 83 /********************************** 84 * TCP CLIENT GLOBAL FUNCTIONS * 85 **********************************/ 86 /** 87 * TCP Client定時發送數據回調函數 88 */ 89 void ICACHE_FLASH_ATTR 90 tcp_client_send_data(void){ 91 char buf[256],length; 92 os_printf("tcp client send data tcp server\r\n"); 93 length = os_sprintf(buf,(char *)"Hi this is ESP8266 client! message num %d",i); 94 i++; 95 espconn_sent(&tcp_client,buf,length); 96 } 97 98 /** 99 * TCP Client初始化函數 100 * @remote_ip 要連接的TCP Server IP地址 101 * @local_ip ESP8266 IP地址 102 * @remote_port 要連接的TCP Server端口號 103 */ 104 void ICACHE_FLASH_ATTR 105 tcp_client_init(uint8 *remote_ip,struct ip_addr *local_ip, int remote_port){ 106 107 uint32 server_ip = ipaddr_addr(remote_ip); 108 109 os_printf("tcp client connect to tcp server\r\n"); 110 tcp_client.proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); 111 tcp_client.type = ESPCONN_TCP; 112 113 os_memcpy(tcp_client.proto.tcp->remote_ip,&server_ip,4);//設置要連接的Server IP地址 114 tcp_client.proto.tcp->remote_port = remote_port;//設置要連接的Server 端口號 115 os_memcpy(tcp_client.proto.tcp->local_ip,local_ip,4);//設置本地IP地址 116 tcp_client.proto.tcp->local_port = espconn_port();//設置本地端口號 117 118 espconn_regist_connectcb(&tcp_client,tcp_client_connect_cb);//注冊連接成功回調函數 119 espconn_regist_reconcb(&tcp_client,tcp_client_recon_cb);//注冊斷連重新連接回調函數 120 121 espconn_connect(&tcp_client);//Client連接Server 122 } 123 124 125 126 /**************************** 127 * TCP SERVER FUNCTIONS * 128 ****************************/ 129 /********************************** 130 * TCP SERVER STATIC PROTOTYPES * 131 **********************************/ 132 static void tcp_server_sent_cb(void *arg); 133 static void tcp_server_recv_cb(void *arg,char *pdata,unsigned short length); 134 static void tcp_server_recon_cb(void *arg,sint8 error); 135 static void tcp_server_discon_cb(void *arg); 136 static void tcp_server_listen_cb(void *arg); 137 138 /********************************** 139 * TCP SERVER STATIC VARIABLES * 140 **********************************/ 141 os_timer_t tcp_server_send_data_timer; 142 struct espconn tcp_server; 143 uint8 z; 144 145 /********************************** 146 * TCP server STATIC FUNCTIONS * 147 **********************************/ 148 149 /** 150 * TCP Server數據發送回調函數 151 */ 152 static void ICACHE_FLASH_ATTR 153 tcp_server_sent_cb(void *arg){ 154 os_printf("tcp server send data successful\r\n"); 155 156 } 157 158 /** 159 * TCP Server數據接收回調函數,可以在這處理收到Client發來的數據 160 */ 161 static void ICACHE_FLASH_ATTR 162 tcp_server_recv_cb(void *arg,char *pdata,unsigned short len){ 163 os_printf("tcp server receive tcp client data\r\n"); 164 os_printf("length: %d \r\ndata: %s\r\n",len,pdata); 165 166 //TO DO 167 168 /** 169 *process the receive data 170 */ 171 } 172 173 /** 174 * TCP Server重連回調函數,可以在此函數里做重連接處理 175 */ 176 static void ICACHE_FLASH_ATTR 177 tcp_server_recon_cb(void *arg,sint8 error){ 178 os_printf("tcp server connect tcp client error %d\r\n",error); 179 os_timer_disarm(&tcp_server_send_data_timer); 180 } 181 182 /** 183 * TCP Server斷開連接回調函數 184 */ 185 static void ICACHE_FLASH_ATTR 186 tcp_server_discon_cb(void *arg){ 187 os_printf("tcp server disconnect tcp client successful\r\n"); 188 os_timer_disarm(&tcp_server_send_data_timer); 189 } 190 191 /** 192 * TCP Server監聽Client連接回調函數 193 */ 194 static void ICACHE_FLASH_ATTR 195 tcp_server_listen_cb(void *arg){ 196 struct espconn *pespconn = arg; 197 198 os_printf("tcp server have tcp client connect\r\n"); 199 espconn_regist_recvcb(pespconn,tcp_server_recv_cb);//注冊收到數據回調函數 200 espconn_regist_sentcb(pespconn,tcp_server_sent_cb);//注冊發送完數據回調函數 201 espconn_regist_disconcb(pespconn,tcp_server_discon_cb);//注冊斷開連接回調函數 202 203 os_timer_disarm(&tcp_server_send_data_timer); 204 os_timer_setfn(&tcp_server_send_data_timer, (os_timer_func_t *) tcp_server_send_data,NULL);//注冊Server定時發送數據回調函數 205 os_timer_arm(&tcp_server_send_data_timer, 1000, true);//設置時間為1s 206 } 207 208 /********************************** 209 * TCP CLIENT GLOBAL FUNCTIONS * 210 **********************************/ 211 212 /** 213 * TCP Server定時發送數據回調函數 214 */ 215 void ICACHE_FLASH_ATTR 216 tcp_server_send_data(void){ 217 char buf[256],length; 218 os_printf("tcp server send data tcp client\r\n"); 219 length = os_sprintf(buf,(char *)"Hi this is ESP8266 server! message num %d",z); 220 z++; 221 espconn_send(&tcp_server,buf,length); 222 } 223 224 /** 225 * TCP Server初始化函數 226 * @local_port 本地監聽端口號,與Client連接的端口號一致 227 */ 228 void ICACHE_FLASH_ATTR 229 tcp_server_init(uint16 local_port){ 230 231 os_printf("tcp server waiting tcp client connect!\r\n"); 232 tcp_server.proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); 233 tcp_server.type = ESPCONN_TCP; 234 235 tcp_server.proto.tcp->local_port = local_port;//設置本地監聽的端口號,等待Client連接 236 237 espconn_regist_connectcb(&tcp_server,tcp_server_listen_cb);//注冊Server監聽回調函數 238 espconn_regist_reconcb(&tcp_server,tcp_server_recon_cb);//注冊斷連重新連接回調函數 239 240 espconn_accept(&tcp_server);//創建Server,開始監聽 241 espconn_regist_time(&tcp_server,360,0);//超時斷開連接時間 242 }
user_main.c中的主要代碼:
1 os_timer_t wifistate_checktimer; 2 void ICACHE_FLASH_ATTR 3 WifiStatus_Check(void){ 4 uint8 wifiStatus; 5 wifiStatus = wifi_station_get_connect_status(); 6 if (wifiStatus == STATION_GOT_IP) { 7 os_printf("WiFi connection is successful!\r\n"); 8 os_timer_disarm(&wifistate_checktimer); 9 struct ip_info local_ip; 10 wifi_get_ip_info(STATION_IF,&local_ip); 11 tcp_client_init(TCP_SERVER_IP,&local_ip.ip,TCP_SERVER_PORT);//TCP Client初始化,Client與Server只能二選一 12 // tcp_server_init(TCP_LOCAL_PORT);//TCP Server初始化,Client與Server只能二選一 13 }else{ 14 os_printf("WiFi connection failed!\r\n"); 15 } 16 } 17 18 /** 19 * Wi-Fi連接回調函數 20 */ 21 void ICACHE_FLASH_ATTR 22 wifiConnectCb(uint8_t status){ 23 24 os_timer_disarm(&wifistate_checktimer); 25 os_timer_setfn(&wifistate_checktimer, (os_timer_func_t *) WifiStatus_Check,NULL); 26 os_timer_arm(&wifistate_checktimer, 2000, true); 27 } 28 29 void ICACHE_FLASH_ATTR 30 user_init(void) 31 { 32 os_printf("\nHello World! ZHIHU IAMLIUBO\n\n"); 33 34 wifi_set_opmode(0x01); 35 36 WIFI_Connect(STA_SSID, STA_PASS, wifiConnectCb);// 37 }
最后附上我的ESP8266倉庫,后面代碼會全部在此倉庫更新,歡迎小伙伴們Star~
本系列文章在知乎同步更新,知乎搜索專欄:IAMLIUBO的神奇物聯網之旅