STM32移植lwip之建立web服务器

2023-10-30

本篇目标:在之前能ping通pc机的工程基础上搭建web服务器,借鉴官方web服务器的程序与网页,能够用pc机浏览器访问web服务器,并返回设置的网页

材料准备:

ps:通过修改官方搭建web服务器的代码,来了解搭建的过程,其中暂时去掉了ssi和cgi的程序,仅仅实现网页数据的返回和网页的跳转,并将官方的代码简化到相对最简,以便以后的学习之用


浏览器请求指令探索

要搭建服务器,首先肯定要先了解远程客户端是怎么访问服务器的,这里pc机的浏览器则作为客户端:

  1. 打开浏览器(谷歌浏览器测试),输入服务器ip;
  2. 浏览器发送请求命令给服务器;
  3. 服务器接收到指令后,通过程序来解析指令,找到对应应该返回的网页;
  4. 服务器发送网页代码给浏览器;
  5. 浏览器显示网页;

接下来再用搭建虚拟服务器的方法,来模拟一下上面的过程:

  • 打开网络调试助手,切换到网络服务器,在服务器操作端口输入80,点击创建,如图;这样我们就创建了一个虚拟的服务器,这个服务器的ip就是pc机的本地ip
    搭建虚拟服务器

  • 查看确认一下本地ip地址,可以在网络连接里面查看,也可以在cmd输命令查看,这里的ip地址为192.168.6.104,如图:
    本地ip地址

  • 打开浏览器(谷歌浏览器测试),输入刚才确认的本地ip地址,这里输入192.168.6.104:
    浏览器访问

  • 返回去看看刚才搭建的服务器有什么变化,会发现有接收到的数据,只要重点观察第一行的数据“GET / HTTP/1.1”,这个字符串将会被服务器解析,然后将网页代码返回回去:
    客户端请求连接

  • 找到一个官方程序有一个fs文件夹,里面有已经做好的网页,打开网页index.html,右击-查看源代码,然后全选复制下来,在网络调试助手的发送区粘贴,并点击发送,如图:
    服务器返回数据

  • 这时,会发现浏览器已经显示了一张网页,但是好像又有点不全,因为图片没有显示,为什么呢?返回网络调试助手,发现接收区又有好多请求,看字面意思,好像就是图片的请求,然而服务器没有返回图片数据,所以图片无法显示

  • 这时候,将所有的浏览器请求列出来比较一下:
    “GET / HTTP/1.1”
    “GET /STM32F4x7_files/ST.gif HTTP/1.1”
    “GET /inchtml-pages-stm32_connectivity_files/pixel.gif HTTP/1.1”
    “GET /STM32F4x7_files/stm32.jpg HTTP/1.1”
    发现请求中 / 后面一部分的内容不相同,所以服务器只需要解析这一部分的字符串内容,来返回对应的网页数据即可


搭建web服务器

现在创建一个新的c文件,取名为 http_server.c ,接下来写几个函数来建立web服务器,抽重要的函数进行总结一下:

  • web服务器初始化函数 Http_Server_Init():
void Http_Server_Init(void)
{
	struct tcp_pcb *http_server_pcb;

	/* 为web服务器分配一个tcp_pcb结构体 */
	http_server_pcb = tcp_new();
		
	/* 绑定本地端号和IP地址 */
	tcp_bind(http_server_pcb, IP_ADDR_ANY, 80);

	/* 监听之前创建的结构体http_server_pcb */
	http_server_pcb = tcp_listen(http_server_pcb);
	
	/* 初始化结构体接收回调函数 */
	tcp_accept(http_server_pcb, http_server_accept);
}

小结:上面函数主要就是为搭建web服务器做准备,包括申请网络结构体、设置80端口号、监听数据、设置接收数据回调函数;

  • 接收数据回调函数 tcp_server_accept() :
static err_t http_server_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
	struct http_state *hs;
	
	/* 分配内存空间 */
	hs = (struct http_state *)mem_malloc(sizeof(struct http_state));
	
	if (hs != NULL)
	{
		memset(hs, 0, sizeof(struct http_state));
	}
		
	/* 确认监听和连接 */
	tcp_arg(pcb, hs);
		
	/* 配置接收回调函数 */
	tcp_recv(pcb, http_server_recv);

	/* 配置轮询回调函数 */
	tcp_poll(pcb, http_server_poll, 4);
	
	/* 配置发送回调函数 */
	tcp_sent(pcb, http_sent);
	
	return ERR_OK;
}

小结:函数中主要配置一些回调函数,比如接收,轮询,发送;

  • 接收数据处理函数 http_server_recv() :
