1. 程式人生 > >記LWIP除錯http server的Out of memory問題

記LWIP除錯http server的Out of memory問題

最近在做IOT控制,主要通過LWIP的http server來做控制,實現手機和電腦瀏覽器控制檢視資料,其中用web server做實時的資料傳輸,遇到了切換網頁是有時會卡在跳轉處很久,有時會直接跳轉失敗,只能重新進入web,於是開啟LWIP的除錯發現卡死時輸出Out of memory的輸出,於是我就重新過了變LWIP,看看是什麼原因導致的。
下面我們來看LWIP的HTTPD的原始碼。

void
httpd_init(void)
{
#if HTTPD_USE_MEM_POOL
  LWIP_ASSERT("memp_sizes[MEMP_HTTPD_STATE] >= sizeof(http_state)"
, memp_sizes[MEMP_HTTPD_STATE] >= sizeof(http_state)); LWIP_ASSERT("memp_sizes[MEMP_HTTPD_SSI_STATE] >= sizeof(http_ssi_state)", memp_sizes[MEMP_HTTPD_SSI_STATE] >= sizeof(http_ssi_state)); #endif LWIP_DEBUGF(HTTPD_DEBUG, ("httpd_init\n")); httpd_init_addr(IP_ADDR_ANY); }

先判斷是否使用記憶體池分配記憶體,在進行初始化,本週就是開啟一個TCP連線,和SOCKET建立的TCP類似,

static void
httpd_init_addr(ip_addr_t *local_addr)
{
  struct tcp_pcb *pcb;
  err_t err;

  pcb = tcp_new();
  LWIP_ASSERT("httpd_init: tcp_new failed", pcb != NULL);
  tcp_setprio(pcb, HTTPD_TCP_PRIO);
  /* set SOF_REUSEADDR here to explicitly bind httpd to multiple interfaces */
  err = tcp_bind(pcb, local_addr, HTTPD_SERVER_PORT);
  LWIP_ASSERT("httpd_init: tcp_bind failed"
, err == ERR_OK); pcb = tcp_listen(pcb); LWIP_ASSERT("httpd_init: tcp_listen failed", pcb != NULL); /* initialize callback arg and accept callback */ tcp_arg(pcb, pcb); tcp_accept(pcb, http_accept); }

這裡面做TCP的建立,寫個socket的應該都比較熟悉,先bind、在listen、然後accept,只是這裡是accept回撥函式,在LWIP裡面SOCKET也是用回撥函式實現的,在linux裡面是使用system call來實現socket呼叫的,我在看下accept在什麼時候被回撥執行。

#define TCP_EVENT_ACCEPT(pcb,err,ret)                          \
  do {                                                         \
    if((pcb)->accept != NULL)                                  \
      (ret) = (pcb)->accept((pcb)->callback_arg,(pcb),(err));  \
    else (ret) = ERR_ARG;                                      \
  } while (0)

在TCP_imple.h裡面實現了多種event事件,用於實現回撥,看下那個程式碼呼叫了accept事件。

static err_t
tcp_process(struct tcp_pcb *pcb)
{
  struct tcp_seg *rseg;
  u8_t acceptable = 0;
  err_t err;

  err = ERR_OK;

  XXX
#if LWIP_CALLBACK_API
        LWIP_ASSERT("pcb->accept != NULL", pcb->accept != NULL);
#endif
        /* Call the accept function. */
        TCP_EVENT_ACCEPT(pcb, ERR_OK, err);
  XXX
  return ERR_OK;
}

在這個process函式裡面其實做了很多事情,不止呼叫了accept函式,還有很多連線等資訊,基本所有的TCP包都要經過他的處理,裡面用狀態機寫的,大家可以看下原始碼,所以這個函式肯定是被TCP介面函式呼叫的。

void
tcp_input(struct pbuf *p, struct netif *inp){
    /* If there is data which was previously "refused" by upper layer */
    if (pcb->refused_data != NULL) {
      if ((tcp_process_refused_data(pcb) == ERR_ABRT) ||
        ((pcb->refused_data != NULL) && (tcplen > 0))) {
        /* pcb has been aborted or refused data is still refused and the new
           segment contains data */
        TCP_STATS_INC(tcp.drop);
        snmp_inc_tcpinerrs();
        goto aborted;
      }
    }
    tcp_input_pcb = pcb;
    err = tcp_process(pcb);
   }

這個函式也異常的長,主要是對tcp的各種包做解析,在做處理,比如是不是broadcast的包等等,

err_t
ip_input(struct pbuf *p, struct netif *inp){
#endif /* LWIP_UDP */
#if LWIP_TCP
    case IP_PROTO_TCP:
      snmp_inc_ipindelivers();
      tcp_input(p, inp);
      break;
#endif /* LWIP_TCP */
}

TCP處理肯定是經過ip包傳上來的,所以ip_put就是做這個操作的,裡面也是用狀態機寫的,包括了各種型別的包解析,比如udp,icmp等等,ip包要經過arp解析後再傳輸的,這個是另個內容了,我這邊是使用freertos,所以ip包經過一個執行緒來實時接收。

static void
tcpip_thread(void *arg)
{
  struct tcpip_msg *msg;
  LWIP_UNUSED_ARG(arg);

  if (tcpip_init_done != NULL) {
    tcpip_init_done(tcpip_init_done_arg);
  }

  LOCK_TCPIP_CORE();
  while (1) {                          /* MAIN Loop */
    UNLOCK_TCPIP_CORE();
    LWIP_TCPIP_THREAD_ALIVE();
    /* wait for a message, timeouts are processed while waiting */
    sys_timeouts_mbox_fetch(&mbox, (void **)&msg);
    LOCK_TCPIP_CORE();
    switch (msg->type) {
#if LWIP_NETCONN
    case TCPIP_MSG_API:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
      msg->msg.apimsg->function(&(msg->msg.apimsg->msg));
      break;
#endif /* LWIP_NETCONN */

#if !LWIP_TCPIP_CORE_LOCKING_INPUT
    case TCPIP_MSG_INPKT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));

      if(msg->msg.inp.p != NULL && msg->msg.inp.netif != NULL) {
#if LWIP_ETHERNET
        if (msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
          ethernet_input(msg->msg.inp.p, msg->msg.inp.netif);
        } else
#endif /* LWIP_ETHERNET */
#if LWIP_IPV6
        if ((*((unsigned char *)(msg->msg.inp.p->payload)) & 0xf0) == 0x60) {
          ip6_input(msg->msg.inp.p, msg->msg.inp.netif);
        } else
#endif /* LWIP_IPV6 */
        {
          ip_input(msg->msg.inp.p, msg->msg.inp.netif);
        }
      }
      memp_free(MEMP_TCPIP_MSG_INPKT, msg);
      break;
#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */

#if LWIP_NETIF_API
    case TCPIP_MSG_NETIFAPI:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: Netif API message %p\n", (void *)msg));
      msg->msg.netifapimsg->function(&(msg->msg.netifapimsg->msg));
      break;
#endif /* LWIP_NETIF_API */

#if LWIP_TCPIP_TIMEOUT
    case TCPIP_MSG_TIMEOUT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: TIMEOUT %p\n", (void *)msg));
      sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;
    case TCPIP_MSG_UNTIMEOUT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: UNTIMEOUT %p\n", (void *)msg));
      sys_untimeout(msg->msg.tmo.h, msg->msg.tmo.arg);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;
#endif /* LWIP_TCPIP_TIMEOUT */

    case TCPIP_MSG_CALLBACK:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK %p\n", (void *)msg));
      msg->msg.cb.function(msg->msg.cb.ctx);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;

    case TCPIP_MSG_CALLBACK_STATIC:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK_STATIC %p\n", (void *)msg));
      msg->msg.cb.function(msg->msg.cb.ctx);
      break;

    default:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: %d\n", msg->type));
      LWIP_ASSERT("tcpip_thread: invalid message", 0);
      break;
    }
  }
}

