1. 程式人生 > >STM32移植lwip之建立web伺服器

STM32移植lwip之建立web伺服器

本篇目標:在之前能ping通pc機的工程基礎上搭建web伺服器,借鑑官方web伺服器的程式與網頁,能夠用pc機瀏覽器訪問web伺服器,並返回設定的網頁

材料準備:

  • 除錯工具:用來除錯tcp連線下的資料接收(網路除錯助手
  • 測試瀏覽器:這裡使用的是Chrome谷歌瀏覽器

ps:通過修改官方搭建web伺服器的程式碼,來了解搭建的過程,其中暫時去掉了ssi和cgi的程式,僅僅實現網頁資料的返回和網頁的跳轉,並將官方的程式碼簡化到相對最簡,以便以後的學習之用

瀏覽器請求指令探索

要搭建伺服器,首先肯定要先了解遠端客戶端是怎麼訪問伺服器的,這裡pc機的瀏覽器則作為客戶端:

  1. 開啟瀏覽器(谷歌瀏覽器測試),輸入伺服器ip;
  2. 瀏覽器傳送請求命令給伺服器;
  3. 伺服器接收到指令後,通過程式來解析指令,找到對應應該返回的網頁;
  4. 伺服器傳送網頁程式碼給瀏覽器;
  5. 瀏覽器顯示網頁;

接下來再用搭建虛擬伺服器的方法,來模擬一下上面的過程:

  • 開啟網路除錯助手,切換到網路伺服器,在伺服器操作埠輸入80,點選建立,如圖;這樣我們就建立了一個虛擬的伺服器,這個伺服器的ip就是pc機的本地ip
    搭建虛擬伺服器

  • 檢視確認一下本地ip地址,可以在網路連線裡面檢視,也可以在cmd輸命令檢視,這裡的ip地址為192.168.6.104,如圖:
    本地ip地址

  • 開啟瀏覽器(谷歌瀏覽器測試),輸入剛才確認的本地ip地址,這裡輸入192.168.6.104:
    瀏覽器訪問

  • 返回去看看剛才搭建的伺服器有什麼變化,會發現有接收到的資料,只要重點觀察第一行的資料“GET / HTTP/1.1”,這個字串將會被伺服器解析,然後將網頁程式碼返回回去:


    客戶端請求連線

  • 找到一個官方程式有一個fs資料夾,裡面有已經做好的網頁,開啟網頁index.html,右擊-檢視原始碼,然後全選複製下來,在網路除錯助手的傳送區貼上,並點擊發送,如圖:
    伺服器返回資料

  • 這時,會發現瀏覽器已經顯示了一張網頁,但是好像又有點不全,因為圖片沒有顯示,為什麼呢?返回網路除錯助手,發現接收區又有好多請求,看字面意思,好像就是圖片的請求,然而伺服器沒有返回圖片資料,所以圖片無法顯示

  • 這時候,將所有的瀏覽器請求列出來比較一下:
    “GET / HTTP/1.1”
    “GET /STM32F4x7_files/ST.gif HTTP/1.1”
    “GET /inchtml-pages-stm32_connectivity_files/pixel.gif HTTP/1.1”


    “GET /STM32F4x7_files/stm32.jpg HTTP/1.1”
    發現請求中 / 後面一部分的內容不相同,所以伺服器只需要解析這一部分的字串內容,來返回對應的網頁資料即可

搭建web伺服器

現在建立一個新的c檔案,取名為 http_server.c ,接下來寫幾個函式來建立web伺服器,抽重要的函式進行總結一下:

  • web伺服器初始化函式 Http_Server_Init():
void Http_Server_Init(void)
{
    struct tcp_pcb *http_server_pcb;

    /* 為web伺服器分配一個tcp_pcb結構體 */
    http_server_pcb = tcp_new();

    /* 繫結本地端號和IP地址 */
    tcp_bind(http_server_pcb, IP_ADDR_ANY, 80);

    /* 監聽之前建立的結構體http_server_pcb */
    http_server_pcb = tcp_listen(http_server_pcb);

    /* 初始化結構體接收回調函式 */
    tcp_accept(http_server_pcb, http_server_accept);
}

小結:上面函式主要就是為搭建web伺服器做準備,包括申請網路結構體、設定80埠號、監聽資料、設定接收資料回撥函式;

  • 接收資料回撥函式 tcp_server_accept() :
static err_t http_server_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
    struct http_state *hs;

    /* 分配記憶體空間 */
    hs = (struct http_state *)mem_malloc(sizeof(struct http_state));

    if (hs != NULL)
    {
        memset(hs, 0, sizeof(struct http_state));
    }

    /* 確認監聽和連線 */
    tcp_arg(pcb, hs);

    /* 配置接收回調函式 */
    tcp_recv(pcb, http_server_recv);

    /* 配置輪詢回撥函式 */
    tcp_poll(pcb, http_server_poll, 4);

    /* 配置傳送回撥函式 */
    tcp_sent(pcb, http_sent);

    return ERR_OK;
}