static err_t http_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *http_recv_pbuf, err_t err)
{
	err_t parsed = ERR_ABRT;
	struct http_state *hs = (struct http_state *)arg;

	/* 告诉tcp已经接收到数据 */
	tcp_recved(pcb, http_recv_pbuf->tot_len);
	
	if (hs->handle == NULL)
	{
		 /* 解析接收到的浏览器请求数据 */
		 parsed = http_parse_request(&http_recv_pbuf, hs, pcb);
	}
		
    /* 清空请求字符串 */
	if (parsed != ERR_INPROGRESS) 
	{		
        if (hs->req != NULL) 
	    {
            pbuf_free(hs->req);
            hs->req = NULL;
        }
    }
		
	if (parsed == ERR_OK)
	{
		/* 发送网页数据 */
		http_send_data(pcb, hs);
	}
	else if (parsed == ERR_ARG)
	{
		/* 关闭连接 */
		close_conn(pcb, hs);
	}

	return ERR_OK;
}

小结:函数主要工作将接收到的数据放入 http_parse_request() 函数进行解析,然后把网页数据发送出去;

  • 接收数据解析函数 http_parse_request():
static err_t http_parse_request(struct pbuf **inp, struct http_state *hs, struct tcp_pcb *pcb)
{
	char *data;
	char *crlf;
	u16_t data_len;
	struct pbuf *p = *inp;
	
	char *sp1, *sp2;
	u16_t uri_len;
	char *uri;
	
	/* 排列字符串 */
	if (hs->req == NULL)
	{
		hs->req = p;
	}
	else
	{
        /* 将多次的请求字符串进行连接排序 */
		pbuf_cat(hs->req, p);
	}
		
	/* 拷贝输入数据 */ 
    if (hs->req->next != NULL)
    {
		data_len = hs->req->tot_len;
        pbuf_copy_partial(hs->req, data, data_len, 0);
    }
	else
	{
		data = (char *)p->payload;
		data_len = p->len;
	}
		
	/* 提取接收到的浏览器字符串,浏览器请求示例:"GET / HTTP/1.1" */
	if (data_len > 7) 
	{
		crlf = strstr(data, "\r\n");
		
		if (crlf != NULL) 
		{
			/* 比较前4个字符是否为 "GET " */
			if (strncmp(data, "GET ", 4) == 0) 
			{
				/* sp1指向字符串 "/ HTTP/1.1" */
				sp1 = (data + 4);
			}
			
			/* 在sp1字符串中寻找字符" ",sp2指向字符串 " HTTP/1.1" */
			sp2 = strstr(sp1, " ");
			
			/* uri_len获取sp1字符串首地址到sp2字符串首地址的长度 */
			uri_len = sp2 - (sp1);

			if ((sp2 != 0) && (sp2 >= (sp1))) 
			{
				/* 将解析的字符串赋给uri,并在最后加上结束符\0,
				   uri指向字符串 "/\0" */
				uri = sp1;
				*(sp1 - 1) = 0;
				uri[uri_len] = 0;
				
				/* 根据字符串寻找对应网页数据 */
				return http_find_file(hs, uri, 0); 
			}
		}
	}
		
	return ERR_OK;
}

小结:这个函数是重要的请求数据解析函数,函数将接收到的字符串(例:“GET /STM32F4x7_files/ST.gif HTTP/1.1”)
分离出重要的判断字符串(例:“ /STM32F4x7_files/ST.gif”),然后根据这个字符串的内容来读取对应的网页数据;

  • 读取对应网页数据函数 http_find_file():
static err_t http_find_file(struct http_state *hs, const char *uri, int is_09)
{
	struct fs_file *file = NULL;

	/* 如果字符串为 "/\0",则打开index网页 */
	if((uri[0] == '/') && (uri[1] == 0)) 
	{
		file = fs_open("/index.html");
	    uri = "/index.html";
	} 
	else 
	{
		/* 如果为其他请求,则打开相应网页 */
	    file = fs_open(uri);
	}
	
	/* 将网页文件数据赋值给http_state结构体,之后发送出去 */
	return http_init_file(hs, file, is_09, uri);
}

小结:此函数就是网页数据读取函数,里面最重要的函数就是 fs_open() 函数了,这个函数在官方建立web服务器工程里的 fs.c 文件里面,这个函数的解析放到后面;

ps:http_server.c 还有头文件的包含,函数的定义;另外再编写一个http_server.h文件,包含宏定义,结构体定义,函数定义;在下面贴出这两个文件的源码;

上面基本包括了几个重要的函数,当然还有其他的函数,包括发送函数等等,这些函数可以看源代码的注释来理解


文件源码##

  • http_server.c
#include "lwip/debug.h"
#include "lwip/stats.h"
#include "lwip/tcp.h"
#include "http_server.h"
#include "fs.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/*
*********************************************************************************************************
*                                            LOCAL TABLES
*********************************************************************************************************
*/
static err_t http_server_accept(void *arg, struct tcp_pcb *pcb, err_t err);
static err_t http_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err);
static err_t http_server_poll(void *arg, struct tcp_pcb *pcb);
static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri);
static err_t http_find_file(struct http_state *hs, const char *uri, int is_09);
static err_t http_parse_request(struct pbuf **inp, struct http_state *hs, struct tcp_pcb *pcb);
static u8_t http_send_data(struct tcp_pcb *pcb, struct http_state *hs);
static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len);
static void close_conn(struct tcp_pcb *pcb, struct http_state *hs);

