1. 程式人生 > >lwIP RAW_API

lwIP RAW_API

truct driver 沒有 完整 不同的應用 設定 有效 其他 收集

lwIP RAW TCP/IP接口

作者: Adam Dunkels, Leon Woestenberg, Christiaan Simons

lwIP為使用TCP/IP協議通信的應用程序編程提供了兩種接口接口(APIs):

* 低層次的稱之為"core" / "callback" 或者 "raw" API

* 高層次的稱之為"sequential" API

lwIP "sequential" API為使用TCP/IP協議棧編程提供符合常規的、通用的途徑,它與BSD socket API非常相似。程序的執行過程同樣是基於"open-read-write-close"模型的。從本質上講,TCP/IP協議棧的通信過程是事件驅動的,因此,TCP/IP的代碼和用戶應用程序的代碼必須在不同的線程裏面。

**以下內容討論"RAW" API**

RAW IP允許應用程序和TCP/IP代碼緊密結合,程序的執行是基於在TCP/IP內核中被調用的回調函數事件驅動的。TCP/IP內核和應用程序可以運行在同一線程。lwIP "sequential" API接口會消耗大量的CPU資源,它並不適用於小型嵌入式系統,因為它必須運行在多線程環境中。

RAW API不僅執行速度快,而且消耗的內存資源更少。它的缺點是應用程序編寫相對較難並不易理解。盡管如此,這種方式仍是資源較少的嵌入式系統的首選方法。

不同的應用程序中我們可以同時使用這兩種APIs,實際上"sequential" API就是由RAW API封裝後得到的。

什麽是回調函數?

RAW API是基於回調函數所驅動的。每一個回調函數實際上只是一個普通的C函數,這個函數在TCP/IP內核中被調用。每一個回調函數都作為一個參數傳遞給當前TCP或UDP連接。而且,為了能夠保存程序的特定狀態,可以向回調函數傳遞一個指定的狀態,並且這個指定的狀態是獨立於TCP/IP協議棧的。

--應用程序設置狀態函數

- void tcp_arg(struct tcp_pcb *pcb, void *arg)

指定傳給所有回調函數的特定狀態參數。參數"pcb"指當前TCP連接控制塊,"arg"指傳遞給回調函數的參數。

--TCP連接函數

這些函數用於建立連接,它們與"sequential" API以及BSD socket API非常相似。使用tcp_new()函數建立一個新的TCP標識符(也就是協議控制塊-PCB)。這個PCB可以用來監聽一個外來的連接(譯註:作為服務器)也可以連接到另一個主機(譯註:作為客戶端)。

- struct tcp_pcb *tcp_new(void)

創建一個新的連接標識符(PCB)。如果沒有有效的存儲空間創建這個新的pcb,返回NULL。

譯註:這個函數創建一個TCP協議控制塊,但並不把它放到任何TCP PCB列表,直到使用tcp_bind()函數綁定。Tcp_new()函數會調用tcp_alloc函數來動態申請一塊內存並初始化它,之後將這塊內存的首地址返回給tcp_new()函數,如果動態內存不成功的話返回NULL。

- err_t tcp_bind(struct tcp_pcb *pcb, struct ip_addr *ipaddr,

u16_t port)

給pcb綁定一個本地IP地址和端口號。如果參數"ipaddr"為IP_ADDR_ANY,則為這個pcb綁定任意本地IP地址。

譯註:這個函數的大部分代碼用於檢驗給出的IP地址和端口號是否合適,如果合適則將給出的IP地址和端口號賦給當前PCB,更新已綁定tcp_pcb列表並返回ERR_OK.如果給出的參數不合適,則返回ERR_USE(表示端口已被使用)。

參數ipaddr如果為IP_ADDR_ANY,表示綁定到任意本地地址,那麽IP_ADDR_ANY是什麽呢?在lwip-1.3.0/src/include/ipv4/lwip/ip_addr.h中定義了:

#define IP_ADDR_ANY ((struct ip_addr *)&ip_addr_any)

ip_addr_any是一個ip_addr型變量,在lwip-1.3.0/src/core/ipv4/ip_addr.c中有如下聲明:

#define IP_ADDR_ANY_VALUE 0x00000000UL

const struct ip_addr ip_addr_any = { IP_ADDR_ANY_VALUE };

