溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何構(gòu)建LwIP raw api 中的http server

發(fā)布時(shí)間:2021-12-30 10:44:01 來源:億速云 閱讀:312 作者:柒染 欄目:互聯(lián)網(wǎng)科技

這篇文章將為大家詳細(xì)講解有關(guān)如何構(gòu)建LwIP raw api 中的http server,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

下面為構(gòu)建LwIP RAW API下HTTP SERVER DEMO的學(xué)習(xí)記錄,內(nèi)容僅為個(gè)人設(shè)計(jì)與理解,所貼代碼均為簡化版,不保證適用性與準(zhǔn)確性。

本人使用的LwIP版本為2.1.2。LwIP應(yīng)用層直接接口為ALTCP,是TCP之上包了一個(gè)抽象層,也可以通過宏開關(guān)LWIP_ALTCP使接口直接對(duì)應(yīng)TCP函數(shù)。

一、服務(wù)初始化

void http_init(void)
{
  struct altcp_pcb *pcb;
  //分配協(xié)議控制塊
  pcb = altcp_tcp_new_ip_type(IPADDR_TYPE_ANY);
  //掛載接口
  altcp_bind(pcb, IP_ANY_TYPE, 80);
  //開啟監(jiān)聽
  pcb = altcp_listen(pcb);
  //注冊(cè)接收回調(diào)
  altcp_accept(pcb, http_accept);
}

初始化的內(nèi)容就是分配協(xié)議控制塊,綁定到http端口號(hào)80上,然后等待客戶端連接。

  • altcp_tcp_new_ip_type函數(shù)內(nèi)調(diào)用TCP接口tcp_new_ip_type創(chuàng)建了控制塊pcb,然后定義altcp_pcb控制塊ret,將pcb裁剪后將幾個(gè)LwIP關(guān)心的部分對(duì)接到ret,上層應(yīng)用只需關(guān)心閹割后的ret而無需再看完整的TCP控制塊結(jié)構(gòu)體,應(yīng)用層的callback也只需跟altcp_pcb對(duì)接即可。若空間不足,TCP會(huì)釋放某些非活躍控制塊,而altcp只是根據(jù)MEMP剩余空間大小不足回復(fù)失敗

    pcb->callback_arg = altcp_pcb;

    pcb->recv = altcp_tcp_recv;

    pcb->sent = altcp_tcp_sent;

    pcb->errf = altcp_tcp_err;

struct altcp_pcb *
altcp_tcp_new_ip_type(u8_t ip_type)
{
  /* Allocate the tcp pcb first to invoke the priority handling code
     if we're out of pcbs */
  struct tcp_pcb *tpcb = tcp_new_ip_type(ip_type);
  if (tpcb != NULL) {
    struct altcp_pcb *ret = altcp_alloc();
    if (ret != NULL) {
      altcp_tcp_setup(ret, tpcb);
      return ret;
    } else {
      /* altcp_pcb allocation failed -> free the tcp_pcb too */
      tcp_close(tpcb);
    }
  }
  return NULL;
}

其上還有一個(gè)altcp_new_ip_type的分配方式,可以用戶自定義alloc函數(shù)和參數(shù)保存在其調(diào)用的altcp_allocator_t結(jié)構(gòu)體變量中。

  • altcp_bind直接操作altcp_tcp_bind,實(shí)際使用tcp_bind,將altcp_pcb對(duì)應(yīng)的pcb和http端口號(hào)80綁定。TCP遍歷已存在的控制塊鏈表,如沒有相同的IP和端口號(hào)則插入鏈表。

  • altcp_listen(conn)啟動(dòng)服務(wù)器監(jiān)聽狀態(tài),其實(shí)際功能函數(shù)altcp_tcp_listen調(diào)用的也是tcp_listen_with_backlog_and_err。由于TCP監(jiān)聽時(shí)會(huì)用節(jié)省資源的tcp_pcb_listen結(jié)構(gòu)體變量替換tcp_pcb,所以需要更新state元素。再將altcp_tcp_accept與tcp_accept對(duì)接,作為客戶端連接的回調(diào),其作用是當(dāng)TCP_EVENT_ACCEPT發(fā)生時(shí),開辟一個(gè)新的altcp_pcb去對(duì)接客戶端連接的new_tpcb,并執(zhí)行arg中預(yù)設(shè)的accept函數(shù)(pcb->callback_arg->accept,即altcp_pcb->accept)

static struct altcp_pcb *
altcp_tcp_listen(struct altcp_pcb *conn, u8_t backlog, err_t *err)
{
  struct tcp_pcb *pcb;
  struct tcp_pcb *lpcb;
  if (conn == NULL) {
    return NULL;
  }
  ALTCP_TCP_ASSERT_CONN(conn);
  pcb = (struct tcp_pcb *)conn->state;
  lpcb = tcp_listen_with_backlog_and_err(pcb, backlog, err);
  if (lpcb != NULL) {
    conn->state = lpcb;
    tcp_accept(lpcb, altcp_tcp_accept);
    return conn;
  }
  return NULL;
}
  • altcp_accept是將上一步中accept階段TCP傳遞到LwIP的回調(diào)注冊(cè)到應(yīng)用層的http_accept,在此函數(shù)內(nèi)處理客戶端的連接請(qǐng)求,完成完整的連接操作。在用戶層的http_accept中開辟一個(gè)變量放置應(yīng)用的狀態(tài)以及下層回調(diào)的參數(shù),并將其與對(duì)應(yīng)的pcb聯(lián)系起來,之后就是將已經(jīng)與TCP連接好的LwIP各個(gè)altcp回調(diào)與應(yīng)用層具體處理函數(shù)對(duì)應(yīng)起來。

