【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請求樣例

至此一個完整的請求 ~ 響應過程就完成了。

 

 

 

 

 

 

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