馮翠麗,劉波濤,王青海,陳憲超
(1.長江大學 計算機科學學院,湖北 荊州 434023;2.勝利油田鉆井工藝研究院信息中心;3. 長沙華德科技開發有限公司)
當前,研究得如火如荼的物聯網技術中,首先要解決的一個問題是將嵌入式設備接入網絡[1].其中,比較常見的一種實現方案是在嵌入式設備中集成精簡后的TCP/IP協議棧而將該設備接入Internet[2-3].在此過程中,如果需要實現基于TCP協議的高層應用,就必須要根據嵌入式設備的具體功能來簡化實現TCP協議,因此如何針對該設備的具體應用來有效地簡化實現TCP協議就是一個技術難點.
TCP(Transmission Control Protocol)傳輸控制協議是TCP/IP協議簇的核心協議, 也是TCP/IP協議簇中最復雜的協議.它是一種面向連接的、可靠的、基于字節流的運輸層通信協議[4].標準的TCP協議會實現流量控制、滑動窗口協議、擁塞控制、TCP各種計時器、TCP重傳、TCP有限狀態機及TCP連接管理等等功能[5].
通用計算機系統有足夠的資源支持系統在內核中實現復雜的TCP重傳機制,然而對于嵌入式Web服務器有限的資源及比較低的處理速度來說,要想實現那些復雜的TCP協議既不現實也沒有必要.在研究嵌入式系統TCP協議的實現過程中,需要解決的幾個關鍵問題是:①如何精簡傳輸控制塊(TCB);②如何精簡TCP協議的幾個計時器;③如何簡化TCP連接管理,裁剪TCP有限狀態機;④如何進行流量控制,簡化滑動窗口協議,并實現TCP的重傳機制.
TCP首部格式的定義需要遵循RFC 793的規定,可定義如下:
typedef struct
{ WORD SrcePort; //源端口號
WORD DestPort; //目的端口號
LWORD SeqNum; //系列號
LWORD AckNum; //確認號
WORD LenFlags; //首部長度及標識,首部=LenFlags&0xf000>>10
WORD WndSize; //窗口大小
WORD CkSum; //TCP校驗和
WORD UrgPtr; //緊急指針
} _TCP_HDR;
標準的TCP選項有三種:最大報文長度選項、窗口擴大因子選項和時間戳選項.最大報文長度選項用于交互的雙方協商TCP數據的最大長度.窗口擴大因子選項用于提高TCP的吞吐量,如果其值為n,則2的n次方與WndSize的積即是新的窗口大小.時間戳選項用于記錄往返時間,便于動態地定義超時時間.在嵌入式TCP中,可以使用固定大小的窗口和簡單的確認機制來簡化程序以節約RAM空間,因此它不需要窗口擴大因子和最大報文長度選項.嵌入式TCP不需要動態定義超時時間,也就是說,它不需要時間戳選項.
為了實現TCP面向連接的、可靠的服務,需要使用一個結構來維持每條連接的相關信息,該結構被稱為TCB(傳輸控制塊).針對標準的TCB,每種剪裁的實現都不一樣,筆者實現的TCB如圖1所示.