#define HTTP_PRIO TCP_PRIO_MIN

static err_t http_accept(void *arg, struct altcp_pcb *pcb, err_t err)
{
  struct http_st *st = http_state_alloc();
  st->pcb = pcb;
  altcp_arg(pcb, hs);

  altcp_setprio(pcb, HTTP_PRIO);
  altcp_recv(pcb, http_recv);
  altcp_poll(pcb, http_poll, HTTP_POLL_INT);
  altcp_sent(pcb, http_sent);
  altcp_err(pcb, http_err);

  return ERR_OK;
}

至此一個(gè)服務(wù)的初始化過程基本完成,接下來就是應(yīng)用層的函數(shù)編寫。

二、應(yīng)用層函數(shù)功能

應(yīng)用層在http_recv處理接收數(shù)據(jù)的回調(diào),其中結(jié)構(gòu)體pbuf是協(xié)議棧分解出的數(shù)據(jù)包。在raw api的數(shù)據(jù)處理中,需要用戶手動(dòng)執(zhí)行altcp_recved通知協(xié)議棧數(shù)據(jù)正確接收,并且pbuf_free釋放數(shù)據(jù)包緩存

static err_t http_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
{
  struct http_st *st = (struct http_st *)arg;

  if((p == NULL) || (err != ERR_OK) || (st == NULL))
  {
    if(p != NULL)
    {
      altcp_recved(pcb, p->tot_len);
      pbuf_free(p);
    }
    http_close_conn(pcb, st);
    return ERR_OK;
  }

  altcp_recved(pcb, p->tot_len);

  if(st->handle == NULL)
  {
    err_t parsed = http_parse_request(p, st, pcb);
    pbuf_free(p);

    if(parsed == ERR_OK)
    {
      http_send(pcb, st);
    }
    else if(parsed == ERR_ARG)
    {
      http_close_conn(pcb, hs);
    }
  }
  else
  {
    pbuf_free(p);
  }
  return ERR_OK;
}

     http_parse_request函數(shù)主要功能為解析接收的HTTP協(xié)議指令,對(duì)正確的“GET”進(jìn)行響應(yīng)。http server預(yù)先儲(chǔ)存了file_handle列表,當(dāng)client發(fā)對(duì)應(yīng)的uri時(shí)傳輸相應(yīng)的文件

    #define CRLF "\r\n"
    
    static err_t http_parse_request(struct pbuf *p, struct http_st *st, struct altcp_pcb *pcb)
    {
      char *data;
      u16_t data_len;
    
      if((st->handle != NULL) || (st->file != NULL))
      {
        //already started sending
        return ERR_USE;
      }
    
      //actual data in pbuf
      data = (char *)p->payload;
      //length of current buffer 
      data_len = p->len;
    
      /* find first \r\n */
      if(lwip_strnstr(data, CRLF, data_len) != NULL)
      {
        char *sp1, *sp2;
        int is_09 = 0;
        u16_t left_len, uri_len;
    
        if(strncmp(data, "GET ", 4))
        {
          //unsupported
          http_find_error_file(st, 501);
        }
    
        sp1 = data + 3;
        //check URI
        left_len = (u16_t)(data_len - ((sp1 + 1) - data));
        sp2 = lwip_strnstr(sp1 + 1, " ", left_len);
        if(sp2 == NULL)
        {
          sp2 = lwip_strnstr(sp1 + 1, CRLF, left_len);
          is_09 = 1;
        }
        uri_len = (u16_t)(sp2 - (sp1 + 1));
    
        if((sp2 != 0) && (sp2 > sp1))
        {
          //find the end of HTTP headers
          if(lwip_strnstr(data, CRLF CRLF, data_len) != NULL)
          {
            char *uri = sp1 + 1;
            *sp1 = 0;
            uri[uri_len] = 0;
    
            return http_find_file(st, uri, is_09);
          }
        }
      }
      return http_find_error_file(st, 400);
    }
    static err_t http_find_error_file(struct http_st *st, u16_t error_nr)
    {
      const char *uri, *uri1, *uri2, *uri3;
    
      if (error_nr == 501) {
        uri1 = "/501.html";
        uri2 = "/501.htm";
        uri3 = "/501.shtml";
      } else {
        /* 400 (bad request is the default) */
        uri1 = "/400.html";
        uri2 = "/400.htm";
        uri3 = "/400.shtml";
      }
      if (fs_open(&st->file_handle, uri1) == ERR_OK) {
        uri = uri1;
      } else if (fs_open(&st->file_handle, uri2) == ERR_OK) {
        uri = uri2;
      } else if (fs_open(&st->file_handle, uri3) == ERR_OK) {
        uri = uri3;
      } else {
        return ERR_ARG;
      }
      return http_init_file(st, &st->file_handle, 0, uri, 0, NULL);
    }

     根據(jù)解析的uri,在本地文件中尋找對(duì)應(yīng)name的文件

    #define LWIP_HTTP_MAX_REQUEST_URI_LEN      63
    static char http_uri_buf[LWIP_HTTP_MAX_REQUEST_URI_LEN + 1];
    
    static err_t http_find_file(struct http_st *st, const char *uri, int is_09)
    {
      size_t loop;
      struct fs_file *file = NULL;
      char *params = NULL;
      err_t err;
      int i;
      u8_t tag_check = 0;
    
      //check uri
      size_t uri_len = strlen(uri);
      if((uri_len > 0) && (uri[uri_len - 1] == '/') &&
          ((uri != http_uri_buf) || (uri_len == 1)))
      {
        size_t copy_len = LWIP_MIN(sizeof(http_uri_buf) - 1, uri_len - 1);
        if(copy_len > 0)
        {
          MEMCPY(http_uri_buf, uri, copy_len);
          http_uri_buf[copy_len] = 0;
        }
        for(loop = 0; loop < NUM_DEFAULT_FILENAMES; loop++)
        {
          const char *file_name;
          if(copy_len > 0)
          {
            size_t len_left = sizeof(http_uri_buf) - copy_len - 1;
            if(len_left > 0)
            {
              size_t name_len = strlen(httpd_default_filenames[loop].name);
              size_t name_copy_len = LWIP_MIN(len_left, name_len);
              MEMCPY(&http_uri_buf[copy_len], httpd_default_filenames[loop].name, name_copy_len);
              http_uri_buf[copy_len + name_copy_len] = 0;
            }
            file_name = http_uri_buf;
          } 
          else
          {
            file_name = httpd_default_filenames[loop].name;
          }
    
          err = fs_open(&st->file_handle, file_name);
    
          if(err == ERR_OK)
          {
            uri = file_name;
            file = &st->file_handle;
            tag_check = httpd_default_filenames[loop].shtml;
          }
        }
      }
    
      if(file == NULL)
      {
        params = (char *)strchr(uri, '?');
        if(params != NULL)
        {
          *params = '\0';
          params++;
        }
        http_cgi_paramcount = -1;
        if(httpd_num_cgis && httpd_cgis)
        {
          for(i = 0; i < httpd_num_cgis; i++)
          {
            if(strcmp(uri, httpd_cgis[i].pcCGIName) == 0)
            {
              http_cgi_paramcount = extract_uri_parameters(hs, params);
              uri = httpd_cgis[i].pfnCGIHandler(i, http_cgi_paramcount, st->params,
                                             st->param_vals);
              break;
            }
          }
        }
        err = fs_open(&st->file_handle, uri);
        if(err == ERR_OK)
        {
          file = &st->file_handle;
        }
        else
        {
          file = http_get_404_file(st, &uri);
        }
        if(file != NULL)
        {
          if(file->flags & FS_FILE_FLAGS_SSI)
          {
            tag_check = 1;
          }
          else
          {
            tag_check = http_uri_is_ssi(file, uri);
          }
        }
      }
    
      if(file == NULL)
      {
        /* None of the default filenames exist so send back a 404 page */
        file = http_get_404_file(st, &uri);
      }
    
      return http_init_file(st, file, is_09, uri, tag_check, params);
    }

     將找到的文件掛載到狀態(tài)量st

    static err_t http_init_file(struct http_st *st, struct fs_file *file, int is_09, const char *uri,
                   u8_t tag_check, char *params)
    {
      if(file != NULL)
      {
        if(tag_check)
        {
          struct http_ssi_state *ssi = http_ssi_state_alloc();
          if(ssi != NULL)
          {
            ssi->tag_index = 0;
            ssi->tag_state = TAG_NONE;
            ssi->parsed = file->data;
            ssi->parse_left = file->len;
            ssi->tag_end = file->data;
            st->ssi = ssi;
          }
        }
        st->handle = file;
        st->file = file->data;
        st->left = (u32_t)file->len;
        st->retries = 0;
    
        if(is_09 && ((st->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0))
        {
          /* HTTP/0.9 responses are sent without HTTP header,
             search for the end of the header. */
          char *file_start = lwip_strnstr(st->file, CRLF CRLF, st->left);
          if(file_start != NULL)
          {
            int diff = file_start + 4 - st->file;
            st->file += diff;
            st->left -= (u32_t)diff;
          }
        }
      }
      else
      {
        st->handle = NULL;
        st->file = NULL;
        st->left = 0;
        st->retries = 0;
      }
    
      if((st->handle == NULL) || ((st->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) == 0))
      {
        get_http_headers(st, uri);
      }
      return ERR_OK;
    }

    關(guān)于如何構(gòu)建LwIP raw api 中的http server就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI