感觉这个不错,给大家分享一下
刚完成win32下的lwip移植 调试测试 程序。
使用winpcap做底层。
只要电脑 不需要额外 硬件就可以马上测试lwip.以后可能可以给大家汇报测试结果
--------------------------------------------------------------- Raw TCP/IP interface for lwIP 0.5
lwIP 0.5 中的原始 TCP/IP 介面
作者:Adam Dunkels,2001/12/12
譯者:ct小貓 (Elisha Chou周錦廷 ct@ccns.ncku.edu.tw)
--
譯注:2004年8月,lwIP已釋出1.0.0版,但本文仍有相當的參考價值。待譯者有空時,將會針對最近版本的差異處進行修改。(當然若有 朋友願意指教,更是感激不盡。)
原文請見:
http://ccns.ncku.edu.tw/~ct/doc/lwIP/lwIP_Raw_TCPIP.htm
--
lwIP 提供了兩組 API ,讓程式和 TCP/IP 程式碼之間進行溝通:循序性API (Sequential API,通常我們可以就稱呼它為“the API”。)(譯注:以下譯文將配合文句流暢性,來決定是否保留原文或簡稱。)與原始TCP/IP介面 (raw TCP/IP interface)。本 文件將對後者進行描述。至於lwIP 0.5以下的版本的API,尚未有文件說明。
「循序性API」為一般循序程式提供了使用 lwIP stack 的途徑。它與BSD的socket API頗為類似。它的執行模式是建立在「開啟→讀取→寫入→關閉」這個典範的基礎之上。由於此種方式中,TCP/IP stack事件的基礎乃是其固有的自然天性,因此TCP/IP程式碼和應用程式必定分別存在於「不同的execution context(亦即不同的執行緒)」當中。
「原始TCP/IP介面」讓應用程式可以和TCP/IP程式碼更緊密地結合在一起。程式的執行會變成一個在TCP/IP程式碼中進行callback 函式呼叫的事件。這時候,TCP/IP程式碼和應用程式會變成「在同一個執行緒中執行」。相較之下,循序性API的負擔太高,實在不適合小型系統的開發,因為它強迫應用程式遵守多執行緒的典範。
使用原始TCP/IP介面,不僅讓程式的執行速度加快,也可以舒緩記憶體的激烈競爭。但「缺點」是程式開發的難度多少增加了一些,而使用原始TCP/IP介面的程式也會更難理解。然而,對於需要在程式大小和記憶體用量上精簡的應用而言,這仍然是個建議的方式。
我們可以同時在不同的應用程式中使用這兩種不同的API。事實上,循序性API就是原始TCP/IP介面的一個應用程式實作。 J
--- Callbacks
程式的執行,是由callback 所趨動的。每個callback都是TCP/IP程式碼中呼叫的一個普通C函式。每個callback函式接受一組TCP或UDP連線狀態作為參數。另外,為了追蹤程式的特定狀態,在呼叫callback函式時也會傳入一個獨立於TCP/IP狀態的程式指定參數。
用來設定應用程式連線狀態的函式是:
void tcp_arg(struct tcp_pcb *pcb, void *arg)
它指定了要傳遞給所有其他callback函式的程式特定狀態。pcb代表目前TCP連線的control block(控制區塊),而arg則是要傳入callback的參數。
--- TCP/IP連線設定
用來將連線設置妥當的函式和循序性API以及BSD socket API中同樣用途的函式十分相似。先用tcp_new()來建立一個新的TCP connection identifier(連線標籤)(也就是一個 PCB,protocol control block)。接著我們可以設定這個PCB,讓它負責傾聽外部連進來的連線,或者是主動連接另外一個host。
struct tcp_pcb *tcp_new(void)
這個函式建立一個新的TCP connection identifier (也就是建立一個新的PCB)。假如配置memory給PCB的過程失敗,就傳回null。
err_t tcp_bind(struct tcp_pcb *pcb, struct ip_addr *ipaddr,u16_t port)
這個函式將PCB與本地端的IP位址及埠號 bind在一起。將IP位址指定為IP_ADDR_ANY能讓它和所有的本地端IP位址連線bind起來。
假如另一個連線也嘗試bind相同的埠號,它會傳回ERR_USE,否則傳回ERR_OK。
struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)
令PCB開始傾聽連進來的連線。一旦接受某個連線要求,tcp_accept()所指定的函式會被呼叫起來。PCB必需用tcp_bind()來和本地端的某個埠bind在一起。
tcp_listen()會傳回一個新的connection identifier,原本被當成參數傳入function的那一個connection identifier則會被釋放掉。這樣做的理由是,一個負責傾聽的connection所需的記憶體極少,所以tcp_listen()會修正原始連線的記憶體需求,配置一塊較小的記憶體區塊給這個負責傾聽的connection。
假如沒有足夠的memory以供配置給這個connection,tcp_listen()將傳回NULL。那麼原本當成參數傳入tcp_listen()的PCB就不會被釋放掉。
void tcp_accept(struct tcp_pcb *pcb,err_t (* accept)(void *arg, struct tcp_pcb *newpcb,err_t err))
指定在listening connection中,一旦建立了新的connection,要去呼叫哪個callback函式。
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連接遠端host的資訊設置妥當,傳送一個開啟連線的初始SYN值。
tcp_connect()會立刻return一個值,而不會等到連線資料全部設定妥當才return。當連線建立時,它會呼叫第四個參數 (connected參數) 所指的函式。假如連線無法正確建立,可能是因為對方host拒絕連線,或是沒有收到該host的回應,那麼 “connected” 函式仍將被呼叫,並傳入相對應的err參數。
假如沒有可用的memory供SYN值的queue使用,tcp_connect()會傳回ERR_MEM。假如SYN值enqueue的動作成功了,tcp_connect()就傳回ERR_OK。
--- 傳送TCP data
TCP data的傳送,是藉由tcp_write()的呼叫來enqueue data。一旦資料成功傳送到遠端主機,應用程式會被通知執行一個指定的callback函式。
err_t tcp_write(struct tcp_pcb *pcb, void *dataptr, u16_t len, u8_t copy)
它會將dataptr所指的data enqueue起來。Data的長度由len參數指定。copy參數的值可以是0或1,代表是否要重新配置一塊記憶體,並將data複製過去。假如copy的值是0,就不會配置新的記憶體,只利用指標將data所在的位址記錄下來。
假如資料長度超過傳送用的buffer大小,或者寄送區段的queue長度超過lwipopts.h中定義的上限,tcp_write()的執行將會失敗,並傳回ERR_MEM。Output queue可用的位元組數量可以透過tcp_sndbuf()函式來修改。
這個函式的正確操作方式,是傳給它的data不應超過tcp_sndbuf()所定的量。假如它傳回ERR_MEM,應用程式應當等待目前queue裡面的部分資料成功地傳送到對方主機,然後再試一次。
--- 接收TCP data
TCP data的接收是以callback為基礎──當新的資料到達時,一個應用程式指定的callback函式會被喚起。應用程式接收data之後,要呼叫tcp_recved()來通知TCP去擴張它的receive window並advertise。
void tcp_recv(struct tcp_pcb *pcb,err_t (* recv)(void *arg, struct tcp_pcb *tpcb,struct pbuf *p, err_t err))
設定新資料抵達時,所要呼叫的callback函式。如果對這個callback函式傳入指向NULL值的pbuf,就代表遠端主機已關閉連線。
void tcp_recved(struct tcp_pcb *pcb, u16_t len)
通常在應用程式接收完data後呼叫。len參數指示所接收資料的長度。
--- 應用程式輪詢 (polling)
當連線idle住的時候(也就是這時候既沒有資料在傳送,也沒有資料在接收),lwIP會藉著呼叫某個特定的callback函式,而不斷地輪詢各個應用程式。這個 功能可以當做watchdog timer,用來將idle太久的connection殺掉,也可以用來等待可用的記憶體。例如,若呼叫tcp_write()後,因為沒有可用的記憶體而造成失敗,應用程式可以利用polling的功能,在connection idle一段時間後,再度喚起tcp_write()。
void tcp_poll(struct tcp_pcb *pcb, u8_t interval,err_t (* poll)(void *arg, struct tcp_pcb *tpcb))
指定輪詢時間間隔,以及用來輪詢應用程式的callback函式。時間間隔通常是用TCP coarse所產生的timer shots(一般來說一秒產生兩次)的倍數來指定。interval是10代表應用程式每隔5秒輪詢一次。
--- 關閉或中斷連線
err_t tcp_close(struct tcp_pcb *pcb)
關閉連線。假如關閉連線所需的memory不足,就傳回ERR_MEM,然後應用程式應當等待,並利用acknowledgment callback或輪詢機制重新嘗試。假如成功關閉,就傳回ERR_OK。
在呼叫tcp_close()之後,TCP程式碼會釋放掉PCB所佔用的記憶體。
void tcp_abort(struct tcp_pcb *pcb)
送一個RST(reset) segment到遠端主機,以便中斷連線。PCB將被釋放。這個函式不會有失敗的情況。
假如connection因為某項錯誤而導致中斷,應用程式將會透過err callback而收到警告。會造成中斷的錯誤是像記憶體資源不足這樣的錯誤。錯誤處理的callback函式可以透過tcp_err()來設定。
void tcp_err(struct tcp_pcb *pcb, void (* err)(void *arg,err_t err))
由於錯誤發生時,PCB可能早已被釋放掉,所以錯誤處理的callback函式並不需要接受PCB做為參數。
--- 較低階的TCP介面
TCP為低階系統提供了一個簡單的介面。在系統初始化時,tcp_init()必須在任何其他的TCP函式被呼叫前先執行。系統執行的過程中必須定期地呼叫tcp_fasttmr()和tcp_slowtmr()這兩個計時器函式。tcp_fasttmr()的執行時間間隔是TCP_FAST_INTERVAL milliseconds(定義在tcp.h中),而tcp_slowtmr()則是每隔TCP_SLOW_INTERVAL milliseconds呼叫一次。
--- UDP介面
UDP介面與TCP介面相似,但是因為UDP的複雜性較低,因此介面也顯得簡單多了。
struct udp_pcb *udp_new(void)
建立新的UDP PCB以供UDP溝通用。PCB在與本地端位址bind起來、或是連接到遠端位址之後,才進入active狀態。
void udp_remove(struct udp_pcb *pcb)
移除並釋放所有的PCB。
err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr,u16_t port)
將PCB與本地端位址bind在一起。將ipaddr設為IP_ADDR_ANY代表它將監聽任何的本地端位址。這個函式目前的傳回值一律是ERR_OK。
err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *ipaddr,u16_t port)
設定PCB的遠地端。這個函式不會造成任何網路流量,只會設定PCB的遠端位址欄位。
err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
傳送packet buffer 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)
指定UDP diagram接收完成後,應當呼叫的callback函式。
===============================================================
无OS情形下驱动层需要提供的函数如下:
(1)收到数据包的处理函数ethernetif_input(ethif层的输入函数),这个函数调用low_level_output函数,从网卡那里接收数据包,然后根据收到的数据包类型,分别调用ip_input函数和etharp_arp_input函数。这个函数跟ne2k_input函数类型,只是其中的netif->input调用被改ip_input函数。这个函数被作为函数netif_add的参数,或者独立,无论哪种情形,该函数都要被不停地调用;
(2)网卡发送数据函数low_level_output,这个函数在ethif层的初始化函数中被初始化到netif的link_output指针上。 (3)网卡接口数据函数low_level_input,这个函数被ethif层ethernetif_input函数调用,用于从网卡那里接收数据包。在实现这个函数时,需要查询网卡硬件,如果硬件收到数据包,那么从硬件那里将数据包拷贝进pbuf里,然后将该pbuf指针返回,将收到的数据包交给ethernetif_input处理;如果没有收到数据包,那么简单地返回NULL。 (4)ethif层的初始化函数ethernetif_init,这个作为netif_add函数的参数。同时这个函数调用网卡底层初始化函数low_level_init。 (5)ethif层的发送函数ethernetif_output,这个函数跟low_level_output的区别是,ethernetif_output带了一个额外的参数ip地址,一般这个函数都是调用etharp:etharp_output来实现。etharp_output根据ip地址从arp表中查询arp表,得到目标IP地址的MAC地址,然后填入到数据包中,然后调用netif->link_output函数将数据包最终发送出去。 (6)arp_timer函数,这个函数需要被定时地调用。这个函数调用etharp_tmr函数来更新arp表中的表项。 (7)网卡硬件初始化函数low_level_output,这个函数被ethernetif_init调用。 在协议栈的主循环NetMainLoop中,不断调用ethernetif_input函数,来驱动协议栈。
|