| 本帖最后由 DKENNY 于 2024-3-7 08:52 编辑 
 #申请原创# @21小跑堂
 
     最近在学习 ETH 模块,ETH 模块具有 HTTP 服务的功能,我将在本帖中探讨如何使用 HTTP 服务搭建一个本地 Web网站,并分享一些我所获得的经验。 
 1. HTTP介绍:HTTP是超文本传输协议(Hypertext Transfer Protocol)的缩写,它是用于传输超文本(如 HTML)数据的应用层协议。HTTP是万维网上数据通信的基础,它被用于在Web浏览器和Web服务器之间传输信息。
 
     HTTP是一个无状态协议,这意味着每个单独的请求都是独立的,服务器不会在多个请求之间保留任何数据。每个HTTP请求从客户端(例如Web浏览器)发送到服务器,然后服务器返回一个响应。这种请求-响应模型使得客户端可以从服务器获取各种信息,如网页、图像、视频等。 
     HTTP通常使用TCP作为其传输层协议,通过使用标准端口号80进行通信(HTTPS使用端口号443)。近年来,随着对安全性的需求增加,基于HTTP的加密版本HTTPS也变得越来越普遍,它通过使用SSL/TLS协议来加密数据传输,确保数据的机密性和完整性。 
 HTTP服务特点:     HTTP服务具有以下几个主要特点: 
     1. 无连接:HTTP是一种无连接的协议,即每个请求和响应之间都是独立的,服务器不会保留关于客户端的任何状态信息。这种无连接的特性使得服务器能够更有效地处理大量的并发请求。 
     2. 无状态:HTTP是一种无状态的协议,即服务器不会在请求之间保留任何状态信息。每个请求都是独立的,服务器不会记住之前的请求信息。这种设计简化了服务器的管理和维护,但也意味着服务器无法跟踪客户端的状态,需要使用其他机制来实现状态管理,如使用Cookies或Session。 
     3. 简单灵活:HTTP的设计简单且灵活,易于实现和使用。它使用文本格式的请求和响应消息,易于调试和理解。同时,HTTP也支持多种不同的请求方法(如GET、POST、PUT、DELETE等),以及多种不同的内容类型(如文本、图像、音频、视频等),使得其适用于各种不同的应用场景。 
     4. 基于请求-响应模型:HTTP是基于请求-响应模型的协议,即客户端发送一个请求给服务器,服务器处理请求并返回一个响应给客户端。这种模型使得客户端能够从服务器获取各种信息,如网页、图像、视频等。 
     5. 支持多媒体内容:HTTP不仅可以传输文本数据(如HTML),还可以传输图像、视频、音频等多媒体内容。这使得互联网上的各种资源可以通过HTTP服务进行传输和访问。 
     6. 基于TCP协议:HTTP通常使用TCP作为其传输层协议,通过使用标准端口号80进行通信(HTTPS使用端口号443)。TCP协议提供了可靠的数据传输机制,确保数据的可靠性和完整性。 
     综上所述,HTTP服务具有无连接、无状态、简单灵活等特点,适用于各种不同的应用场景,是互联网上数据通信的基础。 
 LWIP1.4.1的HTTP服务介绍     lwIP(lightweight IP)是一个轻量级的开源TCP/IP协议栈,用于嵌入式系统和小型设备。lwIP1.4.1版本中包含了一个简单的HTTP服务器,可以用于在嵌入式设备上搭建基本的Web服务器。     lwIP1.4.1版本的HTTP服务器具有以下特点:          1.轻量级:lwIP是一个轻量级的TCP/IP协议栈,适用于资源受限的嵌入式系统和小型设备。其HTTP服务器也是精简设计,适合在资源有限的环境下运行。     2.基于C语言:lwIP的HTTP服务器是用C语言编写的,易于移植和集成到各种嵌入式系统中。     3.支持基本功能:lwIP的HTTP服务器支持基本的HTTP功能,如处理GET请求、发送静态内容(如HTML页面、图像等)、处理简单的动态内容等。     4.定制化:虽然lwIP的HTTP服务器功能相对简单,但可以根据需要进行定制和扩展,以满足特定应用场景的需求。     5.适用性:lwIP的HTTP服务器适用于嵌入式设备上需要提供简单Web服务的场景,如远程监控、配置管理、固件升级等。 
     总的来说,lwIP1.4.1版本的HTTP服务器是一个简单而实用的工具,适合在资源受限的嵌入式系统中搭建基本的Web服务器功能。 2. 本地 Web 服务器搭建的步骤 
 1.新建工程,移植lwip库,并包含其所需要的组件。   
 
 2.准备一些html文件,用于界面显示和控制。   
 
 
 3. 使用makefsdata将html 文件转化为c语言数组形式。(附件含 makefsdata工具) 
 首先打开makefsdata目录,新建fs文件夹。   
 
 把准备好的文件复制到fs文件夹中。   
 
 退回上一级目录,即makefsdata根目录下,打开cmd,输入“makefsdata“,即可把fs文件夹中的html文件转化为fsdata.c文件,该文件包含了其转化后的c语言数组。   
 生成的 fsdata.c 文件,就是我们需要的源文件。
 
   
 这样我们的基础文件数据就准备好了。 
 4. 编写httpd_cgi_ssi.c文件 HTTP的SSI和CGI介绍: 
 当涉及到 Web 服务器上的动态内容处理时,两种常见的方法是Server Side Includes (SSI) 和 Common Gateway Interface(CGI)。 ### 1. Server Side Includes (SSI): SSI 是一种简单的动态内容生成技术,它允许在 HTML 页面中嵌入动态内容。SSI 在 HTML 文件中通过特殊的标签实现,服务器在响应客户端请求时动态地处理这些标签。 
 #### 如何使用SSI: - SSI 标签通常以 <!--# 开始,以 --> 结束。 - 常见的SSI指令包括:   - #include:包含其他文件的内容。   - #echo:输出环境变量或者其他值。   - #exec:执行外部命令并将结果输出。 - SSI 通常在服务器配置中启用,并且需要指定哪些文件扩展名应该被解析为SSI。 
 #### 优点: - 简单易用,无需编写额外的代码。 - 可以直接在 HTML 文件中嵌入动态内容,方便快捷。 
 #### 缺点: - 功能有限,主要用于简单的动态内容生成。 - 对服务器性能有一定影响,因为需要在每个请求中动态解析处理SSI标签。 
 ### 2. Common Gateway Interface (CGI): CGI 是一种更为灵活和强大的动态内容生成技术。它允许服务器调用外部程序来处理客户端请求,并生成动态内容。CGI程序可以用任何编程语言编写,只要能够通过标准输入和输出与 Web 服务器通信即可。 
 #### 如何使用CGI: - CGI 程序通常位于 Web 服务器的特定目录中(如`cgi-bin` 目录)。 - 当服务器收到客户端请求时,会调用相应的 CGI 程序来处理请求,并将结果返回给客户端。 - CGI 程序通过环境变量获取客户端请求信息,并通过标准输出返回动态生成的内容。 
 #### 优点: - 灵活多样,可以使用各种编程语言编写 CGI 程序。 - 可以处理复杂的动态内容生成需求,如表单处理、数据库查询等。 
 #### 缺点: - 比较复杂,需要编写额外的程序。 - 对服务器性能影响较大,每个 CGI 请求都需要启动一个新的进程来处理。 
 综上所述,SSI 适用于简单的动态内容生成需求,而CGI 则更适合处理复杂的动态内容生成任务。选择哪种方法取决于具体的需求和服务器环境。 
 
