關(guān)于uIP的移植以及部分特性解析和勘誤
關(guān)于嵌入式網(wǎng)絡(luò)的領(lǐng)域,uIP是一個(gè)值得去學(xué)習(xí)的輕量級(jí)協(xié)議棧,在我的理解里,uIP具有如下特點(diǎn).
1.封裝性好
封裝性好體現(xiàn)在uIP它能做到的網(wǎng)絡(luò)協(xié)議棧的底層所做的工作都給完成了,當(dāng)然包括最基本的數(shù)據(jù)鏈路層和網(wǎng)絡(luò)層,當(dāng)然,物理層是不確定的,需要我們自己寫驅(qū)動(dòng)程序接口然后進(jìn)行接合.整個(gè)完整的uIP只留給用戶兩個(gè)宏定義(另外一個(gè)為UIP_UDP_APPCALL)
從官方文檔可以得知用戶應(yīng)該在編譯程序的時(shí)候把這個(gè)宏定義給加上去,另外任何用戶事件的發(fā)生都是通過該函數(shù)來執(zhí)行的(新數(shù)據(jù)到來,連接輪詢,tcp鏈接和斷開, 超時(shí)等事件).我們進(jìn)行的任何活動(dòng)都只需要在里面進(jìn)行就可以了.所以甚至我們可以直接把一些文件編譯成lib文件,直接添加.因?yàn)檫@些文件時(shí)用戶無關(guān)的,我們不需要關(guān)心他在干嘛(但是如果你想了解它的運(yùn)行機(jī)制還是得單步跟蹤的.).
2.可移植性強(qiáng)
移植很方便,這是我時(shí)隔半年又一次學(xué)習(xí)uIP的感受,差不多半個(gè)小時(shí)我就已經(jīng)把uIP整個(gè)給搞定了.驅(qū)動(dòng)的話你得在移植uIP之前就應(yīng)該做完了.然后再配置一下時(shí)鐘的話就差不多搞定了.
3.速度快
如果你認(rèn)真的通讀了uIP的官方說明文檔,會(huì)發(fā)現(xiàn)作者把lightweight深入到編程的每一個(gè)細(xì)節(jié),包括盡量減少函數(shù)的調(diào)用,能用宏定義編寫的比較小的固定的函數(shù)進(jìn)行都用宏定義來寫,這樣子雖然代碼容量增大了,但是內(nèi)存確實(shí)是會(huì)變的很小,這對(duì)于51這種小內(nèi)存的主控尤其顯得至關(guān)重要,畢竟再牛逼的51也就1K內(nèi)存就已經(jīng)很不錯(cuò)了,但是flash卻是可以很大,32K用來搞定這個(gè)uIP那已經(jīng)顯得是搓搓有余了.所以這些機(jī)制不止節(jié)約了內(nèi)存,同樣也加快了速度,你可以通過看uIP_process這個(gè)函數(shù)來清晰了解到這個(gè)思想,雖然我不是很贊成這種做法,因?yàn)榇a風(fēng)格很亂,各種goto語句充斥著一個(gè)1000多行的主執(zhí)行函數(shù),那樣子任何人也受不鳥,畢竟我是看你程序,而不是寫,看到一半上面的思路已經(jīng)斷了.
閑話不多說,讓我們開始uIP之旅吧,相信讀完我的博文,你也可以輕松的在uIP上進(jìn)行你的網(wǎng)絡(luò)應(yīng)用開發(fā).
PART1:uIP移植
移植的話我就不多講,這里貼上一個(gè)連接,大家照這樣子做就行了,我說一些比較需要大家注意的地方,是我在移植中碰到的問題.
時(shí)鐘機(jī)制:uip運(yùn)行必不可少的就是時(shí)間機(jī)制,包括超時(shí)重傳,定時(shí)輪詢處理(這點(diǎn)在UDP主動(dòng)發(fā)送數(shù)據(jù)和TCP主動(dòng)發(fā)送數(shù)據(jù)和超時(shí)重傳的時(shí)候非常重要,沒有了timer那么uip就是一個(gè)只會(huì)ping和arp的傻瓜).
/**
* A timer.
*
* This structure is used for declaring a timer. The timer must be set
* with timer_set() before it can be used.
*
* \hideinitializer
*/
struct timer
{
clock_time_t start;
clock_time_t interval;
};
上面這個(gè)是uIP的時(shí)鐘定義函數(shù),它只實(shí)現(xiàn)一個(gè)功能,就是寫入當(dāng)前的系統(tǒng)運(yùn)行時(shí)間start,然后還有多久超時(shí)interval.當(dāng)然,這個(gè)可以通過已經(jīng)做好的api實(shí)現(xiàn)
/*---------------------------------------------------------------------------*/
/**
* Set a timer.
*
* This function is used to set a timer for a time sometime in the
* future. The function timer_expired() will evaluate to true after
* the timer has expired.
*
* \param t A pointer to the timer
* \param interval The interval before the timer expires.
*
*/
void
timer_set(struct timer *t, clock_time_t interval)
{
t->interval = interval;
t->start = clock_time();
}
clock_time()函數(shù)在下面貼出來的代碼里面已經(jīng)實(shí)現(xiàn),這也是移植的一部分,該函數(shù)用來獲取當(dāng)前系統(tǒng)運(yùn)行時(shí)間.
/*---------------------------------------------------------------------------*/
/**
* Check if a timer has expired.
*
* This function tests if a timer has expired and returns true or
* false depending on its status.
*
* \param t A pointer to the timer
*
* \return Non-zero if the timer has expired, zero otherwise.
*
*/
int
timer_expired(struct timer *t)
{
return (clock_time_t)(clock_time() - t->start) >= (clock_time_t)t->interval;
}
這個(gè)函數(shù)則用來判斷當(dāng)前結(jié)構(gòu)體所代表的單元是否已經(jīng)超時(shí),通過當(dāng)前系統(tǒng)時(shí)間減去設(shè)定時(shí)候的時(shí)間是否大于interval間距來判斷,如果超時(shí)就返回1,否則是返回0的.
最重要的就是這三個(gè)函數(shù),當(dāng)然,還有一些函數(shù)可以去timer.c里面慢慢閱讀,在uip1.0里面注釋都很清楚.
網(wǎng)卡驅(qū)動(dòng)接口:
#include "uip.h"
#include "enc28j60.h"
/*---------------------------------------------------------------------------*/
void
tapdev_init(unsigned char *my_mac)
{
enc28j60Init(my_mac);
}
/*---------------------------------------------------------------------------*/
unsigned int
tapdev_read(void)
{
return enc28j60PacketReceive(UIP_CONF_BUFFER_SIZE,uip_buf);
}
/*---------------------------------------------------------------------------*/
void
tapdev_send(void)
{
enc28j60PacketSend(uip_len,uip_buf);
}
/*---------------------------------------------------------------------------*/
這里我所用的網(wǎng)卡是enc28j60,當(dāng)然,如果你用的是dm9000或者是rtl8019等等之類的顯卡,只要測(cè)試到能接到數(shù)據(jù)包就行,剩下的一些網(wǎng)卡運(yùn)行機(jī)制請(qǐng)?jiān)谥袛嘀袑?shí)現(xiàn),比如判斷網(wǎng)線是否斷開,或者網(wǎng)卡硬件緩沖區(qū)是否溢出或者發(fā)送異常接收異常等等.
tapdev_init是網(wǎng)卡初始化函數(shù),它必須帶入mac地址,順便的,不僅要讓網(wǎng)卡知道當(dāng)前的本機(jī)mac地址,也應(yīng)該讓uip也知道自己的mac地址,移植中很容易忽略的問題.
代碼如下:uip_ethaddr就是uip應(yīng)該知道的mac地址.不然接收到的數(shù)據(jù)包會(huì)被無條件丟棄,除非他是廣播包
for (i = 0; i < 6; i++)
{
uip_ethaddr.addr[i] = macaddr[i];
}
printf("MAADR5 = 0x%x\r\n", enc28j60Read(MAADR5));
printf("MAADR4 = 0x%x\r\n", enc28j60Read(MAADR4));
printf("MAADR3 = 0x%x\r\n", enc28j60Read(MAADR3));
printf("MAADR2 = 0x%x\r\n", enc28j60Read(MAADR2));
printf("MAADR1 = 0x%x\r\n", enc28j60Read(MAADR1));
printf("MAADR0 = 0x%x\r\n", enc28j60Read(MAADR0));
#define UIP_UDP_APPCALL MY_UDP_APP
如果開啟了UDP支持,那么必須把UDP應(yīng)用程序接口給定義成自己要用的,比如如上:
void MY_UDP_APP(void)
{
switch (uip_udp_conn->rport)
{
case HTONS(6677):
UDP_6677_APP();
break;
case HTONS(67):
dhcpc_appcall();
break;
case HTONS(68):
dhcpc_appcall();
break;
case HTONS(53):
resolv_appcall();
}
}
定義完之后需要寫udp數(shù)據(jù)分類,uip_udp_conn->rport進(jìn)行解析可以得出remove端口的值,這個(gè)是外部發(fā)送過來的數(shù)據(jù)的端口號(hào).因?yàn)閁DP是運(yùn)輸層,所以通過上面這個(gè)簡(jiǎn)單的函數(shù)可以分類交給應(yīng)用層進(jìn)行解析.另外,因?yàn)橛械腃PU字節(jié)端序和網(wǎng)絡(luò)字節(jié)序不一樣,所以我們統(tǒng)一可以用HTONS來轉(zhuǎn)換我們的順序,這個(gè)函數(shù)會(huì)取決于你所設(shè)定的cpu端順序.反正請(qǐng)謹(jǐn)記,如果是16bit的數(shù)據(jù)是從網(wǎng)絡(luò)上傳過來的,都必須HTONS函數(shù)調(diào)用下才能轉(zhuǎn)化成我們程序能識(shí)別的數(shù)據(jù).
TCP也是一樣.就不贅述了.
uip_tcp_appstate_t:
這個(gè)也是編譯的時(shí)候會(huì)碰到的問題,它說白了就是一個(gè)結(jié)構(gòu)體重定義,在這里面有講解到:
如果你沒有使用任何關(guān)于他之中的tcp應(yīng)用層程序,比如telnet,或者simple_web.那么這個(gè)函數(shù)可以直接定義成
typedef int uip_tcp_appstate_t.
沒有任何意義,當(dāng)然也可以定義成自己的結(jié)構(gòu)體,那么他會(huì)關(guān)聯(lián)到每個(gè)你所使用的tcp鏈接里面,你去使用和定義tcp連接,每個(gè)tcp連接的結(jié)構(gòu)體數(shù)據(jù)都會(huì)獨(dú)立.這樣自己可用性會(huì)變強(qiáng).
在定義這個(gè)時(shí)候,比如你在app.h定義該函數(shù),必須包含關(guān)系如下
app.h <----- uipopt.h
conf.h <----- app.h
uip.c <----- conf.h
當(dāng)調(diào)用uip.h就會(huì)自動(dòng)定義進(jìn)去了.
問題所在:根據(jù)官方文檔:這個(gè)結(jié)構(gòu)體被多次包含之后,會(huì)出現(xiàn)重復(fù)定義的狀況,這是比較糾結(jié)的.如果按照我的水平理解,作者這樣子做是不是還要改他的程序,不然smtp和telnet和web應(yīng)用是不是無法同時(shí)開啟,但是這就不符合我們的協(xié)議棧的設(shè)計(jì)初衷了.
對(duì)此,根據(jù)網(wǎng)上的一些方法,我總結(jié)如下方法:
1.這個(gè)是一個(gè)老外的問題解答,通過上述可知,我們可以把a(bǔ)pp...定義成一個(gè)最大的數(shù)組(完全取決于你的最大結(jié)構(gòu)體的定義).
2.動(dòng)態(tài)分配法,這個(gè)可能會(huì)花費(fèi)的精力比較多點(diǎn),那就是把a(bǔ)pp...更改成一個(gè)指針,然后每次有一個(gè)新的連接建立的時(shí)候,就要去申請(qǐng)一個(gè)結(jié)構(gòu)體的空間,然后指針指向該結(jié)構(gòu)體,連接完成后再釋放,這個(gè)方法我感覺不可取,因?yàn)槎褧?huì)照成碎片,到最后甚至沒法申請(qǐng)到空間,那么程序就會(huì)崩潰,這是致命的.
3.放棄該app...,但是這個(gè)事沒有辦法了,我這邊簡(jiǎn)單介紹下如何使用第一種方法:
因?yàn)樵摻Y(jié)構(gòu)體是包含在uip.h的,然后空間申請(qǐng)是在uip.c里面的,所以,uip.c是決定該空間的最大關(guān)系者.
1 struct StrAppSta
2 {
3 unsigned char strappsta[maxsize];
4 };
5
6 typedef struct StrAppSta uip_tcp_appstate_t;
7
8 // 根據(jù)程序編譯前的拷貝,在包含uip.h時(shí)候要先定義app.
9 #include "uip.h"
10
11 // 下面是uip.c的文件內(nèi)容.......
12 // ..........
13 // .......
警告:在包含uip.h的時(shí)候都要注意,不要隨意疊加包含,可能會(huì)照成如上錯(cuò)誤.
其他有用到uip.h但是沒有用到該結(jié)構(gòu)體的可以如下定義
typedef int uip_tcp_appstate_t;
#include "uip.h"
// 下面other.c 的文件內(nèi)容......
// ................
// ...........
在有用到uip.h的地方,請(qǐng)按如下方法定義:比如telnet
1 #ifndef __TELNETD_H__
2 #define __TELNETD_H__
3
4 void telnetd_appcall(void);
5 void telnetd_init(void);
6
7 #endif /* __TELNETD_H__ */
1 struct telnetd_state
2 {
3 char *lines[TELNETD_CONF_NUMLINES];
4 char buf[TELNETD_CONF_LINELEN];
5 char bufptr;
6 u8_t numsent;
7 u8_t state;
8 };
9
10 typedef struct telnetd_state uip_tcp_appstate_t;
11 #include "uipopt.h"
12 #include "uip.h"
13
14 // 下面是內(nèi)容.............
15 // .............
16 // .....telnet.c
因?yàn)檫@個(gè)這個(gè)結(jié)構(gòu)體說白了也就是一塊內(nèi)存,看我們是怎么去使用它而已,struct就模子,三角形的模子往豆腐一壓豆腐就是三角形,圓形就是圓形豆腐,但是豆腐本質(zhì)是不會(huì)變的,只是看你去怎么吃豆腐,怎么去把豆腐給切成不同的形狀而已,同理,該切的豆腐也是在同一塊地方的,所以不同擔(dān)心會(huì)越界,因?yàn)榻Y(jié)構(gòu)體是按內(nèi)存順序排列的.
方法二比較復(fù)雜,也比較危險(xiǎn),我們可以做如下措施,那就是使用自己的內(nèi)存管理函數(shù),這個(gè)我可以把malloc函數(shù)去掉,寫自己的mymalloc,并且要帶有內(nèi)存管理的,這個(gè)如何書寫我不多述,可以參考ucGUI內(nèi)存管理機(jī)制,之前寫的那個(gè)不知道跑哪去了,不然可以給大家參考下.
uIP的移植和一些勘誤和修改就做到這,還有問題可以直接聯(lián)系我或者留言,我都會(huì)第一時(shí)間幫你解答.共同進(jìn)步是吧.
PART2:uIP的UDP精講(傳輸層挑選)
進(jìn)行UDP傳輸?shù)臅r(shí)候也是要定義一個(gè)接口
#define UIP_UDP_APPCALL MY_UDP_APP
然后示例代碼如下
void MY_UDP_APP(void)
{
switch (uip_udp_conn->rport)
{
case HTONS(6677):
UDP_6677_APP();
break;
case HTONS(67):
dhcpc_appcall();
break;
case HTONS(68):
dhcpc_appcall();
break;
case HTONS(53):
resolv_appcall();
}
}
因?yàn)檫@個(gè)UDP總的用戶接口,所以我們需要對(duì)數(shù)據(jù)包進(jìn)行分類,上面也已經(jīng)說了這個(gè)的具體工作原理.就不贅述了.
在下面,我做了一件簡(jiǎn)單的demo來證明我們的udp是可用的,poll是輪詢函數(shù),每當(dāng)輪詢到來的時(shí)候,就發(fā)送auto send字符串,另外呢,如果接收到新的數(shù)據(jù)就把數(shù)據(jù)送往串口,然后往udp端口送數(shù)據(jù),數(shù)據(jù)為"receive the data!\r\n".待會(huì)給大家上圖,結(jié)果很簡(jiǎn)單.
void UDP_6677_APP(void)
{
/* 判斷當(dāng)前狀態(tài) */
if (uip_poll())
{
char *tmp_dat = "the auto send!\r\n";
uip_send((char *)tmp_dat,strlen(tmp_dat));
}
if (uip_newdata())
{
char *tmp_dat = "receive the data!\r\n";
/* 收到新數(shù)據(jù) */
printf("%d",uip_len);
printf("%s",(char *)uip_appdata);
uip_send((char *)tmp_dat,strlen(tmp_dat));
}
}
當(dāng)然,免不了要做UDP初始化的,首先,我們需要在uip-conf.h中開啟我們的udp支持,另外我們要在初始化完ip地址之后初始化端口,端口初始化函數(shù)如下
uip_ipaddr(ipaddr, 192, 168, 0, 149);
c = uip_udp_new(&ipaddr, HTONS(6677));
if(c != NULL) {
uip_udp_bind(c, HTONS(6677));
}
初始化完就可以正常使用了.
另外大家也一直想問udp是如何主動(dòng)發(fā)送數(shù)據(jù)的,包括tcp,tcp需要在ack函數(shù)中發(fā)送數(shù)據(jù),而udp呢,選擇udp可是因?yàn)樗俣瓤紤],不然一個(gè)沒有滑動(dòng)窗口支持,沒有捎帶確認(rèn),沒有選擇確認(rèn)的uip是沒有辦法達(dá)到所謂的tcp快速傳數(shù)據(jù)的,所以u(píng)dp當(dāng)然就成為了首選.
如果認(rèn)真看上面代碼,可以發(fā)現(xiàn),udp主動(dòng)發(fā)送數(shù)據(jù)就是通過輪詢函數(shù)來實(shí)現(xiàn)的,因?yàn)樗械腶pp接口調(diào)用都要先調(diào)用process函數(shù)才能正常完成一整個(gè)發(fā)送過程,這就要求我們必須切個(gè)入口進(jìn)來,入口嘛,就是poll.
那如何加快發(fā)送速度呢?簡(jiǎn)單,那就是把udp的輪詢函數(shù)放到主函數(shù)的while循環(huán)第一層,那么循環(huán)次數(shù)加快,要發(fā)送什么數(shù)據(jù)沒有呢?
PART3:uIP的TCP精講(傳輸層挑選)
這里我僅對(duì)一個(gè)簡(jiǎn)單的demo進(jìn)行分析:
telnet:
void
telnetd_appcall(void)
{
static unsigned int i;
if(uip_connected())
{
/* tcp_markconn(uip_conn, &s);*/
for(i = 0; i < TELNETD_CONF_NUMLINES; ++i)
{
s.lines[i] = NULL;
}
s.bufptr = 0;
s.state = STATE_NORMAL;
shell_start();
}
if(s.state == STATE_CLOSE)
{
s.state = STATE_NORMAL;
uip_close();
return;
}
if(uip_closed() || uip_aborted() || uip_timedout())
{
closed();
}
if(uip_acked())
{
acked();
}
if(uip_newdata())
{
newdata();
}
if(uip_rexmit() || uip_newdata() || uip_acked() || uip_connected() || uip_poll())
{
senddata();
}
}
uipappcall函數(shù)入口點(diǎn)可以設(shè)定成telneted_appcall,每當(dāng)process進(jìn)來的時(shí)候,都會(huì)自動(dòng)跳轉(zhuǎn)到該函數(shù).
在我們移植telnet的時(shí)候,需要先監(jiān)聽端口,當(dāng)然,這部分都已經(jīng)做好了,那就是
void
telnetd_init(void)
{
uip_listen(HTONS(23));
memb_init(&linemem);
shell_init();
}
它用到了里面的一個(gè)內(nèi)存管理函數(shù),因?yàn)闀r(shí)間有限,所以沒有認(rèn)真研究,另外他監(jiān)聽了一個(gè)端口,那就是uip_listen(HTONS(23)),這個(gè)函數(shù)是用來建立連接的,等輪訓(xùn)函數(shù)一到,那么該連接就會(huì)被建立.
從很多的if語句可以看出uip的用戶程序狀態(tài)機(jī)是通過標(biāo)志位來設(shè)定的,我們通過回調(diào)函數(shù)來看到要執(zhí)行什么動(dòng)作都必須查詢動(dòng)作標(biāo)志位才能執(zhí)行,像比如超時(shí),新數(shù)據(jù)到來,重發(fā)等等.
關(guān)于重發(fā):大家可能會(huì)有這樣的疑問,uIP不是只有一個(gè)緩沖區(qū)嗎,那怎么實(shí)現(xiàn)重發(fā),它這里有個(gè)機(jī)制,那就是等待重發(fā),重發(fā)機(jī)制需要我們自己做,所以我們需要等待到來的ack才能繼續(xù)發(fā)送我們下一個(gè)文件,這就需要我們開設(shè)一個(gè)緩沖區(qū)去緩沖我們要發(fā)送的數(shù)據(jù),但是由于tcp有ack延遲機(jī)制,1s鐘還是只能發(fā)送5個(gè)包,但是uip是沒有ack延遲機(jī)制的,因?yàn)樗鼪]有滑動(dòng)窗口,所以收到telnet發(fā)送過來的數(shù)據(jù),那么就會(huì)直接發(fā)送ack包過去,所以你會(huì)看到你的telnet是可以很流暢的輸入字符的,然后返回的話是一整串返回的.
備注:但是我對(duì)windows的telnet進(jìn)行測(cè)試卻得出如下結(jié)果.
可以看出,服務(wù)器好像針對(duì)telnet的協(xié)議棧做了特別的修改(因?yàn)閠elnet客戶端都是單字節(jié)傳送的),然后呢,可以發(fā)現(xiàn)服務(wù)器直接對(duì)我進(jìn)行回應(yīng)了上一個(gè)包,證明包已經(jīng)收到,請(qǐng)快速發(fā)送下一個(gè)字符.(20多ms的延遲).
所以協(xié)議棧不一定是按照標(biāo)準(zhǔn)寫的.
TCP部分就差不多到這了,都是講一些比較特別的.因?yàn)槲也粦?yīng)該重復(fù)別人做過的事情,這樣子也會(huì)浪費(fèi)大家時(shí)間.
PART4:uIP的DHCP優(yōu)化和勘誤(應(yīng)用層挑選)
要看這部分的時(shí)候請(qǐng)先閱讀dhcp的四次詳細(xì)過程,因?yàn)槔锩嫔婕暗揭恍┻@方面的知識(shí),當(dāng)然,我會(huì)寫的簡(jiǎn)單點(diǎn),這樣子比較容易理解.
dhcp的入口函數(shù)
static
PT_THREAD(handle_dhcp(void))
{
PT_BEGIN(&s.pt);
/* try_again:*/
s.state = STATE_SENDING;
s.ticks = CLOCK_SECOND;
do {
send_discover();
timer_set(&s.timer, s.ticks);
PT_WAIT_UNTIL(&s.pt, uip_newdata() || timer_expired(&s.timer));
if(uip_newdata() && parse_msg() == DHCPOFFER) {
s.state = STATE_OFFER_RECEIVED;
break;
}
if(s.ticks CLOCK_SECOND * 60) {
s.ticks *= 2;
}
} while(s.state != STATE_OFFER_RECEIVED);
s.ticks = CLOCK_SECOND;
do {
send_request();
timer_set(&s.timer, s.ticks);
PT_WAIT_UNTIL(&s.pt, uip_newdata() || timer_expired(&s.timer));
if(uip_newdata() && parse_msg() == DHCPACK) {
s.state = STATE_CONFIG_RECEIVED;
break;
}
if(s.ticks = CLOCK_SECOND * 10) {
s.ticks += CLOCK_SECOND;
} else {
PT_RESTART(&s.pt);
}
} while(s.state != STATE_CONFIG_RECEIVED);
#if 0
printf("Got IP address %d.%d.%d.%d\n",
uip_ipaddr1(s.ipaddr), uip_ipaddr2(s.ipaddr),
uip_ipaddr3(s.ipaddr), uip_ipaddr4(s.ipaddr));
printf("Got netmask %d.%d.%d.%d\n",
uip_ipaddr1(s.netmask), uip_ipaddr2(s.netmask),
uip_ipaddr3(s.netmask), uip_ipaddr4(s.netmask));
printf("Got DNS server %d.%d.%d.%d\n",
uip_ipaddr1(s.dnsaddr), uip_ipaddr2(s.dnsaddr),
uip_ipaddr3(s.dnsaddr), uip_ipaddr4(s.dnsaddr));
printf("Got default router %d.%d.%d.%d\n",
uip_ipaddr1(s.default_router), uip_ipaddr2(s.default_router),
uip_ipaddr3(s.default_router), uip_ipaddr4(s.default_router));
printf("Lease expires in %ld seconds\n",
ntohs(s.lease_time[0])*65536ul + ntohs(s.lease_time[1]));
#endif
dhcpc_configured(&s);
/* timer_stop(&s.timer);*/
/*
* PT_END restarts the thread so we do this instead. Eventually we
* should reacquire expired leases here.
*/
while(1) {
PT_YIELD(&s.pt);
}
PT_END(&s.pt);
}
它這里使用了一種很好的方法,叫做協(xié)程,避開了我們傳統(tǒng)的阻塞方法(通過前后臺(tái)系統(tǒng),在該函數(shù)運(yùn)行的時(shí)候,死循環(huán)等待一個(gè)消息到來,消息可以通過更高優(yōu)先級(jí)的中斷去更新.),它則是每次進(jìn)來都跳轉(zhuǎn)到該個(gè)地方進(jìn)行查詢,即實(shí)現(xiàn)是阻塞所要達(dá)到的目的,同時(shí),也釋放了該cpu資源.
好了,廢話說到這,因?yàn)橄旅鏁?huì)詳細(xì)講述:
看了代碼可以發(fā)現(xiàn)如下錯(cuò)誤,在兩個(gè)do-while循環(huán)中間沒有進(jìn)行標(biāo)志位清空,導(dǎo)致程序誤判以為是dhcp已經(jīng)接收到下一個(gè)數(shù)據(jù)了.另外沒有dhcp租約機(jī)制沒有寫進(jìn)去.所以,基于上述錯(cuò)誤,進(jìn)行修改如下,在stm32平臺(tái)上測(cè)試一切正常,可以在租約時(shí)間里面到達(dá)自動(dòng)發(fā)送request包去重新續(xù)約所要的ip地址.
另外,還有一個(gè)錯(cuò)誤,那就是如果發(fā)送了n個(gè)request包還沒有回應(yīng),那么就會(huì)自動(dòng)回到discover狀態(tài),這個(gè)可以理解,因?yàn)橛行ヾhcp服務(wù)器對(duì)于你所要續(xù)約的ip并不感冒(dhcp服務(wù)器換了,或者該ip地址已經(jīng)有人了,但是有的dhcp服務(wù)器會(huì)給你另外一個(gè)ip,這是不同機(jī)制決定的,你要是想的話也可以自己寫一個(gè),挺簡(jiǎn)單的).
但是上述的卻是沒有把結(jié)構(gòu)體里面的數(shù)據(jù)清空就直接回到discover狀態(tài).所以這是錯(cuò)誤的,這個(gè)可以留給讀者自己更改.我就把第一點(diǎn)改了,可以看我代碼:
1 static
2 PT_THREAD(handle_dhcp(void))
3 {
4 PT_BEGIN(&s.pt);
5
6 /* try_again:*/
7 s.state = STATE_SENDING;
8 s.ticks = CLOCK_SECOND;
9
10 do
11 {
12 send_discover();
13 timer_set(&s.timer, s.ticks);
14 PT_WAIT_UNTIL(&s.pt, uip_newdata() || timer_expired(&s.timer));
15
16 if(uip_newdata() && parse_msg() == DHCPOFFER)
17 {
18 s.state = STATE_OFFER_RECEIVED;
19 break;
20 }
21
22 if(s.ticks < CLOCK_SECOND * 60)
23 {
24 s.ticks *= 2;
25 }
26 }
27 while(s.state != STATE_OFFER_RECEIVED);
28
29 s.ticks = CLOCK_SECOND;
30 uip_flags = 0;
31
32 request_pro:
33 do
34 {
35 send_request();
36 timer_set(&s.timer, s.ticks);
37 PT_WAIT_UNTIL(&s.pt, uip_newdata() || timer_expired(&s.timer));
38
39 if(uip_newdata() && parse_msg() == DHCPACK)
40 {
41 s.state = STATE_CONFIG_RECEIVED;
42 break;
43 }
44
45 if(s.ticks <= CLOCK_SECOND * 10)
46 {
47 s.ticks += CLOCK_SECOND;
48 }
49 else
50 {
51 PT_RESTART(&s.pt);
52 }
53 }
54 while(s.state != STATE_CONFIG_RECEIVED);
55
56 #if 1
57 printf("Got IP address %d.%d.%d.%d\r\n",
58 uip_ipaddr1(s.ipaddr), uip_ipaddr2(s.ipaddr),
59 uip_ipaddr3(s.ipaddr), uip_ipaddr4(s.ipaddr));
60 printf("Got netmask %d.%d.%d.%d\r\n",
61 uip_ipaddr1(s.netmask), uip_ipaddr2(s.netmask),
62 uip_ipaddr3(s.netmask), uip_ipaddr4(s.netmask));
63 printf("Got DNS server %d.%d.%d.%d\r\n",
64 uip_ipaddr1(s.dnsaddr), uip_ipaddr2(s.dnsaddr),
65 uip_ipaddr3(s.dnsaddr), uip_ipaddr4(s.dnsaddr));
66 printf("Got default router %d.%d.%d.%d\r\n",
67 uip_ipaddr1(s.default_router), uip_ipaddr2(s.default_router),
68 uip_ipaddr3(s.default_router), uip_ipaddr4(s.default_router));
69 printf("Lease expires in %ld seconds\r\n",
70 ntohs(s.lease_time[0]) * 65536ul + ntohs(s.lease_time[1]));
71 #endif
72
73 dhcpc_configured(&s);
74
75 /* timer_stop(&s.timer);*/
76
77 /*
78 * PT_END restarts the thread so we do this instead. Eventually we
79 * should reacquire expired leases here.
80 */
81 uip_ipaddr(ipaddr, 192, 168, 0, 149);
82 c = uip_udp_new(&ipaddr, HTONS(6677));
83 if(c != NULL) {
84 uip_udp_bind(c, HTONS(6677));
85 }
86 telnetd_init();
87 /* 判斷超時(shí) */
88 timer_set(&s.timer,(ntohs(s.lease_time[0]) * 65536ul + ntohs(s.lease_time[1]))*50);
89 PT_WAIT_UNTIL(&s.pt, timer_expired(&s.timer));
90 /* 超時(shí)了 */
91 goto request_pro;
92
93 while (1)
94 {
95 PT_YIELD(&s.pt);
96 }
97
98 PT_END(&s.pt);
99 }
dhcp部分就到這里,一些具體函數(shù)我不說了,就是對(duì)包內(nèi)容進(jìn)行處理,沒什么好講的.
PART5:uIP的ProtoThread講解(協(xié)程)
剛才也說了,在dhcp中應(yīng)用了一種非常新穎的技術(shù),它通過對(duì)一些宏定義的包裝和C99新定義標(biāo)準(zhǔn)的應(yīng)用,給我們封裝了非常好用的函數(shù),不得不佩服作者把lightweight深入到這種程度,對(duì)于PT(以下都這么稱呼),主要適用的場(chǎng)合為:一個(gè)函數(shù)需要不斷去輪詢的;函數(shù)中間需要等待某種事件的發(fā)生;沒有中斷的系統(tǒng)中.
原因,如果在主函數(shù)中阻塞,那么你就必須通過中斷來解除阻塞,這對(duì)于沒有中斷的來說是很致命的,所以你能想到的方法就是不斷的進(jìn)該函數(shù),但是每次進(jìn)該函數(shù)都要重頭運(yùn)行到該行,這樣子也不行,所以你就用了很多if語句,甚至不能用while(1),這是很難受的,所以,作者封裝了這個(gè)PT庫(kù),可以讓我們像線程那樣去使用我們的函數(shù),我們甚至可以把這當(dāng)成是一種掛起,而且它很省內(nèi)存,就一個(gè)結(jié)構(gòu)體的占用(用來記錄進(jìn)入該函數(shù)要到哪運(yùn)行),所以沒有獨(dú)立的堆棧使得你使用其他可以得心應(yīng)手.
1 static
2 PT_THREAD(函數(shù)名(void))
3 {
4 PT_BEGIN(&s.pt);
14 PT_WAIT_UNTIL(&s.pt,條件);
15
16 while (1)
17 {
18 PT_YIELD(&s.pt);
19 }
20
21 PT_END(&s.pt);
22 }
PT_begin和pt_end是搭套使用的,我最后把他們都翻譯成沒有封裝的,就這這樣子了:
1 char PT_YIELD_FLAG = 1;
2 switch(s) {
3 case 0:
4
5
6 //PT_WAIT_UNTIL(pt, condition)
7 do {
8 //LC_SET((pt)->lc);
9 s = __LINE__;// line == 20;
10 case __LINE__:// case 20:
11 if(!(condition))
12 {
13 return PT_WAITING;
14 }
15 } while(0)
16
17
18 //PT_WAIT_UNTIL(pt, condition)
19 do {
20 //LC_SET((pt)->lc);
21 s = __LINE__; \// line == 30;
22 case __LINE__:// case 30:
23 if(!(condition))
24 {
25 return PT_WAITING;
26 }
27 } while(0)
28
29 while(1)
30 {
31 //#define PT_YIELD(pt)
32 do {
33 PT_YIELD_FLAG = 0;
34 LC_SET((pt)->lc);
35 if(PT_YIELD_FLAG == 0) {
36 return PT_YIELDED;
37 }
38 } while(0)
39 }
40 //#define PT_END(pt)
41 //LC_END((pt)->lc);
42 PT_YIELD_FLAG = 0;
43 //PT_INIT(pt);
44 pt <= 0;
45 return PT_ENDED;
46 }
這個(gè)看出,這個(gè)就是個(gè)巨大的switch-case函數(shù)封裝庫(kù),說白了他就是通過記錄下當(dāng)前函數(shù)運(yùn)行到哪一行然后記錄下來放進(jìn)靜態(tài)變量里面,下次進(jìn)來的時(shí)候直接根據(jù)上次運(yùn)行的結(jié)果case進(jìn)來,(s = __LINE__,這是C99新增標(biāo)準(zhǔn),一般是用來調(diào)試使用的,沒想到作者這么有才,竟然給他放到了用來跳轉(zhuǎn)行數(shù)使用.)這樣子就可以跳過前面的代碼而直接執(zhí)行你要的判斷,當(dāng)判斷完成之后就可以繼續(xù)執(zhí)行下去了,當(dāng)然,你程序也可以那么寫,只是程序會(huì)比較難看一點(diǎn),所以,用作者寫的庫(kù)是不二選擇.
具體要了解更多可以通讀pt-refman文檔.
PART6:關(guān)于我的個(gè)人協(xié)議棧hIP,點(diǎn)圖片下文件.
PART7:源代碼展示和下載
基于stm32f10x的源代碼下載,pt文檔下載,uip1.0文檔下載
main Code:
1 #ifndef __UIP_CONF_H__
2 #define __UIP_CONF_H__
3
4 #include <inttypes.h>
5
6 /**
7 * 8 bit datatype
8 *
9 * This typedef defines the 8-bit type used throughout uIP.
10 *
11 * \hideinitializer
12 */
13 typedef uint8_t u8_t;
14
15 /**
16 * 16 bit datatype
17 *
18 * This typedef defines the 16-bit type used throughout uIP.
19 *
20 * \hideinitializer
21 */
22 typedef uint16_t u16_t;
23
24 /**
25 * Statistics datatype
26 *
27 * This typedef defines the dataype used for keeping statistics in
28 * uIP.
29 *
30 * \hideinitializer
31 */
32 typedef unsigned short uip_stats_t;
33
34 /**
35 * Maximum number of TCP connections.
36 * 最大的tcp連接端口數(shù)量,每增大一個(gè)連接,都會(huì)消耗一部分內(nèi)存,這個(gè)可以自己做實(shí)驗(yàn)得出結(jié)論
37 * \hideinitializer
38 */
39 #define UIP_CONF_MAX_CONNECTIONS 10
40
41 /**
42 * Maximum number of listening TCP ports.
43 * 最大的tcp監(jiān)聽端口數(shù)量
44 * \hideinitializer
45 */
46 #define UIP_CONF_MAX_LISTENPORTS 10
47
48 /**
49 * uIP buffer size.
50 * 定義網(wǎng)絡(luò)最大接收包大小,這個(gè)看內(nèi)存容量而定,
51 * \hideinitializer
52 */
53 #define UIP_CONF_BUFFER_SIZE 1500
54
55 /**
56 * CPU byte order.
57 * cpu大小端定義,這個(gè)閱讀datasheet就可以得知,比如stm32都是小端順序的,而網(wǎng)絡(luò)字節(jié)序都是大端順序的
58 * \hideinitializer
59 */
60 #define UIP_CONF_BYTE_ORDER LITTLE_ENDIAN
61
62 /**
63 * Logging on or off
64 * 是否開啟日志功能,如果開啟,需要重定向日志輸出函數(shù)
65 * \hideinitializer
66 */
67 #define UIP_CONF_LOGGING 0
68
69 /**
70 * UDP support on or off
71 * UDP協(xié)議支持開關(guān)
72 * \hideinitializer
73 */
74 #define UIP_CONF_UDP 1
75
76 /**
77 * UDP checksums on or off
78 * udp數(shù)據(jù)校驗(yàn)和使能
79 * \hideinitializer
80 */
81 #define UIP_CONF_UDP_CHECKSUMS 1
82
83 /**
84 * uIP statistics on or off
85 * uip統(tǒng)計(jì)功能開啟,這個(gè)只有在調(diào)試的時(shí)候有用,如果不需要,請(qǐng)關(guān)掉,節(jié)約空間
86 * \hideinitializer
87 */
88 #define UIP_CONF_STATISTICS 1
89
90 /* Here we include the header file for the application(s) we use in
91 our project. */
92 /*#include "smtp.h"*/
93 /*#include "hello-world.h"*/
94 #include "telnetd.h"
95 //#include "webserver.h"
96 #include "dhcpc.h"
97 #include "resolv.h"
98 /*#include "webclient.h"*/
99 #include "myAPP.h"
100
101 #endif /* __UIP_CONF_H__ */
102
103 /** @} */
104 /** @} */
1 #ifndef __CLOCK_ARCH_H__
2 #define __CLOCK_ARCH_H__
3 // 關(guān)于這個(gè) CLOCK_CONF_SECOND 是定義uIP的每秒的心跳次數(shù),這個(gè)在uIP的內(nèi)部時(shí)鐘函數(shù)里面會(huì)是無比重要的
4 // 下面還有個(gè)搭配函數(shù)可以用來實(shí)現(xiàn)
5 typedef int clock_time_t;
6 #define CLOCK_CONF_SECOND 100
7
8 #endif /* __CLOCK_ARCH_H__ */
1 #include "clock-arch.h"
2 #include "stm32f10x.h"
3
4 extern __IO int32_t g_RunTime;
5 /*---------------------------------------------------------------------------*/
6 clock_time_t
7 clock_time(void)
8 {
9 return g_RunTime;// 這個(gè)用來返回系統(tǒng)當(dāng)前運(yùn)行時(shí)間,根據(jù)我們上面的設(shè)定,是10ms加1
// 如果是上面定義的是1000 ,那么就是1ms+1
10 }
11 /*---------------------------------------------------------------------------*/
成果展示:
DHCP獲取
UDP主動(dòng)發(fā)送數(shù)據(jù)
UDP數(shù)據(jù)處理
TCP測(cè)試 telnet
畢
2012年1月19日23:16:35
HXH
QQ:250569103