曹欲曉,韓 磊
(南京工程學院 計算機工程學院,江蘇 南京 211167)
隨著嵌入式技術、網絡技術的發展,實現網絡互聯已經成為嵌入式系統發展的一個必然趨勢。在目前的技術條件下,越來越多的嵌入式系統選擇了TCP/IP作為與其他計算機系統互聯的網絡協議。嵌入式TCP/IP協議棧已經成為嵌入式系統研究與應用中的一個重要領域。
由于嵌入式系統的軟硬件資源都較為有限,大多數嵌入式系統中運行的TCP/IP協議棧均根據嵌入式系統的特點進行了相應的裁剪。目前應用比較廣泛的嵌入式TCP/IP 協 議 棧 有 :ucTCP-IP、LWIP、uIP、Linux TCP/IP等。其中uIP是專為8 bit和16 bit的嵌入式微控制器設計的微型TCP/IP協議棧,它具有良好的互操作性,并遵循RFC標準。uIP協議棧的特點是具有很小的代碼量,運行時需要的內存很少,實現了常用的TCP/IP協議;代碼注釋詳盡,可以用于商業或非商業用途[1]。由于具有上述特點,uIP被廣泛應用在嵌入式系統的網絡互聯中。
在使用uIP的嵌入式系統的軟件體系結構中,uIP協議棧相當于一個代碼庫,它通過一系列的函數實現與底層硬件和上層應用程序的通信。uIP協議棧與系統底層和上層應用之間的關系如圖1所示[2]。

圖1 uIP協議棧體系結構
從圖 1可以看出,uIP協議棧主要提供了uip_input()和uip_periodic()2個函數供系統底層調用。uIP協議棧與應用程序的主要接口是UIP_APPCALL()和UIP_UDP_APPCALL()。
uIP初始化時調用uip_init()函數,它的主要功能是初始化協議棧的監聽端口,并把所有連接設置為關閉狀態。當網絡控制芯片驅動程序接收到一個數據包時,驅動程序將數據包放入全局緩沖區uip_buf中,同時把包的大小賦給全局變量uip_len。然后uIP的主控部分調用uip_input()函數,該函數將會根據數據包首部的協議標識處理這個包,并在需要時調用上層應用程序。當uip_input()返回時,一個輸出數據包被放在同一個全局緩沖區uip_buf中,其大小賦給uip_len。如果uip_len是0,則說明沒有包要發送,否則主控部分調用底層系統的發包函數將數據包發送到網絡上[3]。
uIP周期計時用于驅動所有的uIP內部時鐘事件。當周期計時激發后,每一個TCP連接都會調用uIP函數uip_periodic()。 類似于 uip_input()函數,uip_periodic()函數返回時,輸出的IP包要放到uip_buf中,供底層系統查詢uip_len的大小以決定是否發送。
由于使用TCP/IP的應用很多,因此應用程序作為單獨的模塊由用戶實現。uIP提供一系列接口供用戶程序調用,其中大部分接口是作為C的宏命令出現的,之所以這樣做主要是考慮到速度、代碼大小、效率和堆棧的使用。用戶需要把對網絡數據包的處理函數作為接口提供給uIP,并將這個函數定義為宏UIP_APPCALL()或者 UIP_UDP_APPCALL()。UIP_APPCALL()是用戶對 TCP數據包的處理,UIP_UDP_APPCALL()是用戶對 UDP數據包的處理[4]。這樣,uIP在接收到底層傳來的數據包后,在需要送到上層應用程序處理的地方,直接調用UIP_APPCALL()或者 UIP_UDP_APPCALL()即可,無需修改uIP。
當uIP接收到一個UDP數據包后,首先從包頭中取出數據的長度,然后重新對包進行校驗,如果校驗和不對,則直接丟掉這個包。如果校驗無誤,則對收到的包進行解復用。此時進行如下判斷:

上述代碼中用到的主要變量、數據結構和函數的含義是:

在uIP的實現中,如果以上判斷語句為真,則對接收到的數據包進行處理,處理過程包括調用用戶上層處理程序 UIP_UDP_APPCALL()、構造新包的包頭、計算新包的校驗和等,然后將構造好的返回UDP包送到IP層進行處理。
通過對uIP中UDP協議實現過程的分析可以發現,uIP沒有提供初始化指定端口的函數,僅提供了一個對給定IP地址上給定端口建立UDP連接的函數,其原型是struct uip_udp_conn*uip_udp_new(uip_ipaddr_t*ripaddr,u16_t rport)。由于作為服務端運行時必須指定監聽端口[5],而 uIP沒有提供此功能,因此要讓uIP作為服務端運行,必須對uIP進行改進。
UDP協議作為服務端運行時,同TCP一樣,必須在某個指定端口上監聽客戶端是否有數據包發送,如果有則還要接收數據包,這就要求在uIP記錄UDP連接的數據結構uip_udp_conn中設置本地端口號一項,具體實現步驟如圖2所示。

圖2 UDP服務端口初始化
uip_process函數接收到網絡控制芯片驅動程序送來的數據包后,當判斷出收到的包是UDP包,執行2.1中的判斷并且得到結果為真后,但還需要再做以下工作:如果uip_udp_conn中的目的端口號為0,則說明這是一個來自客戶端的首次與服務端進行通信的數據包,服務端尚不知道此客戶端的源端口,因此要把uip_udp_conn中的目的端口號設為收到的包中的源端口號,把uip_udp_conn中的目的IP地址設為收到的包中的源IP地址,具體代碼如下:

UDP服務端的端口應該可以為來自多個客戶端的請求提供服務,而UDP本身是一種無連接的傳輸層協議,因此在每次uIP作為服務端的UDP通信結束之后,還要釋放uip_udp_conn中記錄的目的端口號,以便下次接收來自不同IP、不同端口的新請求,否則當來自其他端口的請求到達時,uIP會不予響應。
在uIP的官方網站上下載到uIP 1.0的源代碼之后,按照本文給出的幾個步驟對uIP 1.0進行改造之后,利用gcc編譯器把uIP 1.0編譯成S3C2410上的可執行代碼,把基于S3C2410的開發板作為UDP服務器,運行Windows XP的PC機作為客戶端,兩者通過一條交叉網線相聯,在PC機上的測試程序發出UDP請求后,運行在S3C2410上的uIP可以對PC通過UDP協議發出的數據進行處理,并給PC作出正確的回復。實驗證明,通過對uIP進行本文所述的改進之后,uIP具有了作為UDP服務端的能力。
[1]http://www.sics.se/~adam/uip/index.php/Main_Page.
[2]ADAM D.The uIP embedded TCP/IP stack the uIP 1.0 reference manual.June 2006.
[3]ADAM D.Full TCP/IP for 8-bit architectures[C].In Proceedings of the First International Conference on Mobile Applications,Systems and Services(MOBISYS 2003), San Francisco, May 2003.
[4]ADAM D, OLIVER S, THIEMO V, et al.Protothreads:simplifying event-driven programming of memory-constrained embedded systems[C].In Proceedings of the Fourth ACM Conference on Embedded Networked Sensor Systems(SenSys 2006), Boulder, Colorado, USA, November 2006.
[5]FOROUZAN B A,FEGAN S C著.TCP/ZP協議簇 [M].謝希仁等,譯.北京:清華大學出版社,2006.