LWIP學習筆記6——使用 NETCONN 接口編程

使用 NETCONN 接口編程

NETCONN API 使用了操作系統的 IPC 機制, 對網絡連接進行了抽象,用戶可以像操作文件一樣操作網絡連接(打開/關閉、讀/寫數據)。 但是 NETCONN API 並不如操作文件的 API 那樣簡單易用。舉個例子,調用 f_read 函數讀文件時,讀到的數據會被放在一個用戶指定的數組中,用戶操作起來很方便,而 NETCONN API 的讀數據 API,就沒有那麼人性化了。 用戶獲得的不是一個數組,而是一個特殊的數據結構 netbuf,用戶如果想使用好它,就需要對內核的 pbuf 和 netbuf 結構體有所瞭解。
netbuf 結構體:
LwIP 爲了更好描述應用線程發送與接收的數據,並且爲了更好管理這些數據的緩衝區,LwIP 定義了一個 netbuf 結構體,它是基於 pbuf 上更高一層的封裝,記錄了主機的 IP 地址與端口號。

struct netbuf
 {
	 struct pbuf *p, *ptr; (1)
	 ip_addr_t addr; (2)
	 u16_t port; (3)
 };

在這裏插入圖片描述
netbuf 相關函數說明:
netbuf 是 LwIP 描述用戶數據很重要的一個結構體,因爲 LwIP 是不可能讓我們直接操作 pbuf 的,因爲分層的思想,應用數據必然是由用戶操作的, 因此 LwIP 會提供很多函數接口讓用戶對 netbuf 進行操作,無論是 UDP 報文還是 TCP 報文段,其本質都是數據,要發送出去的數據都會封裝在 netbuf 中,然後通過郵箱發送給內核線程(tcpip_thread 線程),然後經過內核的一系列處理,放入發送隊列中,然後調用底層網卡發送函數進行發送,反之,應用線程接收到數據,也是通過 netbuf 進行管理。

netconn 結構體:
在 LwIP 中,如 TCP 連接, UDP 通信,都是需要提供一個編程接口給用戶使用的,那麼爲了描述這樣子的一個接口, LwIP 抽象出來一個 nettonn 結構體,它能描述一個連接,供應用程序使用,同時內核的 NETCONN API 接口也對各種連接操作函數進行了統一的封裝,這樣子,用戶程序可以很方便使 netconn 和編程函數,我們暫且將 netconn 稱之爲連接結體。一個連接結構體中包含的成員變量很多,如描述連接的類型,連接的狀態(主要是在TCP 連接中使用),對應的控制塊(如 UDP 控制塊、 TCP 控制塊等等),還有對應線程的消息郵箱以及一些記錄的信息。

struct netconn
 {
 /** netconn 類型 */
 enum netconn_type type;
 /** 當前 netconn 狀態 */
 enum netconn_state state;
 /** LwIP 的控制塊指針,如 TCP 控制塊、 UDP 控制塊 */
 union
 {
	struct ip_pcb *ip;
 	struct tcp_pcb *tcp;
 	struct udp_pcb *udp;
	 struct raw_pcb *raw;
 } pcb;
 err_t pending_err;/** 這個 netconn 最後一個異步未報告的錯誤 */
 sys_sem_t op_completed; //信號量
 /** 消息郵箱,存儲接收的數據,直到它們被提取 */
 sys_mbox_t recvmbox;
 /** 用於 TCP 服務器上的請求連接緩衝區 */
 sys_mbox_t acceptmbox;

 /** socket 描述符,用於 Socket API */
 #if LWIP_SOCKET
 int socket;
 #endif /* LWIP_SOCKET */


 /** 標誌 */
 u8_t flags;
 #if LWIP_TCP
 /** 當調用 netconn_write()函數發送的數據不適合發送緩衝區時,
 數據會暫時存儲在 current_msg 中,等待數據合適的時候進行發送 */
 struct api_msg *current_msg;
 #endif /* LWIP_TCP */
 /** 連接相關的回調函數 */
 netconn_callback callback;
 };

netconn 函數接口說明:

netconn_new()

函數 netconn_new ()本質上是一個宏定義,它用來創建一個新的連接結構, 連接結構的類型可以選擇爲 TCP 或 UDP 等,參數 type 描述了連接的類型,可以爲 NETCONN_TCP或 NETCONN_UDP 等, 在這個函數被調用時,會初始化相關的字段,而並不會創建連接。