所以, IP_ADDR_ANY是等於0x00000000UL的. 在IP地址上規定 0.0.0.0為廣播地址,也就是任意地址的意思。

- struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)

指定一個PCB進入監聽狀態。當一個遠端連接訪問時,函數 tcp_accept()指定的回調函數將被調用。在調用這個函數之前一定要使用tcp_bind()函數綁定一個本地IP和端口號。

tcp_listen() 函數返回一個新的連接標識符,原始的pcb會被釋放,這是為了節省內存,使之更適合小內存系統。

如果監聽連接的內存無效,tcp_listen()函數返回NULL,如果這樣的話,傳入的PCB參數將不會被釋放。

這個函數從原理上看也比較簡單,首先是做一些必要的檢查,判斷原始pcb是否已經處於連接狀態,如果沒有則申請一塊tcp_pcb類型的內存,將原始的必要的pcb內容復制到新的pcb中,設置新的pcb狀態為LISTEN,釋放原始的pcb,並將新pcb連接放入已監聽隊列。

- struct tcp_pcb *tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog)

這個函數和tcp_listen()函數相同,只是限制了TCP監聽隊列連接個數,這個個數由backlog參數指定。為了使用它,你必須在你的lwipopt.h中設置TCP_LISTEN_BACKLOG=1。

- void tcp_accepted(struct tcp_pcb *pcb)

通知lwIP一個傳入的連接已經被接受。通常這個函數在“accept()”函數的回調函數中被調用。這允許lwIP處理自身內部的任務。比如,允許更多傳入的連接進入監聽隊列。

- void tcp_accept(struct tcp_pcb *pcb,

err_t (* accept)(void *arg, struct tcp_pcb *newpcb,

err_t err))

指定應在偵聽連接上的一個新的連接到達時調用的回調函數。

- err_t tcp_connect(struct tcp_pcb *pcb, struct ip_addr *ipaddr,

u16_t port, err_t (* connected)(void *arg,

struct tcp_pcb *tpcb,

err_t err));

設置打開連接的pcb連接到遠程主機並發送初始的SYN段。

函數tcp_connect() 會立即返回;它並不等待這個連接是否被正確設置。相反的,當連接正確建立後它將調用第四個參數("connected"參數)指定的函數。如果這個連接不能正確的建立,可能是主機拒絕這個連接或者主機沒有響應,"connected"函數將被調用並設置一個相應的參數。

當入隊的SYN段內存不可用時,tcp_connect()函數能返回ERR_MEM,表示連接沒有正確建立。如果SYN成功入隊,tcp_connect()函數返回ERR_OK。

---TCP數據發送函數

lwIP會調用tcp_write()函數來發送隊列中的數據。當數據成功的發送到遠程主機,會調用一個指定的回調函數來通知應用程序。

- err_t tcp_write(struct tcp_pcb *pcb, void *dataptr, u16_t len,

u8_t copy)

參數"dataptr"指向數據隊列;參數"len"傳遞數據的長度;參數"copy"的值為0或者1,表明是否需要申請新的內存用於數據的拷貝。如果這個參數為0,則不需要申請新的內存,此時數據只能使用指針來引用。

如果數據長度超過當前發送緩存字節數或者要發送的段隊列長度超過lwipopts.h中定義的上限值,tcp_write()函數執行失敗並返回ERR_MEN。可以使用tcp_sndbuf()函數來返回輸出隊列有效的字節數。

使用這個函數的正確方法是根據tcp_sndbuf() 函數返回的字節數來發送數據。如果函數返回ERR_MEM,應用程序應該等待直到當前隊列數據成功的被遠程主機收到然後嘗試重新發送一次。

- void tcp_sent(struct tcp_pcb *pcb,

err_t (* sent)(void *arg, struct tcp_pcb *tpcb,

u16_t len))

當遠程主機成功接收(也就是應答信號)到數據時,該函數指定的回調函數被調用。傳送給回調函數的"len"參數給出了上一次已經被確認的發送的最大字節數。

--TCP數據接收函數

TCP數據接收是基於回調函數的---當一個新的數據接收到時,應用程序指定的回調函數被調用。當應用程序接收到數據後,它必須調用tcp_recved()函數來指示接收數據的大小。

- void tcp_recv(struct tcp_pcb *pcb,

err_t (* recv)(void *arg, struct tcp_pcb *tpcb,

struct pbuf *p, err_t err))

