1.WebServer功能设计
WebServer需要做什么
当用户访问网址(url)时,加载网页文件

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

EasyWebSvr工具介绍
菜单界面

设置界面

运行日志

EasyWebSvr搭建Web服务器
配置服务器主目录

浏览器输入服务器地址
127.0.0.1/index.html

分析Web服务器运行日志

WebServer功能设计
文件请求响应
传感器数据请求响应
命令请求响应
2.WebServer主线程实现
WebServer代码移植
文件移植
智慧教室项目实战\day08\03-WebServer移植文件

把网页文件拷贝到SD卡中
1.拷贝文件到SD卡"根目录下"
2.开发板断电插入SD卡
3.烧录程序看现象(浏览器输入192.168.1.7)

访问STM32服务器,网页图片加载需要多次刷新问题
1、我们webserver是一个单线程任务,http属于短链接
2、但是浏览器有缓存,我们使用时只需要多刷新几次就可以了
3、也可以通过在前端增加一些js代码(循环加载前端资源(文件))
4、STM32内存太小了,没有办法做长连接,短连接模式可以实现多个客户端连接
WebServer主线程实现
http_server_socket_thread
void http_server_socket_thread(void *p_arg)
{
int sock, newconn, size;
struct sockaddr_in address, remotehost;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("http_server can not create socket");
return;
}
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(sock, 5);
size = sizeof(remotehost);
while (1)
{
newconn = accept(sock, (struct sockaddr *)&remotehost, (socklen_t *)&size);
http_server_serve(newconn);
}
}
http_server_serve
void http_server_serve(int conn)
{
int ret;
ret = read(conn, (unsigned char *)Request_Buf, 1500);
if (ret < 0)
return;
else
{
*(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);
}
close(conn);
}
http解析业务流程
3.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: "
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";
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: ";
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: ";
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: ";
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: ";
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: ";
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: ";
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: ";
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: ";
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: ";
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: ";
http消息结构体
typedef struct
{
char Method;
char *URL;
char FileType;
char *Post_Data;
unsigned int Content_Length;
} Http_Request_MSG_Type;
Respond_Http_Request
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);
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:
}
}
Parse_Http_Request
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;
}
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);
if (Http_Request_MSG->URL[0] == '/' && Http_Request_MSG->URL[1] == '\0')
{
Http_Request_MSG->URL = HOMEPAGE_DEFAULT;
}
Http_Request_MSG->Post_Data = nextstart;
}
Send_Response_File
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);
Make_http_response_head((char *)buf, Http_Request_MSG->FileType, fSize);
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;
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);
}
}
4.前后台交互方法设计
数据结构
typedef struct Web_s{
struct Web_s *next;
const char *cmd;
void (*function)(void *,void *);
} WEB_Server_Struct;
交互数据结构封装
字符串封装
const char Sensor[] = "Sensor";
const char Light[] = "Light";
const char Fan[] = "Fan";
const char Alarm[] = "Alarm";
const char On[] = "On";
const char Off[] = "Off";
数据节点封装
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};
交互函数封装
static void Get_SensorValue(void *buffer, void *value)
{
sprintf(buffer, "{\"temperature\":\"%d\",\"humidity\":\"%d\",\"light\":\"10021.1\"}", SensorData[0], SensorData[1]);
}
static void Post_Cmd_Light(void *buffer, void *value)
{
if (strstr(value, 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))
{
sprintf(buffer, "{\"Status\":\"Off\"}");
HAL_GPIO_WritePin(GPIOF, D6_Pin | D7_Pin | D8_Pin | D9_Pin, GPIO_PIN_SET);
}
else
{
sprintf(buffer, "{\"Status\":\"Error\"}");
}
}
static void Post_Cmd_Fan(void *buffer, void *value)
{
if (strstr(value, On))
{
sprintf(buffer, "{\"Status\":\"On\"}");
FanControl(0x01);
}
else if (strstr(value, Off))
{
sprintf(buffer, "{\"Status\":\"Off\"}");
FanControl(0x0);
}
else
{
sprintf(buffer, "{\"Status\":\"Error\"}");
}
}
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\"}");
}
}
交互数据结构处理
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)
{
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)
{
p = strstr(cmd, f->cmd);
if (p != NULL)
{
p = (char *)cmd;
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 *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))
{
f->function((char *)body_buf, dat);
return 1;
}
}
return 0;
}
5.前后台交互实现
初始化
#define WEB_Registry_Head &SENSOR_WEB_DATA_GET
void http_server_socket_thread(void *p_arg)
{
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);
}
前台交互解析
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);
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:
thisstart = strstr(Http_Request_MSG.Post_Data, "Content-Length:");
if (thisstart != NULL)
{
Http_Request_MSG.Content_Length = atoi(thisstart + 15);
}
thisstart = strstr(thisstart, "\r\n\r\n") + 4;
Http_Request_MSG.Post_Data = thisstart;
length = strlen(thisstart);
if (strstr((const char *)Http_Request_MSG.URL, "/CMD/"))
{
nextstart = thisstart + length;
while ((nextstart - thisstart) < Http_Request_MSG.Content_Length)
{
length = read(ch, (unsigned char *)nextstart, 1500);
if (length > 0)
nextstart += length;
else if (length < 0)
break;
}
*nextstart = '\0';
Http_Request_MSG.Content_Length = nextstart - thisstart;
buf = (char *)pvPortMalloc(128);
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;
}
}
前后台交互响应
void Send_Web_Service_Data(char ch, char *body_buf)
{
char *buf;
char *P_index;
short length = 0;
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;
while (length > 1500)
{
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);
}