在了解这些之后,我们看看下面编写的httpd_cgi_ssi.c文件源码。 #include "lwip/debug.h"
#include "httpd.h"
#include "lwip/tcp.h"
#include "fs.h"
#include "main.h"
#include "Board.h"
#include <string.h>
#include <stdlib.h>
tSSIHandler ADC_Page_SSI_Handler;
uint32_t ADC_not_configured=1;
/* we will use character "t" as tag for CGI */
char const* TAGCHAR="t";
char const** TAGS=&TAGCHAR;
/* CGI handler for LED control */ 
const char * LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);
/* Html request for "/leds.cgi" will start LEDS_CGI_Handler */
const tCGI LEDS_CGI={"/leds.cgi", LEDS_CGI_Handler};
/* Cgi call table, only one CGI used */
tCGI CGI_TAB[1];
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url]  Configures the ADC.
  * @param  None
  * @retval None
  */
static void ADC_Configuration(void)
{
        ADC_Config_T adcConfig;
        ADC_CommonConfig_T adcCommonConfig;
        GPIO_Config_T gpioConfig;
        
        RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC3);
        RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOF);
        
        gpioConfig.pin = GPIO_PIN_9;
        gpioConfig.mode = GPIO_MODE_AN;
        gpioConfig.pupd = GPIO_PUPD_NOPULL;
        GPIO_Config(GPIOF,&gpioConfig);
        
        ADC_CommonConfigStructInit(&adcCommonConfig);
        adcCommonConfig.mode = ADC_MODE_INDEPENDENT;
        adcCommonConfig.prescaler = ADC_PRESCALER_DIV6;
        adcCommonConfig.accessMode = ADC_ACCESS_MODE_DISABLED;
        adcCommonConfig.twoSampling = ADC_TWO_SAMPLING_5CYCLES;
        ADC_CommonConfig(&adcCommonConfig);
        
        ADC_ConfigStructInit(&adcConfig);
        adcConfig.resolution = ADC_RESOLUTION_12BIT;
        adcConfig.scanConvMode = DISABLE;
        adcConfig.continuousConvMode = ENABLE;
        adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
        adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
        adcConfig.nbrOfChannel = 1;
        ADC_Config(ADC3,&adcConfig);
        
        ADC_ConfigRegularChannel(ADC3,ADC_CHANNEL_7,1,ADC_SAMPLETIME_56CYCLES);
        ADC_Enable(ADC3);
    /* ADC3 regular Software Start Conv */ 
    ADC_SoftwareStartConv(ADC3);
}
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url]  ADC_Handler : SSI handler for ADC page 
  */
