對於任何一個協議棧而言,首先要實現的就是TCP Server,下面看看uIP的運行機制:
①IP地址以及端口的綁定
uIP在初始化時進行了本機地址IP地址的默認初始化,並將其存放在一個名爲uip_hostaddr的結構體中,本機默認爲192.168.1.11同樣對子網掩碼和網關也進行了默認設置。如果用戶需要修改時,使用uip_sethostaddr()函數就可以使用新的IP地址對默認值進行覆蓋,當然也要設置相應的網關與子網掩碼。另外,使用uip_listen(HTONS(1000))進行端口的綁定,也就是說本機只偵聽來自端口1000發來的請求。(數據發送是按哪個端口發送呢?)
②下面看一看數據流通信的過程
使用上位機軟件連接遠程IP以及爲192.168.1.11:1000,對其發起連接,實際上是進行三次握手操作,可以看到如下數據流:
在程序的主循環中,輪詢讀取網卡中新的數據包
uip_len= tapdev_read()
如果收到的是一個IP包,那麼要對數據格式進行判斷,進入
uip_input()同時傳入一個flag==1,代表接收到一個新的數據包,首先剝離IP包頭,指向其中的數據,接着對數據包個數進行統計UIP_STAT(++uip_stat.ip.recv);
檢查IP協議版本號,如果正確則計算數據長度:
if((BUF->len[0]<< 8) + BUF->len[1] <= uip_len) {
uip_len = (BUF->len[0] << 8) + BUF->len[1];計算數據長度
檢查數據包的目的IP是否爲本機地址:
if(!uip_ipaddr_cmp(BUF->destipaddr,uip_hostaddr)) {
UIP_STAT(++uip_stat.ip.drop);
goto drop;
}
如果是發往本機的數據包,那麼就檢查數據包校驗和
if(uip_ipchksum()!= 0xffff) ,如果正確的話就說明這是一個完整的IP包,那上層協議是什麼呢?下面檢查協議類型,如果是TCP數據包,就跳轉到goto tcp_input,如果是UDP數據包,就跳轉到goto udp_input去執行,如果是ping包發來的icmp請求就寫入回覆標誌,交換IP,返回迴應包。本次試驗發送的是一個TCP連接請求,所以要對TCP數據包進行處理。
判定了是一個TCP數據包,就要對數據包個數進行計數,有利於數據統計。下面再計算
TCP數據包校驗和是否正確 if(uip_tcpchksum()!= 0xffff) 。
下面的查看現有是否存在有效連接。衆所周知,TCP server允許多個TCP client同時對其連接,使用for(uip_connr = &uip_conns[0]; uip_connr <= &uip_conns[UIP_CONNS- 1];
++uip_connr)查看,如果已經建立了有效連接,就到goto found;此時,通過抓包工具已經可以看出
我們的目標板已經發送了一個數據幀做了SYN的應答,下面可以看到這一過程。
就是,如果沒有發現有效的連接,現在只用接收TCP_SYN同步數據包,如果是一個TCP_SYN同步數據包,並且是已經註冊的監聽端口,就到found_listen處執行進行連接註冊,把源端口、目的端口、源IP地址記錄下來(肯定是發往目標板端口的),然後建立BUF->flags = TCP_SYN | TCP_ACK標誌,tcp_send將回應包發出去。看到這裏就不難明白goto found;之前那幾行代碼的用意了,就是尋找已經建立好的連接。
那麼我們接着往found下面看:
既然是有效的連接,那麼接下來要看發來的數據包有沒有有效的數據,可以計算數據長度得知uip_len的多少,如果if(uip_len > 0) uip_flags |= UIP_NEWDATA;就說明接收到了新的數據,調用UIP_APPCALL()進行處理,之後通過UIP_APPCALL()調用處理之後的函數發出去。
③應用程序調用
下面看一下UIP_APPCALL()是如何對數據進行解析和處理的。在這裏我做了一個簡單的測試,使用TCP&UDP調試工具想目標機發送一個指令,下位機進行解析處理以及返回。先看一個數據結構:struct uip_conn{},這個結構體代表了一個TCP的連接關係,記錄當前數據包的狀態。
在這個結構體的最後定義應用程序定義的結構體tcp_demo_appstate,這裏面可以定義一些狀態作爲TCP數據控制的補充,也可以實現狀態機控制數據處理。TCP數據指針爲uip_appdata,數據長度爲uip_len,可以對其複製新的數據值以及新的數據長度,出去包頭可以傳輸的最大數據段爲1460個字節。