stm32使用LWIP實現DHCP客戶端


    LWIP是一款開源的嵌入式網絡協議棧,支持的功能很多,而且能在多任務環境下和單任務裸機環境下跑,今天說說他的移植過程,芯片為STM32,網卡為ENC28J60,無操作系統

    首先下載LWIP的源代碼,我下載的是1.4.1的源碼,下載后解壓,文件結構如圖

   

將這四個目錄中的文件全部拷貝到工程中,API是一些socket通訊的接口,需要在多任務的環境下實現,core里面存放的內核源碼,我們主要使用IPV4,include目錄下是需要包含的目錄,lwip只要求我們包含include目錄,里面的內層目錄會自動找到,最后建立的工程目錄如下

   

好了,此時源碼已經做好,還有需要做的,在include目錄下新建一個文件夾,必須叫arch,里面存放這幾個文件,自己新建

文件的具體內容如下

cc.h

 

/*
 * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 * 
 * Author: Adam Dunkels <adam@sics.se>
 *
 */
#ifndef __CC_H__
#define __CC_H__

#include "cpu.h"

//編譯器無關的數據類型定義
typedef unsigned   char    u8_t;
typedef signed     char    s8_t;
typedef unsigned   short   u16_t;
typedef signed     short   s16_t;
typedef unsigned   long    u32_t;
typedef signed     long    s32_t;
typedef u32_t mem_ptr_t;
typedef int sys_prot_t;

//lwip調試的時候數據類型定義
#define U16_F "hu"
#define S16_F "d"
#define X16_F "hx"
#define U32_F "u"
#define S32_F "d"
#define X32_F "x"
#define SZT_F "uz" 


//根據不同的編譯器的符號定義
#if defined (__ICCARM__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES

#elif defined (__CC_ARM)

#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__GNUC__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__TASKING__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#endif

#define LWIP_PLATFORM_ASSERT(x) //do { if(!(x)) while(1); } while(0)

#endif /* __CC_H__ */

cpu.h

/*
 * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 * 
 * Author: Adam Dunkels <adam@sics.se>
 *
 */
#ifndef __CPU_H__
#define __CPU_H__

//定義cpu的數據模式,大端小端
#define BYTE_ORDER LITTLE_ENDIAN

#endif /* __CPU_H__ */

perf.h

#ifndef __PERF_H__
#define __PERF_H__

//用於lwip內置的統計功能
//不使能定義為空就可以了
#define PERF_START    /* null definition */
#define PERF_STOP(x)  /* null definition */

#endif /* __PERF_H__ */

sys_arch.h

#ifndef __SYS_RTXC_H__
#define __SYS_RTXC_H__


void init_lwip_timer(void);  //初始化LWIP定時器

u8_t timer_expired(u32_t *last_time,u32_t tmr_interval);    //定時器超時判斷

#endif /* __SYS_RTXC_H__ */

sya_arch.c--注意該文件要加入源文件列表中,這是c文件哦

#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "timerx.h"

//初始化LWIP定時器
void init_lwip_timer(void)
{
     TIM6_Int_Init(1000,719);//100Khz計數頻率,計數到100為10ms
}

//為LWIP提供計時
extern u32_t lwip_timer;//lwip 計時器,每10ms增加1.

u32_t sys_now(void)
{
    return lwip_timer;
}

//定時器超時判斷
//last_time:最近時間
//tmr_interval:定時器溢出周期
u8_t timer_expired(u32_t *last_time,u32_t tmr_interval)
{
    u32_t time;
    time = *last_time;    
    if((lwip_timer-time)>=tmr_interval){
        *last_time = lwip_timer;
        return 1;
    }
    return 0;
}

可以看到我們定義了定時器,那么就要修改相關的定時器文件,文件如下

timerx.c

#include "timerx.h"


u32 lwip_timer=0;//lwip 計時器,每10ms增加1.

//定時器6中斷服務程序     
void TIM6_IRQHandler(void)
{     

    if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) //檢查指定的TIM中斷發生與否:TIM 中斷源 
    {
        TIM_ClearITPendingBit(TIM6, TIM_IT_Update  );  //清除TIMx的中斷待處理位:TIM 中斷源   
        lwip_timer++;//lwip計時器增加1            
    }     

}
 




