【esp8266】②esp8266实现http server服务

源码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:\r\n%s\r\n\r\n", 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接口的返回样例:

health请求样例

至此一个完整的请求 ~ 响应过程就完成了。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章