小結:函式中主要配置一些回撥函式,比如接收,輪詢,傳送;

  • 接收資料處理函式 http_server_recv() :
static err_t http_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *http_recv_pbuf, err_t err)
{
    err_t parsed = ERR_ABRT;
    struct http_state *hs = (struct http_state *)arg;

    /* 告訴tcp已經接收到資料 */
    tcp_recved(pcb, http_recv_pbuf->tot_len);

    if (hs->handle == NULL)
    {
         /* 解析接收到的瀏覽器請求資料 */
         parsed = http_parse_request(&http_recv_pbuf, hs, pcb);
    }

    /* 清空請求字串 */
    if (parsed != ERR_INPROGRESS) 
    {       
        if (hs->req != NULL) 
        {
            pbuf_free(hs->req);
            hs->req = NULL;
        }
    }

    if (parsed == ERR_OK)
    {
        /* 傳送網頁資料 */
        http_send_data(pcb, hs);
    }
    else if (parsed == ERR_ARG)
    {
        /* 關閉連線 */
        close_conn(pcb, hs);
    }

    return ERR_OK;
}

小結:函式主要工作將接收到的資料放入 http_parse_request() 函式進行解析,然後把網頁資料傳送出去;

  • 接收資料解析函式 http_parse_request():
static err_t http_parse_request(struct pbuf **inp, struct http_state *hs, struct tcp_pcb *pcb)
{
    char *data;
    char *crlf;
    u16_t data_len;
    struct pbuf *p = *inp;

    char *sp1, *sp2;
    u16_t uri_len;
    char *uri;

    /* 排列字串 */
    if (hs->req == NULL)
    {
        hs->req = p;
    }
    else
    {
        /* 將多次的請求字串進行連線排序 */
        pbuf_cat(hs->req, p);
    }

    /* 拷貝輸入資料 */ 
    if (hs->req->next != NULL)
    {
        data_len = hs->req->tot_len;
        pbuf_copy_partial(hs->req, data, data_len, 0);
    }
    else
    {
        data = (char *)p->payload;
        data_len = p->len;
    }

    /* 提取接收到的瀏覽器字串,瀏覽器請求示例:"GET / HTTP/1.1" */
    if (data_len > 7) 
    {
        crlf = strstr(data, "\r\n");

        if (crlf != NULL) 
        {
            /* 比較前4個字元是否為 "GET " */
            if (strncmp(data, "GET ", 4) == 0) 
            {
                /* sp1指向字串 "/ HTTP/1.1" */
                sp1 = (data + 4);
            }

            /* 在sp1字串中尋找字元" ",sp2指向字串 " HTTP/1.1" */
            sp2 = strstr(sp1, " ");

            /* uri_len獲取sp1字串首地址到sp2字串首地址的長度 */
            uri_len = sp2 - (sp1);

            if ((sp2 != 0) && (sp2 >= (sp1))) 
            {
                /* 將解析的字串賦給uri,並在最後加上結束符\0,
                   uri指向字串 "/\0" */
                uri = sp1;
                *(sp1 - 1) = 0;
                uri[uri_len] = 0;

                /* 根據字串尋找對應網頁資料 */
                return http_find_file(hs, uri, 0); 
            }
        }
    }

    return ERR_OK;
}

