纳尼?昨天刚刚打印了个“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的神奇物联网之旅
唯有爱与科技不可辜负。