使用 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 保留在隊列中直到再次調用 */