其中用了一個mbox來確定是否資料接收到了,接收到了就進行解析,如果是IP包者送到ip_input函式去,資料傳輸最終呼叫

void
ethernetif_input(struct netif *netif, struct pbuf *p)
{
  struct ethernetif *ethernetif;
  struct eth_hdr *ethhdr;

  if(p == NULL)
    goto _exit;

  if(p->payload == NULL) {
    pbuf_free(p);
    goto _exit;
  }

  if(netif == NULL) {
    goto _exit;
  }

  if (!(netif->flags & NETIF_FLAG_LINK_UP)) {
    pbuf_free(p);
    p = NULL;
    goto _exit;
  }

  ethernetif = netif->state;

  /* points to packet payload, which starts with an Ethernet header */
  ethhdr = p->payload;

  switch (htons(ethhdr->type)) {
  /* IP or ARP packet? */
  case ETHTYPE_IP:
  case ETHTYPE_IPV6:
  case ETHTYPE_ARP:
#if PPPOE_SUPPORT
  /* PPPoE packet? */
  case ETHTYPE_PPPOEDISC:
  case ETHTYPE_PPPOE:
#endif /* PPPOE_SUPPORT */
    /* full packet send to tcpip_thread to process */
    if (netif->input(p, netif)!=ERR_OK)
     { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
       pbuf_free(p);
       p = NULL;
     }
    break;

  default:
    pbuf_free(p);
    p = NULL;
    break;
  }
_exit:
;
}

