檔案說明:
- 這是uIP1.0源碼檔案,apps是作者寫好的應用程式demo,doc是一些文檔,lib裡面隻有一個檔案是記憶體申請與釋放函數的接口,uip是tcp/ip的協定棧了,unix是與外部的接口,我們移植也主要去改這些檔案。
- 說一下一眼看不出作用的檔案。lc.h、lc-addrlabels.h、lc-switch.h、psock.c、psock.h和pt.h這些是一個模拟線程的輕量級庫protothreads。uip_arp.c位址解析協定的接口。uip-fw.c是多重網絡轉發資料包的借口(沒用到)。uiplib.c裡隻有一個函數uiplib_ipaddrconv:把ip位址轉換成字元格式(沒用到)。uip-neighbor.c可能是存儲網絡上一下ip與mac(網絡鄰居?沒用到)。uip-split.c拆包發送的借口,tcp/ip協定規定包的長度不能超過1500個位元組,否則需要拆包發送(沒用到)。
- clock-arch.c裡面隻有一個函數clock_time。main函數自己重寫吧。tapdev.c是與硬體網絡子產品的接口。uip-conf.h是一些配置。
- 為什麼我覺得uip比RL_TCPnet移植更麻煩?就是因為uip的檔案關系有點亂,而且一些資料類型需要重定義,甚至還需要改動庫檔案裡的,可能RL_TCPnet是因為Keil公司專門寫了用于M3核心的庫才沒有這麼些問題吧。下面是簡單的頭檔案包含關系,在移植的時候需要注意。
移植:
- 因為隻做udp、tcp(用戶端與伺服器)和http,是以添加這些檔案就夠了。工程結構如下圖。uip_user.c是自己寫的應用函數。
- 我用的網絡子產品是enc28j60。網絡上有很多該子產品的驅動,隻要改一下spi管腳配置就行。
- tapdev.c就是uip與硬體的接口函數了。具體見下。
void
tapdev_init(unsigned char *my_mac)
{
int i;
/*初始化 enc28j60*/
enc28j60Init(my_mac);
for (i = 0; i < 6; i++)
{
uip_ethaddr.addr[i] = my_mac[i];
}
}
/*---------------------------------------------------------------------------*/
/****************************************************************************
* 名 稱:uint16_t tapdev_read(void)
* 功 能:
* 入口參數:讀取一包資料
* 出口參數: 如果一個資料包收到傳回資料包長度,以位元組為機關,否則為零。
* 說 明:
* 調用方法:
****************************************************************************/
unsigned int tapdev_read(void)
{
return enc28j60PacketReceive(1500,uip_buf);
}
/****************************************************************************
* 名 稱:void tapdev_send(void)
* 功 能:
* 入口參數:發送一包資料
* 出口參數:
* 說 明:
* 調用方法:
****************************************************************************/
void tapdev_send(void)
{
enc28j60PacketSend(uip_len,uip_buf);
}
- uip-conf.h是配置檔案,關閉日志輸出:#define UIP_CONF_LOGGING 0。打開udp連接配接:#define UIP_CONF_UDP 1。隻留下#include webserver.h(裡面有個http回調函數,不過最好在之前自己寫回調函數,因為還要做tcp的其他連接配接)。ps.實際的配置檔案是uipopth.h,不過那裡面的一般不用改。
/* 定義應用程式回調函數 */
#ifndef UIP_APPCALL
#define UIP_APPCALL tcp_appcall
#endif
#define UIP_UDP_APPCALL udp_appcall
//uip-tcp的總回調函數
void tcp_appcall(void)
{
if(uip_conn->lport == HTONS(80))
{
httpd_appcall();//指向了http.c的httpd_appcall()回調函數。
}
if(uip_conn->lport == HTONS(LocalPort))
{
tcp_server_appcall();
}
if(uip_conn->rport == HTONS(RemotePort))
{
tcp_client_appcall();
}
}
- clock-arch.h中隻有一個函數clock_time()。uip這個協定棧需要時間輪詢函數,在clock-arch.h中定義#define CLOCK_CONF_SECOND 100,也就是100毫秒。由于我是用的ucos-ii,是以就寫在系統時間鈎子函數(在os_cpu_c.c裡),如果沒有用的話,也隻需要另開一個定時器就可以了。
volatile INT32U uIP_time = 0;
#if (OS_CPU_HOOKS_EN > 0) && (OS_TIME_TICK_HOOK_EN > 0)
void OSTimeTickHook (void)
{
#if OS_APP_HOOKS_EN > 0
App_TimeTickHook();
#endif
static INT8U s_count = 0;
if (++s_count >= 10)
{
s_count = 0;
uIP_time++; /* 全局運作時間每10ms增1 */
if (uIP_time == 0x80000000)
{
uIP_time = 0;
}
}
#if OS_TMR_EN > 0
OSTmrCtr++;
if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC)) {
OSTmrCtr = 0;
OSTmrSignal();
}
#endif
}
#endif
extern u16 LocalPort; //本地端口
extern u16 RemotePort; //遠端端口
extern u8 RemoteIP[4]; //遠端ip
extern u8 enc_send_buf[256]; //發送資料最大位元組256
extern u8 enc_send_size; //需要發送的位元組數
void Net_Init(void); //網絡初始化
void Udp_Init(void); //udp初始化
void Tcp_Init(void); //tcp初始化
void tcp_appcall(void); //tcp總回調函數
void udp_appcall(void); //udp總回調函數
void Uip_Proc(void); //uip的處理函數(需要循環調用)
void tcp_server_appcall(void); //tcp作為伺服器時的回調函數
void tcp_client_appcall(void); //tcp作為用戶端時的回調函數
void tcp_newdata(void); //tcp接收函數
void tcp_senddata(void); //tcp發送函數
void udp_newdata(void); //udp接收函數
void udp_senddata(void); //udp發送函數
- 到此,uip的移植基本上差不多了。隻需要再修改一些小錯誤:比如,資料類型的重定義,删除一些列印函數,tcp_udp_appstate_t(使用者自定義類型)的重定義在http.c裡面有了,如果不需要用到作者的一些應用函數可以直接定義成int,同樣uip_udp_appstate_t(使用者自定義類型)也可以直接定義為int,當然也可以定義成數組,把資料的指針和長度放裡面。
- pt.h中115行中把PT_YIELD_FLAG定義為volatile可變的,可以減少好的警告資訊。
- psock.c中164行,添加一行uip_flags &= ~UIP_ACKDATA。還有一個udp連接配接bug在uip.c裡,具體可見奮鬥闆。
static char
data_acked(register struct psock *s)
{
if(s->state == STATE_DATA_SENT && uip_acked()) {
uip_flags &= ~UIP_ACKDATA;
if(s->sendlen > uip_mss()) {
s->sendlen -= uip_mss();
s->sendptr += uip_mss();
} else {
s->sendptr += s->sendlen;
s->sendlen = 0;
}
s->state = STATE_ACKED;
return 1;
}
return 0;
}
應用:
void Net_Init(void)
{
uip_ipaddr_t ipaddr;
unsigned char mymac[6] = {0x04, 0x02, 0x35, 0x00, 0x00, 0x01}; //MAC位址
tapdev_init(mymac); //ENC28J60初始化
uip_init(); //UIP協定棧初始化
uip_ipaddr(ipaddr, 172, 72, 100, 212); //設定IP位址
uip_sethostaddr(ipaddr);
uip_ipaddr(ipaddr, 172, 72, 101, 1); //設定預設路由器IP位址
uip_setdraddr(ipaddr);
uip_ipaddr(ipaddr, 255, 255, 252, 0); //設定網絡掩碼
uip_setnetmask(ipaddr);
timer_set(&periodic_timer, CLOCK_SECOND / 2);
timer_set(&arp_timer, CLOCK_SECOND * 10); //設定輪詢時間
}
- udp的初始化連接配接注意一下綁定本地ip。tcp伺服器端隻需要監聽某個端口(80是http服務端口)。tcp用戶端需要建立連接配接。
//建立UDP連接配接初始化*****************************
void Udp_Init(void)
{
uip_ipaddr_t ipaddr;//定義IP類型變量
uip_ipaddr(ipaddr, RemoteIP[0], RemoteIP[1], RemoteIP[2], RemoteIP[3]); //遠端IP
if(my_udp_conn != NULL)
{
uip_udp_remove(my_udp_conn);//如果連接配接已經建立,則删除之
}
my_udp_conn = uip_udp_new(&ipaddr, HTONS(RemotePort));//建立到遠端端口
if(my_udp_conn != NULL)
{
uip_udp_bind(my_udp_conn, HTONS(LocalPort));//綁定本地端口
}
}
//作為tcp用戶端初始化
void Tcp_Init(void)
{
uip_ipaddr_t ipaddr;//定義IP類型變量
uip_ipaddr(ipaddr, RemoteIP[0], RemoteIP[1], RemoteIP[2], RemoteIP[3]); //遠端IP
my_tcp_conn = uip_connect(&ipaddr, HTONS(RemotePort));//建立到遠端端口
}
- 還有一個問題是:作為tcp用戶端的時候,pc機使用網絡調試助手,如果在連接配接後再點斷開的話就重連不上。 PC機斷開連接配接後,那個端口先是timeout然後應該就删除了,斷開後會進入2次uip_closed(),是以判斷一下重新連接配接(會配置設定新的端口)。如果在程式運作開始但是PC機并沒有打開連接配接的話,會一直進入uip_aborted(),在裡面設定重新連接配接,這樣會直到PC機打開。
if(uip_closed())
{
tcp_server_flag++;
if(tcp_server_flag == 2)
{
uip_ipaddr_t ipaddr;//定義IP類型變量
uip_ipaddr(ipaddr, RemoteIP[0], RemoteIP[1], RemoteIP[2], RemoteIP[3]); //遠端IP
my_tcp_conn = uip_connect(&ipaddr, HTONS(RemotePort));//建立到遠端端口
tcp_server_flag = 0;
}
}
if( uip_aborted())
{
uip_ipaddr_t ipaddr;//定義IP類型變量
uip_ipaddr(ipaddr, RemoteIP[0], RemoteIP[1], RemoteIP[2], RemoteIP[3]); //遠端IP
my_tcp_conn = uip_connect(&ipaddr, HTONS(RemotePort)); //建立到遠端端口
}
- 有關http的應用請見下一篇。ps.uip1.0的smtp好像不完整,請參照0.9版。
下載下傳:
- uip1.0英文使用指南下載下傳,請點選。
- stm32移植uip1.0工程源碼下載下傳,請點選。如果使用HG 隻要clone https://[email protected]/zouw96/uip_http