/*
*********************************************************************************************************
*                                      LOCAL FUNCTION PROTOTYPES
*********************************************************************************************************
*/

/***
 * 函数名称 : Http_Server_Init();
 *
 * 函数描述 : web服务器初始化;
 *
 * 传递值	  : 无;
 *
 * 返回值   : 无;
 *
 **/
void Http_Server_Init(void)
{
	struct tcp_pcb *http_server_pcb;

	/* 为web服务器分配一个tcp_pcb结构体 */
	http_server_pcb = tcp_new();
		
	/* 绑定本地端号和IP地址 */
	tcp_bind(http_server_pcb, IP_ADDR_ANY, 80);

	/* 监听之前创建的结构体http_server_pcb */
	http_server_pcb = tcp_listen(http_server_pcb);
	
	/* 初始化结构体接收回调函数 */
	tcp_accept(http_server_pcb, http_server_accept);
}

/***
 * 函数名称 : http_server_accept();
 *
 * 函数描述 : lwip数据接收回调函数,包含对tcp连接的确认,接收回调函数的配置;
 *
 * 传递值	  : *arg, *pcb, err ;
 *
 * 返回值   : ERR_OK 无错误;
 *
 **/
static err_t http_server_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
	struct http_state *hs;
	
	/* 分配内存空间 */
	hs = (struct http_state *)mem_malloc(sizeof(struct http_state));
	
	if (hs != NULL)
	{
		memset(hs, 0, sizeof(struct http_state));
	}
		
	/* 确认监听和连接 */
	tcp_arg(pcb, hs);
		
	/* 配置接收回调函数 */
	tcp_recv(pcb, http_server_recv);

	/* 配置轮询回调函数 */
	tcp_poll(pcb, http_server_poll, 4);
	
	/* 配置发送回调函数 */
	tcp_sent(pcb, http_sent);
	
	return ERR_OK;
}

/***
 * 函数名称 : http_server_recv();
 *
 * 函数描述 : 接受到数据后,根据接收到数据的内容,返回网页;
 *
 * 传递值	  : *arg, *pcb, *http_recv_pbuf, err;
 *
 * 返回值   : ERR_OK无错误;
 *
 **/
static err_t http_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *http_recv_pbuf, err_t err)
{
	err_t parsed = ERR_ABRT;
	struct http_state *hs = (struct http_state *)arg;

	/* 告诉tcp已经接收到数据 */
	tcp_recved(pcb, http_recv_pbuf->tot_len);
	
	if (hs->handle == NULL)
	{
		/* 解析接收到的浏览器请求数据 */
		parsed = http_parse_request(&http_recv_pbuf, hs, pcb);
	}
		
    /* 清空请求字符串 */
	if (parsed != ERR_INPROGRESS) 
	{		
        if (hs->req != NULL) 
		{
            pbuf_free(hs->req);
            hs->req = NULL;
        }
    }
		
	if (parsed == ERR_OK)
	{
		/* 发送网页数据 */
		http_send_data(pcb, hs);
	}
	else if (parsed == ERR_ARG)
	{
		/* 关闭连接 */
		close_conn(pcb, hs);
	}

	return ERR_OK;
}

/***
 * 函数名称 : http_server_poll();
 *
 * 函数描述 : 轮询函数;
 *
 * 传递值	  : *arg, *pcb;
 *
 * 返回值   : ERR_OK无错误;
 *
 **/
static err_t http_server_poll(void *arg, struct tcp_pcb *pcb)
{
	struct http_state *hs = arg;
	
	if (hs == NULL)
	{
		close_conn(pcb, hs);
		return ERR_OK;
	}
	else
	{
		hs->retries++;
		if (hs->retries == 4)
		{
			close_conn(pcb, hs);
			return ERR_OK;
		}
				
        /* 如果连接存在打开的文件,则将会发送剩下的数据;
         * 如果一直没有收到GET请求,那么连接将会立刻关闭 */
		if (hs && (hs->handle))
		{
			if (http_send_data(pcb, hs))
			{
				tcp_output(pcb);
			}
		}
	}
		
	return ERR_OK;
}

/***
 * 函数名称 : http_parse_request();
 *
 * 函数描述 : 对接收到的数据进行解析,根据不同的浏览器请求,返回对应的网页数据;
 *
 * 传递值	  : **inp, *hs, *pcb;
 *
 * 返回值   : ERR_OK无错误;
 *
 **/