圖1 標準TCB及筆者實現的嵌入式TCB對比圖
筆者裁剪TCB的原則如下:①在標準的TCP服務中,客戶端在申請建立連接時和在與服務器建立連接后所使用的目的端口是不同的,前者使用的是HTTP協議的熟知端口80,而后者使用的是一個臨時端口,這種方式提高了系統的吞吐量.因此,標準的TCP服務需要記錄本地的臨時端口,考慮到嵌入式Web服務器的吞吐量不大,因此嵌入式TCB中也不需要記錄臨時端口;②標準的TCP協議支持多穴功能,因此需要記錄本地的IP地址.而嵌入式Web服務器沒有必要實現多穴功能,因此可以將該IP地址設置成一個全局變量,從而使得每個連接的本地IP都可以使用此變量.也就是說,嵌入式TCB不需要定義本地IP字段;③由于筆者沒有使用多任務OS,所以標準TCB中的進程號沒有意義;④協議棧沒有進行Socket封裝,故不需要接口號;⑤因為采用了零拷貝的封包解包技巧[6],因此沒有必要定義緩存指針及緩存大小;⑥筆者使用了簡單的確認機制[6],而避免了實現復雜的滑動窗口協議,因此本地窗口及遠程窗口沒有意義;⑦筆者沒有實現標準TCB中的堅持計時器[5],因此不需要定義往返時間.
在標準的TCP服務中,實現保活定時器是為了防止兩個TCP之間的連接長時期的空閑.假如一個客戶端打開了一個到服務器的連接,傳送一些數據后就出了故障,那么這個連接將永遠的處于打開狀態,這對服務器來說是一個資源的浪費.并且,從安全性的角度考慮,這種服務器容易受到類似的攻擊從而無法完成正常的服務.為了避免這種情況的發生,通常在服務器端設置一個保活定時器,每當服務器收到客戶端信息時都將該定時器復位.超時時間通常設置為2小時,若2小時后服務器還沒有收到客戶端的信息,它就發送10個間隔為75秒的探測報文.在此期間,如若服務器仍然沒有收到客戶端的響應,它就會認為客戶端出現故障而主動終止該連接.
嵌入式TCP服務中也會出現上述問題,因此,筆者在嵌入式TCP中也需要實現簡化的保活定時器:①2個小時的超時時間對嵌入式Web服務器來說太長,因為它是一種檢測、控制的工具,連接時間越長,其安全性越差,這個時間應根據具體的應用通過實驗的方法來獲取比較合理.筆者經過大量的實驗表明,該值取20分鐘比較合理;②嵌入式Web服務器沒有必要發送探測報文,當保活定時器超時后可直接復位客戶端來關閉該連接.
標準的TCP使用了慢啟動的滑動窗口機制,它允許發送方在等待一個確認之前發送多個報文.對于使用了滑動窗口的TCP連接,其確認是一種批量報文的確認.考慮到嵌入式處理器要對多個數據報連續傳輸進行維護和處理,困難較大.仔細考察滑動窗口協議可以發現,滑動窗口的一個極限情況就是對每個報文都對應發一個確認,使用這個方法后,所有的處理只是針對單個數據報的發送進行確認.這樣一來,既節約了系統的資源,又便于維護連接.當然了,為了協議的兼容性,需要在通信的客戶端也使用簡單確認的方法.因為如果客戶端使用了較大的窗口,就可能造成服務器被淹沒.
遵循以上思路,筆者采取了如下方式來實現TCP的簡單確認機制:①在連接建立初期,服務器通過TCP的最大報文長度選項來通知客戶端,它以后的每個報文的最大長度都是一個定值M;②不允許交互雙方的任何一方使用窗口擴大因子;③在三次握手[7]時的ACK+SYN報文中,將WndSize字段的大小取固定值W,通知客戶端其滑動窗口的大小是W;④讓W≤M,使得客戶端在每收到一個報文后就給服務器發送一個對應的確認.W和M的值理論上都可以取到65536,考慮到底層網絡是以太網,其最大傳輸單元MTU等于1518,為了避免IP包被分片傳輸,發送的TCP包大小不能超過1518字節.而最小以太首部、IP首部和TCP首部的長度和為54,還考慮到在建立連接初期要用到四字節的TCP最大報文選項,故發送TCP包的有效載荷數據長度最大只能取1518-54-4=1460字節,筆者建議取W=M=1400.
TCP是一種面向連接的服務.“面向連接”就意味著[8]:①客戶端和服務器彼此交換TCP數據之前,必須先建立一個TCP連接;②建立連接后進行數據傳輸;③數據傳輸完畢后必須終止連接.其中,連接的建立是通過三向握手來建立的;連接的終止由四向握手而正常終止,也有可能由異常復位而帶來異常終止;為了清楚地跟蹤這三個階段中所發生的不同事件,TCP使用了TCP有限狀態機.
三向握手是客戶端主動打開而服務器被動打開連接的情況,還有一種情況是雙方同時主動打開.為了減少程序的復雜度,筆者實現的服務器不支持這種主動打開,也就是說,它只被動的接收客戶端請求.
針對嵌入式Web應用,筆者簡化了四向握手的過程,當服務器收到客戶端的FIN報文后,直接將ACK報文和FIN報文合為一個ACK+FIN報文發送給客戶端.也就是說,筆者設計的服務器不支持連接的半關閉和主動關閉.
在標準的TCP服務中,服務器在連接發生以下情況之一時會向客戶端發送RST報文,使之能夠異常地終止一個不正常的連接:①客戶端TCP請求了一條到服務器并不存在的端口的連接;②服務器發現客戶端TCP已經空閑很長一段時間;③服務器偵測到異常事件,并愿意異常終止該連接.考慮到嵌入式Web服務器的需求,針對上述標準的要求,筆者簡化如下:①由于嵌入式Web服務器沒有使用臨時端口,因此,目的端口不是80的數據包就不可能是TCP包,服務器可以簡單丟棄,不作任何處理;②由于嵌入式Web服務器的保活計時器超時時間設置得比較短,服務器能夠及時發現客戶端TCP已經空閑,從而發出RST報文來關閉該連接;③服務器的異常事件很多,限于嵌入式Web服務器的應用需求,只需要處理必要的異常事件:收到的報文沒有合法的ACK號;重發計時器超時并且重發次數超過規定值.

