本帖最后由 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)
2.
makefsdata.zip
(664.53 KB)
|
通过ETH模块,搭建简易的本地HTTP服务器,并通过网页端控制板载LED的闪烁,实现过程详细,完成度较好