小結:這個函式是重要的請求資料解析函式,函式將接收到的字串(例:“GET /STM32F4x7_files/ST.gif HTTP/1.1”)
分離出重要的判斷字串(例:“ /STM32F4x7_files/ST.gif”),然後根據這個字串的內容來讀取對應的網頁資料;

  • 讀取對應網頁資料函式 http_find_file():
static err_t http_find_file(struct http_state *hs, const char *uri, int is_09)
{
    struct fs_file *file = NULL;

    /* 如果字串為 "/\0",則開啟index網頁 */
    if((uri[0] == '/') && (uri[1] == 0)) 
    {
        file = fs_open("/index.html");
        uri = "/index.html";
    } 
    else 
    {
        /* 如果為其他請求,則開啟相應網頁 */
        file = fs_open(uri);
    }

    /* 將網頁檔案資料賦值給http_state結構體,之後傳送出去 */
    return http_init_file(hs, file, is_09, uri);
}

小結:此函式就是網頁資料讀取函式,裡面最重要的函式就是 fs_open() 函數了,這個函式在官方建立web伺服器工程裡的 fs.c 檔案裡面,這個函式的解析放到後面;

ps:http_server.c 還有標頭檔案的包含,函式的定義;另外再編寫一個http_server.h檔案,包含巨集定義,結構體定義,函式定義;在下面貼出這兩個檔案的原始碼;

上面基本包括了幾個重要的函式,當然還有其他的函式,包括髮送函式等等,這些函式可以看原始碼的註釋來理解

檔案原始碼

  • http_server.c
#include "lwip/debug.h"
#include "lwip/stats.h"
#include "lwip/tcp.h"
#include "http_server.h"
#include "fs.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/*
*********************************************************************************************************
*                                            LOCAL TABLES
*********************************************************************************************************
*/
static err_t http_server_accept(void *arg, struct tcp_pcb *pcb, err_t err);
static err_t http_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err);
static err_t http_server_poll(void *arg, struct tcp_pcb *pcb);
static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri);
static err_t http_find_file(struct http_state *hs, const char *uri, int is_09);
static err_t http_parse_request(struct pbuf **inp, struct http_state *hs, struct tcp_pcb *pcb);
static u8_t http_send_data(struct tcp_pcb *pcb, struct http_state *hs);
static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len);
static void close_conn(struct tcp_pcb *pcb, struct http_state *hs);

/*
*********************************************************************************************************
*                                      LOCAL FUNCTION PROTOTYPES
*********************************************************************************************************
*/

/***
 * 函式名稱 : Http_Server_Init();
 *
 * 函式描述 : web伺服器初始化;
 *
 * 傳遞值    : 無;
 *
 * 返回值   : 無;
 *
 **/
void Http_Server_Init(void)
{
    struct tcp_pcb *http_server_pcb;

    /* 為web伺服器分配一個tcp_pcb結構體 */
    http_server_pcb = tcp_new();

    /* 繫結本地端號和IP地址 */
    tcp_bind(http_server_pcb, IP_ADDR_ANY, 80);

    /* 監聽之前建立的結構體http_server_pcb */
    http_server_pcb = tcp_listen(http_server_pcb);

    /* 初始化結構體接收回調函式 */
    tcp_accept(http_server_pcb, http_server_accept);
}

/***
 * 函式名稱 : http_server_accept();
 *
 * 函式描述 : lwip資料接收回調函式,包含對tcp連線的確認,接收回調函式的配置;
 *
 * 傳遞值    : *arg, *pcb, err ;
 *
 * 返回值   : ERR_OK 無錯誤;
 *
 **/
