前面介紹了基于Socket方式的以太網通訊,接下來給大家介紹基于TCP包的通訊。內容分為基于MM32F3270以太網Client的使用與基于MM32F3270以太網Server的使用。
首先,對TCP有個簡單的介紹:
TCP是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。即客戶端和服務器之間在交換數據之前會先建立一個TCP連接,才能相互傳輸數據。并且提供超時重發,丟棄重復數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。
TCP優點:可靠、穩定,TCP的可靠體現在TCP在傳遞數據之前,會有三次握手來建立連接,而且在數據傳遞時,有確認、窗口、重傳、擁塞控制機制,在數據傳完后,還會斷開連接用來節約系統資源。
TCP的缺點:慢,效率低,占用系統資源高,易被攻擊,TCP在傳遞數據之前,要先建連接,這會消耗時間,而且在數據傳遞時,確認機制、重傳機制、擁塞控制機制等都會消耗大量的時間,而且要在每臺設備上維護所有的傳輸連接,事實上,每個連接都會占用系統的CPU、內存等硬件資源。由于TCP存在確認機制和三次握手機制,這些是導致TCP容易被人利用,實現DOS、DDOS、CC等攻擊。
接下來,介紹Client 的使用實現:
Demo使用MB-039開發板,在工程中使用LwIP+FreeRTOS,實驗展示如何制作一個Client端,并發送數據,實驗使用到的硬件如下:
如圖是MB-039(完整原理圖可以通過MM32官網下載)的ETH部分。
各個信號引腳對應如下:
在進行Client實驗前,我們先了解需要使用到的API:
1)netconn_new () 2)netconn_connect () 3)netconn_write ()
每一個函數實現的功能:
01、netconn_new ()
netconn_new的功能為創建一個新的連接結構,結構類型可以為TCP/UDP其源碼如下:
struct netconn* netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback) { struct netconn* conn; API_MSG_VAR_DECLARE(msg); API_MSG_VAR_ALLOC_RETURN_NULL(msg); conn = netconn_alloc(t, callback); if (conn != NULL) { err_t err; API_MSG_VAR_REF(msg).msg.n.proto = proto; API_MSG_VAR_REF(msg).conn = conn; err = netconn_apimsg(lwip_netconn_do_newconn, API_MSG_VAR_REF(msg)); if (err != ERR_OK) { LWIP_ASSERT("freeing conn without freeing pcb", conn->pcb.tcp == NULL); LWIP_ASSERT("conn has no recvmbox", sys_mbox_valid( conn->recvmbox)); #if LWIP_TCP LWIP_ASSERT("conn->acceptmbox shouldn't exist", !sys_mbox_valid( conn->acceptmbox)); #endif /* LWIP_TCP */ #if !LWIP_NETCONN_SEM_PER_THREAD LWIP_ASSERT("conn has no op_completed", sys_sem_valid( conn->op_completed)); sys_sem_free( conn->op_completed); #endif /* !LWIP_NETCONN_SEM_PER_THREAD */ sys_mbox_free( conn->recvmbox); memp_free(MEMP_NETCONN, conn); API_MSG_VAR_FREE(msg); return NULL; } } API_MSG_VAR_FREE(msg); return conn; }
從源碼中可以看出,其功能為申請并初始化一個netconn結構體,同時在netconn_alloc函數中為conn變量創建一個接收郵箱(recvmbox),和一個信號量(conn->op_completed)。內存申請成功后使用netconn_apimsg函數構建一個消息,使用OS的系統郵箱發送給內核,請求以太網協議棧去執行lwip_netconn_do_newconn()函數,在執行時使用conn->op_completed進行信號量同步,任務處理完成后,釋放一個信號量表示任務完成。
02、netconn_connect ()
netconn_connect作用為建立連接,在調用時將服務器端IP地址、端口號和本地的netconn結構綁定,源碼如下:
err_t netconn_connect(struct netconn* conn, const ip_addr_t* addr, u16_t port) { API_MSG_VAR_DECLARE(msg); err_t err; LWIP_ERROR("netconn_connect: invalid conn", (conn != NULL), return ERR_ARG;); #if LWIP_IPV4 /* Don't propagate NULL pointer (IP_ADDR_ANY alias) to subsequent functions */ if (addr == NULL) { addr = IP4_ADDR_ANY; } #endif /* LWIP_IPV4 */ API_MSG_VAR_ALLOC(msg); API_MSG_VAR_REF(msg).conn = conn; API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr); API_MSG_VAR_REF(msg).msg.bc.port = port; err = netconn_apimsg(lwip_netconn_do_connect, API_MSG_VAR_REF(msg)); API_MSG_VAR_FREE(msg); return err; }
從源碼中可以看出,其功能為使用netconn_apimsg創建一個消息,通過執行lwip_netconn_do_connect進行信號量的同步,將addr、port與conn進行綁定。
03、netconn_write ()
netconn_write()為處于穩定狀態的TCP協議發送數據。TCP協議數據以數據流的方式傳遞,因此只需要知道地址、長度及需要發送的數據即可,其實際函數為netconn_write_vectors_partly(源碼較長,就不貼出來了)。重點關注一下官方API文檔對于apiflags參數的介紹:
* @param apiflags combination of following flags : * - NETCONN_COPY: data will be copied into memory belonging to the stack * - NETCONN_MORE: for TCP connection, PSH flag will be set on last segment sent * - NETCONN_DONTBLOCK: only write the data if all data can be written at once
apiflags的值為NETCONN_COPY時,dataptr指針指向的數據將會被拷貝到為這些數據分配的內部緩沖區,在調用本函數之后可以直接對這些數據進行修改而不會影響數據,但是拷貝的過程是需要消耗系統資源的,CPU需要參與數據的拷貝,而且還會占用新的內存空間。
apiflags值為NETCONN_NOCOPY時,數據不會被拷貝而是直接使用dataptr指針來引用。但是這些數據在函數調用后不能立即被修改,因為這些數據可能會被放在當前TCP連接的重傳隊列中,以防對方未收到數據進行重傳,而這段時間是不確定的。但是如果用戶需要發送的數據在ROM中(靜態數據),這樣子就無需拷貝數據,直接引用數據即可。
apiflags值為NETCONN_MORE時,那么接收端在組裝這些TCP報文段的時候,會將報文段首部的PSH標志置一,這些數據完成組裝的時候,將會被立即遞交給上層應用。
apiflags值為NETCONN_DONTBLOCK時,表示在內核發送緩沖區滿的時候,再調用netconn_write()函數將不會被阻塞,而是會直接返回一個錯誤代碼ERR_VAL告訴應用程序發送數據失敗,應用程序可以自行處理這些數據,在適當的時候進行重傳操作。
apiflags值為NETCONN_NOAUTORCVD時,表示在TCP協議接收到數據的時候,調用netconn_recv_data_tcp()函數的時候不會去更新接收窗口,只能由用戶自己調用netconn_tcp_recvd()函數完成接收窗口的更新操作。
了解了以上3個API,我們開始創建Client工程:
static void client(void* thread_param) { struct netconn* conn; int ret; ip4_addr_t ipaddr; uint8_t send_buf[] = "This is MM32F3270 TCP Client Demon"; //(1) while(1) { conn = netconn_new(NETCONN_TCP); //(2) if (conn == NULL) { // (3) printf("create conn failed!n"); vTaskDelay(10); continue; } IP4_ADDR( ipaddr, DEST_IP_ADDR0, DEST_IP_ADDR1, DEST_IP_ADDR2, DEST_IP_ADDR3); // (4) ret = netconn_connect(conn, ipaddr, DEST_PORT); // (5) if (ret == -1) { printf("Connect failed!n"); netconn_close(conn); vTaskDelay(10); continue; } while (1) { ret = netconn_write(conn, send_buf, sizeof(send_buf), 0); // (6) vTaskDelay(1000); } } }
1)將需要發送的數據裝填進send_buf中
2)申請一個內存區域,類型為TCP
3)如果conn為空表示申請內存失敗
4)將地址賦值給ipaddr
5)創建連接,如果失敗則刪除conn
6)執行數據發送
到這里已經完成了工程的創建,但是還有一步比較重要的,配置我們的IP,將數據發送給服務器端,則需要知道服務器的地址。打開命令行窗口輸入:ipconfig
PC地址為:192.168.105.34,在sys_arch.h文件中對DEST_IP_ADDR0 、DEST_IP_ADDR1、DEST_IP_ADDR2、DEST_IP_ADDR3進行修改,DEST_PORT隨意修改,值得注意的同一個設備是如果創建多個網卡,PORT成不同的值即可,后面我們會進行這類實驗,設備IP需要設置在同一個網段內通信才能進行IP_ADDR0、IP_ADDR1、IP_ADDR2,需要與PC地址保持一致,IP_ADDR3可以隨意設置(和PC地址不一致即可)。
#define DEST_IP_ADDR0 192 #define DEST_IP_ADDR1 168 #define DEST_IP_ADDR2 105 #define DEST_IP_ADDR3 34 #define DEST_PORT 5001 #define IP_ADDR0 192 #define IP_ADDR1 168 #define IP_ADDR2 105 #define IP_ADDR3 137
將程序下載入開發板中,使用SSCOM工具進行如下設置:
點擊偵聽:
可以正常偵聽并接收到數據,表明實驗成功。實驗程序請登錄我們的官網下載MM32F3270 SDK,工程路徑如下:
~MM32F3270_Lib_Samples_V0.90Demo_appEthernet_DemoETH_RTOSFreertos_Client
來源:靈動MM32MCU
審核編輯:湯梓紅
-
以太網
+關注
關注
41文章
5635瀏覽量
175996 -
Socket
+關注
關注
1文章
212瀏覽量
35876 -
TCP
+關注
關注
8文章
1402瀏覽量
81069 -
FreeRTOS
+關注
關注
12文章
493瀏覽量
64369
發布評論請先 登錄
靈動微課堂 (第182講) | 基于MM32F3270 以太網 Client_Socket使用
靈動微課堂 (第184講) | 基于MM32F3270 以太網 UDP使用
靈動微課堂 (第185講) | 基于MM32F3270 以太網 Client使用
MM32F3270系列32位MCU的特點有哪些
靈動微電子MM32F3270系列MCU的特點介紹
基于MM32F3270 以太網 Client使用

評論