圖2 筆者裁減后的TCP有限狀態機
TCP有限狀態機越復雜,維護的開銷就越大,對系統的存儲能力和運算能力要求就越高.標準的TCP有限狀態機太過復雜,筆者精簡如圖2所示.當發生以下事件之一時,發生如圖2中的異常事件:收到RST報文、收到的報文沒有合法的ACK及保活計時器超時.
TCP的重傳機制是TCP成為一種可靠協議的基礎.在標準的TCP協議實現中,重傳機制的核心是計算RTO(Retransmission TimeOut),具體計算過程參見參考文獻[4].而對于嵌入式Web服務器有限的資源及比較低的處理速度來說,花費巨大的代價來計算RTO并不值得.因此,精簡TCP的重傳首要任務是需要尋找一種簡化的方法以便于很容易地得到RTO.這里,筆者采用了最簡化的方式,即取固定的大小的RTO.

圖3 筆者設計的TCP重傳模塊順序鏈表結構示意圖
RTO確定之后,剩下的就是當重傳計時器超時后進行重發TCP包的操作.可以有很多方法來實現該重發操作.筆者的設計思路是利用順序鏈表記錄每個已發送的TCP數據包,而當收到TCP確認報文時就從該順序鏈表中刪除相應的TCP數據包.同時,每隔一個RTO時間就觸發一個TCP重傳事件,以發送那些已經超時但沒有收到確認的TCP數據包.其中,順序鏈表的結構如圖3所示.順序鏈表的頭指針、鏈表長度、該TCP數據包中TCP層數據部分的長度分別由TCB中的MemPoolHeader、MemPkgNum、TcpDataLen字段給出,如圖2所示.圖3中,BuffPtr和TotalPkgLen字段定義了可能需要重發的TCP報文的首地址和總長度,這便于直接調用發送驅動函數進行重發操作,也便于在ARP解析失敗后直接進行ARP重傳操作的處理.Next字段將該連接中所有可能需要重發的TCP報文都掛在一個順序鏈表上.ExpectAck是該結點中的關鍵字,便于在收到TCP確認后進行相應的刪除操作.
根據嵌入式TCP協議的特點,筆者進行了三種測試:①跟蹤三向握手及對應的狀態圖變遷;②測試TCP保活計時器;③跟蹤TCP重傳.這里采用測試方法是將嵌入式Web服務與PC機在RJ-45口及串口分別對接[9].利用串口精靈接收嵌入式Web服務器的輸出并顯示,利用Sniffer Pro抓取PC機發送和接收的數據包.

圖4 服務器收到的SYN報文
如圖4所示,在5.058s時,服務器TCP收到了客戶端TCP發起SYN請求,其源端口是1507,包序號是291941000,這個包首部長度是28字節,由于筆者設計的系統不支持接收包的TCP選項,故該包的此選項被忽略.在收到這個包后,服務端TCP有限狀態機就變成了SYN_RCVE;接著由服務器TCP產生一個SYN+ACK報文,其序號字段值是640001,確認號是291941001,如圖5所示.

圖5 服務器發送SYN+ACK報文

圖6 服務器收到ACK報文