static err_t http_server_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
    struct http_state *hs;

    /* 分配記憶體空間 */
    hs = (struct http_state *)mem_malloc(sizeof(struct http_state));

    if (hs != NULL)
    {
        memset(hs, 0, sizeof(struct http_state));
    }

    /* 確認監聽和連線 */
    tcp_arg(pcb, hs);

    /* 配置接收回調函式 */
    tcp_recv(pcb, http_server_recv);

    /* 配置輪詢回撥函式 */
    tcp_poll(pcb, http_server_poll, 4);

    /* 配置傳送回撥函式 */
    tcp_sent(pcb, http_sent);

    return ERR_OK;
}

/***
 * 函式名稱 : http_server_recv();
 *
 * 函式描述 : 接受到資料後,根據接收到資料的內容,返回網頁;
 *
 * 傳遞值    : *arg, *pcb, *http_recv_pbuf, err;
 *
 * 返回值   : ERR_OK無錯誤;
 *
 **/
static err_t http_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *http_recv_pbuf, err_t err)
{
    err_t parsed = ERR_ABRT;
    struct http_state *hs = (struct http_state *)arg;

    /* 告訴tcp已經接收到資料 */
    tcp_recved(pcb, http_recv_pbuf->tot_len);

    if (hs->handle == NULL)
    {
        /* 解析接收到的瀏覽器請求資料 */
        parsed = http_parse_request(&http_recv_pbuf, hs, pcb);
    }

    /* 清空請求字串 */
    if (parsed != ERR_INPROGRESS) 
    {       
        if (hs->req != NULL) 
        {
            pbuf_free(hs->req);
            hs->req = NULL;
        }
    }

    if (parsed == ERR_OK)
    {
        /* 傳送網頁資料 */
        http_send_data(pcb, hs);
    }
    else if (parsed == ERR_ARG)
    {
        /* 關閉連線 */
        close_conn(pcb, hs);
    }

    return ERR_OK;
}

/***
 * 函式名稱 : http_server_poll();
 *
 * 函式描述 : 輪詢函式;
 *
 * 傳遞值    : *arg, *pcb;
 *
 * 返回值   : ERR_OK無錯誤;
 *
 **/
static err_t http_server_poll(void *arg, struct tcp_pcb *pcb)
{
    struct http_state *hs = arg;

    if (hs == NULL)
    {
        close_conn(pcb, hs);
        return ERR_OK;
    }
    else
    {
        hs->retries++;
        if (hs->retries == 4)
        {
            close_conn(pcb, hs);
            return ERR_OK;
        }

        /* 如果連線存在開啟的檔案,則將會發送剩下的資料;
         * 如果一直沒有收到GET請求,那麼連線將會立刻關閉 */
        if (hs && (hs->handle))
        {
            if (http_send_data(pcb, hs))
            {
                tcp_output(pcb);
            }
        }
    }

    return ERR_OK;
}

/***
 * 函式名稱 : http_parse_request();
 *
 * 函式描述 : 對接收到的資料進行解析,根據不同的瀏覽器請求,返回對應的網頁資料;
 *
 * 傳遞值    : **inp, *hs, *pcb;
 *
 * 返回值   : ERR_OK無錯誤;
 *
 **/
