lwIP源码解析---httpd(一)

lwIP源码解析—httpd(一)

目录
一、lwIP版本
二、网卡netif初始化
三、httpd初始化流程
  3.1 初始化入口
四、处理客户端连接

学习参考:[野火]《LwIP应用开发实战指南》、lwIP官方说明手册:http://www.nongnu.org/lwip/2_1_x/index.html
一、lwIP版本
  此处使用lwip 2.1.2版本以及contrib 2.1.0版本(其中contrib源代码example中httpd展示了如何完整移植http的框架,需要移植一并使用才能构成完整的http应用)。
二、网卡netif初始化
  lwIP使用netif作为抽象出的网卡结构体,用于联系底层硬件驱动和lwIP软件的桥梁,是一个重要的结构,定义在lwip-2.1.2\lwip-2.1.2\src\core\netif.c。
  该网卡结构体中有IP地址,掩码,网关,MAC地址等网卡具备的信息,以及初始化,输入输出等回调函数。
  初始化接口:

struct netif *
netif_add(struct netif *netif,
#if LWIP_IPV4
          const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
#endif /* LWIP_IPV4 */
          void *state, netif_init_fn init, netif_input_fn input)

  调用此函数之后,便用lwIP协议栈创建出了一个网卡。其中按照contrib开发框架,init默认注册ethernetif_init, input默认注册ethernetif_input。作为网卡层的函数。这两个以太网层的接口,需要移植者根据不同网卡的方式进行实现。output函数则默认使用下列三个函数:

#if LWIP_IPV4
  netif->output = etharp_output;
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
  netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */
  netif->linkoutput = low_level_output;

三、http初始化
  3.1 初始化入口
  代码路径:lwip-2.1.2\lwip-2.1.2\src\apps\http\httpd.c

void httpd_init(void);

  初始化流程如下图:

lwIP源码解析---httpd(一)_第1张图片
  初始化httpd时,按顺序做了以下事情:
  1) 申请一个tcp协议控制块,其中altcp_tcp_new_ip_typetcp_new_ip_type是等价的。
altcp_pcb和tcp_pcb是等价的。

#define altcp_pcb tcp_pcb
#define altcp_tcp_new_ip_type tcp_new_ip_type
struct altcp_pcb *pcb = altcp_tcp_new_ip_type(IPADDR_TYPE_ANY); // new tcp_pcb

  tcp协议控制块tcp_pcb是一个重要的结构体,维护了tcp传输层所需要的数据结构,例如ip地址、端口号、滑动窗口、网络IO回调函数、连接管理、重发机制等。结构体定义在tcp.h中;
  2) 设置tcp_pcb中的local_ip和port,同时加入已绑定的tcp控制块链表tcp_bound_pcbs中管理;

/** List of all TCP PCBs bound but not yet (connected || listening) */
struct tcp_pcb *tcp_bound_pcbs;
TCP_REG(&tcp_bound_pcbs, pcb); // reg to tcp_bound_pcbs list

  3) 监听tcp控制块。申请一个新的tcp_pcb_listen类型,将state置为LISTEN,把原tcp控制块必要的端口IP等信息传给监听控制块,释放原来的tcp_pcb控制块,并且将此控制块放到监听链表tcp_listen_pcbs中。(此处新申请tcp_pcb_listen控制块的原因是为了节约空间,这是一个裁剪版的tcp_pcb协议控制块,在未连接之前不需要完整的tcp控制块);

TCP_REG(&tcp_listen_pcbs.pcbs, (struct tcp_pcb *)lpcb); // reg to tcp_listen_pcbs list

   4) 调用altcp_accept()把回调函数http_accept()注册到上述监听tcp控制块的accept回调成员中。

void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
{
  LWIP_ASSERT_CORE_LOCKED();
  if ((pcb != NULL) && (pcb->state == LISTEN)) {
    struct tcp_pcb_listen *lpcb = (struct tcp_pcb_listen *)pcb;
    lpcb->accept = accept; // reg accept to listen tcp_pcb
  }
}

  总结:在lwip的httpd应用中,使用RAW API接口,创建tcp_pcb的方式初始化tcp端口(在RAW API中,没有socket的概念。开发者为了用户的网络编程习惯,在RAW API的基础上封装了类似linux的网络socket编程接口,定义在lwip-2.1.2\lwip-2.1.2\src\api\sockets.c,不过此socket接口虽然使用简单,但效率不及直接操作tcp_pcb编程,故自带的http应用仍采用RAW API的方式)。
  注:由于采用回调函数的方式,用户处理函数直接由内核回调,所以内核程序和用户定义的应用处理程序实际是处于同一线程的,在编程时需要避免应用处理程序耗时过长,导致内核程序长时间没被调用的情况,就有可能丢包或者丢连接。
  监听控制块由TCP内核中的链表tcp_listen_pcbs管理,采用回调函数的方式响应客户端的connect请求。
  在TCP内核,创建了四个链表存放处于不同TCP状态的tcp_pcb控制块,用于进行管理,代码定义在tcp.c中:

/* The TCP PCB lists. */

/** List of all TCP PCBs bound but not yet (connected || listening) */
struct tcp_pcb *tcp_bound_pcbs;
/** List of all TCP PCBs in LISTEN state */
union tcp_listen_pcbs_t tcp_listen_pcbs;
/** List of all TCP PCBs that are in a state in which
 * they accept or send data. */
struct tcp_pcb *tcp_active_pcbs;
/** List of all TCP PCBs in TIME-WAIT state */
struct tcp_pcb *tcp_tw_pcbs;

/** An array with all (non-temporary) PCB lists, mainly used for smaller code size */
struct tcp_pcb **const tcp_pcb_lists[] = {&tcp_listen_pcbs.pcbs, &tcp_bound_pcbs,
         &tcp_active_pcbs, &tcp_tw_pcbs
};

  TCP state一共有十一种理解TCP流程的核心就是理解这十一种状态的含义以及互相之间的转换,状态定义如下代码所示:

static const char *const tcp_state_str[] = {
  "CLOSED",
  "LISTEN",
  "SYN_SENT",
  "SYN_RCVD",
  "ESTABLISHED",
  "FIN_WAIT_1",
  "FIN_WAIT_2",
  "CLOSE_WAIT",
  "CLOSING",
  "LAST_ACK",
  "TIME_WAIT"
};

  可见在内核中,也是基于状态机的形式管理tcp_pcb协议控制块的。
  至此,完成了一个tcp协议控制块的初始化(类比linux网络编程的说法,是初始化了一个监听套接字socket,并且等待客户端连接)。
四、处理客户端连接
  客户端连接时,按网络数据的流向,lwIP内核处理流程如下:
lwIP源码解析---httpd(一)_第2张图片

  在lwIP源码解析—httpd(二)中会进一步分析httpd的函数和处理流程。

你可能感兴趣的:(lwIP源码解析及应用开发,http,tcpip,c语言,stm32)