圖7 客戶端發送的HTTP請求
然后是客戶端給服務器發送ACK報文以確認服務器到客戶端的連接,其序號是291941001,確認號是640002,服務器在收到該報文后,其TCP有限狀態機變遷為ESTABLISHED,如圖6所示.當建立連接后,客戶端緊接著就發送了HTTP請求,如圖7所示.
由上述測試可以得出結論:筆者設計的TCP連接管理模塊工作正常,并且在此期間其TCP有限狀態機按照筆者設計的方式進行變遷.
為了方便測試,筆者做了如下設置:讓保活計時器每隔5s輪詢一次,并設置其超時時間值為15s.這時輸出的信息如圖8所示.從該圖的第3、第4及第7個報文后輸出的信息(即“TCP ActiveTimer update!”)可以看出,在建立連接后,每當TCP收到一個包,它都要更新該連接的保活計時器.在收到客戶端最后一個包(即圖中的第7個包)后,每隔5s輪詢一次Inactivity_Tcp()函數,在該函數中Conn[0].ActTimer字段被減一,當其減至0時,說明保活計時器超時,于是發送RST報文(對于圖中的第8個包)到客戶端.接著關閉服務器端的該TCP連接,并使TCP狀態變成LISTEN態.此后,由于此時沒有TCP連接,故沒有輸出相關的信息.
由上述測試可知,TCP保活計時器工作正常,并且當保活超時時,TCP有限狀態機按預期地方式變遷到LISTEN態.

圖8 測試TCP保活計時器輸出信息

圖9 測試TCP順序鏈表時的輸出信息

圖10 測試TCP重傳計時器輸出信息
由于筆者設計的系統中,TCP重傳的實現依賴于順序鏈表,因此,為了使服務器的重發模塊運行穩定,測試順序鏈表的操作就必不可少.測試順序鏈表時輸出的信息如圖9所示.每當發送一個非RST型的TCP報文都被插入到順序鏈表中,如圖9中信息“Insert_Mem(): firstly insert PkgNum=6”表明:第2個TCP報文被插入到順序鏈表的第一個位置();而每當收到報文時,就用該報文的ACK號在順序鏈表中找到該報文,如“Search_Mem(): find PkgNum=6 Pkg”;接著刪掉順序鏈表中該報文以前的TCP報文,如“Delete_Mem():the first Pkg of PkgNum=5 deleted”和“Delete_BeforSeqMem(): total 2 pkgs are deleted”表明服務器利用收到的報文9的ACK號刪掉了順序鏈表中對應的報文5和報文6;信息“Free_Mem(): free the PkgNum=5 success”則表明該包的空間被成功地釋放.
由于將系統直接接到PC上時,網絡環境比較穩定,為了迫使TCP啟動重傳模塊,可以在服務器TCP層發送數據時人為將系統與PC機的網絡斷開一會兒后迅速連上.此時,調試信息如圖10所示.由圖10可以看出,由于沒有及時收到客戶端的ACK而導致重發了報文5和報文6,并且在收到的報文9后刪掉了順序鏈表中對應的報文5和報文6.
通過上述跟蹤TCP收發包的過程及重傳操作的測試可知,順序鏈表的操作無誤、TCP重傳模塊工作正常.
筆者詳細討論了如何設計并實現精簡的TCP協議,并做了相關測試,驗證了該設計思路的可行性.這為嵌入式設備中順利實現嵌入式TCP/IP協議棧,進而實現物聯網技術打下了堅實的基礎.
參考文獻:
[1]International Telecommunication Union UIT[R].ITU Internet Reports 2005: The Internet of Things. 2005.
[2]馮翠麗,劉波濤.一種嵌入式TCP/IP協議棧的設計與實現[J].長江大學學報(自然科學版)理工卷,2008,5(4):331~333.
[3]李金梁,景博.嵌入式Internet中TCP協議的設計與實現[J].微計算機信息,2005,21(7):40~138.
[4]RFC793 - Transmission Control Protocol [S]. IETF,1981.
[5]謝希仁.計算機網絡[M] .北京:電子工業出版社,2008:187~219.
[6]劉波濤.物聯網中嵌入式TCP/IP協議棧的設計技巧[J].通化師范學院學報,2011,32(2):40~42.
[7]夏春濤,杜學繪,郝耀輝,等.基于.NET 平臺的SYN Flood攻擊測試的實現[J].計算機工程與設計,2011,32(6):1918~1921.
[8]李立清,路海.應用于嵌入式系統的TCP簡化實現方法[J].計算機工程與應用,2004(7):142~145.
[9]劉波濤,馮翠麗,王青海,等.應用RTL8019AS的嵌入式Web服務器硬件實現[J].長江大學學報(自然科學版),2008,5(1),75~78.