static err_t http_parse_request(struct pbuf **inp, struct http_state *hs, struct tcp_pcb *pcb)
{
	char *data;
	char *crlf;
	u16_t data_len;
	struct pbuf *p = *inp;
	
	char *sp1, *sp2;
	u16_t uri_len;
	char *uri;
	
	/* 排列字符串 */
	if (hs->req == NULL)
	{
		hs->req = p;
	}
	else
	{
	    /* 将多次的请求字符串进行连接排序 */
		pbuf_cat(hs->req, p);
	}
		
	/* 拷贝输入数据 */ 
    if (hs->req->next != NULL)
    {
		data_len = hs->req->tot_len;
        pbuf_copy_partial(hs->req, data, data_len, 0);
    }
	else
	{
		data = (char *)p->payload;
		data_len = p->len;
	}
		
	/* 提取接收到的浏览器字符串,浏览器请求示例:"GET / HTTP/1.1" */
	if (data_len > 7) 
	{
		crlf = strstr(data, "\r\n");
		if (crlf != NULL) 
		{
			/* 比较前4个字符是否为 "GET " */
			if (strncmp(data, "GET ", 4) == 0) 
			{
				/* sp1指向字符串 "/ HTTP/1.1" */
				sp1 = (data + 4);
			}
			/* 在sp1字符串中寻找字符" ",sp2指向字符串 " HTTP/1.1" */
			sp2 = strstr(sp1, " ");
			/* uri_len获取sp1字符串首地址到sp2字符串首地址的长度 */
			uri_len = sp2 - (sp1);

			if ((sp2 != 0) && (sp2 >= (sp1))) 
			{
				/* 将解析的字符串赋给uri,并在最后加上结束符\0,
				   uri指向字符串 "/\0" */
				uri = sp1;
				*(sp1 - 1) = 0;
				uri[uri_len] = 0;
							
				/* 根据字符串寻找对应网页数据 */
				return http_find_file(hs, uri, 0); 
				}
			}
	}
		
	return ERR_OK;
}

/***
 * 函数名称 : http_find_file();
 *
 * 函数描述 : 对提取的数据进行判断,读取对应的网页数据;
 *
 * 传递值	  : *hs, *uri, is_09;
 *
 * 返回值   : ERR_OK无错误;
 *
 **/
static err_t http_find_file(struct http_state *hs, const char *uri, int is_09)
{
	struct fs_file *file = NULL;

	/* 如果字符串为 "/\0",则打开index网页 */
	if((uri[0] == '/') && (uri[1] == 0)) 
	{
		file = fs_open("/index.html");
	    uri = "/index.html";
	} 
	else 
	{
		/* 如果为其他请求,则打开相应网页 */
	    file = fs_open(uri);
	}
	
	/* 将网页文件数据赋值给http_state结构体,之后发送出去 */
	return http_init_file(hs, file, is_09, uri);
}

/***
 * 函数名称 : http_init_file();
 *
 * 函数描述 : 将要发送的数据保存到http_state结构体当中;
 *
 * 传递值	  : *hs, *file, is_09, *uri;
 *
 * 返回值   : ERR_OK无错误;
 *
 **/
static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri)
{
	if (file != NULL) 
	{
	    hs->handle = file;
		/* 将网页数据赋值给http_state */
	    hs->file = (char*)file->data;
		/* 将网页长度赋值给http_state */
	    hs->left = file->len;
	    hs->retries = 0;
	} 
	else 
	{
	    hs->handle = NULL;
	    hs->file = NULL;
	    hs->left = 0;
	    hs->retries = 0;
	}

	return ERR_OK;
}

/***
 * 函数名称 : http_send_data();
 *
 * 函数描述 : 数据发送函数;
 *
 * 传递值	  : *pcb, *hs;
 *
 * 返回值   : ERR_OK无错误;
 *
 **/
static u8_t http_send_data(struct tcp_pcb *pcb, struct http_state *hs)
{
	err_t err = ERR_OK;
	u16_t len;
	u8_t data_to_send = 0;
		
	/* 配置发送数据长度,如果发送数据过长则分批发送 */
	if (tcp_sndbuf(pcb) < hs->left)
	{
		len = tcp_sndbuf(pcb);
	}
	else
	{
		len = (u16_t)hs->left;
	}		

    /* 发送网页数据 */
	err = tcp_write(pcb, hs->file, len, 1);
		
	if (err == ERR_OK)
	{
		data_to_send = 1;
		hs->file += len;
		hs->left -= len;
	}
		
	if ((hs->left == 0) && (fs_bytes_left(hs->handle) <= 0))
	{
        /* 关闭连接 */
		close_conn(pcb, hs);
		return 0;
	}
		
	return data_to_send;
}


/***
 * 函数名称 : http_sent();
 *
 * 函数描述 : 数据已经被发送,并且被远程主机确定;
 *
 * 传递值	  : *arg, *pcb, len;
 *
 * 返回值   : ERR_OK无错误;
 *
 **/
