Advertisement

物联网智慧教室项目(八):网页web服务器功能开发(stm32)

阅读量:

一、WebServer功能设计

(一)WebServer需要做什么

当用户访问网址(url)时,加载网页文件

在这里插入图片描述

当用户点击控制按钮,传感器定时刷新与服务器进行交互

在这里插入图片描述

(二)EasyWebSvr工具介绍

菜单界面

在这里插入图片描述

设置界面

在这里插入图片描述

日志

在这里插入图片描述

(三)EasyWebSvr搭建Web服务器

配置服务器主目录

在这里插入图片描述

浏览器输入服务器地址

复制代码
0.0.1/index.html
    
    
    url
在这里插入图片描述

分析Web服务器日志

在这里插入图片描述

(四)WebServer功能设计

文件请求响应

BrowserWebServerFileSystemGET /index.htmlopen(index.html)index.htmlResponse index.htmlBrowserWebServerFileSystem

传感器数据请求响应

BrowserWebServerSensorTaskGET /DATA/SensorRead SensorDataSensorDataResponse SensorDataBrowserWebServerSensorTask

命令请求响应

BrowserWebServerCMDTaskPOST /CMD/LightSend CmdCmd StatusResponse Cmd StatusBrowserWebServerCMDTask

二、WebServer主线程实现

(一)WebServer代码移植

文件移植

复制代码
    智慧教室项目实战\day08\03-WebServer移植文件
    
    
    c
    
    
在这里插入图片描述

把网页文件拷贝到SD卡中

复制代码
拷贝文件到SD卡"根目录下"
    2.开发板断电插入SD卡
    3.烧录程序看现象(浏览器输入192.168.1.7)
    
    
    c
    
    
在这里插入图片描述

访问STM32服务器,网页图片加载需要多次刷新问题

复制代码
    1、我们webserver是一个单线程任务,http属于短链接//http没有记忆功能,在一次socket通信中能获取多少数据,就只能获取多少数据
    2、但是浏览器有缓存,我们使用时只需要多刷新几次就可以了
    3、也可以通过在前端增加一些js代码(循环加载前端资源(文件))
    4、STM32内存太小了,没有办法做长连接,短连接模式可以实现多个客户端连接
    
    
    c
    
    

(二)WebServer主线程实现

http_server_socket_thread

复制代码
    /** * @brief  http server thread 
      * @param arg: pointer on argument(not used here) 
      * @retval None
      */
    void http_server_socket_thread(void *p_arg)
    {
    	int sock, newconn, size;
    	struct sockaddr_in address, remotehost;
    
    	/* create a TCP socket */
    	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    	{
    		printf("http_server can not create socket");
    		return;
    	}
    
    	/* bind to port 80 at any interface */
    	address.sin_family = AF_INET;
    	address.sin_port = htons(80);
    	address.sin_addr.s_addr = INADDR_ANY;
    
    	if (bind(sock, (struct sockaddr *)&address, sizeof(address)) == -1)
    	{
    		printf("http_server can not bind socket");
    		return;
    	}
    
    	/* listen for incoming connections (TCP listen backlog = 5) */
    	listen(sock, 5);
    
    	size = sizeof(remotehost);
    /*先不关心,但是很重要
    	printf("\r\n--------------Web_Server_Task----------------\r\n");
    	WEB_Service_Registration(&CONTROL_LIGHT_CMD_POST);
    	WEB_Service_Registration(&CONTROL_FAN_CMD_POST);
    	WEB_Service_Registration(&CONTROL_ALARM_CMD_POST);
    */
    // 循环等待客户端的接入
    	while (1)
    	{
        //等待客户端接入
    		newconn = accept(sock, (struct sockaddr *)&remotehost, (socklen_t *)&size);
        //针对客户端做服务处理  请求-->响应
    		http_server_serve(newconn);
    	}
    }
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/Eu4acxGNbPMdjtqTVsJCQklDvBH5.png)

http_server_serve

