上一節(jié)說了UDP,這一節(jié)就聊聊TCP,畢竟它倆經(jīng)常同時出現(xiàn)。優(yōu)缺點上一節(jié)也提了一下:安全性好,速度慢。
除了這兩點,還有就是:
TCP通信之前是需要建立連接的,如同打電話之前先撥號一樣,而UDP無連接;
TCP只能一對一通信,UDP不止一對一,還支持一對多;
TCP對系統(tǒng)資源要求更多,UDP相對少一些。
所以兩者各有優(yōu)缺點,大家在選擇通信協(xié)議的時候一定要根據(jù)自己的實際情況來確定。
然后就是客戶端,這是啥?和它伴隨的,還有一個詞經(jīng)常出現(xiàn),就是:服務(wù)器端。這兩者又是什么關(guān)系?
基本上,這兩者在TCP通信過程中,都是結(jié)伴出現(xiàn)的。以瀏覽器為例,它就是一個客戶端,當我們想上網(wǎng)的時候,輸入一個網(wǎng)址。瀏覽器會根據(jù)我們輸入的網(wǎng)址向相應的服務(wù)器端發(fā)出請求,然后服務(wù)器端返回相應的網(wǎng)頁給瀏覽器。這就是它們的應用場景之一。
所以,在TCP通信過程中,一般都是由客戶端發(fā)起請求,服務(wù)器端相應請求。
那么,在windows/linux下,是如何進行這方面的編程的?步驟如下(參考百度):
TCP編程的服務(wù)器端一般步驟是:
1、創(chuàng)建一個socket,用函數(shù)socket();
2、設(shè)置socket屬性,用函數(shù)setsockopt(); * 可選
3、綁定IP地址、端口等信息到socket上,用函數(shù)bind();
4、開啟監(jiān)聽,用函數(shù)listen();
5、接收客戶端上來的連接,用函數(shù)accept();
6、收發(fā)數(shù)據(jù),用函數(shù)send()和recv(),或者read()和write();
7、關(guān)閉網(wǎng)絡(luò)連接;
8、關(guān)閉監(jiān)聽;
TCP編程的客戶端一般步驟是:
1、創(chuàng)建一個socket,用函數(shù)socket();
2、設(shè)置socket屬性,用函數(shù)setsockopt();* 可選
3、綁定IP地址、端口等信息到socket上,用函數(shù)bind();* 可選
4、設(shè)置要連接的對方的IP地址和端口等屬性;
5、連接服務(wù)器,用函數(shù)connect();
6、收發(fā)數(shù)據(jù),用函數(shù)send()和recv(),或者read()和write();
7、關(guān)閉網(wǎng)絡(luò)連接;
可以看到,從第四步開始,客戶端和服務(wù)器端的工作內(nèi)容有了不一樣。
socket是啥?
簡單來說,是個函數(shù),用來創(chuàng)建套接字。
那么,套接字又是什么鬼?
為了防止本篇日志閑扯太多,我盡量簡單說,根據(jù)字面意思,socket字面意思是(電源)插座,而套接字本質(zhì)上是一種網(wǎng)絡(luò)編程接口,用來完成兩個應用程序之間的數(shù)據(jù)傳輸。你把設(shè)備插頭插到電源插座上,設(shè)備通上電了,同理,你把應用程序的端口插到socket里,數(shù)據(jù)就通上了。
注:因為本人是硬件出身,所以對這些協(xié)議的理解全靠百度和工作積累,如果有表達錯誤的,歡迎指正。
這一節(jié)先在8266上面寫一個客戶端的程序,咱們看一下實現(xiàn)的效果。跟上一節(jié)一樣,還是要借助一個網(wǎng)絡(luò)調(diào)試助手。同時,本節(jié)代碼都是在上一節(jié)基礎(chǔ)上修改來的,所以,如果上一節(jié)看懂了,這一節(jié)就很好理解,反之,你懂的~
先說步驟,依然很多,參考上面提到的“TCP編程的客戶端一般步驟”,這里大致分為7步:
1 包含頭文件
#include "espconn.h"
#include "mem.h"
主要是"espconn.h",涉及到TCP通訊所需的各種數(shù)據(jù)結(jié)構(gòu)。上一節(jié)已經(jīng)包含了,可以略過。
2 設(shè)置工作模式為station+ soft-ap模式,連接到當前環(huán)境下的wifi
因為前幾步跟上一節(jié)一模一樣,所以整合了一下:
wifi_set_opmode(0x03); // station+ soft-ap模式
struct softap_config config; //定義AP參數(shù)結(jié)構(gòu)體,
wifi_softap_get_config(&config); //獲取當前AP模式的參數(shù)
os_memcpy(config.ssid,"ESP8266",strlen("ESP8266"));
//修改AP名稱
os_memcpy(config.password,"123456789",strlen("123456789"));
//修改AP密碼
config.ssid_len=strlen("ESP8266"); //修改 AP名稱的長度
wifi_softap_set_config(&config); //使修改后的參數(shù)生效
然后連到我家wifi,你們要改成你們家里或者辦公室里的wifi。
3 確定TCP連接的參數(shù)
這里要確定幾點:你是誰?你要和誰連接?連接的端口是多少?
你是誰——ESP8266,8266連接家里路由成功之后,會自動獲得一個IP,這是客戶端IP
你要和誰連接——因為是在我電腦上使用網(wǎng)絡(luò)調(diào)試助手模擬TCP服務(wù)器端,所以服務(wù)器端的IP是我電腦的IP:192.168.1.103
連接的端口——長話短說,TCP連接的端口從0到65535都有,但一般0~1023是公有的,從1024開始往后,可以選為自己的端口。這里選1024.
struct ip_info info;
const char remote_ip[4]={192,168,1,103}; //TCP服務(wù)端IP
wifi_get_ip_info(STATION_IF,&info); //獲取8266的WIFI信息
tcp_client_init((struct ip_addr *)remote_ip,&info.ip,1024);
4 TCP客戶端初始化
其實就是第三步里面的tcp_client_init函數(shù),咱們主要看一下函數(shù)內(nèi)部實現(xiàn)的功能。首先是在client.h文件中定義了一個espconn格式的結(jié)構(gòu)體:
struct espconn user_tcp_conn; //對應網(wǎng)絡(luò)連接的結(jié)構(gòu)體
然后在tcp_client_init函數(shù)中,對結(jié)構(gòu)體的各個部分進行配置:
{
//TCP通信時,對應的espconn參數(shù)配置
user_tcp_conn.type=ESPCONN_TCP;
user_tcp_conn.state=ESPCONN_NONE;
user_tcp_conn.proto.tcp=(esp_tcp *)os_zalloc(sizeof(esp_tcp));
os_memcpy(user_tcp_conn.proto.tcp->local_ip,local_ip,4);
os_memcpy(user_tcp_conn.proto.tcp->remote_ip,remote_ip,4);
user_tcp_conn.proto.tcp->local_port=espconn_port();
user_tcp_conn.proto.tcp->remote_port=remote_port;
//注冊連接回調(diào)函數(shù)和重連回調(diào)函數(shù)
espconn_regist_connectcb(&user_tcp_conn,user_tcp_connect_cb);
espconn_regist_reconcb(&user_tcp_conn,user_tcp_recon_cb);
//啟用連接
espconn_connect(&user_tcp_conn);
}
函數(shù)中可以看到,espconn的參數(shù)設(shè)置完成之后,注冊了兩個回調(diào)函數(shù),連接完成回調(diào)函數(shù)(連接完成以后,你想干嘛?)和重連回調(diào)函數(shù)(重連的時候,該咋辦?):
espconn_regist_connectcb(&user_tcp_conn,user_tcp_connect_cb);
espconn_regist_reconcb(&user_tcp_conn,user_tcp_recon_cb);
最后,開始TCP連接:
espconn_connect(&user_tcp_conn); //連接TCP server,連接成功返回0.
5 定義連接成功的回調(diào)函數(shù)
void ICACHE_FLASH_ATTR user_tcp_connect_cb(void *arg){
struct espconn *pespconn=arg;
espconn_regist_recvcb(pespconn,user_tcp_recv_cb);
espconn_regist_sentcb(pespconn,user_tcp_sent_cb);
espconn_regist_disconcb(pespconn,user_tcp_discon_cb);
espconn_sent(pespconn,"hello,this is esp8266!",strlen("hello,this is esp8266!"));
}
函數(shù)內(nèi)部進行了幾個操作:
注冊接收完成的回調(diào)函數(shù):接收完成以后,你想做點啥~
注冊發(fā)送完成的回調(diào)函數(shù):發(fā)送完成以后,你想做點啥~
注冊斷開TCP連接的回調(diào)函數(shù):斷開TCP連接以后,你想做點啥~
TCP連接下,發(fā)送數(shù)據(jù):hello,this is esp8266!
6 定義user_tcp_connect_cb函數(shù)內(nèi)部注冊的回調(diào)函數(shù)
這里先說一下,基本上從上一節(jié)開始,代碼的編寫就進入了回調(diào)函數(shù)套回調(diào)函數(shù)的情形。如果是沒接觸過回調(diào)函數(shù)的,剛開始看肯定有些別扭。但如果適應以后,你會發(fā)現(xiàn)這樣操作還是很方便的。
因為每個回調(diào)函數(shù),在手冊里都有說明,功能、參數(shù)、返回值,都很清晰。基本上只要看著手冊和官方SDK里的例程,大部分問題都能解決。
//接收完成回調(diào)函數(shù),把收到的數(shù)據(jù)打印出來,延時,斷開連接
void ICACHE_FLASH_ATTR user_tcp_recv_cb(void *arg,
char *pdata,
unsigned short len){
os_printf("receive data:%s ",pdata);
os_delay_us(300);
espconn_disconnect((struct espconn *)arg);
}
//發(fā)送完成回調(diào)函數(shù),打印發(fā)送完成標志
void ICACHE_FLASH_ATTR user_tcp_sent_cb(void *arg){
os_printf("send success!");
}
//斷開TCP連接的回調(diào)函數(shù),打印相關(guān)信息
void ICACHE_FLASH_ATTR user_tcp_discon_cb(void *arg){
os_printf("disconnect success!");
}
7 定義TCP重連的回調(diào)函數(shù)
在第4步里注冊了兩個回調(diào)函數(shù),一個是連接成功的回調(diào)函數(shù),第5步已經(jīng)說了。另一個就是重連的回調(diào)函數(shù):
//如果連接錯誤,打印一下故障碼,然后重新連接
void ICACHE_FLASH_ATTR user_tcp_recon_cb(void *arg, sint8 err){
os_printf("error,error code is%d ",err);
espconn_connect((struct espconn *)arg);
}
好,到此為止,程序修改完畢。
注:本例程里提到的大部分函數(shù),都參照手冊2c-esp8266_sdk_api_guide_cn_v1.5.4。
再說一點,很多函數(shù)定義的時候,后面會跟一個參數(shù):void *arg,這是什么?
以第5步里注冊回調(diào)函數(shù)為例:
espconn_regist_recvcb(pespconn,user_tcp_recv_cb);
注冊了user_tcp_recv_cb函數(shù),這個函數(shù)在定義的時候就有如下幾個參數(shù)
(void *arg,char *pdata,unsigned short len)
從哪來的?
打開手冊2c-esp8266_sdk_api_guide_cn_v1.5.4,查找espconn_regist_recvcb函數(shù),可以看到如下說明;
根據(jù)其中的espconn_recv_callback,咱們繼續(xù)向下找:
至此,可以看到相關(guān)參數(shù)已經(jīng)在回調(diào)函數(shù)的格式里定義好了,咱們只需要照著寫就行。
程序修改完成,保存、清理、編譯、下載一條龍,然后重新上電。這里,需要借助串口助手和網(wǎng)絡(luò)調(diào)試助手兩個工具來查看效果。效果如下所示:
設(shè)置網(wǎng)絡(luò)調(diào)試助手:
可以看到,在網(wǎng)絡(luò)調(diào)試助手上,分別顯示了client上線的時間和發(fā)來的數(shù)據(jù)。如果這時候咱們手動給client發(fā)一個數(shù)據(jù):mcu lover。
可以在串口助手上看到:
顯示了收到的數(shù)據(jù),最后斷開TCP連接。
至此,TCP客戶端通信說完了。還是希望大家多動手,畢竟這類東西要動手才有收獲。后面會說一下TCP服務(wù)器端的用法(跟這個差不多),然后是POST和GET的用法,再然后,就可以根據(jù)GET,搞一個天氣預報的小應用,相信很多人會比較感興趣。
最后嘮叨一下,這篇日志寫的比較痛苦,因為我是從一個硬件工程師的視角去說這些東西,所以希望跟我類似的人能比較好的理解日志中出現(xiàn)的這些網(wǎng)絡(luò)協(xié)議。因為通常來說,搞硬件的就是搞硬件,畫畫PCB、搞搞焊接、給單片機寫寫程序,甚至再寫一寫上位機,在linux下寫一些應用。至于說整天研究TCP/UDP,或者POST、GET,比較少,或者說不算硬件工程師/單片機工程師的范疇了。
但是物聯(lián)網(wǎng)芯片的出現(xiàn)打破了這一屏障,它小巧,單片機級別的資源就能使用;但它又強大,可以聯(lián)網(wǎng),實現(xiàn)各種網(wǎng)絡(luò)通信。所以,我們要不停的學習,千萬不要自我滿足。
-
TCP
+關(guān)注
關(guān)注
8文章
1353瀏覽量
79074 -
TCP通信
+關(guān)注
關(guān)注
0文章
146瀏覽量
4223
原文標題:ESP8266_12 ESP8266客戶端模式下的TCP通信
文章出處:【微信號:gh_dae0718828df,微信公眾號:gh_dae0718828df】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論