//基本定時器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寄存器                                  
}

timerx.h

#ifndef __TIMER_H_
#define __TIMER_H_

#include "sys.h"




void TIM6_Int_Init(u16 arr,u16 psc);


#endif

好了,這個時候移植就基本完成了,但是編譯是過不了的,因為我們缺少一個配置文件.h,這個文件放哪都可以,只要工程包含這個文件的目錄,另外,該文件名稱不能改動

lwipopts.h

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__



//回環模式
//#define LWIP_HAVE_LOOPIF  1

/* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */
#define NO_SYS                          1    //是否使用操作系統
#define LWIP_NETCONN                    0
#define LWIP_SOCKET                     0


#define LWIP_DHCP                       1     //使能DHCP模塊
#define MEM_ALIGNMENT                   4      //必須4字節對齊 曾出現在memset的時候hardfault
#define LWIP_DNS                        1



#define MEM_SIZE                        16000        //使用多大一塊內存做lwip內存
#define TCP_SND_QUEUELEN                40            //tcp發送序列長度
#define MEMP_NUM_TCP_SEG                TCP_SND_QUEUELEN

#define TCP_MSS                     1460            
#define TCP_WND                     (4 * TCP_MSS)
#define TCP_SND_BUF                 (8 * TCP_MSS)



#define ETHARP_SUPPORT_STATIC_ENTRIES   1



#endif /* __LWIPOPTS_H__ */

可以看到,我們沒有使用操作系統並且使能了dhcp功能

這時候就可以編譯通過了,但是還有一個必須做的工作,移植網卡的驅動到lwip中,

也就是netif文件夾中的ethernetif.c文件,該文件修改為如下內容

//網卡驅動層文件

#if 1 /* don't build, this is only a skeleton, see previous comment */

#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include <lwip/stats.h>
#include <lwip/snmp.h>
#include "netif/etharp.h"
#include "netif/ppp_oe.h"
#include "enc28j60.h"
#include "netif/ethernetif.h"
#include "string.h"
#include "delay.h"

/* Define those to better describe your network interface. */
#define IFNAME0 'e'
#define IFNAME1 'n'

//MAC地址
const u8 mymac[6]={0x99,0x02,0x35,0x04,0x45,0x61};    //MAC地址
//定義發送接受緩沖區
u8 lwip_buf[1500*2];


//返回網卡地址
struct ethernetif 
{
  struct eth_addr *ethaddr;
  /* Add whatever per-interface state that is needed here. */
};


//網卡的初始化
static err_t low_level_init(struct netif *netif)
{
  //mac地址
  netif->hwaddr_len = ETHARP_HWADDR_LEN;
  /* set MAC hardware address */
  netif->hwaddr[0] = mymac[0];
  netif->hwaddr[1] = mymac[1];
  netif->hwaddr[2] = mymac[2];
  netif->hwaddr[3] = mymac[3];
  netif->hwaddr[4] = mymac[4];
  netif->hwaddr[5] = mymac[5];
    
  //最大傳輸單元
  netif->mtu = MAX_FRAMELEN; 
  if(ENC28J60_Init((u8*)mymac))    //初始化ENC28J60    
  {
    return ERR_IF;            //底層網絡接口錯誤
  }
  //指示燈狀態:0x476 is PHLCON LEDA(綠)=links status, LEDB(紅)=receive/transmit
  //PHLCON:PHY 模塊LED 控制寄存器        
  ENC28J60_PHY_Write(PHLCON,0x0476);      
    
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
   
  return ERR_OK;
}

//從指定網卡輸出一部分數據
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  struct pbuf *q;
  int send_len=0;
  
#if ETH_PAD_SIZE
  pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

  for(q = p; q != NULL; q = q->next) {
    /* Send the data from the pbuf to the interface, one pbuf at a
       time. The size of the data in each pbuf is kept in the ->len
       variable. */
    //send data from(q->payload, q->len);
    memcpy((u8_t*)&lwip_buf[send_len], (u8_t*)q->payload, q->len);
    send_len +=q->len;
  }
   // signal that packet should be sent();
  ENC28J60_Packet_Send(send_len,lwip_buf);


#if ETH_PAD_SIZE
  pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
  
  LINK_STATS_INC(link.xmit);

  return ERR_OK;
}