复制代码
    /** * @brief serve tcp connection  
      * @param conn: connection socket 
      * @retval None
      */
    void http_server_serve(int conn)
    {
    	int ret;
    	/* Read in the request */
    	ret = read(conn, (unsigned char *)Request_Buf, 1500);
    
    	if (ret < 0)
    		return;
    	else
    	{
         //把请求内容最后一个字节填充\0,以后直接用字符串解析
    		*(Request_Buf + ret) = 0;
    #ifdef WEB_DEBUG
    		printf("\r\nWEB服务器,接收请求,内容:\r\n%s\r\n", (const char *)Request_Buf);
    #endif
        //请求响应代码
    		Respond_Http_Request(conn, (char *)Request_Buf);
    	}
    	//关闭socket 这就一个短链接的实现
    	close(conn);
    }
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/Al6Xa2kdYLj5BJP37C0rSvHopZw8.png)

(三)http解析业务流程

请求解析

方法类型

ERROR

GET

POST

错误响应

GET类型

数据读取响应

文件读取响应

POST类型

命令下发响应

文件写入响应

三、Http文件请求响应

(一)数据结构

http文件请求响应首部封装

复制代码
    #define HOMEPAGE_DEFAULT "index.html"
    
    #define INVALID_CMD "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\nContent-Length: 16\r\n\r\ninvalid cmd!"
    #define POST_REQUEST_OK "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/css\r\nCache-control: no-cache\r\nExpires: Thu, 15 Apr 2000 20:00:00 GMT\r\nContent-Length: 18\r\n\r\nPOST Successfully!"
    #define POST_REQUEST_FAIL "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/css\r\nCache-control: no-cache\r\nExpires: Thu, 15 Apr 2000 20:00:00 GMT\r\nContent-Length: 13\r\n\r\nPOST Failure!"
    #define RETURN_cmd_OK "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nCache-Control: no-cache, no-store, max-age=0\r\nExpires: 1L\r\nConnection: close\r\nContent-Length: "
    /* html文件请求错误响应 */
    const char ERROR_HTML_PAGE[] = "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: 78\r\n\r\n<HTML>\r\n<BODY>\r\nSorry, the page you requested was not found.\r\n</BODY>\r\n</HTML>\r\n\0";
    /* 数据命令请求错误响应 */
    const char ERROR_REQUEST_PAGE[] = "HTTP/1.0 500 Pafe Not Found\r\nConnection: close\r\nContent-Type: text/html\r\nContent-Length: 50\r\n\r\n<HTML>\r\n<BODY>\r\nInvalid request.\r\n</BODY>\r\n</HTML>\r\n\0";
    /* Response header for HTML*/
    const char RES_HTMLHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/html\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for TEXT */
    const char RES_TEXTHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/plain\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for GIF */
    const char RES_GIFHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: image/gif\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for JPEG */
    const char RES_JPEGHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: image/jpeg\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for MPEG */
    const char RES_PNGHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: image/png\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for MP3 */
    const char RES_MP3HEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: audio/mpeg\r\nContent-Range: bytes 0-40123/40124\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for JS */
    const char RES_JSHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\ncontent-type:application/x-javascript\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for ICO */
    const char RES_ICOHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: image/x-icon\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for CSS */
    const char RES_CSSHEAD_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: text/css\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    /* Response head for APP */
    const char RES_APP_OK[] = "HTTP/1.0 200 OK\r\nServer: WFM-Control\r\nConnection: close\r\nContent-Type: application/octet-stream\r\nCache-control: max-age=315360000000\r\nExpires: Thu, 15 Apr 2100 20:00:00 GMT\r\nContent-Length: ";
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/LnSHuaOltkK60eXZDWfoBJrF3iqg.png)

http消息结构体

复制代码
    typedef struct
    {
    	char Method;				 //请求方法: GET 、HEAD 、POST
    	char *URL;					 //URL
    	char FileType;				 //文件类型
    	char *Post_Data;			 //POST数据
    	unsigned int Content_Length; //POST数据的长度
    } Http_Request_MSG_Type;		 //定义HTTP请求报文消息结构体
    
    
    c
    
    

(二)Respond_Http_Request

复制代码
    /******************************************************************************* * 函数名称:  void Respond_Http_Request(SOCKET ch ,char* Request_Msg)
    * 函数说明: 响应HTTP请求
    * 输入参数: socket 端口:ch ;HTTP请求数据包指针	:Request_Msg
    * 返回参数: 无
    *******************************************************************************/
    void Respond_Http_Request(char ch, char *Request_Msg)
    {
    //创建http解析结构体
    	Http_Request_MSG_Type Http_Request_MSG;
    //定义两个指针,用于解析数据记录跟踪
    	char *thisstart = NULL;
    	char *nextstart = NULL;
    //缓冲数据指针
    	char *buf;
    //缓存数据长度
    	int length = 0;
    //进行数据解析,解析完毕后,会填充Http_Request_MSG
    	Parse_Http_Request(Request_Msg, &Http_Request_MSG); //解析HTTP请求类型
    	switch (Http_Request_MSG.Method)
    	{
    	case METHOD_ERR:
         //把错误响应返回
    		write(ch, (const unsigned char *)ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
    		break;
    	case METHOD_HEAD:
    	case METHOD_GET:
    		if (strstr((const char *)Http_Request_MSG.URL, "/DATA/")) //判断是否是通讯指令,并解析指令帧
    		{
    			//传感器数据请求响应
    		}
    		else
    		{
            //响应文件请求
    			Send_Response_File(ch, &Http_Request_MSG); //发送请求的文件
    		}
    		break;
    	case METHOD_POST:
    		//post响应
    	}	
    }
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/CS8FGmEKxR7ueJ65hzBfqcAVMbWN.png)

(三)Parse_Http_Request

复制代码
    /******************************************************************************* * 函数名称:  void Parse_Http_Request(char * Request_Msg ,Http_Request_MSG_Type *Http_Request_MSG)
    * 函数说明: 解析HTTP请求
    * 输入参数: 请求数据包指针:Request_Msg ;  请求信息类型 :Http_Request_MSG
    * 返回参数: 无
    *******************************************************************************/
    void Parse_Http_Request(char *Request_Msg, Http_Request_MSG_Type *Http_Request_MSG)
    {
    	char *thisstart = NULL;
    	char *nextstart = NULL;
    
    	thisstart = strtok_r(Request_Msg, " ", &nextstart);
    	if (thisstart == NULL)
    	{
    		Http_Request_MSG->Method = METHOD_ERR;
    		Http_Request_MSG->URL = NULL;
    		return;
    	}
    	//解析请求所用的方法:GET,HEAD,POST
    	if (!strcmp(thisstart, "GET") || !strcmp(thisstart, "get"))
    	{
    		Http_Request_MSG->Method = METHOD_GET;
    	}
    	else if (!strcmp(thisstart, "HEAD") || !strcmp(thisstart, "head"))
    	{
    		Http_Request_MSG->Method = METHOD_HEAD;
    	}
    	else if (!strcmp(thisstart, "POST") || !strcmp(thisstart, "post"))
    	{
    		Http_Request_MSG->Method = METHOD_POST;
    	}
    	else
    	{
    		Http_Request_MSG->Method = METHOD_ERR;
    		Http_Request_MSG->URL = NULL;
    		return;
    	}
    
    	if (nextstart == NULL)
    	{
    		Http_Request_MSG->Method = METHOD_ERR;
    		Http_Request_MSG->URL = NULL;
    		return;
    	}
    
    	Http_Request_MSG->URL = strtok_r(NULL, " ?", &nextstart);				 //解析URL
    	if (Http_Request_MSG->URL[0] == '/' && Http_Request_MSG->URL[1] == '\0') //如果url仅是一个“/”,则默认为主页
    	{
    		Http_Request_MSG->URL = HOMEPAGE_DEFAULT; //设置默认页
    	}
    	Http_Request_MSG->Post_Data = nextstart; //保存下一字符串指针
    }
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/8czTWiuaQBwR64DpEyd9oFnkHIMU.png)

(四)Send_Response_File

