四、LwIP的API的實現(xiàn)
LwIP的API的實現(xiàn)主要有兩部分組成:一部分駐留在用戶進程中,一部分駐留在TCP/IP協(xié)議棧進程中。這兩個部分間通過操作系統(tǒng)模擬層提供的進程通信機制(IPC)進行通信,從而完成用戶進程與協(xié)議棧間的通信,IPC包括共享內(nèi)存、消息傳遞和信號量。通常盡可能多的工作在用戶進程內(nèi)的API部分實現(xiàn),例如運算量及時間開銷大的工作;TCP/IP協(xié)議棧進程中的API部分只完成少量的工作,主要是完成進程間的通訊工作。兩部分API之間通過共享內(nèi)存?zhèn)鬟f數(shù)據(jù),對于共享內(nèi)存區(qū)的描述是采用和pbuf類似的結(jié)構(gòu)來實現(xiàn)。綜上,可以用簡單的一段話來描述這種API實現(xiàn)的機制:API 函數(shù)庫中處理網(wǎng)絡(luò)連接的函數(shù)駐留在 TCP/IP 進程中。位于應(yīng)用程序進程中的API函數(shù)使用郵箱這種通訊協(xié)議向駐留在TCP/IP 進程中的API函數(shù)傳遞消息。這個消息包括需要協(xié)議棧執(zhí)行的操作類型及相關(guān)參數(shù)。駐留在 TCP/IP 進程中的API函數(shù)執(zhí)行這個操作并通過消息傳遞向應(yīng)用程序返回操作結(jié)果。
很早以前描述過結(jié)構(gòu)pbuf,它是協(xié)議棧內(nèi)部用來描述數(shù)據(jù)包的一種方式。這里介紹數(shù)據(jù)結(jié)構(gòu)netbuf,它是API用來描述數(shù)據(jù)的一種方式。netbuf是基于pbuf來實現(xiàn)的,其結(jié)構(gòu)如下所示,很明顯,它就是包含了幾個典型結(jié)構(gòu)的指針。
struct netbuf {
struct pbuf *p, *ptr;
struct ip_addr *addr;
u16_t port;
};
注意,這里的netbuf只是相當(dāng)于一個數(shù)據(jù)頭,而真正保存數(shù)據(jù)的還是字段p指向的pbuf鏈表。字段ptr也是指向該netbuf的pbuf鏈表,但與p的區(qū)別在于:p一直指向pbuf鏈表中的第一個pbuf結(jié)構(gòu),而ptr則不然,它可能指向鏈表中的其他位置,源文檔里面把它描述為fragment pionter,與該指針調(diào)整密切相關(guān)的函數(shù)是netbuf_next和netbuf_first。還有兩個字段addr和port分別表示發(fā)出netbuf數(shù)據(jù)端的IP地址和端口號,這兩個字段實際用處似乎不大,API定義了宏netbuf_fromaddr和netbuf_fromport分別用于返回某個netbuf結(jié)構(gòu)中這兩個字段的值。
與netbuf相關(guān)的處理函數(shù)很多,在源文檔15.2節(jié)中對每個函數(shù)也有了詳細(xì)的說明,這里說說比較重要的幾個。netbuf_new用于分配一個新的netbuf結(jié)構(gòu),注意這里只是一個頭部結(jié)構(gòu),而真正需要的存儲數(shù)據(jù)區(qū)域是在函數(shù)netbuf_alloc中分配的,同理函數(shù)netbuf_delete用于刪除一個netbuf結(jié)構(gòu),同時函數(shù)pbuf_free會被調(diào)用,用以刪除數(shù)據(jù)區(qū)域的空間。以上這幾個函數(shù)使用的簡單例子如下:
struct netbuf *buf; // 申明指針
buf = netbuf_new(); // 申請新的netbuf
netbuf_alloc(buf, 200); // 為netbuf分配200 字節(jié)的空間
。。。。 // 使用buf做相關(guān)事情
netbuf_delete(buf); // 刪除buf
講過了API的內(nèi)存管理,再來講具體的API函數(shù)。與前面的對應(yīng),API函數(shù)由兩部分組成,分別在文件api_lib.c和api_msg.c中。前者包括了用戶程序直接調(diào)用的API接口函數(shù),后者包括了與協(xié)議棧進程通信的API函數(shù),用戶程序不可直接調(diào)用。這兩部分API函數(shù)之間通過郵箱傳遞的消息進行通信。這里又要涉及到兩個重要的數(shù)據(jù)結(jié)構(gòu):API應(yīng)用接口部分提供給上層應(yīng)用以描述一個網(wǎng)絡(luò)連接的數(shù)據(jù)結(jié)構(gòu)netconn,以及描述兩部分API函數(shù)間傳遞的消息結(jié)構(gòu)的api_msg。
應(yīng)用程序要使用API函數(shù)建立一個連接連接,首先應(yīng)該建立一個netconn的結(jié)構(gòu)來描述這個連接的各種屬性。netconn結(jié)構(gòu)如下,其中去掉了不必要的編譯選項和注釋。
struct netconn {
enum netconn_type type; // 連接的類型,包括TCP, UDP等
enum netconn_state state; // 連接的狀態(tài)
union { // 共用體,內(nèi)核用來描述某個連接
struct ip_pcb *ip;
struct tcp_pcb *tcp;
struct udp_pcb *udp;
struct raw_pcb *raw;
} pcb;
err_t err; // 該連接最近一次發(fā)生錯誤的編碼
sys_sem_t op_completed; // 用于兩部分API間同步的信號量
sys_mbox_t recvmbox; // 接收數(shù)據(jù)的郵箱
sys_mbox_t acceptmbox; // 服務(wù)器用來接受外部連接的郵箱
int socket; // 該字段只在socket實現(xiàn)中使用
u16_t recv_avail; //
struct api_msg_msg *write_msg; // 對數(shù)據(jù)不能正常處理時,保存信息
int write_offset; // 同上,表示已經(jīng)處理數(shù)據(jù)的多少
netconn_callback callback; // 回調(diào)函數(shù),在發(fā)生與該netconn相關(guān)的事件時可以調(diào)用
};
write_msg是api_msg_msg類型的指針,該結(jié)構(gòu)后續(xù)講到;netconn_callback是API定義的一種函數(shù)指針類型,其定義為:
typedef void (* netconn_callback)(struct netconn *, enum netconn_evt, u16_t len);
關(guān)鍵字typedef的功能是定義新的類型。這里它用于定義一種netconn_callback的類型,這種類型為指向某種函數(shù)的指針,且這種函數(shù)有三個類型的輸入?yún)?shù),返回類型為void。在這定義以后就可以像使用int,char一樣使用netconn_callback,也即它也表示一種數(shù)據(jù)類型了。所以上面的callback字段表示一個函數(shù)指針,當(dāng)把某個函數(shù)名賦給該字段后,該字段就記錄了這個函數(shù)的起始地址。
netconn的其他字段后面用到時再詳解。接下來看看兩部分API函數(shù)間傳遞的消息結(jié)構(gòu)的api_msg:
struct api_msg {
void (* function)(struct api_msg_msg *msg); // 函數(shù)指針
struct api_msg_msg msg; // 函數(shù)執(zhí)行時需要的參數(shù)
}
字段function是一個函數(shù)指針,通常當(dāng)消息被構(gòu)造時,該字段被填充為api_msg.c中的某個與協(xié)議棧接口的API函數(shù),然后該消息被投遞到協(xié)議棧進程中進行處理,協(xié)議棧進程解析出并執(zhí)行該函數(shù),這樣應(yīng)用程序與協(xié)議棧之間的通信就完成了。字段msg中包含了這個函數(shù)執(zhí)行時需要的所有參數(shù),api_msg_msg是個枚舉類型,所以對于不同的function,msg有著不同的結(jié)構(gòu)。api_msg_msg結(jié)構(gòu)如下所示:
struct api_msg_msg {
struct netconn *conn; // 與消息相關(guān)的某個連接
union {
struct netbuf *b; // 函數(shù)do_send的參數(shù)
struct { // 函數(shù)do_newconn的參數(shù)
u8_t proto;
} n;
struct { // 函數(shù)do_bind和do_connect的參數(shù)
struct ip_addr *ipaddr;
u16_t port;
} bc;
struct { // 函數(shù)do_getaddr的參數(shù)
struct ip_addr *ipaddr;
u16_t *port;
u8_t local;
} ad;
struct { // 函數(shù)do_write的參數(shù)
const void *dataptr;
int len;
u8_t apiflags;
} w;
struct { // 函數(shù)do_recv的參數(shù)
u16_t len;
} r;
} msg;
};
這個結(jié)構(gòu)體只包含了兩個字段:描述連接信息的conn和枚舉類型msg。在api_msg_msg中保存conn字段是必須的,因為conn結(jié)構(gòu)中包含了與該連接相關(guān)的信箱和信號量等信息,協(xié)議棧進程要用這些信息來完成與應(yīng)用進程間的同步與通信。枚舉類型msg的各個成員與調(diào)用它的函數(shù)密切相關(guān)。因此在構(gòu)建一個消息時,API應(yīng)首先填充消息的function字段,并針對特定的function種類往api_msg_msg的枚舉字段寫入?yún)?shù),函數(shù)function被協(xié)議棧解析執(zhí)行時,直接按照自己已定的格式取參數(shù)。這意味著,對于構(gòu)造消息的API函數(shù)和解析消息的function函數(shù),它們對參數(shù)個數(shù)、結(jié)構(gòu)的認(rèn)識是預(yù)先約定的,不能有其他變數(shù)。這一點體現(xiàn)出LwIP呆板僵硬的一面。
評論