netconn_bind()

netconn_bind()函數用於將一個 IP 地址及端口號與 netconn 連接結構進行綁定,如果作爲服務器端,這一步操作是必然需要的,作爲客戶端,不需要這一步,系統會自動分配端口號,使用默認網卡發送數據。同樣的, 該函數會調用 netconn_apimsg()函數構造一個 API 消息,並且請求內核執行 lwip_netconn_do_bind()函數, 然後通過 netconn 連接結構的信號量進行同步,事實上內核線程的處理也是通過函數調用 xxx_bind(xxx_bing 可以是 udp_bing、 tcp_bing、 raw_bing,具體是哪個函數內核是根據 netconn 的類型決定的) 完成相應控制塊的綁定工作。

netconn_connect()

netconn_connect()函數是用於連接服務器的函數,它一般在客戶端中調用,將服務器端的 IP 地址和端口號與本地的 netconn 連接結構綁定,當 TCP 協議使用該函數的時候就是進行握手的過程,調用的應用線程將阻塞至握手完成;而對於 UDP 協議來說,調用該函數只是設置 UDP 控制塊的目標 IP 地址與目標端口號,其實這個函數也是通過調用netconn_apimsg()函數構造一個 API 消息,並且請求內核執行 lwip_netconn_do_connect()函數, 然後通過 netconn 連接結構的信號量進行同步,在 lwip_netconn_do_connect()函數中,根據 netconn 的類型不同, 調用對應的 xxx_connect()函數進行對應的處理,如果是 TCP 連接,將調用 tcp_connect();如果是 UDP 協議,將調用 udp_connect();如果是RAW,將調用 raw_connect()函數處理。

netconn_recv()

它可以接收一個 UDP 或者 TCP的數據包,從 recvmbox 郵箱中獲取pbuf數據包,如果該郵箱中沒有數據包,那麼線程調用這個函數將會進入阻塞狀態以等待消息的到來, 如果在等待 TCP 連接上的數據時,遠端主機終止連接,將返回一個終止連接的錯誤代碼(ERR_CLSD),應用程序可以根據錯誤的類型進行不一樣的處理。對應 TCP 連接, netconn_recv()函數將調用 netconn_recv_data_tcp()函數去獲取 TCP 連接上的數據,在獲取數據的過程中,調用 netconn_recv_data()函數從 recvmbox 郵箱獲取pbuf, 然後通過 netconn_tcp_recvd_msg()->netconn_apimsg()函數構造一個 API 消息投遞給系統郵箱, 請求內核執行 lwip_netconn_do_recv()函數, 該函數將調用 tcp_recved()函數去更新 TCP 接收窗口,同時netconn_recv()函數將完成 pbuf 數據包封裝在 netbuf 中,返回個應用程序; 而對於 UDP 協議、 RAW 連接,將簡單多了,將直接調用netconn_recv_data()函數獲取數據,完成 pbuf 封裝在 netbuf 中,返回給應用程序。
在這裏插入圖片描述

netconn_send()

該函數會調用 netconn_apimsg()函數構造一個 API 消息,並且請求內核執行 lwip_netconn_do_send()函數, 這個函數會通過消息得到目標 IP 地址與端口號以及 pbuf 數據報等信息, 然後調用 raw_send()/udp_send()等函數發送數據,最後通過 netconn 連接結構的信號量進行同步。

netconn_write()

用於處於穩定連接狀態的 TCP 協議發送數據,這個函數的功能是把 dataptr 指針指向的數據放在屬於 conn 連接的 TCP 連接的發送隊列中, size 參數指定了數據的長度, apiflags 參數有以下幾種:

/* 沒有標誌位(默認標誌位) */
 #define NETCONN_NOFLAG 0x00

 /* 不拷貝數據到內核線程 */
 #define NETCONN_NOCOPY 0x00
 /* 拷貝數據到內核線程 */
 #define NETCONN_COPY 0x01

 /* 儘快遞交給上層應用 */
 #define NETCONN_MORE 0x02

 /* 當內核緩衝區滿時,不會被阻塞,而是直接返回 */
 #define NETCONN_DONTBLOCK 0x04

 /* 不自動更新接收窗口,需要調用 netconn_tcp_recvd()函數完成 */
 #define NETCONN_NOAUTORCVD 0x08

 /* 上層已經收到數據,將 FIN 保留在隊列中直到再次調用 */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章