UIP是單片機界聯網的一個很好地選擇,移植這個庫有點複雜,首先是第一步,網卡驅動要寫好,使用的網卡芯片爲ENC28J60,驅動能夠再工程包裏面找到數組
//配置網卡硬件,並設置MAC地址 //返回值:0,正常;1,失敗; u8 tapdev_init(u8* macaddr) { u8 i,res=0; res=ENC28J60_Init((u8*)macaddr); //初始化ENC28J60 //把IP地址和MAC地址寫入緩存區 for (i = 0; i < 6; i++)uip_ethaddr.addr[i]=macaddr[i]; //指示燈狀態:0x476 is PHLCON LEDA(綠)=links status, LEDB(紅)=receive/transmit //PHLCON:PHY 模塊LED 控制寄存器 ENC28J60_PHY_Write(PHLCON,0x0476); return res; } //讀取一包數據 uint16_t tapdev_read(void) { return ENC28J60_Packet_Receive(MAX_FRAMELEN,uip_buf); } //發送一包數據 void tapdev_send(void) { ENC28J60_Packet_Send(uip_len,uip_buf); }
分別是初始化,讀,寫緩存
這些驅動會在一個叫作uip_call的函數中用到,其次,要設置uip的時鐘,這個時鐘適用於arp表的更新的服務器
#include "clock-arch.h" #include "sys.h" //時鐘驅動文件, //uip時鐘 extern u32 uip_timer;//uip 計時器,每10ms增長1. /*---------------------------------------------------------------------------*/ clock_time_t clock_time(void) { return uip_timer; /* 10ms 單位 */ }
u32 uip_timer=0;//uip 計時器,每10ms增長1. //定時器6中斷服務程序 void TIM6_IRQHandler(void) { if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) //檢查指定的TIM中斷髮生與否:TIM 中斷源 { uip_timer++;//uip計時器增長1 } TIM_ClearITPendingBit(TIM6, TIM_IT_Update ); //清除TIMx的中斷待處理位:TIM 中斷源 } //基本定時器6中斷初始化 //這裏時鐘選擇爲APB1的2倍,而APB1爲36M //arr:自動重裝值。 //psc:時鐘預分頻數 //這裏使用的是定時器3! void TIM6_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); //時鐘使能 TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器週期的值 計數到5000爲500ms TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來做爲TIMx時鐘頻率除數的預分頻值 10Khz的計數頻率 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位 TIM_ITConfig( TIM6,TIM_IT_Update|TIM_IT_Trigger,ENABLE);//使能定時器6更新觸發中斷 TIM_Cmd(TIM6, ENABLE); //使能TIMx外設 NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; //TIM3中斷 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先佔優先級0級 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //從優先級3級 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器 }
定時器的定時長度取決於這個宏定義網絡
#ifndef __CLOCK_ARCH_H__ #define __CLOCK_ARCH_H__ typedef int clock_time_t; #define CLOCK_CONF_SECOND 100 #endif /* __CLOCK_ARCH_H__ */
上面是100,也就是說定時器的長度應該是10MS架構
接下來是配置回調函數app
//uip事件處理函數 //必須將該函數插入用戶主循環,循環調用. void uip_polling(void) { u8 i; static struct timer periodic_timer, arp_timer; static u8 timer_ok=0; if(timer_ok==0)//僅初始化一次 { timer_ok = 1; timer_set(&periodic_timer,CLOCK_SECOND/2); //建立1個0.5秒的定時器 timer_set(&arp_timer,CLOCK_SECOND*10); //建立1個10秒的定時器 } uip_len=tapdev_read(); //從網絡設備讀取一個IP包,獲得數據長度.uip_len在uip.c中定義 if(uip_len>0) //有數據 { //處理IP數據包(只有校驗經過的IP包纔會被接收) if(BUF->type == htons(UIP_ETHTYPE_IP))//是不是IP包? { uip_arp_ipin(); //去除以太網頭結構,更新ARP表 uip_input(); //IP包處理 //當上面的函數執行後,若是須要發送數據,則全局變量 uip_len > 0 //須要發送的數據在uip_buf, 長度是uip_len (這是2個全局變量) if(uip_len>0)//須要迴應數據 { uip_arp_out();//加以太網頭結構,在主動鏈接時可能要構造ARP請求 tapdev_send();//發送數據到以太網 } }else if (BUF->type==htons(UIP_ETHTYPE_ARP))//處理arp報文,是不是ARP請求包? { uip_arp_arpin(); //當上面的函數執行後,若是須要發送數據,則全局變量uip_len>0 //須要發送的數據在uip_buf, 長度是uip_len(這是2個全局變量) if(uip_len>0)tapdev_send();//須要發送數據,則經過tapdev_send發送 } }else if(timer_expired(&periodic_timer)) //0.5秒定時器超時 { timer_reset(&periodic_timer); //復位0.5秒定時器 //輪流處理每一個TCP鏈接, UIP_CONNS缺省是40個 for(i=0;i<UIP_CONNS;i++) { uip_periodic(i); //處理TCP通訊事件 //當上面的函數執行後,若是須要發送數據,則全局變量uip_len>0 //須要發送的數據在uip_buf, 長度是uip_len (這是2個全局變量) if(uip_len>0) { uip_arp_out();//加以太網頭結構,在主動鏈接時可能要構造ARP請求 tapdev_send();//發送數據到以太網 } } #if UIP_UDP //UIP_UDP //輪流處理每一個UDP鏈接, UIP_UDP_CONNS缺省是10個 for(i=0;i<UIP_UDP_CONNS;i++) { uip_udp_periodic(i); //處理UDP通訊事件 //當上面的函數執行後,若是須要發送數據,則全局變量uip_len>0 //須要發送的數據在uip_buf, 長度是uip_len (這是2個全局變量) if(uip_len > 0) { uip_arp_out();//加以太網頭結構,在主動鏈接時可能要構造ARP請求 tapdev_send();//發送數據到以太網 } } #endif //每隔10秒調用1次ARP定時器函數 用於按期ARP處理,ARP表10秒更新一次,舊的條目會被拋棄 if(timer_expired(&arp_timer)) { timer_reset(&arp_timer); uip_arp_timer(); } } }
這個函數是uip的靈魂,能夠說所有的功能都是在這個函數裏面實現的,而後定義網卡數據回調函數框架
//通訊程序狀態字(用戶能夠本身定義) enum { STATE_CMD = 0, //命令接收狀態 STATE_TX_TEST = 1, //連續發送數據包狀態(速度測試) STATE_RX_TEST = 2 //連續接收數據包狀態(速度測試) }; //定義 uip_tcp_appstate_t 數據類型,用戶能夠添加應用程序須要用到 //成員變量。不要更改結構體類型的名字,由於這個類型名會被uip引用。 //uip.h 中定義的 struct uip_conn 結構體中引用了 uip_tcp_appstate_t struct tcp_appstate { u8_t state; u8_t *textptr; int textlen; }; struct uip_appstate { u8_t state; u8_t *textptr; int textlen; }; typedef struct tcp_appstate uip_tcp_appstate_t; typedef struct uip_appstate uip_udp_appstate_t; //TCP的回調 void tcp_appcall(void); void tcp_client_appcall(void); //tcp客戶端的回調,PC是服務器 void tcp_server_appcall(void); //tcp服務器的回調,pc是客戶端 //UDP的回調 void udp_appcall(void); void udp_send_appcall(void); void udp_recv_appcall(void); //定義應用程序回調函數 #ifndef UIP_APPCALL #define UIP_APPCALL tcp_appcall //定義回調函數爲 tcp_demo_appcall #endif #ifndef UIP_UDP_APPCALL #define UIP_UDP_APPCALL udp_appcall //定義回調函數爲 udp_demo_appcall #endif
UIP_UDP_APPCALL和UIP_APPCALL分別是TCP通信和udp通信的回調函數,實現的架構以下tcp
//TCP應用接口函數(UIP_APPCALL) //完成TCP服務(包括server和client)和HTTP服務 void tcp_appcall(void) { switch(uip_conn->lport)//本地監聽端口對應的事件處理程序 { case HTONS(80): // httpd_appcall(); break; case HTONS(1200): tcp_server_appcall(); break; default: break; } switch(uip_conn->rport) //遠程鏈接1400端口 { case HTONS(1400): //遠程鏈接端口號 tcp_client_appcall(); break; default: break; } } void udp_appcall(void) { switch(uip_udp_conn->lport)//本地監聽端口1600 { case HTONS(1600): udp_recv_appcall(); break; default: break; } switch(uip_udp_conn->rport) //遠程鏈接1500端口,也就是數據發送端 { case HTONS(1500): udp_send_appcall(); break; default: break; } }
能夠看到,處理過程是分端口處理的,分別是四個,TCP客戶端,服務器,UDP客戶端,UDP服務器,分別說明函數
tcp_client_connect(); //嘗試鏈接到TCP Server端,用於TCP Client
u8 tcp_client_databuf[200]; //發送數據緩存 u8 tcp_client_sta; //客戶端狀態 //[7]:0,無鏈接;1,已經鏈接; //[6]:0,無數據;1,收到客戶端數據 //[5]:0,無數據;1,有數據須要發送 //這是一個TCP 客戶端應用回調函數。 //該函數經過UIP_APPCALL(tcp_demo_appcall)調用,實現Web Client的功能. //當uip事件發生時,UIP_APPCALL函數會被調用,根據所屬端口(1400),肯定是否執行該函數。 //例如 : 當一個TCP鏈接被建立時、有新的數據到達、數據已經被應答、數據須要重發等事件 void tcp_client_appcall(void) { struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate; if(uip_aborted())tcp_client_aborted(); //鏈接終止 if(uip_timedout())tcp_client_timedout(); //鏈接超時 if(uip_closed())tcp_client_closed(); //鏈接關閉 if(uip_connected())tcp_client_connected(); //鏈接成功 if(uip_acked())tcp_client_acked(); //發送的數據成功送達 //接收到一個新的TCP數據包 if (uip_newdata()) { if((tcp_client_sta&(1<<6))==0)//還未收到數據 { if(uip_len>199) { ((u8*)uip_appdata)[199]=0; } strcpy((char*)tcp_client_databuf,uip_appdata); tcp_client_sta|=1<<6;//表示收到客戶端數據 } }else if(tcp_client_sta&(1<<5))//有數據須要發送 { s->textptr=tcp_client_databuf; s->textlen=strlen((const char*)tcp_client_databuf); tcp_client_sta&=~(1<<5);//清除標記 } //當須要重發、新數據到達、數據包送達、鏈接創建時,通知uip發送數據 if(uip_rexmit()||uip_newdata()||uip_acked()||uip_connected()||uip_poll()) { tcp_client_senddata(); } } //這裏咱們假定Server端的IP地址爲:192.168.1.101 //這個IP必須根據Server端的IP修改. //嘗試從新鏈接 void tcp_client_connect() { uip_ipaddr_t ipaddr; uip_ipaddr(&ipaddr,192,168,1,100); //設置IP爲192.168.1.103 uip_connect(&ipaddr,htons(1400)); //端口爲1400 } //終止鏈接,回調函數 void tcp_client_aborted(void) { tcp_client_sta&=~(1<<7); //標誌沒有鏈接 tcp_client_connect(); //嘗試從新鏈接 uip_log("tcp_client aborted!\r\n");//打印log } //鏈接超時,回調函數 void tcp_client_timedout(void) { tcp_client_sta&=~(1<<7); //標誌沒有鏈接 uip_log("tcp_client timeout!\r\n");//打印log } //鏈接關閉,回調函數 void tcp_client_closed(void) { tcp_client_sta&=~(1<<7); //標誌沒有鏈接 tcp_client_connect(); //嘗試從新鏈接 uip_log("tcp_client closed!\r\n");//打印log } //鏈接創建,回調函數 void tcp_client_connected(void) { tcp_client_sta|=1<<7; //標誌鏈接成功 uip_log("tcp_client connected!\r\n");//打印log } //發送的數據成功送達 void tcp_client_acked(void) { struct tcp_appstate *s=(struct tcp_appstate *)&uip_conn->appstate; s->textlen=0;//發送清零 uip_log("tcp_client acked!\r\n");//表示成功發送 } //發送數據給服務端 void tcp_client_senddata(void) { struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate; //s->textptr:發送的數據包緩衝區指針 //s->textlen:數據包的大小(單位字節) if(s->textlen>0)uip_send(s->textptr, s->textlen);//發送TCP數據包 }
TCP客戶端的使用如上,服務器的使用以下測試
uip_listen(HTONS(1200)); //監聽1200端口,用於TCP Server
監聽端口,天然就是服務器了,回調以下
u8 tcp_server_databuf[200]; //發送數據緩存 u8 tcp_server_sta; //服務端狀態 //[7]:0,無鏈接;1,已經鏈接; //[6]:0,無數據;1,收到客戶端數據 //[5]:0,無數據;1,有數據須要發送 //這是一個TCP 服務器應用回調函數。 //該函數經過UIP_APPCALL(tcp_demo_appcall)調用,實現Web Server的功能. //當uip事件發生時,UIP_APPCALL函數會被調用,根據所屬端口(1200),肯定是否執行該函數。 //例如 : 當一個TCP鏈接被建立時、有新的數據到達、數據已經被應答、數據須要重發等事件 void tcp_server_appcall(void) { struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate; if(uip_aborted())tcp_server_aborted(); //鏈接終止 if(uip_timedout())tcp_server_timedout(); //鏈接超時 if(uip_closed())tcp_server_closed(); //鏈接關閉 if(uip_connected())tcp_server_connected(); //鏈接成功 if(uip_acked())tcp_server_acked(); //發送的數據成功送達 //接收到一個新的TCP數據包 if (uip_newdata())//收到客戶端發過來的數據 { if((tcp_server_sta&(1<<6))==0)//還未收到數據 { if(uip_len>199) { ((u8*)uip_appdata)[199]=0; } strcpy((char*)tcp_server_databuf,uip_appdata); tcp_server_sta|=1<<6;//表示收到客戶端數據 } }else if(tcp_server_sta&(1<<5))//有數據須要發送 { s->textptr=tcp_server_databuf; s->textlen=strlen((const char*)tcp_server_databuf); tcp_server_sta&=~(1<<5);//清除標記 } //當須要重發、新數據到達、數據包送達、鏈接創建時,通知uip發送數據 if(uip_rexmit()||uip_newdata()||uip_acked()||uip_connected()||uip_poll()) { tcp_server_senddata(); } } //終止鏈接 void tcp_server_aborted(void) { tcp_server_sta&=~(1<<7); //標誌沒有鏈接 uip_log("tcp_server aborted!\r\n");//打印log } //鏈接超時 void tcp_server_timedout(void) { tcp_server_sta&=~(1<<7); //標誌沒有鏈接 uip_log("tcp_server timeout!\r\n");//打印log } //鏈接關閉 void tcp_server_closed(void) { tcp_server_sta&=~(1<<7); //標誌沒有鏈接 uip_log("tcp_server closed!\r\n");//打印log } //鏈接創建 void tcp_server_connected(void) { // struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate; //uip_conn結構體有一個"appstate"字段指向應用程序自定義的結構體。 //聲明一個s指針,是爲了便於使用。 //不須要再單獨爲每一個uip_conn分配內存,這個已經在uip中分配好了。 //在uip.c 中 的相關代碼以下: // struct uip_conn *uip_conn; // struct uip_conn uip_conns[UIP_CONNS]; //UIP_CONNS缺省=10 //定義了1個鏈接的數組,支持同時建立幾個鏈接。 //uip_conn是一個全局的指針,指向當前的tcp或udp鏈接。 tcp_server_sta|=1<<7; //標誌鏈接成功 uip_log("tcp_server connected!\r\n");//打印log } //發送的數據成功送達 void tcp_server_acked(void) { struct tcp_appstate *s=(struct tcp_appstate *)&uip_conn->appstate; s->textlen=0;//發送清零 uip_log("tcp_server acked!\r\n");//表示成功發送 } //發送數據給客戶端 void tcp_server_senddata(void) { struct tcp_appstate *s = (struct tcp_appstate *)&uip_conn->appstate; //s->textptr : 發送的數據包緩衝區指針 //s->textlen :數據包的大小(單位字節) if(s->textlen>0)uip_send(s->textptr, s->textlen);//發送TCP數據包 }
到此,TCP結束,另外,在UIP的初始化的時候要指明IP地址網關子網掩碼子類的,以下
//配置IP地址 uip_ipaddr(ipaddr, 192,168,1,103); //設置本地設置IP地址 uip_sethostaddr(ipaddr); uip_ipaddr(ipaddr, 192,168,1,1); //設置網關IP地址(其實就是你路由器的IP地址) uip_setdraddr(ipaddr); uip_ipaddr(ipaddr, 255,255,255,0); //設置網絡掩碼 uip_setnetmask(ipaddr);
而UDP的通信是無鏈接的,不分客戶端和服務器,只分爲接收端和發送端,接收端以下
u8 udp_recv_databuf[200]; //發送數據緩存 u8 udp_recv_sta; //客戶端狀態 void udp_recv_appcall(void) { // struct uip_appstate *s = (struct uip_appstate *)&uip_udp_conn->appstate; //接收到一個新的udp數據包 if (uip_newdata())//收到客戶端發過來的數據 { if((udp_recv_sta&(1<<6))==0)//還未收到數據 { if(uip_len>199) { ((u8*)uip_appdata)[199]=0; } strcpy((char*)udp_recv_databuf,uip_appdata); udp_recv_sta|=1<<6;//表示收到客戶端數據 } } if(uip_poll())//udp空轉 { uip_log("udp_server uip_poll!\r\n");//打印log } } //創建UDP接收連接 //創建UDP服務器須要將目標IP設置爲全1 並對應端口爲0,綁定相應的數據端口 void udp_recv_connect(void) { uip_ipaddr_t ipaddr; static struct uip_udp_conn *c=0; uip_ipaddr(&ipaddr,0xff,0xff,0xff,0xff); //將遠程IP設置爲 255.255.255.255 具體原理見uip.c的源碼 if(c!=0) //已經創建鏈接則刪除鏈接 { uip_udp_remove(c); } c = uip_udp_new(&ipaddr,0); //遠程端口爲0 if(c) { uip_udp_bind(c, HTONS(1600)); } }
其回調函數不發送數據,只接收數據,發送端以下
u8 udp_send_databuf[200]; //發送數據緩存 u8 udp_send_sta; //發送端狀態 //這是一個udp 發送端應用回調函數。 //該函數經過UIP_APPCALL(udp_demo_appcall)調用,實現Web Client的功能. //當uip事件發生時,UIP_APPCALL函數會被調用,根據所屬端口(1400),肯定是否執行該函數。 //例如 : 當一個udp鏈接被建立時、有新的數據到達、數據已經被應答、數據須要重發等事件 void udp_send_appcall(void) { struct uip_appstate *s = (struct uip_appstate *)&uip_udp_conn->appstate; if(uip_poll())//當前鏈接空閒輪訓 { uip_log("udp_send uip_poll!\r\n");//打印log if(udp_send_sta&(1<<5))//須要發送數據 { s->textptr=udp_send_databuf; s->textlen=strlen((const char*)udp_send_databuf); udp_send_sta&=~(1<<5);//清除標記 uip_send(s->textptr, s->textlen);//發送udp數據包 uip_udp_send(s->textlen); } } } //創建一個udp_client的鏈接 void udp_send_connect() { uip_ipaddr_t ipaddr; static struct uip_udp_conn *c=0; uip_ipaddr(&ipaddr,192,168,1,101); //設置IP爲192.168.1.101 if(c!=0) { //已經創建鏈接則刪除鏈接 uip_udp_remove(c); } c = uip_udp_new(&ipaddr,htons(1500)); //端口爲1500 //發送端發送的數據端口爲1500 }
只發送數據不接收數據
基本上到這裏整個程序的框架就作好了,測試用的一段代碼也貼上來
uip_polling(); //處理uip事件,必須插入到用戶程序的循環體中 if(tcp_client_tsta!=tcp_client_sta)//TCP Client狀態改變 { if(tcp_client_sta&(1<<7)) LCD_ShowString(0,12,240,320,(u8*)"TCP Client Connected ",LCD_BLACK); else LCD_ShowString(0,12,240,320,(u8*)"TCP Client Disconnected ",LCD_BLACK); if(tcp_client_sta&(1<<6)) //收到新數據 { LCD_ShowString(18,24,240,320,(u8*)" ",LCD_BLACK); LCD_ShowString(18,24,240,320,(u8*)tcp_client_databuf,LCD_BLACK); printf("TCP Client RX:%s\r\n",tcp_client_databuf);//打印數據 tcp_client_sta&=~(1<<6); //標記數據已經被處理 } tcp_client_tsta=tcp_client_sta; } if(tcp_server_tsta!=tcp_server_sta)//TCP Server狀態改變 { if(tcp_server_sta&(1<<7)) LCD_ShowString(0,48,240,320,(u8*)"TCP Server Connected ",LCD_BLACK); else LCD_ShowString(0,48,240,320,(u8*)"TCP Server Disconnected ",LCD_BLACK); if(tcp_server_sta&(1<<6)) //收到新數據 { LCD_ShowString(18,60,240,320,(u8*)" ",LCD_BLACK); LCD_ShowString(18,60,240,320,(u8*)tcp_server_databuf,LCD_BLACK); printf("TCP Server RX:%s\r\n",tcp_server_databuf);//打印數據 tcp_server_sta&=~(1<<6); //標記數據已經被處理 } tcp_server_tsta=tcp_server_sta; } if(udp_recv_sta & (1<<6)) { LCD_ShowString(18,120,240,320,(u8*)" ",LCD_BLACK); LCD_ShowString(18,120,240,320,(u8*)udp_recv_databuf,LCD_BLACK); udp_recv_sta =~(1<<6);//標記數據已經被處理 } if(keyValue == KEY_LEFT) { if(tcp_client_sta&(1<<7)) //鏈接還存在 { sprintf((char*)tcp_client_databuf,"TCP Client OK %d\r\n",tclientcnt); LCD_ShowString(24,36,240,320,(u8*)" ",LCD_BLACK); LCD_ShowString(24,36,240,320,(u8*)tcp_client_databuf,LCD_BLACK); tcp_client_sta|=1<<5;//標記有數據須要發送 tclientcnt++; keyValue = 0; } } if(keyValue == KEY_RIGHT) { if(tcp_server_sta&(1<<7)) //鏈接還存在 { sprintf((char*)tcp_server_databuf,"TCP Server OK %d\r\n",tserivcecnt); LCD_ShowString(24,72,240,320,(u8*)" ",LCD_BLACK); LCD_ShowString(24,72,240,320,(u8*)tcp_server_databuf,LCD_BLACK); tcp_server_sta|=1<<5;//標記有數據須要發送 tserivcecnt++; keyValue = 0; } } if(keyValue == KEY_DOWN) { sprintf((char*)udp_send_databuf,"UDP SEND OK %d\r\n",usendcnt); LCD_ShowString(18,96,240,320,(u8*)" ",LCD_BLACK); LCD_ShowString(18,96,240,320,(u8*)udp_send_databuf,LCD_BLACK); udp_send_sta |= 1<<5;//標記有數據須要發送 keyValue = 0; usendcnt++; } }
下一章再說說怎麼實現DHCP
工程下載地址
http://download.csdn.net/detail/dengrengong/8542905