u16_t ADC_Handler(int iIndex, char *pcInsert, int iInsertLen)
{
  /* We have only one SSI handler iIndex = 0 */
  if (iIndex ==0)
  {  
    char Digit1=0, Digit2=0, Digit3=0, Digit4=0; 
    uint32_t ADCVal = 0;        
     /* configure ADC if not yet configured */
     if (ADC_not_configured ==1)       
     {
        ADC_Configuration();
        ADC_not_configured=0;
     }
     
     /* get ADC conversion value */
     ADCVal = ADC_ReadConversionValue(ADC3);
     
     /* convert to Voltage,  step = 0.8 mV */
     ADCVal = (uint32_t)(ADCVal * 0.8);  
         printf("ADC Value: %d\r\n",ADCVal);
     
     /* get digits to display */
     
     Digit1= ADCVal/1000;
     Digit2= (ADCVal-(Digit1*1000))/100 ;
     Digit3= (ADCVal-((Digit1*1000)+(Digit2*100)))/10;
     Digit4= ADCVal -((Digit1*1000)+(Digit2*100)+ (Digit3*10));
        
     /* prepare data to be inserted in html */
     *pcInsert       = (char)(Digit1+0x30);
     *(pcInsert + 1) = (char)(Digit2+0x30);
     *(pcInsert + 2) = (char)(Digit3+0x30);
     *(pcInsert + 3) = (char)(Digit4+0x30);
    
    /* 4 characters need to be inserted in html*/
    return 4;
  }
  return 0;
}
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url]  CGI handler for LEDs control 
  */
const char * LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
  uint32_t i=0;
  
  /* We have only one SSI handler iIndex = 0 */
  if (iIndex==0)
  {
    /* All leds off */
          
    APM_TINY_LEDOff(LED2);
    APM_TINY_LEDOff(LED3);
    
    /* Check cgi parameter : example GET /leds.cgi?led=2&led=4 */
    for (i=0; i<iNumParams; i++)
    {
      /* check parameter "led" */
      if (strcmp(pcParam[i] , "led")==0)   
      {
        /* switch led2 ON if 2 */
        if(strcmp(pcValue[i], "2") ==0) 
          APM_TINY_LEDOn(LED2);
        
        /* switch led3 ON if 3 */
        else if(strcmp(pcValue[i], "3") ==0) 
          APM_TINY_LEDOn(LED3);
      }
    }
  }
  /* uri to send after cgi call*/
  return "/APM32F407LED.html";  
}
/**
 * Initialize SSI handlers
 */
void httpd_ssi_init(void)
{  
  /* configure SSI handlers (ADC page SSI) */
  http_set_ssi_handler(ADC_Handler, (char const **)TAGS, 1);
}
/**
 * Initialize CGI handlers
 */