复制代码
    /******************************************************************************* * 函数名称:  void Send_Response_File(SOCKET ch,Http_Request_MSG_Type *Http_Request_MSG) 
    * 函数说明: 回应请求的文件
    * 输入参数: SOCKET 通道号  ,Http_Request_MSG_Type  文件类型信息
    * 返回参数: 无
    *******************************************************************************/
    void Send_Response_File(char ch, Http_Request_MSG_Type *Http_Request_MSG)
    {
    	FRESULT res;
    	FIL *f;
    	unsigned int bytes_ret;
    	unsigned char *buf;
    	unsigned char *buf1;
    	uint32_t fSize;
    	
    	f = (FIL *)pvPortMalloc(sizeof(FIL));		//开辟内存空间
    	buf = (unsigned char *)pvPortMalloc(1500);	//开辟内存空间
    	buf1 = (unsigned char *)pvPortMalloc(1500); //开辟内存空间
    	if (f == NULL || buf == NULL || buf1 == NULL)
    	{
    		printf("内存分配失败\r\n");
    		vPortFree(f);			 //释放内存空间
    		vPortFree(buf);			 //释放内存空间
    		vPortFree(buf1);		 //释放内存空间
    		
    		write(ch, (const unsigned char *)ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
    		return;
    	}
    //打开文件
    	res = f_open(f, Http_Request_MSG->URL, FA_OPEN_EXISTING | FA_READ);
    //获取文件大小
    	fSize = f_size(f);
    	printf("http file size is %d\r\n", fSize);
    	if (res == FR_OK)
    	{
    		Http_Request_MSG->FileType = Parse_URL_File_Type(Http_Request_MSG->URL); //分析请求URL中包含的文件的文件类型
    		Make_http_response_head((char *)buf, Http_Request_MSG->FileType, fSize); //生成HTTP报文
        //响应首部
    		write(ch, (const unsigned char *)buf, strlen((const char *)buf));
        //响应主题------- 文件
    		while (1)
    		{
            //读取文件
    			res = f_read(f, buf, 1500, &bytes_ret);
            //读取文件错误
    			if (res != FR_OK)
    			{
    				printf("读取文件失败!文件名:%s,错误代码:0x%02x 文件大小:%d\r\n", (const char *)Http_Request_MSG->URL, res, bytes_ret);
    				SD_initialize(0);
    				break;
    			}
            //读取文件内容为空
    			if (bytes_ret == 0)
    				break;
            //读取文件正确,写回socket
    			write(ch, (const unsigned char *)buf, bytes_ret);
            //继续读取文件
    			res = f_read(f, buf1, 1500, &bytes_ret);
    			if (res != FR_OK)
    			{
    				printf("读取文件失败!文件名:%s,错误代码:0x%02x 文件大小:%d\r\n", (const char *)Http_Request_MSG->URL, res, bytes_ret);
    				SD_initialize(0);
    				break;
    			}
    			if (bytes_ret == 0)
    				break;
            //读取文件正确写回
    			write(ch, (const unsigned char *)buf1, bytes_ret);
    		}
        //关闭文件
    		f_close(f);
    		vPortFree(f);			 //释放内存空间
    		vPortFree(buf);			 //释放内存空间
    		vPortFree(buf1);		 //释放内存空间
    		
    	}
    	else //文件打开错误
    	{
    		f_close(f);
    		vPortFree(f);			 //释放内存空间
    		vPortFree(buf);			 //释放内存空间
    		vPortFree(buf1);		 //释放内存空间
    		//写入错误请求响应
    		write(ch, (const unsigned char *)ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
    		printf("打开文件失败!文件名:%s,错误代码:0x%02x\r\n", (const char *)Http_Request_MSG->URL, res);
    		SD_initialize(0);
    	}
    }
    
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/fKxEyDV7th2mUnlYcQJrNjP0A9ie.png)

四、前后台交互方法设计

(一)数据结构

复制代码
    typedef struct Web_s{
    	struct  Web_s 	*next;   //单链表节点 
    	const char      *cmd;    //get&post具体消息内容存放位置
    	void            (*function)(void *,void *);//不同响应的处理方法
    } WEB_Server_Struct;
    
    
    c
    
    

(二)交互数据结构封装

字符串封装

复制代码
    const char Sensor[] = "Sensor";	// get方法内消息 /DATA/Sensor 用来解析此请求
    const char Light[] = "Light";//post方法内消息 /CMD/Light 用来解析此请求
    const char Fan[] = "Fan";//post方法内消息 /CMD/Fan 用来解析此请求
    const char Alarm[] = "Alarm";//post方法内消息 /CMD/Alarm 用来解析此请求
    const char On[] = "On";//post方法内消息 /CMD/xxx 消息内容为开启
    const char Off[] = "Off";//post方法内消息 /CMD/xxx 消息内容为关闭
    
    
    c
    
    

数据节点封装

复制代码
    //我们项目中需要4中交互,传感器数据获取,开关灯,风扇,报警器,定义下面结构体
    WEB_Server_Struct SENSOR_WEB_DATA_GET = {NULL, Sensor, Get_SensorValue};
    WEB_Server_Struct CONTROL_LIGHT_CMD_POST = {NULL, Light, Post_Cmd_Light};
    WEB_Server_Struct CONTROL_FAN_CMD_POST = {NULL, Fan, Post_Cmd_Fan};
    WEB_Server_Struct CONTROL_ALARM_CMD_POST = {NULL, Alarm, Post_Cmd_Alarm};
    
    
    c
    
    

交互函数封装

复制代码
    /****************************************************************************************************
     函数原型:void Get_SensorValue(void *buffer,void *value)
     入口参数:发送缓冲区指针,设定值指针
     出口参数:无
     函数功能:登录验证
     ****************************************************************************************************/
    static void Get_SensorValue(void *buffer, void *value)
    {
    	//把传感器数据填充到我们的buffer里面,之后进行响应就ok了
    	sprintf(buffer, "{\"temperature\":\"%d\",\"humidity\":\"%d\",\"light\":\"10021.1\"}", SensorData[0], SensorData[1]);
    }
    
    /****************************************************************************************************
     函数原型:void Post_Cmd_Light(void *buffer,void *value)
     入口参数:发送缓冲区指针,设定值指针
     出口参数:无
     函数功能:登录验证
     ****************************************************************************************************/
    static void Post_Cmd_Light(void *buffer, void *value)
    {
    	//判断value是on还是off
    	if (strstr(value, On))
    	{   
         //响应状态为on  Status:On
    		sprintf(buffer, "{\"Status\":\"On\"}");
    		HAL_GPIO_WritePin(GPIOF, D6_Pin | D7_Pin | D8_Pin | D9_Pin, GPIO_PIN_RESET);
    	}
    	else if (strstr(value, Off))
    	{	
          //响应状态为Off Status:Off
    		sprintf(buffer, "{\"Status\":\"Off\"}");
    		HAL_GPIO_WritePin(GPIOF, D6_Pin | D7_Pin | D8_Pin | D9_Pin, GPIO_PIN_SET);
    	}
    	else
    	{
         //响应错误
    		sprintf(buffer, "{\"Status\":\"Error\"}");
    	}
    }
    
    /****************************************************************************************************
     函数原型:void Post_Cmd_Fan(void *buffer,void *value)
     入口参数:发送缓冲区指针,设定值指针
     出口参数:无
     函数功能:登录验证
     ****************************************************************************************************/
    static void Post_Cmd_Fan(void *buffer, void *value)
    {
    	if (strstr(value, On))
    	{
    		sprintf(buffer, "{\"Status\":\"On\"}");
        //风扇控制  ----- 后面zigbee项目讲解
    		FanControl(0x01);
    	}
    	else if (strstr(value, Off))
    	{
    		sprintf(buffer, "{\"Status\":\"Off\"}");
    		FanControl(0x0);
    	}
    	else
    	{
    		sprintf(buffer, "{\"Status\":\"Error\"}");
    	}
    }
    
    /****************************************************************************************************
     函数原型:void Post_Cmd_Alarm(void *buffer,void *value)
     入口参数:发送缓冲区指针,设定值指针
     出口参数:无
     函数功能:登录验证
     ****************************************************************************************************/
    static void Post_Cmd_Alarm(void *buffer, void *value)
    {
    	if (strstr(value, On))
    	{
    		sprintf(buffer, "{\"Status\":\"On\"}");
    		HAL_GPIO_WritePin(BUZ_GPIO_Port, BUZ_Pin, GPIO_PIN_SET);
    	}
    	else if (strstr(value, Off))
    	{
    		sprintf(buffer, "{\"Status\":\"Off\"}");
    		HAL_GPIO_WritePin(BUZ_GPIO_Port, BUZ_Pin, GPIO_PIN_RESET);
    	}
    	else
    	{
    		sprintf(buffer, "{\"Status\":\"Error\"}");
    	}
    }
    
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/MJZyN8sU5YgF4PRDu7aBj1fmqwct.png)