static err_t http_parse_request(struct pbuf **inp, struct http_state *hs, struct tcp_pcb *pcb)
{
    char *data;
    char *crlf;
    u16_t data_len;
    struct pbuf *p = *inp;

    char *sp1, *sp2;
    u16_t uri_len;
    char *uri;

    /* 排列字串 */
    if (hs->req == NULL)
    {
        hs->req = p;
    }
    else
    {
        /* 將多次的請求字串進行連線排序 */
        pbuf_cat(hs->req, p);
    }

    /* 拷貝輸入資料 */ 
    if (hs->req->next != NULL)
    {
        data_len = hs->req->tot_len;
        pbuf_copy_partial(hs->req, data, data_len, 0);
    }
    else
    {
        data = (char *)p->payload;
        data_len = p->len;
    }

    /* 提取接收到的瀏覽器字串,瀏覽器請求示例:"GET / HTTP/1.1" */
    if (data_len > 7) 
    {
        crlf = strstr(data, "\r\n");
        if (crlf != NULL) 
        {
            /* 比較前4個字元是否為 "GET " */
            if (strncmp(data, "GET ", 4) == 0) 
            {
                /* sp1指向字串 "/ HTTP/1.1" */
                sp1 = (data + 4);
            }
            /* 在sp1字串中尋找字元" ",sp2指向字串 " HTTP/1.1" */
            sp2 = strstr(sp1, " ");
            /* uri_len獲取sp1字串首地址到sp2字串首地址的長度 */
            uri_len = sp2 - (sp1);

            if ((sp2 != 0) && (sp2 >= (sp1))) 
            {
                /* 將解析的字串賦給uri,並在最後加上結束符\0,
                   uri指向字串 "/\0" */
                uri = sp1;
                *(sp1 - 1) = 0;
                uri[uri_len] = 0;

                /* 根據字串尋找對應網頁資料 */
                return http_find_file(hs, uri, 0); 
                }
            }
    }

    return ERR_OK;
}

/***
 * 函式名稱 : http_find_file();
 *
 * 函式描述 : 對提取的資料進行判斷,讀取對應的網頁資料;
 *
 * 傳遞值    : *hs, *uri, is_09;
 *
 * 返回值   : ERR_OK無錯誤;
 *
 **/
static err_t http_find_file(struct http_state *hs, const char *uri, int is_09)
{
    struct fs_file *file = NULL;

    /* 如果字串為 "/\0",則開啟index網頁 */
    if((uri[0] == '/') && (uri[1] == 0)) 
    {
        file = fs_open("/index.html");
        uri = "/index.html";
    } 
    else 
    {
        /* 如果為其他請求,則開啟相應網頁 */
        file = fs_open(uri);
    }

    /* 將網頁檔案資料賦值給http_state結構體,之後傳送出去 */
    return http_init_file(hs, file, is_09, uri);
}

/***
 * 函式名稱 : http_init_file();
 *
 * 函式描述 : 將要傳送的資料儲存到http_state結構體當中;
 *
 * 傳遞值    : *hs, *file, is_09, *uri;
 *
 * 返回值   : ERR_OK無錯誤;
 *
 **/
static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri)
{
    if (file != NULL) 
    {
        hs->handle = file;
        /* 將網頁資料賦值給http_state */
        hs->file = (char*)file->data;
        /* 將網頁長度賦值給http_state */
        hs->left = file->len;
        hs->retries = 0;
    } 
    else 
    {
        hs->handle = NULL;
        hs->file = NULL;
        hs->left = 0;
        hs->retries = 0;
    }

    return ERR_OK;
}

/***
 * 函式名稱 : http_send_data();
 *
 * 函式描述 : 資料傳送函式;
 *
 * 傳遞值    : *pcb, *hs;
 *
 * 返回值   : ERR_OK無錯誤;
 *
 **/
static u8_t http_send_data(struct tcp_pcb *pcb, struct http_state *hs)
{
    err_t err = ERR_OK;
    u16_t len;
    u8_t data_to_send = 0;

    /* 配置傳送資料長度,如果傳送資料過長則分批發送 */
    if (tcp_sndbuf(pcb) < hs->left)
    {
        len = tcp_sndbuf(pcb);
    }
    else
    {
        len = (u16_t)hs->left;
    }       

    /* 傳送網頁資料 */
    err = tcp_write(pcb, hs->file, len, 1);

    if (err == ERR_OK)
    {
        data_to_send = 1;
        hs->file += len;
        hs->left -= len;
    }

    if ((hs->left == 0) && (fs_bytes_left(hs->handle) <= 0))
    {
        /* 關閉連線 */
        close_conn(pcb, hs);
        return 0;
    }

    return data_to_send;
}