void httpd_cgi_init(void)
{ 
  /* configure CGI handlers (LEDs control CGI) */
  CGI_TAB[0] = LEDS_CGI;
  http_set_cgi_handlers(CGI_TAB, 1);
}
    这段代码是一个基于lwIP(LightweightIP)的HTTP服务器的实现,它允许通过网页控制单片机上的LED,并且能够实时获取ADC(模数转换器)的值并显示在网页上。让我们逐个分析每个函数及其功能: 
 - ADC_Configuration():    - 这个函数配置了微控制器的ADC模块,使其准备好进行模拟信号的数字化转换。    - 配置了ADC3通道7,并启动了ADC转换。 
 -  ADC_Handler():    - 这个函数是SSI(Server Side Include)的处理函数,用于处理ADC页面的SSI标签。    - 它读取ADC转换的值,将其转换为电压值,并将其插入到HTML页面的指定位置。    - 该函数返回要插入的字符数量。 
 -  LEDS_CGI_Handler():    - 这个函数是CGI(Common Gateway Interface)的处理函数,用于处理LED控制的CGI请求。    - 它检查CGI参数,根据参数设置LED的状态(开或关)。    - 返回一个字符串,指示CGI调用完成后应该跳转的页面。 
 - httpd_ssi_init():    - 这个函数初始化SSI处理程序,将ADC页面的SSI处理函数注册到HTTP服务器。 
 -  httpd_cgi_init():    - 这个函数初始化CGI处理程序,将LED控制的CGI处理函数注册到HTTP服务器。 
     总体来说,这段代码实现了一个基本的HTTP服务器,可以通过网页界面控制LED,并实时显示ADC转换的值。通过SSI和CGI,可以动态地生成网页内容,并实现与单片机硬件的交互。 
 5.编写main函数 int main(void)
{
    char LCDDisplayBuf[100] = {0};
    struct ip_addr DestIPaddr;
    uint8_t flag = 0;
    USART_Config_T usartConfig;
    /* User config the different system Clock */
    UserRCMClockConfig();
    /* Configure SysTick */
    ConfigSysTick();
    /* Configure USART */
    usartConfig.baudRate = 115200;
    usartConfig.wordLength = USART_WORD_LEN_8B;
    usartConfig.stopBits = USART_STOP_BIT_1;
    usartConfig.parity = USART_PARITY_NONE ;
    usartConfig.mode = USART_MODE_TX_RX;
    usartConfig.hardwareFlow = USART_HARDWARE_FLOW_NONE;
    APM_BOARD_COMInit(COM1,&usartConfig);
    /* Configures LED2 and LED3 */
    APM_BOARD_LEDInit(LED2);
    APM_BOARD_LEDInit(LED3);
    /* KEY init*/
    APM_BOARD_PBInit(BUTTON_KEY1, BUTTON_MODE_GPIO);
    APM_BOARD_PBInit(BUTTON_KEY2, BUTTON_MODE_GPIO);
        printf("This is a Demo!\r\n");
    /* Configure ethernet (GPIOs, clocks, MAC, DMA) */
    ConfigEthernet();
    /* Initilaize the LwIP stack */
    LwIP_Init();
        
        httpd_init();
    /* Use Com printf static IP address*/
    sprintf(LCDDisplayBuf,"TINY board Static IP address \r\n");
    printf("%s",LCDDisplayBuf);
    sprintf(LCDDisplayBuf,"IP: %d.%d.%d.%d \r\n",
    IP_ADDR0,
    IP_ADDR1,
    IP_ADDR2,
    IP_ADDR3);
    printf("%s",LCDDisplayBuf);
    sprintf(LCDDisplayBuf,"NETMASK: %d.%d.%d.%d \r\n",
    NETMASK_ADDR0,
    NETMASK_ADDR1,
    NETMASK_ADDR2,
    NETMASK_ADDR3);
    printf("%s",LCDDisplayBuf);
    sprintf(LCDDisplayBuf,"Gateway: %d.%d.%d.%d \r\n",
    GW_ADDR0,
    GW_ADDR1,
    GW_ADDR2,
    GW_ADDR3);
    printf("%s",LCDDisplayBuf);
    while(1)
    {
        /* check if any packet received */
        if (ETH_CheckReceivedFrame())
        {
            /* process received ethernet packet */
            LwIP_Pkt_Handle();
        }
        /* handle periodic timers for LwIP */
        LwIP_Periodic_Handle(ETHTimer);
    }
}
 6.配置开发板静态IP地址 
 3. 实验现象: 1.用以太网接口线,连接开发板与PC端,打开浏览器,输入开发板的IP地址,进入网页端。 
 2.在网页端点击Led control,跳转到led控制界面。 3.点击ADC StatusBar,跳转到ADC采集界面。 
 附件是本次分享的工程源码以及 makefsdata 工具,有需要的可自行下载,欢迎各位讨论交流。
 
 附件:
 1.
  ETH_HTTP_Server.zip
(1.66 MB, 下载次数: 33) 2.
  makefsdata.zip
(664.53 KB, 下载次数: 33) 
 
 
 |