最近在做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