/***
 * 函式名稱 : http_sent();
 *
 * 函式描述 : 資料已經被髮送,並且被遠端主機確定;
 *
 * 傳遞值    : *arg, *pcb, len;
 *
 * 返回值   : ERR_OK無錯誤;
 *
 **/
static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{
    struct http_state *hs = (struct http_state *)arg;

    if (hs == NULL)
    {
        return ERR_OK;
    }

    hs->retries = 0;

    http_send_data(pcb, hs);

    return ERR_OK;
}

/***
 * 函式名稱 : close_conn();
 *  * 函式描述 : 關閉tcp連線;
 *  * 傳遞值     : *pcb, *hs;
 *  * 返回值   : 無;
 *  **/
static void close_conn(struct tcp_pcb *pcb, struct http_state *hs)
{
    tcp_arg(pcb, NULL);
    tcp_recv(pcb, NULL);
    tcp_err(pcb, NULL);
    tcp_poll(pcb, NULL, 0);
    tcp_sent(pcb, NULL);

    if (hs != NULL) 
    {
        if(hs->handle) 
        {
            fs_close(hs->handle);
            hs->handle = NULL;
        }
            mem_free(hs);
    }

    tcp_close(pcb);
}
  • http_server.h
#ifndef HTTP_SERVER_H
#define HTTP_SERVER_H

/*
*********************************************************************************************************
*                                              INCLUDE FILES
*********************************************************************************************************
*/


/*
*********************************************************************************************************
*                                               CONSTANTS
*********************************************************************************************************
*/


/*
*********************************************************************************************************
*                                             PERIPH DEFINES
*********************************************************************************************************
*/


/*
*********************************************************************************************************
*                                               DATA TYPES
*********************************************************************************************************
*/


/*
*********************************************************************************************************
*                                            GLOBAL VARIABLES
*********************************************************************************************************
*/

struct http_state {
  struct fs_file *handle;
  char *file;       /* Pointer to first unsent byte in buf. */

#if 1
  struct pbuf *req;
#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */

#if 1
  char *buf;        /* File read buffer. */
  int buf_len;      /* Size of file read buffer, buf. */
#endif /* LWIP_HTTPD_SSI || LWIP_HTTPD_DYNAMIC_HEADERS */
  u32_t left;       /* Number of unsent bytes in buf. */
  u8_t retries;
#if 0
  const char *parsed;     /* Pointer to the first unparsed byte in buf. */
#if 1
  const char *tag_started;/* Poitner to the first opening '<' of the tag. */
#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG */
  const char *tag_end;    /* Pointer to char after the closing '>' of the tag. */
  u32_t parse_left; /* Number of unparsed bytes in buf. */
  u16_t tag_index;   /* Counter used by tag parsing state machine */
  u16_t tag_insert_len; /* Length of insert in string tag_insert */
#if 0
  u16_t tag_part; /* Counter passed to and changed by tag insertion function to insert multiple times */
#endif /* LWIP_HTTPD_SSI_MULTIPART */
  u8_t tag_check;   /* true if we are processing a .shtml file else false */
  u8_t tag_name_len; /* Length of the tag name in string tag_name */
  char tag_name[LWIP_HTTPD_MAX_TAG_NAME_LEN + 1]; /* Last tag name extracted */
  char tag_insert[LWIP_HTTPD_MAX_TAG_INSERT_LEN + 1]; /* Insert string for tag_name */
  enum tag_check_state tag_state; /* State of the tag processor */
#endif /* LWIP_HTTPD_SSI */
#if 0
  char *params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */
  char *param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */
#endif /* LWIP_HTTPD_CGI */
#if 0
  const char *hdrs[NUM_FILE_HDR_STRINGS]; /* HTTP headers to be sent. */
  u16_t hdr_pos;     /* The position of the first unsent header byte in the
                        current string */
  u16_t hdr_index;   /* The index of the hdr string currently being sent. */
#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
#if 0
  u32_t time_started;
#endif /* LWIP_HTTPD_TIMING */
#if 0
  u32_t post_content_len_left;
#if 0
  u32_t unrecved_bytes;
  struct tcp_pcb *pcb;
  u8_t no_auto_wnd;
#endif /* LWIP_HTTPD_POST_MANUAL_WND */
#endif /* LWIP_HTTPD_SUPPORT_POST*/
};