可以看到netif->input(p, netif)也是回撥函式,這個函式被廠商封裝了,最終通過這個介面釋放mbox,將資料寫入pbuf裡面。
到這裡一條完整的線路已經很清晰了,http的接收和傳送時類似的,我們現在看下接收函式,
當資料到來,且最終accept被呼叫了

static err_t
http_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
  struct http_state *hs;
  struct tcp_pcb_listen *lpcb = (struct tcp_pcb_listen*)arg;
  LWIP_UNUSED_ARG(err);
  LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept %p / %p\n", (void*)pcb, arg));

  /* Decrease the listen backlog counter */
  tcp_accepted(lpcb);
  /* Set priority */
  tcp_setprio(pcb, HTTPD_TCP_PRIO);

  /* Allocate memory for the structure that holds the state of the
     connection - initialized by that function. */
  hs = http_state_alloc();
  if (hs == NULL) {
    LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept: Out of memory, RST\n"));
    return ERR_MEM;
  }
  hs->pcb = pcb;

  /* Tell TCP that this is the structure we wish to be passed for our
     callbacks. */
  tcp_arg(pcb, hs);

  /* Set up the various callback functions */
  tcp_recv(pcb, http_recv);
  tcp_err(pcb, http_err);
  tcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL);
  tcp_sent(pcb, http_sent);

  return ERR_OK;
}

這裡面註冊了接收很傳送的回撥函式,POLL函式是重點,本次問題的解決方法就是對POLL進行操作的,rev和send也是在TCP_IMPL.h裡面定義了事件

#define TCP_EVENT_SENT(pcb,space,ret)                          \
  do {                                                         \
    if((pcb)->sent != NULL)                                    \
      (ret) = (pcb)->sent((pcb)->callback_arg,(pcb),(space));  \
    else (ret) = ERR_OK;                                       \
  } while (0)

#define TCP_EVENT_RECV(pcb,p,err,ret)                          \
  do {                                                         \
    if((pcb)->recv != NULL) {                                  \
      (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),(p),(err));\
    } else {                                                   \
      (ret) = tcp_recv_null(NULL, (pcb), (p), (err));          \
    }                                                          \
  } while (0)

看下rev在哪裡被回撥,想想應該也知道是在tcp_input裡面呼叫,http是基於TCP的,所以通過TCP在push到上層去,我們看下tcp_input

        if (recv_data != NULL) {
          LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL);
          if (pcb->flags & TF_RXCLOSED) {
            /* received data although already closed -> abort (send RST) to
               notify the remote host that not all data has been processed */
            pbuf_free(recv_data);
            tcp_abort(pcb);
            goto aborted;
          }

          /* Notify application that data has been received. */
          TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
          if (err == ERR_ABRT) {
            goto aborted;
          }

重註釋也能看出是從這裡呼叫給上層http使用的,我們看下http rev對資料做了什麼處理