static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{
	struct http_state *hs = (struct http_state *)arg;
	
	if (hs == NULL)
	{
		return ERR_OK;
	}
	
	hs->retries = 0;

	http_send_data(pcb, hs);

	return ERR_OK;
}

/***
 * 函数名称 : close_conn();
 *  * 函数描述 : 关闭tcp连接;
 *  * 传递值	  : *pcb, *hs;
 *  * 返回值   : 无;
 *  **/
static void close_conn(struct tcp_pcb *pcb, struct http_state *hs)
{
	tcp_arg(pcb, NULL);
	tcp_recv(pcb, NULL);
	tcp_err(pcb, NULL);
	tcp_poll(pcb, NULL, 0);
	tcp_sent(pcb, NULL);
	
    if (hs != NULL) 
	{
		if(hs->handle) 
		{
			fs_close(hs->handle);
			hs->handle = NULL;
		}
			mem_free(hs);
	}

	tcp_close(pcb);
}
  • http_server.h
#ifndef HTTP_SERVER_H
#define HTTP_SERVER_H

/*
*********************************************************************************************************
*                                              INCLUDE FILES
*********************************************************************************************************
*/


/*
*********************************************************************************************************
*                                               CONSTANTS
*********************************************************************************************************
*/


/*
*********************************************************************************************************
*                                             PERIPH DEFINES
*********************************************************************************************************
*/


/*
*********************************************************************************************************
*                                               DATA TYPES
*********************************************************************************************************
*/


/*
*********************************************************************************************************
*                                            GLOBAL VARIABLES
*********************************************************************************************************
*/

struct http_state {
  struct fs_file *handle;
  char *file;       /* Pointer to first unsent byte in buf. */

#if 1
  struct pbuf *req;
#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */

#if 1
  char *buf;        /* File read buffer. */
  int buf_len;      /* Size of file read buffer, buf. */
#endif /* LWIP_HTTPD_SSI || LWIP_HTTPD_DYNAMIC_HEADERS */
  u32_t left;       /* Number of unsent bytes in buf. */
  u8_t retries;
#if 0
  const char *parsed;     /* Pointer to the first unparsed byte in buf. */
#if 1
  const char *tag_started;/* Poitner to the first opening '<' of the tag. */
#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG */
  const char *tag_end;    /* Pointer to char after the closing '>' of the tag. */
  u32_t parse_left; /* Number of unparsed bytes in buf. */
  u16_t tag_index;   /* Counter used by tag parsing state machine */
  u16_t tag_insert_len; /* Length of insert in string tag_insert */
#if 0
  u16_t tag_part; /* Counter passed to and changed by tag insertion function to insert multiple times */
#endif /* LWIP_HTTPD_SSI_MULTIPART */
  u8_t tag_check;   /* true if we are processing a .shtml file else false */
  u8_t tag_name_len; /* Length of the tag name in string tag_name */
  char tag_name[LWIP_HTTPD_MAX_TAG_NAME_LEN + 1]; /* Last tag name extracted */
  char tag_insert[LWIP_HTTPD_MAX_TAG_INSERT_LEN + 1]; /* Insert string for tag_name */
  enum tag_check_state tag_state; /* State of the tag processor */
#endif /* LWIP_HTTPD_SSI */
#if 0
  char *params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */
  char *param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */
#endif /* LWIP_HTTPD_CGI */
#if 0
  const char *hdrs[NUM_FILE_HDR_STRINGS]; /* HTTP headers to be sent. */
  u16_t hdr_pos;     /* The position of the first unsent header byte in the
                        current string */
  u16_t hdr_index;   /* The index of the hdr string currently being sent. */
#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
#if 0
  u32_t time_started;
#endif /* LWIP_HTTPD_TIMING */
#if 0
  u32_t post_content_len_left;
#if 0
  u32_t unrecved_bytes;
  struct tcp_pcb *pcb;
  u8_t no_auto_wnd;
#endif /* LWIP_HTTPD_POST_MANUAL_WND */
#endif /* LWIP_HTTPD_SUPPORT_POST*/
};

/*
*********************************************************************************************************
*                                                 MACRO'S
*********************************************************************************************************
*/



/*
*********************************************************************************************************
*                                           FUNCTION PROTOTYPES
*********************************************************************************************************
*/

void Http_Server_Init(void);

/*
********************************************************************************************************
*                                             MODULE END
*********************************************************************************************************
*/

#endif /* HTTP_SERVER_H */

官方部分函数解析

读取网页数据文件 fs.c (路径:Project->Standalone->web_server->http):

struct fs_file *fs_open(const char *name)
{
	struct fs_file *file;
	const struct fsdata_file *f;

	/* 分配空间 */
	file = fs_malloc();
	if(file == NULL) 
	{
		return NULL;
	}