當接收到數據時,本函數設置的回調函數將被調用。如果傳遞給回調函數一個NULL pbuf則說明遠程主機關閉了這個連接。如果函數正常運行並且回調函數返回ERR_OK,則必須釋放這個pbuf,如果其它情況,必須保存這個pbuf,這樣才能讓lwIP內核保存它以供應用程序檢查並恢復錯誤。

- void tcp_recved(struct tcp_pcb *pcb, u16_t len)

當應用程序接收到數據後必須調用這個函數。參數"len"表明接收到的數據的長度。

--- 應用程序輪詢函數

當一個連接空的時候(也就是說,既沒有數據接收也沒有數據發送),lwIP會通過調用一個指定的回調函數來重復輪詢應用程序。這可以用作一個看門狗定時器,用來終止空閑時間太長的連接;或者用作等待內存有效的一種方法。舉例來說,如果調用tcp_write()函數時因為內存無效而失敗,應用程序可以使用輪詢功能在連接空閑的時候再次調用tcp_write()。

- void tcp_poll(struct tcp_pcb *pcb, u8_t interval,

err_t (* poll)(void *arg, struct tcp_pcb *tpcb))

指定輪詢間隔和應用程序輪詢時調用的回調函數。這個間隔是以TCP粗粒度定時器為單位的,即500毫秒一次。如果參數"interval"的值為10,則意味著每5秒輪詢一次應用程序。

---關閉和終止連接函數

- err_t tcp_close(struct tcp_pcb *pcb)

關閉連接。如果關閉的連接內存無效,函數返回ERR_MEM,如果是這樣的話,應用程序應該等待並通過使用acknowledgment回調函數或者輪詢功能重新關閉連接。如果連接關閉成功,函數返回WRR_OK。

TCP內核調用tcp_close()後,參數"pcb"指定的連接被解除。

- void tcp_abort(struct tcp_pcb *pcb)

通過向遠程主機發送一個RST(復位)段來終止連接。這個函數從不會失敗。

如果這個連接因為一個錯誤而被終止,則應用程序可以通過err回調函數靈活的處理這個事件。通常一個連接因錯誤而終止的原因是內存不足。這時使用tcp_err()函數設置的回調函數被調用。

- void tcp_err(struct tcp_pcb *pcb, void (* err)(void *arg,

err_t err))

指定一個處理錯誤的回調函數,該回調函數不能得到本函數的"pcb"作為它的參數,因為這個pcb可能已經被解除。

--- 低層次TCP接口

在系統的較低層,TCP提供一個簡單的接口。在系統初始化的時候,任何其他TCP函數被調用之前必須先調用tcp_init()函數。當系統已經運行,兩個定時器函數tcp_fasttmr() 和tcp_slowtmr()必須定期被調用。tcp_fasttmr()函數必須每隔TCP_FAST_INTERVAL(定義在tcp.h中)個毫秒被調用一次,tcp_slowtmr() 函數必須每隔TCP_SLOW_INTERVAL個毫秒被調用一次。

--- UDP 接口

相比之下,UDP接口要比TCP接口類似,但UDP在低層次的復雜程度上明顯比TCP簡單。

- struct udp_pcb *udp_new(void)ige

創建一個用於UDP通訊的UDP pcb。這個pcb直到綁定本地地址或者連接到遠程地址後才被激活。

- void udp_remove(struct udp_pcb *pcb)

刪除一個指定的連接。

-err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)

為pcb綁定一個本地地址。參數"ipaddr"為IP_ADDR_ANY時,指定可以監聽任何本地IP地址。這個函數一般都會返回ERR_OK。

- err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)

設置pcb連接到遠程主機。這個函數不產生任何流量,僅設置pcb的遠程地址。

- err_t udp_disconnect(struct udp_pcb *pcb)

刪除遠程端的pcb。這個函數不產生任何流量,近視刪除pcb的遠程地址。

- err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)

發送pbuf結構指針p指向的數據。這個pbuf不會被釋放。

- void udp_recv(struct udp_pcb *pcb,

void (* recv)(void *arg, struct udp_pcb *upcb,

struct pbuf *p, struct

ip_addr *addr,

u16_t port),

void *recv_arg)

當接收到一個數據包後,該函數指定的回調函數將被調用。

---系統初始化