static err_t
http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err){
XXXXXXX
#if LWIP_HTTPD_SUPPORT_POST
  if (hs->post_content_len_left > 0) {
    /* reset idle counter when POST data is received */
    hs->retries = 0;
    /* this is data for a POST, pass the complete pbuf to the application */
    http_post_rxpbuf(hs, p);
    /* pbuf is passed to the application, don't free it! */
    if (hs->post_content_len_left == 0) {
      /* all data received, send response or close connection */
      http_send(pcb, hs);
    }
    return ERR_OK;
  } else
#endif /* LWIP_HTTPD_SUPPORT_POST */
  {
    if (hs->handle == NULL) {
      parsed = http_parse_request(&p, hs, pcb);
      LWIP_ASSERT("http_parse_request: unexpected return value", parsed == ERR_OK
        || parsed == ERR_INPROGRESS ||parsed == ERR_ARG
        || parsed == ERR_USE || parsed == ERR_MEM);
    } else {
      LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: already sending data\n"));
    }
#if LWIP_HTTPD_SUPPORT_REQUESTLIST
    if (parsed != ERR_INPROGRESS) {
      /* request fully parsed or error */
      if (hs->req != NULL) {
        pbuf_free(hs->req);
        hs->req = NULL;
      }
    }
#else /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
    if (p != NULL) {
      /* pbuf not passed to application, free it now */
      pbuf_free(p);
    }
#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
    if (parsed == ERR_OK) {
#if LWIP_HTTPD_SUPPORT_POST
      if (hs->post_content_len_left == 0)
#endif /* LWIP_HTTPD_SUPPORT_POST */
      {
        LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: data %p len %"S32_F"\n", hs->file, hs->left));
        http_send(pcb, hs);
      }
    } else if (parsed == ERR_ARG || parsed == ERR_MEM) {
      /* @todo: close on ERR_USE? */
      http_close_conn(pcb, hs);
    }
 XXXXXXXXXXXXXXXXXX
 }

rev的函式也很長,我這裡截取了重點,其他都是對資料處理的和判斷的,這裡先判斷是POST還是GET的方法,其實http還有其他幾個方法,只是不常有這裡也沒有實現,解析主要在parsed = http_parse_request(&p, hs, pcb);這個函式裡面,在這裡面會對資料進行判斷,看是否呼叫了html,js,css等檔案,這裡的這些檔案已經被處理成陣列了,總之最終查詢到的話就會將資料填充到這個結構體裡

struct http_state {
#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
  struct http_state *next;
#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
  struct fs_file file_handle;
  struct fs_file *handle;
  char *file;       /* Pointer to first unsent byte in buf. */

  u8_t is_websocket;

  struct tcp_pcb *pcb;
#if LWIP_HTTPD_SUPPORT_REQUESTLIST
  struct pbuf *req;
#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */

#if LWIP_HTTPD_DYNAMIC_FILE_READ
  char *buf;        /* File read buffer. */
  int buf_len;      /* Size of file read buffer, buf. */
#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
  u32_t left;       /* Number of unsent bytes in buf. */
  u8_t retries;
#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
  u8_t keepalive;
#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
#if LWIP_HTTPD_SSI
  struct http_ssi_state *ssi;
#endif /* LWIP_HTTPD_SSI */
#if LWIP_HTTPD_CGI
  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 LWIP_HTTPD_DYNAMIC_HEADERS
  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 LWIP_HTTPD_TIMING
  u32_t time_started;
#endif /* LWIP_HTTPD_TIMING */
#if LWIP_HTTPD_SUPPORT_POST
  u32_t post_content_len_left;
#if LWIP_HTTPD_POST_MANUAL_WND
  u32_t unrecved_bytes;
  u8_t no_auto_wnd;
  u8_t post_finished;
#endif /* LWIP_HTTPD_POST_MANUAL_WND */
#endif /* LWIP_HTTPD_SUPPORT_POST*/
};

最後呼叫http_send(pcb, hs);這個函式,這個函式我們在看下,是整個傳送的關鍵

static u8_t
http_send(struct tcp_pcb *pcb, struct http_state *hs)
{
  u8_t data_to_send = HTTP_NO_DATA_TO_SEND;

  LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_send: pcb=%p hs=%p left=%d\n", (void*)pcb,
    (void*)hs, hs != NULL ? (int)hs->left : 0));

#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
  if (hs->unrecved_bytes != 0) {
    return 0;
  }
#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */

  /* If we were passed a NULL state structure pointer, ignore the call. */
  if (hs == NULL) {
    return 0;
  }

#if LWIP_HTTPD_FS_ASYNC_READ
  /* Check if we are allowed to read from this file.
     (e.g. SSI might want to delay sending until data is available) */
  if (!fs_is_file_ready(hs->handle, http_continue, hs)) {
    return 0;
  }
#endif /* LWIP_HTTPD_FS_ASYNC_READ */