(三)交互数据结构处理

复制代码
    /******************************************************************************* * 函数名称:  void WEB_Service_Registration(WEB_Server_Struct *next)
    * 函数说明: WEB数据服务注册,与应用程序间的映射建立。
    * 输入参数: 相应应用程序的链表类型指针
    * 返回参数: 无
    *******************************************************************************/
    void WEB_Service_Registration(WEB_Server_Struct *next)
    {
    //首先获取头结点
    	WEB_Server_Struct *f = WEB_Registry_Head;
    //传入的节点下一个指向空
    	next->next = NULL;
    //遍历找到空节点位置
    	while (f->next != NULL)
    	{
    		f = f->next;
    	}
    //把节点插入到链表中
    	f->next = next;
    }
    
    /******************************************************************************* * 函数名称:  char Search_match_the_analytical_method(const char *cmd , char *body_Buf)
    * 函数说明: 根据命令搜寻匹配解析方法
    * 输入参数: WBE网页发来的命令
    * 返回参数: 搜寻匹配成功返回0  ; 未找到匹配命令返回1
    *******************************************************************************/
    char Search_match_the_analytical_method(const char *cmd, char *body_Buf)
    {
    //找到头结点
    	WEB_Server_Struct *f = WEB_Registry_Head;
    	char *p;
    
    	printf("CMD:\r\n");
    	printf("%s\r\n", cmd);
    	for (f = WEB_Registry_Head; f != NULL; f = f->next)
    	{
        //判断cmd是否在链表内
    		p = strstr(cmd, f->cmd);
    		if (p != NULL)
    		{
             //获取命令数据
    			p = (char *)cmd;
            //获取命令 value指针
    			p = p + strlen(f->cmd) + 1;
            //进行响应处理
    			f->function(body_Buf, p); //此函数不可重入,所以停止任务调度
    			return 1;
    		}
    	}
    	return 0;
    }
    /******************************************************************************* * 函数名称:  char POST_Search_match_the_analytical_method(char *cmd,char *dat)
    * 函数说明: POST方式下获取的命令和数据,根据命令搜寻匹配解析方法
    * 输入参数: WBE网页POST发来的命令和数据包
    * 返回参数: 搜寻匹配成功返回0  ; 未找到匹配命令返回1
    *******************************************************************************/
    char POST_Search_match_the_analytical_method(char *cmd, char *body_buf, char *dat)
    {
    	WEB_Server_Struct *f = WEB_Registry_Head;
    
    	for (f = WEB_Registry_Head; f != NULL; f = f->next)
    	{
    		if (strstr(cmd, f->cmd))
    		{
            //dat在上层应用获取到,直接传入value值
    			f->function((char *)body_buf, dat); //此函数不可重入,所以停止任务调度
    			return 1;
    		}
    	}
    	return 0;
    }
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/DPFsteHwcxACYuT3Simv5khgJnbI.png)

