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