#if LWIP_HTTPD_DYNAMIC_HEADERS
  /* Do we have any more header data to send for this file? */
  if(hs->hdr_index < NUM_FILE_HDR_STRINGS) {
    data_to_send = http_send_headers(pcb, hs);
    if (data_to_send != HTTP_DATA_TO_SEND_CONTINUE) {
      return data_to_send;
    }
  }
#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */

  /* Have we run out of file data to send? If so, we need to read the next
   * block from the file. */
  if (hs->left == 0) {
    if (!http_check_eof(pcb, hs)) {
      return 0;
    }
  }

#if LWIP_HTTPD_SSI
  if(hs->ssi) {
    data_to_send = http_send_data_ssi(pcb, hs);
  } else
#endif /* LWIP_HTTPD_SSI */
  {
    data_to_send = http_send_data_nonssi(pcb, hs);
  }

  if((hs->left == 0) && (fs_bytes_left(hs->handle) <= 0)) {
    /* We reached the end of the file so this request is done.
     * This adds the FIN flag right into the last data segment. */
    LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
    http_eof(pcb, hs);
    return 0;
  }
  LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("send_data end.\n"));
  return data_to_send;
}

這裡先做一些判斷,因為我這邊只開啟了SSI,所以最終會呼叫data_to_send = http_send_data_ssi(pcb, hs);這個函式,進入這個函式

err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));

裡面一大段對SSI的處理,我對SSI不是很熟悉,所以跳過,直接看呼叫的傳送函式

static err_t
http_write(struct tcp_pcb *pcb, const void* ptr, u16_t *length, u8_t apiflags)
{
   u16_t len;
   err_t err;
   LWIP_ASSERT("length != NULL", length != NULL);
   len = *length;
   if (len == 0) {
     return ERR_OK;
   }
   do {
     LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Trying to send %d bytes\n", len));
     err = tcp_write(pcb, ptr, len, apiflags);
     if (err == ERR_MEM) {
       if ((tcp_sndbuf(pcb) == 0) ||
           (tcp_sndqueuelen(pcb) >= TCP_SND_QUEUELEN)) {
         /* no need to try smaller sizes */
         len = 1;
       } else {
         len /= 2;
       }
       LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE,
                   ("Send failed, trying less (%d bytes)\n", len));
     }
   } while ((err == ERR_MEM) && (len > 1));

   if (err == ERR_OK) {
     LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Sent %d bytes\n", len));
   } else {
     LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Send failed with err %d (\"%s\")\n", err, lwip_strerr(err)));
   }

   *length = len;
   return err;
}

這裡傳送的時候會對分配到的記憶體是否足夠進行判斷,如果不夠就會先減少傳送的大小,在迴圈檢測直到傳送完畢,在看下tcp_write這個函式,我們看下注釋
/**
* Write data for sending (but does not send it immediately).
*
* It waits in the expectation of more data being sent soon (as
* it can send them more efficiently by combining them together).
* To prompt the system to send data now, call tcp_output() after
* calling tcp_write().
*
* @param pcb Protocol control block for the TCP connection to enqueue data for.
* @param arg Pointer to the data to be enqueued for sending.
* @param len Data length in bytes
* @param apiflags combination of following flags :
* - TCP_WRITE_FLAG_COPY (0x01) data will be copied into memory belonging to the stack
* - TCP_WRITE_FLAG_MORE (0x02) for TCP connection, PSH flag will be set on last segment sent,
* @return ERR_OK if enqueued, another err_t on error
*/
裡面做了大量的事情,比如是否copy的形式進行資料傳輸等等,最重要的是這個過程並沒有呼叫實際的輸出函式,所以他只會對pbuf裡面的快取值一直寫入知道資料超出(如果未及時傳送資料),最終會導致資料無法在申請到記憶體,那麼他到底在什麼時候資料被髮送的呢,從http工作原理我們可以知道http是被動形的,所以不會主動做傳送,所以應該就是在rev處理完後直接呼叫send的


          /* If the upper layer can't receive this data, store it */
          if (err != ERR_OK) {
            pcb->refused_data = recv_data;
            LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));
          }
        }

        /* If a FIN segment was received, we call the callback
           function with a NULL buffer to indicate EOF. */
        if (recv_flags & TF_GOT_FIN) {
          if (pcb->refused_data != NULL) {
            /* Delay this if we have refused data. */
            pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN;
          } else {
            /* correct rcv_wnd as the application won't call tcp_recved()
               for the FIN's seqno */
            if (pcb->rcv_wnd != TCP_WND) {
              pcb->rcv_wnd++;
            }
            TCP_EVENT_CLOSED(pcb, err);
            if (err == ERR_ABRT) {
              goto aborted;
            }
          }
        }

        tcp_input_pcb = NULL;
        /* Try to send something out. */
        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("TCP Output data\n"));
        tcp_output(pcb);