一個完整通用的lwIP初始化步驟是不可能實現的,因為它還取決於配置文件(lwipopts.h)的編寫以及初始化額外運行時的環境(例如硬件定時器)。

當你使用RAW API時,我們可以給你一些建議。

我們假設你使用一個單一的以太網netif和UDP、TCP傳輸層、IPv4和DHCP客戶端。

安以下順序調用這些函數:

- stats_init()

清楚運行時被收集的統計結構。

- sys_init()

沒有多大用處,因為我們在lwipopts.h中設置NO_SYS 1

Not of much use since we set the NO_SYS 1 option in lwipopts.h, to be called for easy

configuration changes.

- mem_init()

通過定義MEM_SIZE初始化動態存儲堆

- memp_init()

通過定義MEMP_NUM_x初始化內存池。

- pbuf_init()

通過定義PBUF_POOL_SIZE初始化pbuf內存池。

- etharp_init()

初始化ARP表和隊列。

註:在這個初始化之後你必須每隔 ARP_TMR_INTERVAL(5秒)個周期間隔調用etharp_tmr 函數。

- ip_init()

不常用,處理將要放生的改變時被調用。

- udp_init()

清除UDP PCB列表。

- tcp_init()

清除TCP PCB列表並清除一些內部定時器。

註:在這個初始化函數之後,你必須按預先確定的每個周期內調用tcp_fasttmr() 和 tcp_slowtmr()函數。

- netif_add(struct netif *netif, struct ip_addr *ipaddr,

struct ip_addr *netmask, struct ip_addr *gw,

void *state, err_t (* init)(struct netif *netif),

err_t (* input)(struct pbuf *p, struct netif *netif))

向netif_list列表中增加你的網絡接口。分配一個netif結構體並傳遞一個指向這個結構體的指針作為第一個參數。當使用DHCP時給定的ip_addr結構體會被清除,或者用其它數據填充它們。"state"指針可能為NULL。

函數指針"init"必須指向你的以太網netif接口初始化函數,下面舉例說用該函數的應用。

err_t netif_if_init(struct netif *netif)

{

u8_t i;

for(i = 0; i < ETHARP_HWADDR_LEN; i++) netif->hwaddr[i] = some_eth_addr[i];

init_my_eth_device();

return ERR_OK;

}

為使用以太網驅動器(For ethernet drivers),函數指針"input"必須指向"netif/etharp.h"中聲明的ethernet_input() 函數。其它驅動器(Other drivers)必須使用"lwip/ip.h"中聲明的ip_input()函數。

- netif_set_default(struct netif *netif)

註冊默認網絡接口

- netif_set_up(struct netif *netif)

當netif完全配置後,這個函數必須被調用。

- dhcp_start(struct netif *netif)

在第一次調用時為這個接口創建一個新的DHCP客戶端。

註:啟動這個客戶端後你必須按照預先設定的間隔周期性的調用dhcp_fine_tmr() 和dhcp_coarse_tmr()函數。

你可以通過結構體netif->dhcp查看真實的DHCP狀態。

--- 優化提示

首先要做的是優化src/core/inet.c中的lwip_standard_checksum()程序。你可以使用

#define LWIP_CHKSUM <your_checksum_routine>

來重寫這個標準函數。

inet.c中使用C語言編寫的例子,你也可以使用匯編語言編寫。

RFC1071是這個主題的很好的介紹。

如果你使用小端處理器,另一個有效的改善是用匯編語言或者內聯函數代替htons() 和 htonl()函數。

#define LWIP_PLATFORM_BYTESWAP 1

#define LWIP_PLATFORM_HTONS(x) <your_htons>

#define LWIP_PLATFORM_HTONL(x) <your_htonl>

如果你的網絡讀到的速度比最大線速還要大,檢查你的網絡接口。如果硬件不能提供良好的服務,會經常快速的發生緩沖區溢出現象。舉例來說,當使用cs8900處理器時,調用cs8900if_service(ethif)函數可能很頻繁出現上述現象。當使用的RTOS允許cs8900使用中斷喚醒一個服務於一個你的使用一個二進制信號量或事件標誌的驅動程序的高優先級任務。

當產品發布時,建議設置LWIP_STATS為0。

註意速度性能的提高和多方面有關,並不是簡單的提高內存的容量。

lwIP RAW_API