	for(f = FS_ROOT; f != NULL; f = f->next) 
	{
		/* 循环比较,如果输入的请求与网页的头数据一致,则返回该网页数据 */
	    if (!strcmp(name, (char *)f->name)) 
	    {
		    file->data = (const char *)f->data;
		    file->len = f->len;
		    file->index = f->len;
		    file->pextension = NULL;
		    file->http_header_included = f->http_header_included;

		    return file;
		}
	}
	fs_free(file);
	return NULL;
}

这里关注 f 变量的结构体 fsdata_file,定义在 fsdata.h:

struct fsdata_file 
{
	const struct fsdata_file *next;
	const unsigned char *name;
	const unsigned char *data;
	int len;
	u8_t http_header_included;
};

**结构体中有三个重要的变量*next、name、data
而在文件fsdata.c中,拉到最后,发现有几个 fsdata_file 的结构体变量,取其中一个来解析一下:

const struct fsdata_file file__index_html[] = 
{ 
	{
		/* 变量*next,指向下一个要循环比较的数据 */
		file__404_html,
		/* 变量*name,指向数据数组 data__index_html[] */
		data__index_html,
		/* 变量*data,指向数据数组 data__index_html[]12个之后的数据 */
		data__index_html + 12,
		/* 网页数据长度 */
		sizeof(data__index_html) - 12,
		1,
	}
};
  • 变量*name指向的数组前12个数据是字符串 “/index.html” 的ascii码,用于与输入的浏览器请求 “GET /index.html HTTP/1.1” 进行对比;
  • 而*data指向数组的12个后的数据,便是网页源代码的16进制数据,这些数据将会由发送函数发送给浏览器,使浏览器显示网页;

web服务器测试

将工程编译后,烧进stm32,将网线与pc机连接:

  • 打开浏览器(谷歌浏览器测试)
  • 输入服务器ip(这里搭建的服务器ip:192.168.0.10),Enter;
  • 浏览器会显示网页,点击网页上的按钮即可以切换不同的网页

如图:
服务器返回网页数据


总结:从上面的一系列过程可以get到搭建web服务器的核心思想,然而,现在并没有加入ssi和cgi,所以还无法用网页控制stm32,后面会加上ssi、cgi、post与get请求来完善整个web服务器;

