概述
源码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服务器
for ( ; ; )
{
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任务
VOID WEB_StartWebServerTheard( VOID )
{
xTaskCreate(WEB_WebServerTask, "WEB_WebServerTask", 512, NULL, 4, &xWebServerHandle);
}
停止http server任务,通过设置bWebServerTaskTerminate为true,在WEB_WebServerTask任务中判断该标志为true时且响应完所有http请求后主动结束http server任务。
VOID 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重启
STATIC 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请求
VOID WEB_StartHandleTheard( VOID *Para )
{
xTaskCreate(WEB_WebHandleTask, "WEB_WebHandleTask", 512, Para, 3, &xWebHandle);
}
WEB_WebHandleTask任务实现:
1,接受客户端发送过来的数据
2,解析htpp的请求头,主要是method和url
3,根据method和url来匹配到具体的函数来进行处理请求
STATIC 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的请求头结构:
typedef 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
VOID 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为例
HTTP_RouterRegiste(HTTP_METHOD_GET, "/health", HTTP_GetHealth, "HTTP_GetHealth");
会匹配到HTTP_GetHealth函数,则执行HTTP_GetHealth函数:
UINT 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 server服务说明:代码实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复