//從指定網卡讀取一幀數據回來
static struct pbuf *low_level_input(struct netif *netif)
{
//  struct ethernetif *ethernetif = netif->state;
  struct pbuf *p, *q;
  u16_t len;
  int rev_len=0;
    
  /* Obtain the size of the packet and put it into the "len"
     variable. */
  len = ENC28J60_Packet_Receive(MAX_FRAMELEN,lwip_buf);

#if ETH_PAD_SIZE
  len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif

  /* We allocate a pbuf chain of pbufs from the pool. */
  p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
  
  if (p != NULL) {

#if ETH_PAD_SIZE
    pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

    /* We iterate over the pbuf chain until we have read the entire
     * packet into the pbuf. */
    for(q = p; q != NULL; q = q->next) {
      /* Read enough bytes to fill this pbuf in the chain. The
       * available data in the pbuf is given by the q->len
       * variable.
       * This does not necessarily have to be a memcpy, you can also preallocate
       * pbufs for a DMA-enabled MAC and after receiving truncate it to the
       * actually received size. In this case, ensure the tot_len member of the
       * pbuf is the sum of the chained pbuf len members.
       */
      //read data into(q->payload, q->len);
        memcpy((u8_t*)q->payload, (u8_t*)&lwip_buf[rev_len],q->len);
        rev_len +=q->len;
        
    }
   // acknowledge that packet has been read();

#if ETH_PAD_SIZE
    pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif

    LINK_STATS_INC(link.recv);
  } else {
    //drop packet();
    LINK_STATS_INC(link.memerr);
    LINK_STATS_INC(link.drop);
  }

  return p;  
}


/**
 * This function should be called when a packet is ready to be read
 * from the interface. It uses the function low_level_input() that
 * should handle the actual reception of bytes from the network
 * interface. Then the type of the received packet is determined and
 * the appropriate input function is called.
 *
 * @param netif the lwip network interface structure for this ethernetif
 */
void ethernetif_input(struct netif *netif)
{
//  struct ethernetif *ethernetif;
  struct eth_hdr *ethhdr;
  struct pbuf *p;

 // ethernetif = netif->state;

  /* move received packet into a new pbuf */
  p = low_level_input(netif);
  /* no packet could be read, silently ignore this */
  if (p == NULL) return;
  /* points to packet payload, which starts with an Ethernet header */
  ethhdr = p->payload;

  switch (htons(ethhdr->type)) {
  /* IP or ARP packet? */
  case ETHTYPE_IP:
  case ETHTYPE_ARP:
#if PPPOE_SUPPORT
  /* PPPoE packet? */
  case ETHTYPE_PPPOEDISC:
  case ETHTYPE_PPPOE:
#endif /* PPPOE_SUPPORT */
    /* full packet send to tcpip_thread to process */
    if (netif->input(p, netif)!=ERR_OK)
     { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
       pbuf_free(p);
       p = NULL;
     }
    break;

  default:
    pbuf_free(p);
    p = NULL;
    break;
  }
}



/**
 * Should be called at the beginning of the program to set up the
 * network interface. It calls the function low_level_init() to do the
 * actual setup of the hardware.
 *
 * This function should be passed as a parameter to netif_add().
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return ERR_OK if the loopif is initialized
 *         ERR_MEM if private data couldn't be allocated
 *         any other err_t on error
 */
err_t ethernetif_init(struct netif *netif)
{
  struct ethernetif *ethernetif;

  LWIP_ASSERT("netif != NULL", (netif != NULL));
    
  ethernetif = mem_malloc(sizeof(struct ethernetif));
  if (ethernetif == NULL) {
    LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
    return ERR_MEM;
  }

#if LWIP_NETIF_HOSTNAME
  /* Initialize interface hostname */
  netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */

  /*
   * Initialize the snmp variables and counters inside the struct netif.
   * The last argument should be replaced with your link speed, in units
   * of bits per second.
   */
  NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);

  netif->state = ethernetif;
  netif->name[0] = IFNAME0;
  netif->name[1] = IFNAME1;
  /* We directly use etharp_output() here to save a function call.
   * You can instead declare your own function an call etharp_output()
   * from it if you have to do some checks before sending (e.g. if link
   * is available...) */
  netif->output = etharp_output;
  netif->linkoutput = low_level_output;
  
  ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]);
  
  /* initialize the hardware */
  return low_level_init(netif);

}