ps:有部分细节的地方解析的不是很清楚,而且自己也没有想明白,需要再加把劲看一些其他的资料来填补空白,共勉~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32移植lwip之建立web服务器 的相关文章

  • 51单片机 数码管中断操作

    实践目的 1 掌握中断的概念和思想 2 掌握51单片机中断系统和相关软硬件设计 实践内容 1 利用单片机的P0口接数码管的字段脚 P1 0脚接共阴极 P3 2 P3 3引脚接独立按键产生外部中断信号 编写程序 当程序正常运行时数码管显示H字
  • 在地址“0xXXXXXX”处中断,没有可用的调试信息,或在程序代码之外

    配置 使用 Nucleo L476RG 使用 GNU ARM Eclipse 我从 STM32CubeMX 生成了一个极简代码 我已经在我的板载 ST Link 中刷新了 J link 驱动程序 一直在尝试为我的代码运行调试器 但我的程序计
  • 在 MCU 内部 FLASH 中从一个固件跳转到另一个固件

    我目前正在开发针对 STM32F030C8 的引导加载程序固件应用程序 我在分散文件中指定引导加载程序应用程序将占用主内存位置 0x08000000 到 0x08002FFF 扇区 0 到扇区 2 我还编写了一个主固件应用程序 存储在0x0
  • 139-基于stm32单片机老人居家监护报警系统Proteus仿真+源程序

    资料编号 139 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 MQ4传感器 电位器模拟 MQ2传感器 电位器模拟 蜂鸣器 电机 制作一个基于stm32单片机老人居家监护报警系统Proteus仿真 2 通过MQ2传
  • 135-基于stm32单片机超声波非接触式感应水龙头控制系统Proteus仿真+源程序

    资料编号 135 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 DHT11传感器 电机 超声波传感器 制作一个基于stm32单片机超声波非接触式感应水龙头控制系统Proteus仿真 2 通过DHT11传感器检测当前
  • HAL库STM32常用外设教程(二)—— GPIO输入\输出

    HAL库STM32常用外设教程 二 GPIO输入 输出 文章目录 HAL库STM32常用外设教程 二 GPIO输入 输出 前言 一 GPIO功能概述 二 GPIO的HAl库驱动 三 GPIO使用示例 1 示例功能 四 代码讲解 五 总结
  • rt-thread studio中新建5.0不能用

    文章目录 一 版本对比 二 文件和文件夹打斜杠 在使用RT Thread studio创建新工程5 0版本的时候 结果发现新建完成之后程序不能正常运行 但是创建4 10版本的时候却能运行 那肯定是新版本出现了BUG 一 版本对比 首先对比了
  • Push_back() 导致程序在进入 main() 之前停止

    我正在为我的 STM32F3 Discovery 板使用 C 进行开发 并使用 std deque 作为队列 在尝试调试我的代码 直接在带有 ST link 的设备上或在模拟器中 后 代码最终在 main 中输入我的代码之前在断点处停止 然
  • STM32F4 通过软复位跳转到引导加载程序,无需 BOOT0 和 BOOT1 引脚

    我问这个问题是因为可以在这里找到类似问题的答案 通过应用程序跳转到 STM32 中的引导加载程序 即从用户闪存在引导模式下使用引导 0 和引导 1 引脚 用户 JF002 JF002回答 当我想跳转到引导加载程序时 我在其中一个备份寄存器中
  • STM32F103

    提示 来源正点原子 参考STM32F103 战舰开发指南V1 3PDF资料 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 提示 这里可以添加本文要记录的大概内容 开发环境硬件普中科技 接线图在g
  • 物联网网关

    物联网网关是 连接物联网设备和互联网的重要桥梁 它负责将物联网设备采集到的数据进行处理 存储和转发 使其能够与云端或其它设备进行通信 物联网网关的作用是实现物联网设备与云端的无缝连接和数据交互 物联网网关功能 数据采集 物联网网关可以从物联
  • STM32F103概要

    The STM32F103x4 STM32F103x6 STM32F103xC STM32F103xD and STM32F103xE are a drop in replacement for STM32F103x8 B medium d
  • STM32 GPIO工作原理详解

    STM32 GPIO介绍 1 STM32引脚说明 GPIO是通用输入 输出端口的简称 是STM32可控制的引脚 GPIO的引脚与外部硬件设备连接 可实现与外部通讯 控制外部硬件或者采集外部硬件数据的功能 以STM32F103ZET6芯片为例
  • 跟着野火学FreeRTOS:第一段(任务定义,切换以及临界段)

    在裸机系统中 系统的主体就是 C P U CPU CP U 按照预先设定的程序逻辑在 m a i n
  • 1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

    1 69寸SPI接口240 280TFT液晶显示模块使用中碰到的问题说明并记录一下 在网上买了1 69寸液晶显示模块 使用spi接口 分辨率240 280 给的参考程序是GPIO模拟的SPI接口 打算先移植到FreeRtos测试 再慢慢使用
  • 1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

    1 69寸SPI接口240 280TFT液晶显示模块使用中碰到的问题说明并记录一下 在网上买了1 69寸液晶显示模块 使用spi接口 分辨率240 280 给的参考程序是GPIO模拟的SPI接口 打算先移植到FreeRtos测试 再慢慢使用
  • Freertos低功耗管理

    空闲任务中的低功耗Tickless处理 在整个系统运行得过程中 其中大部分时间都是在执行空闲任务的 空闲任务之所以执行 因为在系统中的其他任务处于阻塞或者被挂起时才会执行 因此可以将空闲任务的执行时间转换成低功耗模式 在其他任务解除阻塞而准
  • NTP请求包

    我试图弄清楚我需要在 NTP 请求包中发送 客户端 什么才能从服务器检索 NTP 包 我正在 Cortex M3 Stellaris LM3S6965 上使用 LWIP 据我了解 我将收到 UDP 标头 然后收到具有不同时间戳的 NTP 协
  • 在 Contiki 程序中使用 malloc

    考虑以下 Contiki 程序 include
  • 使用 STM32F0 ADC 单独读取不同的输入

    STM32F072CBU 微控制器 我有多个 ADC 输入 并且希望单独读取它们 STMcubeMX 生成样板代码 假设我希望按顺序读取所有输入 但我无法弄清楚如何纠正这个问题 这篇博文 http blog koepi info 2015

