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

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

[复制链接]
 楼主| 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文件源码。
  1. #include "lwip/debug.h"
  2. #include "httpd.h"
  3. #include "lwip/tcp.h"
  4. #include "fs.h"
  5. #include "main.h"
  6. #include "Board.h"

  7. #include <string.h>
  8. #include <stdlib.h>

  9. tSSIHandler ADC_Page_SSI_Handler;
  10. uint32_t ADC_not_configured=1;

  11. /* we will use character "t" as tag for CGI */
  12. char const* TAGCHAR="t";
  13. char const** TAGS=&TAGCHAR;

  14. /* CGI handler for LED control */
  15. const char * LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);

  16. /* Html request for "/leds.cgi" will start LEDS_CGI_Handler */
  17. const tCGI LEDS_CGI={"/leds.cgi", LEDS_CGI_Handler};

  18. /* Cgi call table, only one CGI used */
  19. tCGI CGI_TAB[1];


  20. /**
  21.   * [url=home.php?mod=space&uid=247401]@brief[/url]  Configures the ADC.
  22.   * @param  None
  23.   * @retval None
  24.   */
  25. static void ADC_Configuration(void)
  26. {
  27.         ADC_Config_T adcConfig;
  28.         ADC_CommonConfig_T adcCommonConfig;
  29.         GPIO_Config_T gpioConfig;
  30.         
  31.         RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC3);
  32.         RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOF);
  33.         
  34.         gpioConfig.pin = GPIO_PIN_9;
  35.         gpioConfig.mode = GPIO_MODE_AN;
  36.         gpioConfig.pupd = GPIO_PUPD_NOPULL;
  37.         GPIO_Config(GPIOF,&gpioConfig);
  38.         
  39.         ADC_CommonConfigStructInit(&adcCommonConfig);
  40.         adcCommonConfig.mode = ADC_MODE_INDEPENDENT;
  41.         adcCommonConfig.prescaler = ADC_PRESCALER_DIV6;
  42.         adcCommonConfig.accessMode = ADC_ACCESS_MODE_DISABLED;
  43.         adcCommonConfig.twoSampling = ADC_TWO_SAMPLING_5CYCLES;
  44.         ADC_CommonConfig(&adcCommonConfig);
  45.         
  46.         ADC_ConfigStructInit(&adcConfig);
  47.         adcConfig.resolution = ADC_RESOLUTION_12BIT;
  48.         adcConfig.scanConvMode = DISABLE;
  49.         adcConfig.continuousConvMode = ENABLE;
  50.         adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
  51.         adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
  52.         adcConfig.nbrOfChannel = 1;
  53.         ADC_Config(ADC3,&adcConfig);
  54.         
  55.         ADC_ConfigRegularChannel(ADC3,ADC_CHANNEL_7,1,ADC_SAMPLETIME_56CYCLES);
  56.         ADC_Enable(ADC3);

  57.     /* ADC3 regular Software Start Conv */
  58.     ADC_SoftwareStartConv(ADC3);
  59. }

  60. /**
  61.   * [url=home.php?mod=space&uid=247401]@brief[/url]  ADC_Handler : SSI handler for ADC page
  62.   */
  63. u16_t ADC_Handler(int iIndex, char *pcInsert, int iInsertLen)
  64. {
  65.   /* We have only one SSI handler iIndex = 0 */
  66.   if (iIndex ==0)
  67.   {  
  68.     char Digit1=0, Digit2=0, Digit3=0, Digit4=0;
  69.     uint32_t ADCVal = 0;        

  70.      /* configure ADC if not yet configured */
  71.      if (ADC_not_configured ==1)      
  72.      {
  73.         ADC_Configuration();
  74.         ADC_not_configured=0;
  75.      }
  76.      
  77.      /* get ADC conversion value */
  78.      ADCVal = ADC_ReadConversionValue(ADC3);
  79.      
  80.      /* convert to Voltage,  step = 0.8 mV */
  81.      ADCVal = (uint32_t)(ADCVal * 0.8);  
  82.          printf("ADC Value: %d\r\n",ADCVal);
  83.      
  84.      /* get digits to display */
  85.      
  86.      Digit1= ADCVal/1000;
  87.      Digit2= (ADCVal-(Digit1*1000))/100 ;
  88.      Digit3= (ADCVal-((Digit1*1000)+(Digit2*100)))/10;
  89.      Digit4= ADCVal -((Digit1*1000)+(Digit2*100)+ (Digit3*10));
  90.         
  91.      /* prepare data to be inserted in html */
  92.      *pcInsert       = (char)(Digit1+0x30);
  93.      *(pcInsert + 1) = (char)(Digit2+0x30);
  94.      *(pcInsert + 2) = (char)(Digit3+0x30);
  95.      *(pcInsert + 3) = (char)(Digit4+0x30);
  96.    
  97.     /* 4 characters need to be inserted in html*/
  98.     return 4;
  99.   }
  100.   return 0;
  101. }

  102. /**
  103.   * [url=home.php?mod=space&uid=247401]@brief[/url]  CGI handler for LEDs control
  104.   */
  105. const char * LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
  106. {
  107.   uint32_t i=0;
  108.   
  109.   /* We have only one SSI handler iIndex = 0 */
  110.   if (iIndex==0)
  111.   {
  112.     /* All leds off */
  113.          
  114.     APM_TINY_LEDOff(LED2);
  115.     APM_TINY_LEDOff(LED3);
  116.    
  117.     /* Check cgi parameter : example GET /leds.cgi?led=2&led=4 */
  118.     for (i=0; i<iNumParams; i++)
  119.     {
  120.       /* check parameter "led" */
  121.       if (strcmp(pcParam[i] , "led")==0)   
  122.       {
  123.         /* switch led2 ON if 2 */
  124.         if(strcmp(pcValue[i], "2") ==0)
  125.           APM_TINY_LEDOn(LED2);
  126.         
  127.         /* switch led3 ON if 3 */
  128.         else if(strcmp(pcValue[i], "3") ==0)
  129.           APM_TINY_LEDOn(LED3);
  130.       }
  131.     }
  132.   }
  133.   /* uri to send after cgi call*/
  134.   return "/APM32F407LED.html";  
  135. }

  136. /**
  137. * Initialize SSI handlers
  138. */
  139. void httpd_ssi_init(void)
  140. {  
  141.   /* configure SSI handlers (ADC page SSI) */
  142.   http_set_ssi_handler(ADC_Handler, (char const **)TAGS, 1);
  143. }

  144. /**
  145. * Initialize CGI handlers
  146. */
  147. void httpd_cgi_init(void)
  148. {
  149.   /* configure CGI handlers (LEDs control CGI) */
  150.   CGI_TAB[0] = LEDS_CGI;
  151.   http_set_cgi_handlers(CGI_TAB, 1);
  152. }

    这段代码是一个基于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函数
  1. int main(void)
  2. {
  3.     char LCDDisplayBuf[100] = {0};

  4.     struct ip_addr DestIPaddr;

  5.     uint8_t flag = 0;

  6.     USART_Config_T usartConfig;

  7.     /* User config the different system Clock */
  8.     UserRCMClockConfig();

  9.     /* Configure SysTick */
  10.     ConfigSysTick();

  11.     /* Configure USART */
  12.     usartConfig.baudRate = 115200;
  13.     usartConfig.wordLength = USART_WORD_LEN_8B;
  14.     usartConfig.stopBits = USART_STOP_BIT_1;
  15.     usartConfig.parity = USART_PARITY_NONE ;
  16.     usartConfig.mode = USART_MODE_TX_RX;
  17.     usartConfig.hardwareFlow = USART_HARDWARE_FLOW_NONE;

  18.     APM_BOARD_COMInit(COM1,&usartConfig);

  19.     /* Configures LED2 and LED3 */
  20.     APM_BOARD_LEDInit(LED2);
  21.     APM_BOARD_LEDInit(LED3);

  22.     /* KEY init*/
  23.     APM_BOARD_PBInit(BUTTON_KEY1, BUTTON_MODE_GPIO);
  24.     APM_BOARD_PBInit(BUTTON_KEY2, BUTTON_MODE_GPIO);

  25.         printf("This is a Demo!\r\n");

  26.     /* Configure ethernet (GPIOs, clocks, MAC, DMA) */
  27.     ConfigEthernet();

  28.     /* Initilaize the LwIP stack */
  29.     LwIP_Init();
  30.         
  31.         httpd_init();

  32.     /* Use Com printf static IP address*/
  33.     sprintf(LCDDisplayBuf,"TINY board Static IP address \r\n");
  34.     printf("%s",LCDDisplayBuf);

  35.     sprintf(LCDDisplayBuf,"IP: %d.%d.%d.%d \r\n",
  36.     IP_ADDR0,
  37.     IP_ADDR1,
  38.     IP_ADDR2,
  39.     IP_ADDR3);
  40.     printf("%s",LCDDisplayBuf);

  41.     sprintf(LCDDisplayBuf,"NETMASK: %d.%d.%d.%d \r\n",
  42.     NETMASK_ADDR0,
  43.     NETMASK_ADDR1,
  44.     NETMASK_ADDR2,
  45.     NETMASK_ADDR3);
  46.     printf("%s",LCDDisplayBuf);

  47.     sprintf(LCDDisplayBuf,"Gateway: %d.%d.%d.%d \r\n",
  48.     GW_ADDR0,
  49.     GW_ADDR1,
  50.     GW_ADDR2,
  51.     GW_ADDR3);
  52.     printf("%s",LCDDisplayBuf);

  53.     while(1)
  54.     {
  55.         /* check if any packet received */
  56.         if (ETH_CheckReceivedFrame())
  57.         {
  58.             /* process received ethernet packet */
  59.             LwIP_Pkt_Handle();
  60.         }
  61.         /* handle periodic timers for LwIP */
  62.         LwIP_Periodic_Handle(ETHTimer);
  63.     }
  64. }

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, 下载次数: 32)
    2. makefsdata.zip (664.53 KB, 下载次数: 32)


  

打赏榜单

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

评论

通过ETH模块,搭建简易的本地HTTP服务器,并通过网页端控制板载LED的闪烁,实现过程详细,完成度较好  发表于 2024-3-12 17:44
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 | 显示全部楼层
学习学习
gangong 发表于 2024-10-25 14:51 | 显示全部楼层
写得很好,学习学习
查询无结果 发表于 2024-10-25 15:00 | 显示全部楼层
最近正在学web server,感谢分享!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

59

主题

105

帖子

16

粉丝
快速回复 在线客服 返回列表 返回顶部

59

主题

105

帖子

16

粉丝
快速回复 在线客服 返回列表 返回顶部