TCP/IP在51單片機上的實現特點和方法
李章林1 ,張立民1
( 1 南開大學信息技術科學學院, 天津 300071 )
摘要(yao):為了實現51單片(pian)機(ji)接入internet,開(kai)發基于(yu)51單片(pian)機(ji)的(de)(de)(de)(de)TCP/IP具有重要意義。為此(ci)開(kai)發了zlIP,它(ta)是針(zhen)對(dui)51單片(pian)機(ji)的(de)(de)(de)(de)特點(dian)使用(yong)KeilC51編(bian)程語(yu)言編(bian)寫的(de)(de)(de)(de)TCP/IP,具有代(dai)碼量小(xiao)和(he)(he)(he)兼(jian)容(rong)BSD套接字(socket)用(yong)戶(hu)接口(kou)等(deng)特點(dian)。zlIP1.0版(ban)注重于(yu)運行速度,zlIP2.0版(ban)注重于(yu)用(yong)戶(hu)接口(kou)的(de)(de)(de)(de)易用(yong)性,以從不同的(de)(de)(de)(de)角度試驗在51單片(pian)機(ji)上(shang)實現(xian)TCP/IP的(de)(de)(de)(de)特點(dian)。通(tong)過(guo)比較(jiao)兩(liang)個版(ban)本的(de)(de)(de)(de)優缺點(dian)和(he)(he)(he)吸收國(guo)內外其它(ta)TCP/IP的(de)(de)(de)(de)優點(dian),分(fen)析(xi)了在單片(pian)機(ji)上(shang)實現(xian)TCP/IP的(de)(de)(de)(de)速度、程序(xu)大(da)小(xiao)、內存大(da)小(xiao)、編(bian)譯器等(deng)特點(dian),并針(zhen)對(dui)這些(xie)特點(dian)總結和(he)(he)(he)提(ti)出多種技巧(qiao)和(he)(he)(he)方(fang)法(fa),并對(dui)這些(xie)技巧(qiao)、方(fang)法(fa)的(de)(de)(de)(de)優缺點(dian)進行了分(fen)析(xi)。最后講述了幾點(dian)關(guan)鍵技術:設計清晰的(de)(de)(de)(de)TCP/IP和(he)(he)(he)應用(yong)層的(de)(de)(de)(de)接口(kou)、采用(yong)前后臺和(he)(he)(he)多線程程序(xu)結構的(de)(de)(de)(de)比較(jiao),內存管(guan)理(li)方(fang)法(fa)和(he)(he)(he)防止多余(yu)的(de)(de)(de)(de)內存拷貝,實現(xian)數據包(bao)整序(xu)重發和(he)(he)(he)窗口(kou)控制等(deng)。
關鍵詞:TCP/IP;單片機;zlIP
中(zhong)文分(fen)類號(hao): 文獻標識碼:A 文章編號:1006-8740(2003)-00-0000-00
1 引言
隨著網絡應用的不斷擴大,將各類電子設備接入Internet的需求越來(lai)越大。電(dian)子(zi)設(she)備入網(wang)有多種解(jie)決方案:例(li)如使用嵌入式系統,如使用ARM+Linux;一些實現TCP/IP的芯(xin)片(pian)也(ye)已經(jing)可以獲得,例(li)如Analog
Devices推出的Internet Modem(1);在51系列單片機運行TCP/IP協議棧等。前兩種方案具有良好的性能,而在單片機上實現TCP/IP的方案具有很低的價格,在某些對網絡速度要求不高的領域,有廣闊的應用前景。
2 TCP/IP在單片機上實現的特點
2.1 速度慢
我們先來了解51單片機網絡傳輸的極限速率。TCP/IP發送過程中主要的運算量集中在三個部分:應用程序將數據拷貝到RAM、計算TCP校驗和、將RAM中的數據包拷貝到網絡設備的發送緩沖區。對每一個字節數據,兩次拷貝大致共使用12×2=24個指令周期;計算TCP校驗和使用16個指令周期。采用12M的晶振,最高網絡傳輸速度為25K字節/秒。實際上要比這個速度慢,zlIP第一版速度只有11.752K字節/秒。
為了提高速度可以采用快速的單片機比如Winbond公司的77E58或者AVR單片(pian)機,當然還可以提高晶振頻率。除此之(zhi)外還有(you):使(shi)用(yong)KeilC時,盡量避免使(shi)用(yong)Reentrant函(han)數,Reentrant類型的函(han)數比(bi)一(yi)般(ban)函(han)數速度要(yao)(yao)慢很多,但是某些時候為了程序(xu)結構(gou)的需要(yao)(yao)必須使(shi)用(yong)Reentrant,這(zhe)就需要(yao)(yao)在速度和(he)結構(gou)之(zhi)間作一(yi)個選(xuan)擇;指(zhi)針(zhen)使(shi)用(yong)“指(zhi)定(ding)存儲類型”的指(zhi)針(zhen)(memory-specific
pointer)(2);精簡協議棧去除運算量大但是用處不大的功能,目前zlIP中TCP定時重發時間是固定的,也沒有擁塞窗口控制,也沒有IP層路由算法;防止數據包的不必要的拷貝;優化計算校驗和和內存拷貝函數。
2.2 程序存儲空間和外部RAM空間不大
通常TCP/IP協議棧需要大量的RAM來存儲需要被應答的TCP包,如果規定時間內沒有被應答則重發這個TCP包,被應答以后釋放這個TCP包。
為了減小RAM使用量,能否不存儲需要被應答的TCP包?(3)。當數據包需要重新發送時,如果能夠重新產生數據包所需的數據,那時就可以不存儲。例如存在于EEPROM中的html網頁。但是這種方法存在以下兩個缺點:一,TCPIP和應用層接口變得復雜。當需要重發時,必須需要應用層重新產生數據,實際上將TCP負責的重發機制轉移到了應用層。應用層程序編寫變得復雜。二,對數據無法重新產生的應用不適用。例如語音采集。
2.3編譯器
TCP/IP一般采用C語言或者混合匯編來寫。以KeilC516.0編譯器為例。與X8086編輯的代碼不同,使用KeilC要注意函數重入、指針、函數指針這三個問題。使用可重入函數和一般指針(generic
pointer)使得程序代碼增大,運行速度變慢。使用函數指針時,要么需要手動重建調用樹(Call tree),要么將通過函數指針調用的函數都設置為可重入函數。所以盡量少用重入函數、函數指針和一般指針。
3 zlIP的特點和實現技巧
3.1
特(te)點
其它的TCP/IP有lwIP、uIP、ucIP、tinyTCP等。其中lwIP、uIP、tinyTCP已經成功地移植到了單片機。lwIP是專門為微處理器設計的TCP/IP協議棧,lwIP的功能很全面,但是相對來說代碼較大,有人做過移植lwip+ucOSII代碼量為60K(4)。uIP側重于減小代碼量(選擇AVR為目標器件時,代碼為5K左右)和減小RAM使用量(100字節左右)。uIP采用了不保存需要應答的數據包的RAM使用方案,沒有和BSD的套接字接口兼容,應用層接口較復雜。zlIP介于uIP和lwIP之間,它針對單片機設計,有中等代碼量和RAM使用量,使用套接字的應用層接口,所有的外部變量都使用了xdata類型,全部指針都為明確存儲類型的指針,需要重入的函數已經聲明為reentant,使用KeilC的小模式下編譯。使用12M晶振、KeilC編譯器、89C52單片下測試的技術參數如下:
表1:zlIP技術參數(Technical parameter of zlIP)
zlIP的版本 |
代碼(ma)量(字(zi)節) |
外部RAM使用量(字節(jie)) |
發送速度(字節/秒) |
1.0 |
6791 |
20K |
11.752K |
2.0 |
14464 |
4K |
5.892K |
2.0版主要功能有:支持套接字形式的多個TCP連接。支持多個網絡設備。支持通過網關發送數據包和數據包轉發功能。響應ping命令。支持TCP包的整序、重發和窗口控制流量控制。
3.2 zlIP實現TCP/IP的技巧和方法
3.2.1設計套接字接口
zlIP接口函數基本和BSD的套接字接口相同。提供的用戶接口函數有:
l
TCPSocket()。函數原型:socket
* TCPSocket(IP_ADDR ScrIP)。功能:申請一個套接字。ScrIP是這個套接字的本地IP地址。返回socket類型指針,如果申請失敗返回NULL。
l
TCPConnect()。函數原型:BOOL
TCPConnect(socket * pTCB, IP_ADDR DestIP, WORD DestPort,void (* recv)(void *
buf,WORD size),void (* close)(socket * pSocket))。功能:向IP地址為DestIP的服務器的DestPort端口發起連接。參數recv和close用于設置當接收到數據包和對方要求關閉TCP連接時應該調用的回調函數指針。連接成功返回TRUE,否則返回FALSE。
l
TCPSend()。函數原型:BOOL
TCPSend(socket * pTCB,void *buf,WORD DataSize)。功能:發送數據。發送數據的TCP連接是套接字指針pTCB對應的連接,發送的數據的起始地址為buf,大小為DataSize。發送成功返回TRUE,否則返回FALSE。
l
TCPListen()。函數原型:BOOL
TCPListen(socket *pTCB,WORD ScrPort,void (* accept)(socket *pNewTCB)) 。功能:使用套接字pTCB在ScrPort端口監聽。參數accept是當有客戶端向這個監聽端口連接成功時調用的回調函數指針。
l
TCPClose()。函數原型:void
TCPClose(socket *pTCB)。功能:我方主動關閉連接時調用TCPClose函數,它將要求關閉套接字pTCB對應的連接。TCPClose返回以后這個TCP連接可能保持,因為另一方還沒有發起關閉請求。
l
TCPAbort()。函數原型:void
TCPAbort(socket *pTCB)。功能:當使用完這個套接字以后,調用TCPAbort,將這個套接字釋放,還給系統。
TCP/IP協議運行中,接收數據包到達、另一方發起關閉連接、另一方向我方發起連接這些事件發生以后如何通知應用層?下面以收到數據包為例提供幾種思路:(1)TCP/IP模塊設置一個變量bRecv表征是否有數據包到達,應用層必須反復的查詢這個變量,如果為TRUE,則調用一個接收函數接收這個數據包。但是這種方法增加了應用層程序的復雜性。(2)固定的回調函數。當TCP層接收到一個數據包后調用OnReceive(pTCB,buf,size)函數。用戶必須在應用層定義一個函數名為OnReceive的函數。然后在OnReceive函數中處理接收的數據。(3)回調函數指針。每個套接字保存函數指針recv,接收到數據時TCP調用recv指向的函數。這樣每個套接字可以獨立定義接收函數,并且函數名可以任意。zlIP使用了第三種思路。它的回調函數指針有:
l
recv。原型為:void
(* recv)(void * buf,WORD size)。TCP接收到數據包時將調用這個函數。接收的數據的起始地址為buf,大小為size。
l
close。原型為:void (* close)(socket * pSocket)。TCP發現對方想關閉連接時調用這個函數。pSocket指出了是哪個連接。
l
accept。原型為:void (* accept)(socket *pNewTCB)。TCP發現另一(yi)方成功連接(jie)到我方某個(ge)端口時(shi)調用這個(ge)函(han)數(shu)。pNewTCB是將要接(jie)管這個(ge)TCP連接(jie)的(de)(de)套接(jie)字指(zhi)(zhi)針。在accept()函(han)數(shu)中還要設置(zhi)pNewTCB的(de)(de)回調函(han)數(shu)指(zhi)(zhi)針recv和(he)close。
3.2.2 zlIP的輸入輸出流程簡介
和其它的多數TCP/IP協議一樣,zlIP采用了協議分層的結構。分為應用層、TCP層、IP層和網絡設備接口層。圖1描述了zlIP輸入和輸出數據包的流程以及需要調用的函數。輸出時,TCP層先查看unsend隊列,發現非空,將數據包插入隊列;發現為空,則查看對方窗口是否夠大能夠接收這個數據包,然后填寫TCP頭部信息。IP層需要選擇一個網絡設備接口,選擇的方法是:目的IP和該接口的子網掩碼相與是否等于子網掩碼。然后調用這個接口的Output函數來發送。zlIP提供了NetIfAdd()函數,可以動態添加網絡設備接口。輸入時,Timer()函數調用每個接口的Input函數。IP層判斷IP版本、IP校驗和、判斷是否應該轉發數據包,然后根據IP頭部的protocol字段將包傳給相應的高層處理。TCP層,需要判斷TCP校驗和,然后在現有的套接字中查找,判斷是否有套接字可以接收這個數據包,判斷TCP序號是否為希望的,然后更新這個連接的狀態(包括釋放被應答的數據包和TCP狀態機的轉化等),然后調用該套接字的回調函數recv。需要強調一下,如果接收的TCP的序號不在我方滑動窗口內,那么應該馬上發送一個TCP應答包,因為這很可能是我方發送的應答包丟失了,我方接收的數據包是對方重發的TCP包。
3.2.3 單片機上實現TCP/IP的兩種程序結構
從圖1可以看到,右方有一個Timer()函數。它的一個功能是調用TCPTimer(),TCPTimer用于處理TCP數據包的重發等功能。另一個功能是調用每個接口的Input()函數接收到達的數據包。Timer()函數必須在短時間(一般20ms)內被調用一次,否則接收數據包和TCP定時等功能將停止。Timer()函數的調用有兩種方式查詢方式和中斷方式,Timer()函數的不同調用方式決定了兩種程序結構。
(1)前后臺程(cheng)序結構(5)
查詢方式的調用對應前后臺程序結構。實現方法是:設置一變量bTimerOut,在定時中斷中將bTimerOut設置為真,應用層在程序流程中反復查詢bTimerOut是否為真,真則調用Timer(),然后置bTimerOut為假。程序主流程必須是類似圖2的形式:程序主流程是一個大循環,在循環中處理發送數據包等應用層協議同時查詢bTimeOut。
缺點:由于Timer()必須在短時間內被反復調用,這就要求大循環循環一次的時間要在20ms以內。這給應用程序的編寫帶來了限制,例如有時程序可能需要在大循環中等待鍵盤按下,但是這里這種長時間的等待是不允許的。
(2)多線程程序結構
另一種方案是使用多線程。Timer()函數會自動地每隔20ms被調用一次。實現多線程有兩種方法:①
在單片機的定時中斷中調用Timer函數;② 使用操作系統。
缺點:多線程程序結構解決了前后臺程序的缺點。應用程序再也不用套用固定的程序格式。但是,這是有代價的。使用多線程,這就意味著某些函數可能被重入,這些函數必須定義為reentrant類型,從而降低了運行速度。
多線程結構還要注意網絡設備驅動函數被重入的問題。以NE2K的以太網卡驅動為例,拷貝數據包到網卡緩存前要先設置寄存器(例如起始地址),然后開始拷貝。如果設置完寄存器以后中斷發生,并且驅動函數被重入,那么寄存器的設置被修改,中斷返回以后拷貝將出錯。可以使用禁止中斷、全局標志位、信號量等方法防止重入。
3.2.4內存管理方法和無多余數據包拷貝的實現
TCP/IP的內存的管理方法這里介紹兩種:分頁方法和鏈表方法。
(1)分(fen)頁方法(fa)(6):內存劃分為多個128字節大小的小頁和少量1536字節大小的大頁。一個頁分配給一個數據包。用一個數組memFlag記錄各個內存頁是否已經被分配。分配內存的時候只要查找數組membFlag,以獲得一個空閑的內存頁。為了提高查找的效率,可以將每次查找的起始頁設為上次找到的空閑頁的下一個頁。釋放時,將memFlag相應的元素置為FALSE。在協議層之間傳送數據包只要傳送頁的序號就可以了。這種內存管理方法,分配和釋放內存的速度較快。但是由于頁的大小固定,不能和數據包大小相適應,造成內存的浪費。
(2)鏈(lian)表方(fang)法:鏈(lian)表方(fang)法根據(ju)數據(ju)包大(da)小分(fen)(fen)配(pei)相應(ying)大(da)小的(de)內(nei)存(cun)(cun)(cun)塊(kuai)。如(ru)圖3所示(shi)(shi),鏈(lian)表將內(nei)存(cun)(cun)(cun)塊(kuai)鏈(lian)接起來(lai),used字(zi)段表示(shi)(shi)該(gai)(gai)內(nei)存(cun)(cun)(cun)塊(kuai)是否正(zheng)在使用,pSstart和pEend表示(shi)(shi)數據(ju)部(bu)(bu)分(fen)(fen)有(you)效數據(ju)的(de)開始地址和結(jie)束地址。分(fen)(fen)配(pei)時(shi)(shi),搜索內(nei)存(cun)(cun)(cun)鏈(lian)表找(zhao)到(dao)一(yi)個沒(mei)有(you)分(fen)(fen)配(pei)的(de)比所需空(kong)間大(da)的(de)內(nei)存(cun)(cun)(cun)塊(kuai),截(jie)(jie)取(qu)所需的(de)大(da)小。該(gai)(gai)內(nei)存(cun)(cun)(cun)塊(kuai)被截(jie)(jie)取(qu)以(yi)后可(ke)能還有(you)較多剩(sheng)(sheng)余(yu),這(zhe)時(shi)(shi)將剩(sheng)(sheng)余(yu)部(bu)(bu)分(fen)(fen)從(cong)原內(nei)存(cun)(cun)(cun)塊(kuai)中(zhong)分(fen)(fen)離出來(lai),成(cheng)為(wei)一(yi)個新(xin)的(de)內(nei)存(cun)(cun)(cun)塊(kuai),并(bing)插(cha)入鏈(lian)表。釋放(fang)時(shi)(shi),將used置為(wei)假,如(ru)果pNext或者pPre指向的(de)內(nei)存(cun)(cun)(cun)塊(kuai)也是空(kong)閑的(de),將其和自己合并(bing),以(yi)防止內(nei)存(cun)(cun)(cun)分(fen)(fen)片(pian)(7)。在協議層之間傳送數據包只要傳送內存塊的起始地址就可以了。這種內存管理方法空間浪費小但是運算量相對較大。
無數據包拷貝是指除了獲得數據到RAM和數(shu)據包到網絡(luo)設備發(fa)送緩存(cun)這兩次拷(kao)貝外(wai)沒(mei)有數(shu)據包的拷(kao)貝。這節省了拷貝時間。介紹兩種實現方法:
(1)鏈表方式:例如當應用層將DataSize大小的應用層數據交給TCP層發送,一般的做法是申請一個DataSize+TCPHeadSize大小的內存然后填寫TCP頭部,并將數據包拷貝到TCP的載荷中。使用鏈表方式:TCP層只申請TCPHeadSize大小的內存,然后將這個TCPHead用鏈表連接到應用層數據。這種方式缺點是:同一個數據包的內存不連續,這加大了計算校驗和內存釋放的復雜度,運算量大。
(2)預留空間方式:應用層為DataSize大小的數據包申請內存的時候,實際申請的是DataSize+AllHeadSize,其中AllHeadSize表示所有協議頭部大小總和。拷貝應用層數據時在其前面留出AllHeadSize大小的空余空間。內存塊頭部的pStart指示了程序所在層的有效數據的開始,例如在應用層時指向應用層數據包的開始地址。應用層將這個數據包傳給TCP層以后,TCP層只要在pStart-TCPHeadSize開始的內存空間加一個TCP頭部即可。這種方式運算量很小,但是應用層必須事先知道其底層的協議頭大小之和,違反了下層協議和上層無關的要求。
3.2.5如何實現整序、重發和窗口控制
zlIP使用了隊列緩存的方式來實現。這里隊列的一個元素指向一個數據包,隊列的最大長度沒有限制。對于整序,使用ooSeq隊列(7),如果發現接收的TCP包序號并不是希望的,但是序號在接收窗口內,此時我們不能立刻接收這個包也不應丟棄,先將這個包放入ooSeq隊列。每當,一個希望的TCP包被接收以后,再查看ooSeq隊列現在是否有TCP包成為了希望的數據包,如果有則將其取出并處理。對于重發,使用unacked隊列,每一個需要被應答的TCP數據包發送以后都要放入unacked隊列,等到被應答以后才從隊列中刪除。TCP重發定時只針對unacked隊列第一個TCP包,如果定時超出,重新發送,重發次數超出規定值,則報錯。對于窗口控制,使用unsend隊列,如果發現對方的窗口過小無法接收這個數據包,則只發送部分數據,將多余部分放入unsend隊列,等待對方發來TCP包通知新的窗口大小時,再次判斷是否可以發送了。如果在unsend隊列不為空的情況下,我方應用層傳來需要發送的數據包都應插入unsend隊列。我方的TCP窗口的大小就是剩余內存空間的大小。
3.2.6 捎帶應答的實現
捎帶應答指的是,當對方一個需要應答的TCP包到達時,我方不馬上給予應答,而是等待一個較短的時間。如果在這段時間內,我方有數據發送,則會捎帶給予了應答,這減少了包的發送數量。
參考文獻
[1] (電子文獻)中國電子網.ADI具有TCP/IP棧的單片Modem[Z].//www.21ic.com.2000-10-11.
[2] (電子文獻)德國Keil公司.Cx51 Compiler[Z].//www.keil.com.2001-5.103-110.
[3] (電子文獻)Adam Dunkels.uIP - A Free
Small TCP/IP Stack[Z].//dunkels.com/adam/uip/index.html.2002-1-15.1
[4] (電子文獻)Adam Dunkels.lwIP - News
Archive[Z].//www.sics.se/~adam/lwip/news.html.2001-1-9.
[5] (專著)jean j labrosse.μc/os-II-源碼公開的實時嵌入式操作系統.邵貝貝等譯.[M]北京:中國電力出版社,2001.
29-30.
[6] (專著)Douglas E Comer,
David L stevens.用TCP/IP進行網際互連第二卷[M]北京:電子工業出版社 ,2000.24-25.
[7] (電子文獻)Adam Dunkels.Design and
Implementation of the lwIP TCP/IP Stack[Z].//www.sics.se/~adam/lwip/
documentation.html.2001-2-20.10-19.
了解單(dan)片機TCP/IP更(geng)多方案://515x.com.cn/products_serial_server.htm
The specialty and
method in implementation
of TCP/IP in 51 serial MCU
Li Zhanglin1 ,Zhang Limin1
(1
ABSTRACT:In order to connect a 51 serial MCU to internet, it’s
necessary to develop a TCP/IP especially for 51 serial MCU. So We developed
zlIP, a TCP/IP designed especially for 51 serial MCU with KeilC51. Its
specialties include small code size, compatibility with BSD socket interface
etc. In order to test different aspects in implementation TCP/IP on 51, zlIP
1.0 emphasized on speed while zlIP 2.0 emphasized on facility of user
interface. The thesis analyzed specialties, which include speed, code size, ram
usage and complier, by comparing the tow editions of zlIP and absorbing the
strong points of other TCPIP. The thesis promoted and summarized some methods according
to these specialties and compared these methods. Finally, the thesis analyzed
some key techniques: TCP/IP user interface design, comparison of back-front and
multithread programming structure, memory management and avoidance of redundant
copy of packets, realization of packets arrangement, resending and window
control.
Key word: TCPIP;
MCU; zlIP