/*
*********************************************************************************************************
*                                                 MACRO'S
*********************************************************************************************************
*/



/*
*********************************************************************************************************
*                                           FUNCTION PROTOTYPES
*********************************************************************************************************
*/

void Http_Server_Init(void);

/*
********************************************************************************************************
*                                             MODULE END
*********************************************************************************************************
*/

#endif /* HTTP_SERVER_H */

官方部分函式解析

讀取網頁資料檔案 fs.c (路徑:Project->Standalone->web_server->http):

struct fs_file *fs_open(const char *name)
{
    struct fs_file *file;
    const struct fsdata_file *f;

    /* 分配空間 */
    file = fs_malloc();
    if(file == NULL) 
    {
        return NULL;
    }

    for(f = FS_ROOT; f != NULL; f = f->next) 
    {
        /* 迴圈比較,如果輸入的請求與網頁的頭資料一致,則返回該網頁資料 */
        if (!strcmp(name, (char *)f->name)) 
        {
            file->data = (const char *)f->data;
            file->len = f->len;
            file->index = f->len;
            file->pextension = NULL;
            file->http_header_included = f->http_header_included;

            return file;
        }
    }
    fs_free(file);
    return NULL;
}

這裡關注 f 變數的結構體 fsdata_file,定義在 fsdata.h:

struct fsdata_file 
{
    const struct fsdata_file *next;
    const unsigned char *name;
    const unsigned char *data;
    int len;
    u8_t http_header_included;
};

結構體中有三個重要的變數*next、*name、*data
而在檔案fsdata.c中,拉到最後,發現有幾個 fsdata_file 的結構體變數,取其中一個來解析一下:

const struct fsdata_file file__index_html[] = 
{ 
    {
        /* 變數*next,指向下一個要迴圈比較的資料 */
        file__404_html,
        /* 變數*name,指向資料陣列 data__index_html[] */
        data__index_html,
        /* 變數*data,指向資料陣列 data__index_html[]12個之後的資料 */
        data__index_html + 12,
        /* 網頁資料長度 */
        sizeof(data__index_html) - 12,
        1,
    }
};
  • 變數*name指向的陣列前12個數據是字串 “/index.html” 的ascii碼,用於與輸入的瀏覽器請求 “GET /index.html HTTP/1.1” 進行對比;
  • 而*data指向陣列的12個後的資料,便是網頁原始碼的16進位制資料,這些資料將會由傳送函式傳送給瀏覽器,使瀏覽器顯示網頁;

web伺服器測試

將工程編譯後,燒進stm32,將網線與pc機連線:

  • 開啟瀏覽器(谷歌瀏覽器測試)
  • 輸入伺服器ip(這裡搭建的伺服器ip:192.168.0.10),Enter;
  • 瀏覽器會顯示網頁,點選網頁上的按鈕即可以切換不同的網頁

如圖:
伺服器返回網頁資料

總結:從上面的一系列過程可以get到搭建web伺服器的核心思想,然而,現在並沒有加入ssi和cgi,所以還無法用網頁控制stm32,後面會加上ssi、cgi、post與get請求來完善整個web伺服器;

ps:有部分細節的地方解析的不是很清楚,而且自己也沒有想明白,需要再加把勁看一些其他的資料來填補空白,共勉~