tcp_output(pcb);就是實際的傳送函式,所以會在rev函式處理完資料後才傳送,這時候我們想提前做判斷,看是否可以傳送資料了怎麼辦,這裡就是前面的POLL函式,POLL函式是起到輪詢檢視的作用,檢視是否已經有資料,有的話就發生出去

static err_t
http_poll(void *arg, struct tcp_pcb *pcb)
{
  struct http_state *hs = (struct http_state *)arg;
  LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_poll: pcb=%p hs=%p pcb_state=%s\n",
    (void*)pcb, (void*)hs, tcp_debug_state_str(pcb->state)));

  if (hs == NULL) {
    err_t closed;
    /* arg is null, close. */
    LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: arg is NULL, close\n"));
    closed = http_close_conn(pcb, NULL);
    LWIP_UNUSED_ARG(closed);
#if LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR
    if (closed == ERR_MEM) {
       tcp_abort(pcb);
       return ERR_ABRT;
    }
#endif /* LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR */
    return ERR_OK;
  } else {
    hs->retries++;
    if (hs->retries == ((hs->is_websocket) ? WS_TIMEOUT : HTTPD_MAX_RETRIES)) {
      LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: too many retries, close\n"));
      http_close_conn(pcb, hs);
      return ERR_OK;
    }

    /* If this connection has a file open, try to send some more data. If
     * it has not yet received a GET request, don't do this since it will
     * cause the connection to close immediately. */
    if(hs && (hs->handle)) {
      LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_poll: try to send more data\n"));
      if(http_send(pcb, hs)) {
        /* If we wrote anything to be sent, go ahead and send it now. */
//        LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("tcp_output\n"));
        LWIP_DEBUGF(1, ("polling the data,starting send data \n"));
        tcp_output(pcb);
      }
    }
  }

  return ERR_OK;
}

這裡確實是這麼做的,那為什麼還是會出現空間不夠的情況呢,tcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL);poll的最後一個引數是用來設定輪詢速度的


/** Maximum retries before the connection is aborted/closed.
 * - number of times pcb->poll is called -> default is 4*500ms = 2s;
 * - reset when pcb->sent is called
 */
#ifndef HTTPD_MAX_RETRIES
#define HTTPD_MAX_RETRIES                   4
#endif

/** The poll delay is X*500ms */
#ifndef HTTPD_POLL_INTERVAL
#define HTTPD_POLL_INTERVAL                 4
#endif

我們在看下poll函式的註釋

/**
 * The poll function is called every 2nd second.
 * If there has been no data sent (which resets the retries) in 8 seconds, close.
 * If the last portion of a file has not been sent in 2 seconds, close.
 *
 * This could be increased, but we don't want to waste resources for bad connections.
 */

預設是2秒,8秒無資料就會關閉連線,所以這裡連個引數都會影響到頁面的顯示狀況,如果設定的HTTPD_MAX_RETRIES過大,會導致對於一些已經不用的連線一直在輪詢而導致後面的連線無法正常使用,頁面會一直在接收,如果HTTPD_POLL_INTERVAL過大,會導致傳送大資料導致out of memery的情況發生。設定的過小有又會導致系統資源的浪費了,所以大家自行做個判斷。

總結

雖然看似一個很小的問題,解決起來卻是走過千山萬水的感覺,最近有個想法,有過linux開發的同學應該都有使用dump_stack的經驗,確實是除錯linux核心很好的工具,所以我想做一個嵌入式平臺比較通用的dump_stack,這對於程式碼的追蹤想必是極好的,以上原始碼程式碼都在我的github上,本身問題是出在另一款晶片上的,由於還在開發且保密,在這裡我就我就放esp8266的http程式碼,我已做了移植和修改。
have fun ! enjoy it!
https://github.com/zwxf/ESP8266_RTOS_SDK