五、前后台交互实现

(一)初始化

复制代码
    //把我们传感器数据获取节点,定义为链表的头结点
    #define WEB_Registry_Head 	 &SENSOR_WEB_DATA_GET
    /** * @brief  http server thread 
      * @param arg: pointer on argument(not used here) 
      * @retval None
      */
    void http_server_socket_thread(void *p_arg)
    {
    //.......
    
    	printf("\r\n--------------Web_Server_Task----------------\r\n");
    //把CMD相关的节点插入到链表当中
    	WEB_Service_Registration(&CONTROL_LIGHT_CMD_POST);
    	WEB_Service_Registration(&CONTROL_FAN_CMD_POST);
    	WEB_Service_Registration(&CONTROL_ALARM_CMD_POST);
    
    //........
    
    }
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/0MjbRYIGCmu9rTzykWiHneA4wo6d.png)

(二)前台交互解析

复制代码
    /******************************************************************************* * 函数名称:  void Respond_Http_Request(SOCKET ch ,char* Request_Msg)
    * 函数说明: 响应HTTP请求
    * 输入参数: socket 端口:ch ;HTTP请求数据包指针	:Request_Msg
    * 返回参数: 无
    *******************************************************************************/
    void Respond_Http_Request(char ch, char *Request_Msg)
    {
    	Http_Request_MSG_Type Http_Request_MSG;
    	char *thisstart = NULL;
    	char *nextstart = NULL;
    	char *buf;
    	int length = 0;
    	Parse_Http_Request(Request_Msg, &Http_Request_MSG); //解析HTTP请求类型
    	switch (Http_Request_MSG.Method)
    	{
    	case METHOD_ERR:
    		write(ch, (const unsigned char *)ERROR_REQUEST_PAGE, sizeof(ERROR_REQUEST_PAGE));
    		break;
    	case METHOD_HEAD:
    	case METHOD_GET:
    		if (strstr((const char *)Http_Request_MSG.URL, "/DATA/")) //判断是否是通讯指令,并解析指令帧
    		{
    			char *buf;
    			buf = (char *)pvPortMalloc(128);									
    			if (Search_match_the_analytical_method((const char *)(Http_Request_MSG.URL + 6), buf)) //匹配解析方法,匹配成功,执行相应动作
    			{
    				Send_Web_Service_Data(ch, buf); //回应请求的数据
    			}
    			else
    			{
    				write(ch, INVALID_CMD, sizeof(INVALID_CMD)); //无此指令,回应无效请求
    			}
    			vPortFree(buf); //释放内存空间
    		}
    		else
    		{
            //发送请求的文件
    		}
    		break;
    	case METHOD_POST:
    		//获取POST内容
    		thisstart = strstr(Http_Request_MSG.Post_Data, "Content-Length:");
    		if (thisstart != NULL)
    		{
    			Http_Request_MSG.Content_Length = atoi(thisstart + 15); //获取POST内容的大小
    		}
    		thisstart = strstr(thisstart, "\r\n\r\n") + 4;
         //Post_Data = "On"/"Off"
    		Http_Request_MSG.Post_Data = thisstart;
    		length = strlen(thisstart); //修改bug
    
    		//解析POST内存
    		if (strstr((const char *)Http_Request_MSG.URL, "/CMD/")) //判断是否是通讯指令,并解析指令帧
    		{
    			//本次接收的数据尾指针获取
    			nextstart = thisstart + length;
             //nextstart - thisstart 本次socket接收数据的内容
            // Content_Length 是这次post body数据的长度
    			while ((nextstart - thisstart) < Http_Request_MSG.Content_Length) //可能数据量很大,获取POST完整内容
    			{
                 //后续还有数据,再调用read进行读取
    				length = read(ch, (unsigned char *)nextstart, 1500);
    				if (length > 0)
                      //把数据尾指针更新
    					nextstart += length;
                      //没有后续数据
    				else if (length < 0)
    					break;
    			}
             //填充\0保证一个完整字符串
    			*nextstart = '\0';
             //更新整个post下发的长度字段
    			Http_Request_MSG.Content_Length = nextstart - thisstart;
    
    			//解析数据包
    			buf = (char *)pvPortMalloc(128); //开辟内存空间--原先2048
    			if (POST_Search_match_the_analytical_method(Http_Request_MSG.URL + 5, buf, Http_Request_MSG.Post_Data))
    			{
    				Send_Web_Service_Data(ch, buf);
    			}
    			else
    			{
    				write(ch, INVALID_CMD, sizeof(INVALID_CMD));
    			}
    			vPortFree(buf);
    		}
    		else
    		{
            	//下发文件
    		}
    		break;
    	default:
    		break;
    	}
    }
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/kOEMHmXP89WrtcUbQ7xlB6sqwZpV.png)

