uIP TCP/IP协议栈
轉自yxwkaifa微博
第1章? uIP TCP/IP協議棧
uIP TCP/IP協議棧的目標是:即便是8位微控制器也可以使用TCP/IP協議棧進行網絡通信。雖然小而簡單, uIP不須要與他們通信的節點配有復雜,全尺寸協議棧,僅僅要通過執行輕量級協議棧可以通信便可。
代碼僅僅有區區幾k字節 。RAM消耗最低也僅僅有幾百個字節。
1.1? uIP?介紹
隨著互聯網的成功,TCP/IP 協議簇已成為全球通訊標準。
TCP/IP 是底層協議用于通過進行網頁傳輸。 電子郵件傳送。文件傳輸以及點對點網絡互聯。對于嵌入式系統,能夠執行本地 TCP/IP 使得系統能夠直連企業內部網甚至是全球互聯網。嵌入式設備有了全功能的TCP/IP 支持,將能夠與網絡中的其它主機進行通信。
傳統的 TCP/IP 實現,其代碼體積大占用資源多,對于8位或者16位的系統來說顯得有點吃力。對于僅能容納小于100k的系統。是不可能移植一個完整的TCP/IP協議棧的。
uIP設計僅僅實現了進行網絡通信所需的必要的TCP/IP組件。
提供基礎的UDP服務,重點是IP,ICMP(ping), TCP服務。uIP是用C語言編寫的。
更多為小型系統設計的TCP/IP實現都假定嵌入式設備會和一個執行全尺寸TCP/IP協議棧的工作站級別的機器通信。
在這樣的情形下。去除TCP/IP協議簇中非常少使用的功能模塊成為可能。
可是當和執行相同受限,比方是執行分布式點對點服務和協議的的設備通信時,那些功能卻又是必需的。uIP被設計成RFC兼容以使嵌入式設備有相同的通信能力,同一時候,uIP TCP/IP并非針對特定應用的。是通用的能進行網絡通信所必需組件的合集。
1.2? TCP/IP?通信
全尺寸TCP/IP協議簇包括了為數眾多的協議,覆蓋底層的ARP協議。用以將IP地址轉換成MAC地址,到應用層的協議,比方SMTP,用于電子郵件的傳輸。uIP關心的TCP和IP協議以及高層的協議,我們稱之為“應用”,對于底層協議(比方數據鏈路層協議)。這些一般由硬件或者固件實現,我們稱之為由網絡驅動程序控制的“網絡設備”。
圖 1-1? TCP/IP 模型
TCP是面向連接的。有保障的通信協議。應用層生成數據向下傳遞到傳輸層,依據使用的協議(TCP or UDP)不同加上不同協議頭信息,并把數據切割成互聯網層所能傳遞的最大數據單元(MTU,一般默認1500的不是),繼續向下傳遞到互聯網層,該層非常負責路由(依據IP地址找路),并加上對應的頭信息(包括目的地址,源地址等等),然后向下傳給網絡接口層。該層一般實現為數據鏈路層和物理層,數據鏈路層增加和設備相連的鏈路類型(以太網,還是點對點鏈路?),并把它們增加頭部。物理層就是把bit流轉換成電壓電流信號發出去;對面相連的設備進行相反地操作。
協議的相關文檔以RFC形式公布。看TCP/IP協議最權威的就是看RFC文檔了。能夠看看RFC1122文檔,他定義了端到端的通信和模型中層與層之間的通信所應遵循的。
在uIP中。影響host-to-host通信的RFC要求都實現了。僅僅是為了減小代碼體積。移除了不必要的應用程序和協議棧之間的接口,比方軟件錯誤報告機制和動態的TCP連接相關的服務類型配置。由于非常少有軟件使用這方面的機制。移除了也不失一般性。
1.3??內存管理
uIP所針對的目標架構,其RAM都是稀缺的。這樣TCP/IP協議棧能用到的RAM就很有限。或許就那么幾K。
也就是不能像傳統的TCP/IP實現那樣,資源不夠。
uIP并不顯式的使用動態內存分配。
相反,他使用一個單獨的全局buffer:uip_buf來處理數據包。同一時候使用一個固定大小的表來保持連接狀態。包緩存buffer被設計的足夠大能夠容納最大的包分組。當網絡設備接收到數據時,網絡驅動程序會將數據放到這個全局的buffer中,然后調用TCP/IP協議棧,進行對應處理。這里有一個linux內核架構圖,或許對你了解應用程序、協議棧、驅動、控制器之間的關系有所幫助。假設包中有數據(有可能僅僅是握手協議數據,沒有應用數據)。應用程序須要盡快的從buffer中取走數據處理或者拷貝到第二緩存中去稍后處理,由于僅僅有一個全局buffer,不然的話可能會被下一個包覆蓋掉。直到數據被應用程序處理完之前,包緩存是不會被新包覆蓋的(?)。假設應用程序正在進行處理數據,興許的包必需進行硬件級或者驅動級的排隊。大多數的以太網控制器有一個硬件緩存至少可容納4個最大尺寸的以太網幀。
假設緩存滿了。興許的包就會丟棄。僅僅有當打開多個連接時,才可能出現這樣的情形以致影響性能。原因在于uIP通告了一個非常小的接收窗體(窗體,協議中的概念),也就意味著每一個連接上僅僅有個TCP分組。
在uIP中,用于接收包的全局包緩沖buffer相同適用于數據報頭的發送。假如應用程序須要發送動態大小的數據,可能會使用不作為報頭暫時緩存的全局緩存的一部分。為了發送數據。應用程序會向協議棧傳遞指向數據的指針和數據的長度。
一旦TCP/IP報頭生成并拷貝到全局緩存后。設備驅動就會發送出報頭和數據(數據不是被一層層的協議頭封裝的么)。假設須要重傳數據僅僅能又一次生成數據了。而不會有隊列等待的。
uIP的內存消耗非常大程度上取決于實現其的設備上應用程序的情況。內存配置決定了系統所能處理的流量值和同一時候在線連接的數量。在發送一個超大郵件的同一時候還執行著大量動態網頁請求的webserver所消耗的內存肯定要比僅僅執行簡單Telnetserver的大的多。
僅有200字節RAM的設備上執行uIP也是能夠的。僅僅是這樣的配置極大的影響了網絡的吞吐以及可同一時候在線的連接數。
1.4??應用程序接口 (API)
應用程序接口 (API) 定義了應用程序怎樣和TCP/IP協議棧進行交互。
最長使用的API是大多數unix類系統中BSD風格的套接字API,并且對Windows下的WinSock API也有著巨大的影響。由于套接字API使用stop-and-wait語義,因而須要多任務操作系統的支持。
這樣一來,繁重的任務管理以及上下文切換,任務棧分配對于uIP的目標群體來說是不現實的。也就是說BSD套接字對于uIP的不合適的。
相反,uIP使用事件驅動接口,有事件發生時相應的應用程序(UIP_APPCALL( ),如無標注下文中的應用程序均指UIP_APPCALL宏相應的函數)會被調用。
一個被實現為C函數執行在uIP頂層的應用程序會被uIP調用來響應特定的事件。就像telnet.h文件里的:
#ifndef UIP_APPCALL
#define UIP_APPCALL???? telnetd_app
#endif
當接收到數據、成功將數據發送到連接的還有一端、建立了一新連接亦或是重傳了數據時,uIP就會調用對應的應用程序(uip_process->UIP_APPCALL();)。應用程序同一時候會周期的輪詢以查看是否有新數據到達。由應用程序映射不同服務到不同port和連接。對于棧僅僅提供一個回調函數。也就是說盡可能的降低協議棧的響應時間,既便是低端的系統也能做到高速響應到來的數據或者連接請求,處理數據就推到上層去吧
不像其它的TCP/IP協議棧,uIP重傳機制要借助于應用程序。像有的TCP/IP協議棧會保留一個要發送數據的副本在內存里直到收到對方的接收成功反饋才丟到數據,即便須要重傳時應用程序生成數據速度非常快,由于內存多多保存會兒沒事。要重傳直接發送就得了。
前文說了。uIP的目標架構起RAM都是非常少的,保存個副本等待反饋是不明智的。uIP的作法相同是把這事推給上層去做:應用程序負責生成數據并重傳。
當網絡設備驅動將數據發送出去之后,uIP并不追蹤包的內容,丟了還是怎么滴,他僅僅要求上層的應用可以積極參與重傳便好,各司其職嘛。當uIP決定要重傳某個片段(segment)時,會設置重傳標記(????? ? UIP_STAT(++uip_stat.tcp.rexmit);)然后告訴應用程序:那個誰把這個重傳一下。
應用程序就檢查一下這個標記,然后就生成數據重傳。
從應用程序角度看重傳無異于數據原始發送。就是說重傳的代碼和發送數據的代碼是可以復用的。雖然重傳是應用程序進行的。但何時須要重傳,協議棧必需負起責任。
假設協議棧什么也不做會由于應用程序參與重傳而添加其復雜度。
1.4.1? ?應用程序事件
當有事件發生時。uIP就會調用有C語言實現的應用程序(這里應用程序指的是上層處理協議棧接收到的數據的函數。比方telnet中的telnet_app)UIP_APPCALL(),是以C語言宏的形式定義的,在詳細的應用協議(telnet,smtp)中會檢查宏定義。沒有定義則賦值。差別不同的事件是由對應的響應測試函數完畢的。
1.4.2? ?連接指針
一旦應用程序被uIP調用。全局變量uip_conn會指向表示當前連接的uip_conn結構(uip_connect()中會返回該結構)。
uip_conn結構中的域能夠告知當前所連接的ip地址,以及通過uip_conn->lport來獲得提供的服務。比方,假設lport為80則相應的是http服務;假設是23則是telnet服務。
1.4.3? ?接收數據
uIP通過調用uip_newdata()能夠知道遠程主機已經發送了新的數據。其長度能夠通過調用uip_datalen()來獲得。
uIP并不會緩存數據,并且一旦從應用程序返回,數據就會被覆蓋。因此。應用程序要么直接處理到來的數據,要么拷貝到其它什么地方去稍后處理。
1.4.4? ?發送數據
當發送數據時。uIP會依據可利用buffer大小以及TCP窗來調節應用程序發過來的數據。通過uip_mss()函數來獲得當前連接所能傳輸的最大分組大小。
應用程序通過調用uIP中uip_send()函數發送數據。uip_send()使用倆個參數:要發送的數據指針和數據的長度。和通常的TCP/IP協議棧一樣,棧和驅動層使用一個緩存:sk_buff;uip_send僅僅是將應用程序的數據指針拷貝到全局的uip_sappdata中,其會賦給uip_appdata。終于會賦給uip_buf。假設應用程序要RAM空間來生成實際的數據。能夠使用包緩存(指向uip_appdata指針)來實現。
在uip協議棧中。是怎樣傳遞給驅動層的呢?由于uip_buff是全局的,這樣驅動直接就調用dev_send_data(uip_buff,len);把數據發送出去了。
在一個連接上,應用程序一次僅僅能發送一塊數據,在數據發送完畢前,同一時候對uip_send()進行多次調用時不可能的。
1.4.5? ?數據重傳
重傳是由TCP計數器來驅動的。每一次對周期計數器的調用,都會對每連接重傳計數器進行減一操作。當重傳計數為零時,就須要進行重傳了。uIP不追蹤由網卡驅動發送出去的包,這就要求應用程序可以積極參與重傳。當uIP決定須要重傳時。應用程序通過uip_rexmit()來檢測是否有UIP_REXMIT標志,有則開始重傳。telnet.c文件里:
if(uip_rexmit()|| uip_newdata() ||? uip_acked()) {
???senddata(s);
? }else if(uip_poll()) {???
???senddata(s);
? }
1.4.6? ?關閉連接
應用程序通過調用uip_close()來關閉當前的連接:
if(s->flags & FLAG_CLOSE) {
???uip_close();
???return;
? }
也可通過調用uip_abort()函數來終止產生了致命錯誤的連接:
if(s->flags & FLAG_ABORT) {
???uip_abort();
???return;
? }
假設連接已由遠端關閉,uip_closed()返回1,應用程序可能會做必要的清理。
1.4.7??差錯報告
uIP通過uip_aborted()和uip_timeout()來測試是否發生了對應的錯誤。
1.4.8??輪詢
當連接處于空暇狀態時,uIP會周期的輪詢各個應用程序。應用程序則通過uip_poll()來檢測是否正在被uIP輪詢。輪詢就採取動作。
輪詢有倆個主要作用:一、讓應用程序能夠能夠關掉空暇太久的連接;二、讓準備好數據的應用程序能夠發送數據。應用程序僅僅有被uIP調用的時候才干發數據,也就是說想在空暇連接上發送數據僅僅有等到輪詢到的時候才行。
1.4.9??監聽port
uIP維護著一個TCPport監聽列表。
uip_listen()用于向uip_listenports[UIP_LISTENPORTS]數組加入一個新的監聽port。當一個在指定port上的連接請求到達時。uIP會創建一個新的連接并調用對應的應用程序。
一個連接已建立時,uip_connected()會返回真。
檢查uip_conn結構中lport域能夠知道哪個端口和當前連接相相應。
這個過程是在uip_connect(*ripaddr,rport)函數中完畢對uip_conn結構賦值。并建立一個連接:通過找一個未使用的最小的端口號賦給lport,就實現和rport相應。
1.4.10??打開連接
uIP能夠通過uip_connect()打開一個新的連接,正如上小節說的uip_connect()會分配一個本地port號,初始化一些域,設置tcp狀態標志位,賦值ripaddr等等。最后返回表示一個連接的uip_conn結構。當然假設最大連接數已滿就返回NULL指針。
由于uIP用包括倆個16bit元素的數組來表示一個32位的ip地址,使用uip_ipaddr()能夠用來封裝一個ip地址到2個16位的。
以下展示了兩個樣例,第一個展示了使用uip_connect()嘗試建立一個到TCPport號為8080的連接,假設達到最大連接數,則其返回NULL。而且使用uip_abort()來中止當前連接。
void connect_example1_app(void) {
if(uip_connect(uip_conn->ripaddr,HTONS(8080)) == NULL) {
uip_abort();
}
}
第二個樣例展示了怎樣打開一個到指定ip地址的連接,該例中沒有進行錯誤檢查。
void connect_example2(void) {
u16_t ipaddr[2];
uip_ipaddr(ipaddr, 192,168,0,1);
uip_connect(ipaddr, HTONS(8080));
}
1.5? uIP設備驅動
圖 1-5
從網絡設備驅動角度看。如圖1-5,uIP由uip_input()和uip_periodic()這兩個C語言函數組成。
當接收到IP包時設備驅動會調用uip_input()來把包放到包緩存uip_buf中。uip_input()負責處理包。當他返回時往外發送的數據或許應經準備好并放在uip_buf中,接著調用網絡設備驅動的dev_send()把數據發出去。
uip_periodic()會周期的輪詢各個連接。典型的是每隔1s進行輪詢。uIP用該函數來驅動協議計數器和重傳。一旦他返回了,須要的包數據可能已經在uip_buf中了。接著就須要掉用驅動程序的dev_send()函數發出數據;
uip_input()和uip_periodic()都是對uip_process(arg)的調用;
1.6??架構相關的函數
uIP要求那些打算執行uIP的目標架構須要實現架構相關的函數。
uIP代碼中也提供了一些通用的C實現函數。在uip-arch.c文件里實現了簡單的CRC校驗。
1.6.1??校驗和計算
TCP和IP協議為TCP和IP的數據和協議頭包實現了校驗和。
因為該校驗和是通過對發送和接收數據一個字節一個字節的進行計算,故而效率必需高效。也就是說uIP的目標架構對于校驗和的計算也是架構相關的。進而uIP沒有實現架構相關的校驗和計算函數,在uip-arch.c文件里。你必須實現uip_ipchksum()和uip_tcpchksum()這倆個架構相關的函數。
處于對效率的考慮,你能夠用匯編而不是C語言來寫。
uIP發行版中提供了一個用C語言實現的校驗和計算函數。相同是在uip-arch.c文件里。
1.6.2 ?32位運算
由于TCP協議使用32bit的序列號。所以任一個TCP實現作為對常規協議的處理都會涉及到32位的計算。可是uIP的目標架構一般都是8位或者16位的。因此uIP的主代碼并沒有涉及到32位的計算,而是把這塊留給了架構相關的代碼去負責。
架構相關的代碼中必需實現uip_add32()這個用來進行32位加法的函數,其結果存儲在uip_acc32這個全局變量中了。
1.7??實例
這一小節中介紹一些很easy的uIP應用程序,在uIP發行包中包括了一些更復雜的應用。telnet,smtp等等。
1.7.1??一個很easy的應用程序
第一個簡單的應用程序用來監聽1234port上的連接。一旦連接建立完畢,他會用“ok”來應答全部發給他的數據。
void example1_init(void) {????????? ?????? // 初始化函數
uip_listen(HTONS(1234));?? // 在port1234上監聽
}
// 這意味著你須要在應用程序文件里進行例如以下的宏定義:
// #ifndef UIP_APPCALL
// #define UIP_APPCALL?? example1_app
// #endif
void example1_app(void) {?????????
if(uip_newdata() || uip_rexmit()) {? //有新數據要發送或者須要重傳了
uip_send("ok\n", 3);?????????? ?????? //發送“ok”
}
}
初始化函數調用uIP的uip_listen()函數來注冊一個要監聽的port號。應用程序example1_app()用uip_newdata()和uip_rexmit()來推斷被調用的原因。
假設是連接還有一端發送了數據,就用“ok”來應答。假設是數據丟失須要重傳也用“ok”進行應答。
樣例已經展示了一個完整的應用范本。
并沒有限定應用程序必須實現全部的事件類型比方uip_connected()或者uip_timedout()。
1.7.2??一個高級應用
第二個樣例展示了uip_conn結構體中應用程序狀態域是怎樣使用的。
和第一個樣例相似,當監聽的port上有數據發來時就用“ok”進行應答。
最大的差別在于,第二個樣例還會在連接建立完畢時輸出一個“Welcome。”。
雖然看上去沒啥差別。可是這一點細微的變化卻相應用程序的實現產生了不小的影響。
復雜度添加的原因在于假設網絡中的數據丟失了,應用程序必須知道哪個數據須要重傳。假設是“Welcome!”消息丟了,應用程序必須重傳welcome,假設是“ok”丟了。就得又一次發送ok。
假設遠程主機沒有對“Welcome。”作出應答。應用程序可一斷定數據丟讀了。可是一旦主機作出應答就能夠確信丟掉的數據是“ok”。
這樣應用程序就處在倆中狀態之中的一個:WELCOM-SENT,welcome已經發出去,可是沒有收到應答。WELCOME-ACKED,發送成功且收到應答。
當遠端主機成功連接到應用程序。應用程序會發送“Welcome!”而且把自身狀態設置成WELCOME-SENT。當遠端主機應答成功,狀態變成WELCOME-ACKED。
假設主機發送進一步的數據,應用程序就以發送“ok”來應答。
假設請求應用程序重傳上一個消息。應用程序首先看一下他的狀態,假設是WELCOME-SENT,他就知道welcome發送出去了可是沒有收到應答,該重傳的是“welcome!
”。
假設處在WELCOME-ACKED狀態,則須要重傳的是”ok“。
應用程序實現例如以下,一些配置信息緊隨其后(一個簡單的狀態機):
struct example2_state {
?????? enum{WELCOME_SENT, WELCOME_ACKED} state;
};
void example2_init(void) {
?? ? uip_listen(HTONS(2345));
}
void example2_app(void) {
?????? structexample2_state *s;
?????? s= (struct example2_state *)uip_conn->appstate;
?????? if(uip_connected()){
????????????? s->state= WELCOME_SENT;
????????????? uip_send("Welcome!\n",9);
????????????? return;
?????? }
?????? if(uip_acked()&& s->state == WELCOME_SENT) {
????????????? s->state= WELCOME_ACKED;
?????? }
?????? if(uip_newdata()){
????????????? uip_send("ok\n",3);
?????? }
?????? if(uip_rexmit()){
????????????? switch(s->state){
????????????? caseWELCOME_SENT:
???????????????????? uip_send("Welcome!\n",9);
???????????????????? break;
????????????? caseWELCOME_ACKED:
???????????????????? uip_send("ok\n",3);
???????????????????? break;
????????????? }
?????? }
}
配置:
#define UIP_APPCALL ??? example2_app
#define UIP_APPSTATE_SIZE sizeof(structexample2_state)
1.7.3??怎樣區分不同的應用程序
假設一個系統要執行多個應用時,區分他們做好的方法就是用TCPport號。
以下的代碼顯示了怎樣將上面倆個樣例綁定到一個應用上去:
void example3_init(void) {
?????? example1_init();
?????? example2_init();
}
void example3_app(void) {
?????? switch(uip_conn->lport){
?????? caseHTONS(1234):
????????????? example1_app();
????????????? break;
?????? caseHTONS(2345):
????????????? example2_app();
????????????? break;
?????? }
}
1.7.4??使用TCP流量控制
以下的樣例展示了向一個主機發送HTTP請求下載文件到一個慢速的存儲設備上。怎樣使用流量控制功能:
void example4_init(void) {
?????? u16_tipaddr[2];
?????? uip_ipaddr(ipaddr,192,168,0,1);
?????? uip_connect(ipaddr,HTONS(80));
}
void example4_app(void) {
?????? if(uip_connected()|| uip_rexmit()) {
????????????? uip_send("GET/file HTTP/1.0\r\nServer:192.186.0.1\r\n\r\n", 48);
????????????? return;
?????? }
?????? if(uip_newdata()){
????????????? device_enqueue(uip_appdata,uip_datalen());
????????????? if(device_queue_full()){
???????????????????? uip_stop();// 這里僅僅是設置了tcpstateflag標志位而已
????????????? }
?????? }
?????? if(uip_poll()&& uip_stopped()) {? // 推斷是輪詢且之前是被停止的
????????????? if(!device_queue_full()){??????????? // 隊列未滿則重新啟動連接
???????????????????? uip_restart();
????????????? }
?????? }
}
由于改應用程序僅僅發送GET請求,所以不管是連接成功還是須要重傳其代碼是一樣的;當從遠程主機接收到數據時調用設備驅動中的device_enqueue()函數將數據入列。注意:這里假定device_enqueue()會把數據拷貝到他自己的buffer中。uip_appdata中的數據會被下一次到來的包覆蓋掉。
假設設備的緩沖隊列滿了,應用程序通過調用uIP的uip_stop()函數來停止繼續下載文件。在調用uip_restart()調用之前。應用程序能夠確保不會繼續接收數據。應用程序輪詢事件能夠用來檢查設備隊列是否還是滿的,未滿則數據流能夠通過uip_restart()又一次啟用。
1.7.5??簡單的webserver
以下的代碼是一個簡單的文件server,其監聽2個port,依據port號來選擇發送哪個文件:
struct example5_state{
?????? char *dataptr;
?????? unsigned int dataleft;
};
voidexample5_init(void) {
?????? uip_listen(HTONS(80));
?????? uip_listen(HTONS(81));
}
voidexample5_app(void) {
?????? struct example5_state *s;
?????? s = (structexample5_state)uip_conn->appstate;
?????? if(uip_connected()) {
????????????? switch(uip_conn->lport) {
????????????? case HTONS(80):
???????????????????? s->dataptr =data_port_80;
???????????????????? s->dataleft =datalen_port_80;
???????????????????? break;
????????????? case HTONS(81):
???????????????????? s->dataptr =data_port_81;
???????????????????? s->dataleft =datalen_port_81;
???????????????????? break;
????????????? }
????????????? uip_send(s->dataptr,s->dataleft);
????????????? return;
?????? }
?????? if(uip_acked()) {
????????????? if(s->dataleft < uip_mss()){
???????????????????? uip_close();
???????????????????? return;
????????????? }
????????????? s->dataptr += uip_conn->len;
????????????? s->dataleft -=uip_conn->len;
????????????? uip_send(s->dataptr,s->dataleft);
?????? }
}
程序狀態由數據指針和要發送數據大小兩部分組成。這里的appstate[UIP_APPSTATE_SIZE]非常像驅動中的*private_data結構用于存儲設備相關的結構體。
1.7.6??結構化應用程序設計
以下的樣例給出了一個結構化設計范本:
voidexample6_app(void) {
?????? if(uip_aborted()) {????? //?連接中止
????????????? aborted();
?????? }
?????? if(uip_timedout()) {???? //?超時
????????????? timedout();
?????? }
?????? if(uip_closed()) {?????? ?????? //?連接關閉
????????????? closed();
?????? }
?????? if(uip_connected()) {?? //?已建立連接
????????????? connected();
?????? }
?????? if(uip_acked()) {??????? //?應答
????????????? acked();
?????? }
?????? if(uip_newdata()) {???? //?處理新數據,設置要發送數據指針
????????????? newdata();
?????? }
?????? if(uip_rexmit() ||? uip_newdata() ||? //重傳、有新數據、應答、已連接、輪詢?發送數據
???????????????????? uip_acked()||uip_connected() || uip_poll()) {
????????????? senddata();
?????? }
}
函數從檢查不論什么的錯誤開始:uip_aborted()或者uip_timedout()。假設確實有錯誤產生。則運行對應的動作。接著調用uip_connected()檢查是否已建立連接,是則調用connected()函數運行對應的動作(發送“Welcome!”等等),比方還有初始化應用狀態。最后一個if語句中,建立完畢后可能要傳數據,則senddata()。
接下來的代碼,告訴我們上面用到的一些處理函數其形式大概是啥樣子的:應用簡單的等待連接上的數據到達,并通過發送“Hello World!”來應答。
而且,為了展示怎樣編寫狀態機。消息被拆分成倆部分進行發送:“Hello”和“World!”:
#define STATE_WAITING 0
#define STATE_HELLO???????????? 1
#define STATE_WORLD?????????? 2
struct example6_state{ //?程序狀態變量
?????? u8_t ?????? state;??????? //?當前狀態
?????? char ?????? *textptr;?????????? //?數據指針
?????? int ???????? textlen;???????????? //?數據長度
};
static void aborted(void){}
static voidtimedout(void) {}
static voidclosed(void) {}
static voidconnected(void) {
?????? struct example6_state *s = (structexample6_state *)uip_conn->appstate;
?????? s->state = STATE_WAITING;
?????? s->textlen = 0;
}
static voidnewdata(void) {
?????? struct example6_state *s = (structexample6_state *)uip_conn->appstate;
?????? if(s->state == STATE_WAITING) {
????????????? s->state = STATE_HELLO;
????????????? s->textptr = "Hello";
????????????? s->textlen = 6;
?????? }
}
static voidacked(void) {
?????? struct example6_state *s = (structexample6_state *)uip_conn->appstate;
?????? s->textlen -= uip_conn->len;
?????? s->textptr += uip_conn->len;
?????? if(s->textlen == 0) {
????????????? switch(s->state) {
????????????? case STATE_HELLO:
???????????????????? s->state = STATE_WORLD;
???????????????????? s->textptr ="world!\n";
???????????????????? s->textlen = 7;
???????????????????? break;
????????????? case STATE_WORLD:
???????????????????? uip_close();
???????????????????? break;
????????????? }
?????? }
}
static voidsenddata(void) {
?????? struct example6_state *s = (structexample6_state *)uip_conn->appstate;
?????? if(s->textlen > 0) {
????????????? uip_send(s->textptr,s->textlen);
?????? }
}
?
?
?
?
?
?
圖?1-7-6?簡單的狀態機
真正進行數據發送的是senddata()函數。acked()和newdata()僅僅是標識有數據須要發送,其長度是多少。
senddata()終于回調用uip_send()來發送數據。
切記senddata()函數永遠不能做改變應用程序狀態的事。相反這些應該在acked()和newdata()進行。
總結
以上是生活随笔為你收集整理的uIP TCP/IP协议栈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 台式计算机售后行业标准,电脑“三包”还有
- 下一篇: MTK芯片资料下载集锦(部分芯片系列,正