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