(三)前后台交互响应

复制代码
    /******************************************************************************* * 函数名称:  void Send_Web_Service_Data(SOCKET ch,char*body_buf) 
    * 函数说明: 发送Web回应数据包 
    * 输入参数: SOCKET 通道号 ,body_buf 要发送的数据包
    * 返回参数: 无
    *******************************************************************************/
    void Send_Web_Service_Data(char ch, char *body_buf)
    {
    	char *buf;
    	char *P_index;
    	short length = 0;
    //获取body长度
    	length = strlen((const char *)body_buf);
    //开辟内存空间
    	buf = (char *)pvPortMalloc(length + sizeof(RETURN_cmd_OK) + 50); //开辟内存空间
    	sprintf(buf, "%s%u\r\n\r\n%s", RETURN_cmd_OK, length, body_buf);
    	length = strlen((const char *)buf);
    	P_index = buf;
    //写入socket按照1500个字节进行传输
    	while (length > 1500)
    	{
         //如果write单次发送的长度太大,lwip占用动态内存比较多,这是时候,我们需要手动分片
    		write(ch, (const unsigned char *)P_index, 1500);
    		length -= 1500;
    		P_index += 1500;
    	}
    	if (length > 0)
    	{
    		write(ch, (const unsigned char *)P_index, length);
    	}
    	vPortFree(buf); //释放内存空间
    }
    
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/9asZ60cX7Pe2oxN8BpDLdY1EwUTb.png)

全部评论 (0)

还没有任何评论哟~