发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表
[APM32F4]

探索使用以太网(ETH),搭建一个简单的本地HTTP服务器

[复制链接]
1983|7
手机看帖
扫描二维码
随时随地手机跟帖
DKENNY|  楼主 | 2024-3-7 08:48 | 显示全部楼层 |阅读模式
本帖最后由 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库,并包含其所需要的组件。
image001.png

2.准备一些html文件,用于界面显示和控制。
image003.png


3. 使用makefsdata将html 文件转化为c语言数组形式。(附件含 makefsdata工具)

首先打开makefsdata目录,新建fs文件夹。
image005.png

把准备好的文件复制到fs文件夹中。
image007.png

退回上一级目录,即makefsdata根目录下,打开cmd,输入“makefsdata“,即可把fs文件夹中的html文件转化为fsdata.c文件,该文件包含了其转化后的c语言数组。
image009.png

生成的 fsdata.c 文件,就是我们需要的源文件。
image011.png
这样我们的基础文件数据就准备好了。

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地址
image013.png

3. 实验现象:
1.用以太网接口线,连接开发板与PC端,打开浏览器,输入开发板的IP地址,进入网页端。
image015.png

2.在网页端点击Led control,跳转到led控制界面。
image017.png
image032.jpg
3.点击ADC StatusBar,跳转到ADC采集界面。

image021.png

    附件是本次分享的工程源码以及 makefsdata 工具,有需要的可自行下载,欢迎各位讨论交流。

附件:
    1. ETH_HTTP_Server.zip (1.66 MB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 100.00 元 2024-03-12
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2024-3-12 17:44 回复TA
通过ETH模块,搭建简易的本地HTTP服务器,并通过网页端控制板载LED的闪烁,实现过程详细,完成度较好 
kai迪皮| | 2024-3-13 09:22 | 显示全部楼层
学到了

使用特权

评论回复
AProgrammer| | 2024-3-13 09:22 | 显示全部楼层
牛人!

使用特权

评论回复
804879880| | 2024-3-14 15:51 | 显示全部楼层
很有帮助 对新入很有激励

使用特权

评论回复
lulugl| | 2024-3-15 06:51 | 显示全部楼层
大佬,这教程写得绝对经典,感谢分享!

使用特权

评论回复
储小勇_526| | 2024-3-15 16:52 | 显示全部楼层
虽然和STM32的很类似,但毕竟你换了个平台,还是挺有心的,我们公司也有类似需求,不过觉得太难暂时没深入了解,值得点赞。

使用特权

评论回复
WoodData| | 2024-3-26 11:14 | 显示全部楼层
学习学习

使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

17

主题

30

帖子

4

粉丝