源码github地址:https://github.com/linzhongpaihuai/smartplug
①烧录方法:https://blog.csdn.net/u010177891/article/details/90348729
②esp8266实现http server服务详解:https://blog.csdn.net/u010177891/article/details/100024710
③esp8266对接天猫精灵实现语音控制:https://blog.csdn.net/u010177891/article/details/100026511
④esp8266对接贝壳物联平台详解:https://blog.csdn.net/u010177891/article/details/100058124
说明:
通过修改esp8266 RTOS的固件实现http服务器,这样可以通过浏览器完成与esp8266的交互。看下做好的样子:
代码实现
1,启动http服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15for ( ; ; ) { if ( uiCurStatus == STATION_GOT_IP && WEB_GetWebSvcStatus() == FALSE ) { LOG_OUT(LOGOUT_INFO, "WEB_StartWebServerTheard."); WEB_StartWebServerTheard(); } if ( uiCurStatus != STATION_GOT_IP && WEB_GetWebSvcStatus() == TRUE ) { LOG_OUT(LOGOUT_INFO, "WEB_StopWebServerTheard."); WEB_StopWebServerTheard(); } vTaskDelay( 1000/portTICK_RATE_MS ); }
当esp8266连接好wifi时进入for死循环,uiCurStatus 为wifi的连接状态,WEB_GetWebSvcStatus() 返回http server是否启动。 WEB_StartWebServerTheard()会创建http server任务。 WEB_StopWebServerTheard()停止http server任务。该for死循环可以保证wifi断开重连时重新启动http server服务
创建http server任务,启动WEB_WebServerTask任务
1
2
3
4VOID WEB_StartWebServerTheard( VOID ) { xTaskCreate(WEB_WebServerTask, "WEB_WebServerTask", 512, NULL, 4, &xWebServerHandle); }
停止http server任务,通过设置bWebServerTaskTerminate为true,在WEB_WebServerTask任务中判断该标志为true时且响应完所有http请求后主动结束http server任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15VOID WEB_StopWebServerTheard( VOID ) { if ( xWebServerHandle != NULL ) { bWebServerTaskTerminate = TRUE; while( bWebServerTaskTerminate == TRUE ) { LOG_OUT(LOGOUT_DEBUG, "web server task stop..."); vTaskDelay(1000/portTICK_RATE_MS); } } WEB_SetWebSvcStatus( FALSE ); LOG_OUT(LOGOUT_INFO, "stop web server successed"); }
WEB_WebServerTask主要实现:
1,创建tcp socket等待tcp客户端的连接
2,当有客户端连接时启动一个任务来响应请求,在请求完成时结束这个任务
3,当连接个数超过一定限制时等待其他连接释放后才响应。至于为什么要限制连接个数,主要是因为esp8266的RAM大小有限,除去系统本身占用的大小实际可用也就四五十K。每响应一个连接就需要消耗10~20K所以要限制连接个数避免内存消耗完导致esp8266重启
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153STATIC VOID WEB_WebServerTask( VOID *Para ) { struct sockaddr_in stServerAddr; struct sockaddr_in stClientAddr; INT32 iClientAddrLen = sizeof( struct sockaddr_in ); INT32 iClientFd = -1; INT32 iRet = 0; INT32 Reuseaddr = 1; INT32 iLoop = 0; fd_set stFdRead; struct timeval stSelectTimeOut = {1, 0}; struct timeval stRecvTimeOut = {1, 0}; LOG_OUT(LOGOUT_INFO, "WEB_WebServerTask started."); bWebServerTaskTerminate = FALSE; WEB_SetWebSvcStatus( TRUE ); memset(&stServerAddr, 0, sizeof(stServerAddr)); stServerAddr.sin_family = AF_INET; stServerAddr.sin_addr.s_addr = INADDR_ANY; stServerAddr.sin_len = sizeof(stServerAddr); stServerAddr.sin_port = htons(80); iSocketFd = socket( AF_INET, SOCK_STREAM, 0 ); if ( iSocketFd == -1 ) { LOG_OUT(LOGOUT_ERROR, "socket failed started. iSocketFd:%d", iSocketFd); } LOG_OUT(LOGOUT_DEBUG, "socket ok, iSocketFd:%d.", iSocketFd); //不知道为什么一直失败 //iRet = setsockopt(iSocketFd, SOL_SOCKET, SO_REUSEADDR, (const char*)&Reuseaddr, sizeof(Reuseaddr)); //iRet = setsockopt(iSocketFd, SOL_SOCKET, SO_RCVTIMEO, (char *)&stRecvTimeOut, sizeof(stRecvTimeOut)); //if ( 0 != iRet ) //{ // LOG_OUT(LOGOUT_ERROR, "setsockopt failed, iRet:%d.", iRet); //} //LOG_OUT(LOGOUT_DEBUG, "setsockopt ok, iSocketFd:%d.", iSocketFd); do { iRet = bind( iSocketFd, (struct sockaddr *)&stServerAddr, sizeof(stServerAddr)); if ( 0 != iRet ) { LOG_OUT(LOGOUT_ERROR, "bind failed, iRet:%d.", iRet); vTaskDelay(1000/portTICK_RATE_MS); } } while ( iRet != 0 ); LOG_OUT(LOGOUT_DEBUG, "bind ok, iSocketFd:%d.", iSocketFd); do { iRet = listen( iSocketFd, WEB_MAX_FD ); if (iRet != 0) { vTaskDelay(1000/portTICK_RATE_MS); } } while ( iRet != 0 ); //LOG_OUT(LOGOUT_DEBUG, "listen ok, iSocketFd:%d.", iSocketFd); FD_ZERO( &stFdRead ); FD_SET( iSocketFd, &stFdRead ); WEB_WebCtxInitAll(); HTTP_RouterInit(); for ( ;; ) { if ( bWebServerTaskTerminate == TRUE ) { goto end; } FD_ZERO( &stFdRead ); FD_SET( iSocketFd, &stFdRead ); //等待有新的客户端连接 iRet = select( WEB_MAX_FD+1, &stFdRead, NULL, NULL, &stSelectTimeOut ); if ( iRet < 0 ) { LOG_OUT(LOGOUT_ERROR, "select accept error, errno:%d, iRet:%d", errno, iRet); vTaskDelay(1000/portTICK_RATE_MS); continue; } else if ( 0 == iRet || errno == EINTR ) { //LOG_OUT(LOGOUT_DEBUG, "WEB_WebServerTask, select timeout."); vTaskDelay(100/portTICK_RATE_MS); continue; } //LOG_OUT(LOGOUT_DEBUG, "WEB_WebServerTask, select ok, iRet:%d.", iRet); //客户端已满,等待释放 while (1) { for ( iLoop = 0; iLoop < WEB_MAX_FD; iLoop++ ) { if ( stWebCtx[iLoop].iClientFd < 0 ) { break; } } //有其他客户端的连接被释放,退出等待开始处理请求 if ( iLoop < WEB_MAX_FD ) { break; } LOG_OUT(LOGOUT_DEBUG, "connect full, fd num:%d", WEB_MAX_FD); vTaskDelay(1000/portTICK_RATE_MS); } if ( FD_ISSET(iSocketFd, &stFdRead )) { //LOG_OUT(LOGOUT_DEBUG, "accept..."); //接受客户端的连接 iClientFd = accept(iSocketFd, (struct sockaddr *)&stClientAddr, (socklen_t *)&iClientAddrLen); if ( -1 != iClientFd ) { FD_CLR(iSocketFd, &stFdRead); for ( iLoop = 0; iLoop < WEB_MAX_FD; iLoop++ ) { if ( stWebCtx[iLoop].iClientFd < 0 ) { LOG_OUT(LOGOUT_DEBUG, "fd:%d connect", iClientFd); stWebCtx[iLoop].iClientFd = iClientFd; //UINT oldHeap = system_get_free_heap_size(); //启动一个新任务来响应客户端的请求 WEB_StartHandleTheard( &stWebCtx[iLoop] ); //UINT newHeap = system_get_free_heap_size(); //LOG_OUT(LOGOUT_DEBUG, "Free heap:%d, used:%d", newHeap, oldHeap-newHeap); break; } } } else { LOG_OUT(LOGOUT_ERROR, "fd:%d, accept error", iClientFd); } } } end: LOG_OUT(LOGOUT_INFO, "WEB_WebServerTask stopped."); close( iSocketFd ); iSocketFd = -1; bWebServerTaskTerminate = FALSE; vTaskDelete( NULL ); }
启动WEB_WebHandleTask任务处理http请求
1
2
3
4
5
6VOID WEB_StartHandleTheard( VOID *Para ) { xTaskCreate(WEB_WebHandleTask, "WEB_WebHandleTask", 512, Para, 3, &xWebHandle); }
WEB_WebHandleTask任务实现:
1,接受客户端发送过来的数据
2,解析htpp的请求头,主要是method和url
3,根据method和url来匹配到具体的函数来进行处理请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104STATIC VOID WEB_WebHandleTask( VOID *Para ) { CHAR* pcRecvBuf = NULL; struct timeval stRdTimeOut = {1, 0}; fd_set stFdRead; INT iRetN = 0; INT32 iRet = 0; HTTP_CTX *pstCtx = Para; //LOG_OUT(LOGOUT_INFO, "fd:%d, WEB_WebHandleTask started", pstCtx->iClientFd); pcRecvBuf = ( CHAR* )malloc( WEB_RECVBUF_SIZE + 10 ); if ( NULL == pcRecvBuf ) { LOG_OUT(LOGOUT_ERROR, "malloc pcRecvBuf failed."); goto end; } FD_ZERO( &stFdRead ); for ( ;; ) { FD_SET( pstCtx->iClientFd, &stFdRead ); //等待接收数据 iRet = select( pstCtx->iClientFd + 1, &stFdRead, NULL, NULL, &stRdTimeOut ); if ( iRet < 0 ) { LOG_OUT(LOGOUT_ERROR, "fd:%d, read error, errno:%d, iRet:%d.", pstCtx->iClientFd, errno, iRet); goto end; } //等待接收超时 else if ( 0 == iRet ) { pstCtx->uiCostTime ++; //LOG_OUT(LOGOUT_DEBUG, "fd:%d, select timeout, uiCostTime:%d", pstCtx->iClientFd, pstCtx->uiCostTime); //客户端超过一定的时间没有数据发送过来就断开这个连接,避免资源占用 if ( pstCtx->uiCostTime >= WEB_CONTINUE_TMOUT ) { //LOG_OUT(LOGOUT_DEBUG, "fd:%d, recv timeout closed", pstCtx->iClientFd); goto end; } continue; } if ( !FD_ISSET(pstCtx->iClientFd, &stFdRead )) { LOG_OUT(LOGOUT_ERROR, "fd:%d, stFdRead failed", pstCtx->iClientFd ); goto end; } FD_CLR( pstCtx->iClientFd, &stFdRead ); pstCtx->uiCostTime = 0; //开始接受客户端发送过来的数据 iRetN = recv( pstCtx->iClientFd, pcRecvBuf, WEB_RECVBUF_SIZE, 0 ); //数据接收出错 if ( iRetN <= 0 ) { LOG_OUT(LOGOUT_DEBUG, "fd:%d recv failed, client closed", pstCtx->iClientFd ); goto end; } pcRecvBuf[iRetN] = 0; //LOG_OUT(LOGOUT_DEBUG, "recv:rn%srnrn", pcRecvBuf); //LOG_OUT(LOGOUT_DEBUG, "fd:%d, recv:%d", pstCtx->iClientFd, iRetN ); //解析http请求头,method,url等等参数 iRet = HTTP_ParsingHttpHead( pstCtx, pcRecvBuf, iRetN ); if ( iRet != OK ) { LOG_OUT(LOGOUT_INFO, "fd:%d Parsing http header failed", pstCtx->iClientFd ); goto end; } //LOG_OUT(LOGOUT_DEBUG, "fd:%d, ParsingHttpHead", pstCtx->iClientFd ); //根据http请求头中的method和url来匹配对应的函数来处理 iRet = HTTP_RouterHandle( pstCtx ); if ( iRet != OK ) { LOG_OUT(LOGOUT_INFO, "fd:%d Router handle failed", pstCtx->iClientFd ); goto end; } //LOG_OUT(LOGOUT_DEBUG, "fd:%d, HTTP_RouterHandle", pstCtx->iClientFd ); //判断http响应是否完成,完成的话需要将数据全部清零等待下一个连接 if ( HTTP_IS_SEND_FINISH( pstCtx ) ) { LOG_OUT(LOGOUT_INFO, "fd:%d, [Response] Method:%s URL:%s Code:%s", pstCtx->iClientFd, szHttpMethodStr[pstCtx->stReq.eMethod], pstCtx->stReq.szURL, szHttpCodeMap[pstCtx->stResp.eHttpCode]); //初始化数据等待下一个连接 HTTP_RequestInit( pstCtx ); } } end: //LOG_OUT(LOGOUT_INFO, "fd:%d, WEB_WebHandleTask over", pstCtx->iClientFd); WEB_CloseWebCtx( pstCtx ); FREE_MEM( pcRecvBuf ); vTaskDelete( NULL ); }
解析的http的请求头结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16typedef struct tagHttpRequest { HTTP_METHOD_E eMethod; // GET, POST PUT DELETE CHAR szURL[HTTP_URL_MAX_LEN]; // /index.html HTTP_USERAGENT_E eUserAgent; // windows, Android, CHAR szHost[HTTP_HOST_MAX_LEN]; // 192.168.0.102:8080 CHAR* pcRouter; //匹配到的Router HTTP_PROCESS_E eProcess; UINT uiRecvTotalLen; // 请求body体长度,不包括head长度 UINT uiRecvLen; // 已收到body的长度 UINT uiRecvCurLen; // 本次收到body的长度 CHAR* pcResqBody; // 请求体 }HTTP_REQ_S;
路由信息已在WEB_WebServerTask中注册,HTTP_RouterHandle中会根据method和url来匹配到具体的函数来处理,如果匹配不到就返回404 Not Found
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44VOID HTTP_RouterInit( VOID ) { HTTP_RouterMapInit(); HTTP_RouterRegiste(HTTP_METHOD_GET, "/", HTTP_GetHome, "home"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/health", HTTP_GetHealth, "HTTP_GetHealth"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/info", HTTP_GetInfo, "info"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/timer/:timer", HTTP_GetTimerData, "HTTP_GetTimerData"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/delay/:delay", HTTP_GetDelayData, "HTTP_GetDelayData"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/infrared/:infrared", HTTP_GetInfraredData, "HTTP_GetInfraredData"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/infrared/:infrared/switch/:switch", HTTP_GetInfraredValue, "HTTP_GetInfraredValue"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/system", HTTP_GetSystemData, "HTTP_GetSystemData"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/cloudplatform", HTTP_GetCloudPlatformData, "HTTP_GetCloudPlatformData"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/temperature", HTTP_GetTemperature, "HTTP_GetTemperature"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/html/header", HTTP_GetHtmlHeader, "HTTP_GetHtmlHeader"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/html/header", HTTP_PostHtmlHeader,"HTTP_PostHtmlHeader"); HTTP_RouterRegiste(HTTP_METHOD_PUT, "/html/:html", HTTP_PutHtml, "HTTP_PutHtml"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/timer", HTTP_PostTimerData, "HTTP_PostTimerData"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/delay", HTTP_PostDelayData, "HTTP_PostDelayData"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/infrared", HTTP_PostInfraredData, "HTTP_PostInfraredData"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/system", HTTP_PostSystemData, "HTTP_PostSystemData"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/cloudplatform", HTTP_PostCloudPlatformData, "HTTP_PostCloudPlatformData"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/control", HTTP_PostDeviceControl, "HTTP_PostDeviceControl"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/scanwifi", HTTP_GetScanWifi, "HTTP_GetScanWifi"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/relaystatus", HTTP_GetRelayStatus, "HTTP_GetRelayStatus"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/relaystatus", HTTP_PostRelayStatus, "HTTP_PostRelayStatus"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/date", HTTP_GetDate, "HTTP_GetDate"); HTTP_RouterRegiste(HTTP_METHOD_POST, "/date", HTTP_PostDate, "HTTP_PostDate"); HTTP_RouterRegiste(HTTP_METHOD_PUT, "/upgrade", HTTP_PutUpgrade, "HTTP_PutUpgrade"); HTTP_RouterRegiste(HTTP_METHOD_GET, "/upload", HTTP_GetUploadHtml, "HTTP_GetUploadHtml"); HTTP_FileListRegiste(); }
以http://192.168.1.101/health为例
1HTTP_RouterRegiste(HTTP_METHOD_GET, "/health", HTTP_GetHealth, "HTTP_GetHealth");
会匹配到HTTP_GetHealth函数,则执行HTTP_GetHealth函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36UINT HTTP_GetHealth( HTTP_CTX *pstCtx ) { UINT uiRet = 0; pstCtx->stResp.eHttpCode = HTTP_CODE_Ok; pstCtx->stResp.eContentType = HTTP_CONTENT_TYPE_Json; pstCtx->stResp.eCacheControl = HTTP_CACHE_CTL_TYPE_No; HTTP_Malloc(pstCtx, HTTP_BUF_1K); //填充http响应头 uiRet = HTTP_SetHeader( pstCtx ); if ( uiRet != OK ) { LOG_OUT( LOGOUT_ERROR, "fd:%d, set header failed", pstCtx->iClientFd ); return FAIL; } //设置http的响应体为‘{"health":true}’ uiRet = HTTP_SetResponseBody(pstCtx, "{"health":true}"); if ( uiRet != OK ) { LOG_OUT( LOGOUT_ERROR, "fd:%d, set response body failed", pstCtx->iClientFd ); return FAIL; } //发送响应 uiRet = HTTP_SendOnce(pstCtx); if ( uiRet != OK ) { LOG_OUT( LOGOUT_ERROR, "fd:%d, send once failed", pstCtx->iClientFd ); return FAIL; } return OK; }
用浏览器访问health接口的返回样例:
至此一个完整的请求 ~ 响应过程就完成了。
最后
以上就是虚拟绿茶最近收集整理的关于【esp8266】②esp8266实现http server服务说明:代码实现的全部内容,更多相关【esp8266】②esp8266实现http内容请搜索靠谱客的其他文章。
发表评论 取消回复