随机推荐

  • Link Cut Tree (动态树)【P3690】

    题目链接 给定n个点以及每个点的权值 要你处理接下来的m个操作 操作有4种 操作从0到3编号 点从1到n编号 0 后接两个整数 x y 代表询问从x到y的路径上的点的权值的xor和 保证x到y是联通的 1 后接两个整数 x y 代表连接x到
  • springboot websocket 传递 头信息 协议头 token 的前后端解决方案

    文章目录 一 前言 二 js websocket 传递token 2 1 基于协议头 三 后台取出websocket协议头的参数 3 1 取出token 3 2 注意大坑 四 结尾 一 前言 关于springboot websocket 可
  • python3 爬虫实战之爬取网易新闻APP端

    一 使用工具 这里使用了火狐浏览器的user agent插件 不懂的可以点这里火狐插件使用 二 爬虫操作步骤 百度 网易新闻并选择 步骤一 步骤二 步骤三 步骤四 最后一步 注意点 1 网易新闻类型 一共是下面的几种 BBM54PGAwan
  • DataSpell学习

    今天刚发现一个有用的数据分析软件DataSpell 下载地址 https www jetbrains com zh cn dataspell features 安装很简单 按照步骤一路来就行了 支持ipynb文件 其实和jupyter no
  • pip3 install命令执行时指定源+指定版本

    1 指定源 用法 pip3 install i https pypi tuna tsinghua edu cn simple 3rd MODULE NAME 例如 pip3 install i https pypi tuna tsinghu
  • Ubuntu下为可执行文件和脚本文件(.sh)生成桌面快捷方式

    1 前言 在Ubuntu中 有些软件是安装的 如deb的安装包 用dpkg i安装 有些软件可能是不需要安装 直接解压tar gz这类文件直接就可以运行了 但是每次运行的时候都要输入一遍命令十分麻烦 因此想把它建个快捷方式放到桌面或启动栏里
  • Segformer网络数据流机制

    代码来源 https github com bubbliiiing segformer pytorch 网络特点 结合了Transformers与轻量级的多层感知机 MLP 解码器 包含一个新颖的分层结构的Transformer编码器 该编
  • Anaconda虚拟环境,更新库

    以管理员身份启动 Anaconda Prompt 1 升级Anaconda前需要先升级conda 升级conda conda update conda 2 升级anaconda conda update anaconda base环境 直接
  • qnap安装Linux程序,[Troy]瞎折腾 篇一:【智能家居】威联通QNAP TS-251A安装Ubuntu+Hassio+Samba经验分享...

    原标题 Troy 瞎折腾 篇一 智能家居 威联通QNAP TS 251A安装Ubuntu Hassio Samba经验分享 Home Assistant是一款基于 Python 的智能家居开源系统 支持众多品牌的智能家居设备 可以轻松实现设
  • win11 vs2019下的qt5.15安装配置

    一 vs2019 先前安装过的版本 在此不做赘述 仅为前提条件 二 qt安装 1 qt版本选择 目前qt更新到6 3 但因为6的版本太新 而5 15是一个LTS长期维护版本 维护期一直到2025年 所以在此选择qt5 15版本 2 qt在线
  • 给Tomcat添加第三方jar包、如何在IDEA中启动部署Web模板

    给Tomcat添加第三方jar包 第一种方式 1 将jar包放到lib目录中 2 将jar包加入到模块中 Add as Library 第二种方式 1 可以打开项目结构菜单项目操作界面 添加一个自己的类库 2 添加你类库需要的jar包 3
  • 美国读研计算机 回国后好就业吗,美国留学归国就业前景如何

    很多在美国留学的小伙伴们都会选择在毕业后回国发展 那么 美国留学归国的就业前景如何呢 感兴趣的小伙伴快来阅读出国留学网的这篇文章吧 希望可以为大家提供参考 美国留学回国就业前景 1 医药领域专业人才和相关人才需求量增加比重最大 其中对应的包
  • LaTex加入新package方法

    1 前几天去 https www ctan org 下载booktabs宏包 下载的文件中没有sty文件 有ins文件 用winedit打开ins文件 用late编译 同一个文件夹中得到了一个sty文件 2 将sty文件拷贝到相应的late
  • numpy--广播及np.shape的案例

    numpy广播 最近有一个小需求 给定 a 0 1 2 M 1 1 1 1 求得 T 0 0 0 0 1 1 1 1 2 2 2 2 经过尝试 终于采用如下代码成功 a reshape 3 1 M reshape 1 4 reshape 3
  • PID算法,计算的是差值,是差值

    typedef struct float Kp 比例系数Proportional float Ki 积分系数Integral float Kd 微分系数Derivative float Ek 当前误差 float Ek1 前一次误差 e k
  • JAVA代码实现抖音转载视频无水印视频,亲测通过

    许多小伙伴想做抖音视频 无奈没有摄影器材 也没有取景材料 就想着去用别人人气视频来提高自己的粉丝量 可问题又来了 别人的视频通过分享 或者链接根本不是原创 上面还带着水印 视频一挂上去就被发现了 小则视频不通过 给出警告 大则封号 降低视频
  • Linux centos8安装docker

    1 下载docker ce的repo curl https download docker com linux centos docker ce repo o etc yum repos d docker ce repo 2 安装依赖 yu
  • vue3实现导航栏绑定内容锚点+滚动动画

    目前用的两种方法实现 第一种 原生js实现 注意 因为移动端可滚动区域可能会嵌套在其他架子下 所以需要用到ref获取滚动区域 正常获取scrollTop 前者基于html 后者基于body scrollTop document docume
  • 017-Java-008

    实例变量 实例变量声明在一个类中 但在方法 构造方法和语句块之外 当一个对象被实例化之后 每个实例变量的值就跟着确定 实例变量在对象创建的时候创建 在对象被销毁的时候销毁 实例变量的值应该至少被一个方法 构造方法或者语句块引用 使得外部能够
  • STM32移植lwip之建立web服务器

    本篇目标 在之前能ping通pc机的工程基础上搭建web服务器 借鉴官方web服务器的程序与网页 能够用pc机浏览器访问web服务器 并返回设置的网页 材料准备 基础工程 修改后能ping通pc机的工程 STM32官方移植lwip修改代码