#endif 

主要是三個功能,網卡數據輸入,網卡數據輸入,網卡初始化,而且我們可以看到,lwip的配置是可以支持多個網卡滴

要是沒有網卡驅動的可以看我之前的例程,里面有

這時候我們算是完整完成了lwip的移植,現在到了使用階段了

這是我們的main函數

int main(void)
{
    NVIC_Group_Init();//系統默認中斷分組
    Debug_Serial_Init(115200);
    Delay_Init();
    Led_Init();
    Key_Exti_Init();
    LCD_Init();
    LCD_Clear(LCD_BLACK);
    
    ipaddr.addr = 0;
    netmask.addr = 0;
    gw.addr = 0;
    init_lwip_timer();  //初始化LWIP定時器
    //初始化LWIP協議棧,執行檢查用戶所有可配置的值,初始化所有的模塊
    lwip_init();
    
    //添加網絡接口
    while((netif_add(&enc28j60_netif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input)==NULL))
    {
        LCD_ShowString(0,0,240,320,(u8*)"ENC28J60 Init Failed             ",LCD_BLACK);
        Delay_Ms(200);
        LCD_ShowString(0,0,240,320,(u8*)"                                 ",LCD_BLACK);
        Delay_Ms(200);
    }
    LCD_ShowString(0,0,240,320,(u8*)"ENC28J60 Init OK                     ",LCD_BLACK);
    //注冊默認的網絡接口
    netif_set_default(&enc28j60_netif);
    //建立網絡接口用於處理通信
    netif_set_up(&enc28j60_netif); 
    
    dhcp_start(&enc28j60_netif);             //為網卡創建一個新的DHCP客戶端
    
    while(1)
    {
        LWIP_Polling();
        Led_Set(0,0);
        Delay_Ms(100);
        Led_Set(0,1);
        Delay_Ms(500);
    }
}

初始化定時器,初始化協議棧,添加網絡接口(該函數同時完成網卡的初始化,mac地址在之前的文件中),注冊網絡接口,建立網絡通訊接口,因為是dhcp,所以完全不用管網絡的地址是多少,直接給0,后面他會自己獲得到的

接下來啟動dhcp服務,因為是裸機程序,沒有多任務再跑,所以我們必須輪詢系統事件並處理,也就是LWIP_Polling(),該函數如下

#define CLOCKTICKS_PER_MS 10    //定義時鍾節拍

static ip_addr_t ipaddr, netmask, gw;     //定義IP地址
struct netif enc28j60_netif;              //定義網絡接口
u32_t input_time;
u32_t last_arp_time;            
u32_t last_tcp_time;    
u32_t last_ipreass_time;

u32_t last_dhcp_fine_time;            
u32_t last_dhcp_coarse_time;  
u32 dhcp_ip=0;

//LWIP查詢
void LWIP_Polling(void)
{
    if(timer_expired(&input_time,5)) //接收包,周期處理函數
    {
        ethernetif_input(&enc28j60_netif); 
    }
    if(timer_expired(&last_tcp_time,TCP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//TCP處理定時器處理函數
    {
        tcp_tmr();
    }
    if(timer_expired(&last_arp_time,ARP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//ARP處理定時器
    {
        etharp_tmr();
    }
    if(timer_expired(&last_ipreass_time,IP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//IP重新組裝定時器
    { 
        ip_reass_tmr();
    }                                   
    if(timer_expired(&last_dhcp_fine_time,DHCP_FINE_TIMER_MSECS/CLOCKTICKS_PER_MS))//dhcp服務
    {
        dhcp_fine_tmr();
    }
    if(timer_expired(&last_dhcp_coarse_time,DHCP_COARSE_TIMER_MSECS/CLOCKTICKS_PER_MS))//dhcp服務
    {
        dhcp_coarse_tmr();
    }  
}

在循環中處理好相應的事件,因為我們沒建立tcp鏈接所以tcp其實可以不要,另外dhcp服務使用的是udp,端口使用的是udp的57,58

到這里編譯連接,系統啟動就能查看到路由器自動分配的IP了

具體工程在下面下載

http://download.csdn.net/detail/dengrengong/8548851

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM