LWIP应用开发|TCP/IP设计原理一

TCP/IP设计原理一

1. lwIP内核定时事件管理

网络协议要想实现高效的通信,离不开对定时事件的管理,比如ARP协议中的缓存表项定时、IP协议中的分片数据重组定时、TCP协议中的快定时与慢定时,它们都是协议栈功能实现的基本保障。lwIP设计时保证了与操作系统或底层硬件间的独立性,它自身并不维护硬件定时器,更不会对硬件定时器进行管理,那LwIP怎样实现上述定时机制呢 ?

1.1 定时事件数据结构

LwIP通过数据结构sys_timeo来记录一个定时事件,其数据结构描述如下:

/* 定时回调函数指针 */
typedef void (*sys_timeout_handler)(void *arg);
 
/* 定时器事件 */
struct sys_timeo 
{
  struct sys_timeo *next;        //下一个定时事件
  u32_t time;                    //定时时间
  sys_timeout_handler h;         //定时回调函数
  void *arg;                     //定时回调参数
};

next用于将定时结构组织在定时链表中,time表示当前定时事件成为链表第一个节点后,它还需要等待多久(毫秒)才会被执行;当定时时间到后,h指向的函数被系统回调执行,同时arg作为参数传递给回调函数

所有的定时事件最终被串接在一个链表上。下图展示了有三个定时事件被组织在定时链表上的情况:

/* 定时事件队列 */
static struct sys_timeo *next_timeout;

在这里插入图片描述

1.2 定时事件注册

向协议栈内核注册一个定时事件,即向定时链表中添加一个定时结构,实现这个功能的函数叫sys_timeout,上图已经展示过,该函数如下(省略了实现代码):

void sys_timeout(u32_t msecs, sys_timeout_handler handler, void *arg);
1.3 定时事件处理

lwIP针对有无操作系统模拟层两种情况,提供了两种不同的定时事件处理方案,如下代码所示:

#if NO_SYS
void sys_check_timeouts(void);
#else /* NO_SYS */
void sys_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg);
#endif /* NO_SYS */
2. lwIP核心业务逻辑

lwIP核心业务逻辑如下图示:

在这里插入图片描述
tcpip_box是协议栈初始化时建立的用于tcpip_thread接收消息的邮箱,该函数能够识别的消息类型是tcpip_msg结构的,因此不管是API部分还是IP数据包输入部分,都必须将信息封装成tcpip_msg结构

  • tcpip_msg结构以及消息类型
/***************7种tcpip_msg消息类型*****************/
enum tcpip_msg_type {
  TCPIP_MSG_API,//用户调用应用层的接口时,就属于API消息类型
  TCPIP_MSG_API_CALL,//应用层API接口需要回传信息,比如说connect接口,回传(信号量)
  TCPIP_MSG_INPKT,//网络数据包消息,即网卡上的数据
  TCPIP_MSG_TIMEOUT,//创建超时定时器
  TCPIP_MSG_UNTIMEOUT,//删除超时定时器
  TCPIP_MSG_CALLBACK,//回调函数消息(动态生成)  
  TCPIP_MSG_CALLBACK_STATIC	//回调函数消息(静态生成) 
};
/***************tcpip_msg结构体*****************/
struct tcpip_msg {  
  enum tcpip_msg_type type;	//消息类型 
  union {//消息内容,共用体,不同消息类型使用不同的结构
    struct {      
      tcpip_callback_fn function;//回调函数  
      void* msg;//消息内容
    } api_msg;
    struct {      
      tcpip_api_call_fn function;//回调函数     
      struct tcpip_api_call_data *arg; //函数参数    
      sys_sem_t *sem;//信号量
    } api_call;
    struct {      
      struct pbuf *p;//pbuf数据包      
      struct netif *netif;//对应的网卡接口     
      netif_input_fn input_fn; //网卡数据接收处理函数
    } inp;
    struct {      
      tcpip_callback_fn function;//回调函数     
      void *ctx;//回调函数参数
    } cb;
    struct {
      u32_t msecs;//超时时间     
      sys_timeout_handler h;//超时回调函数     
      void *arg;//超时回调函数参数
    } tmo;
  } msg;
};
  • 内核协议栈进程 tcpip_thread
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);
  }
  while (1) {                         
    //调用sys_timeouts_mbox_fetch,阻塞在邮箱上接收要处理的消息
    TCPIP_MBOX_FETCH(&mbox, (void **)&msg);
    //判断消息是否正确,如果错误,继续等待消息邮箱
    if (msg == NULL) {
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: NULL\n"));
      LWIP_ASSERT("tcpip_thread: invalid message", 0);
      continue;
    }
    //根据消息类型进行遍历处理
    switch (msg->type) {
    case TCPIP_MSG_API:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
      //根据API消息内容,进行消息解析处理
      msg->msg.api_msg.function(msg->msg.api_msg.msg);
      break;
    case TCPIP_MSG_API_CALL:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API CALL message %p\n", (void *)msg));
      //传入API-CALL参数,并进行回调处理,同时读取返回值
      msg->msg.api_call.arg->err = msg->msg.api_call.function(msg->msg.api_call.arg);
      //释放信号量,让其他等待信号量的任务继续执行
      sys_sem_signal(msg->msg.api_call.sem);
      break;
    case TCPIP_MSG_INPKT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
      //传入pbuf netif 进行网络数据包处理(tcpip_input)
      msg->msg.inp.input_fn(msg->msg.inp.p, msg->msg.inp.netif);
      //消息处理完毕,释放消息占用的内存(内存池里)
      memp_free(MEMP_TCPIP_MSG_INPKT, msg);
      break;
    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;
    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;
    }
  }
}
3. lwIP消息处理机制
3.1 IP数据包消息处理

底层向内核进程递交接收到的IP数据包是通过调用网络接口结构netif中指针input所指向的tcpip_input函数来实现的

在这里插入图片描述

  • tcpip_input和tcpip_inpkt函数
err_t tcpip_input(struct pbuf *p, struct netif *inp){
#if LWIP_ETHERNET
  if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
    return tcpip_inpkt(p, inp, ethernet_input);
  } else
#endif /* LWIP_ETHERNET */
  return tcpip_inpkt(p, inp, ip_input);
}
err_t tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn){
//判断tcpip内核是否上锁,若上锁,则tcpip_thread不能用
#if LWIP_TCPIP_CORE_LOCKING_INPUT
  err_t ret;
  LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_inpkt: PACKET %p/%p\n", (void *)p, (void *)inp));
  LOCK_TCPIP_CORE();
  //直接调用输入解析函数
  ret = input_fn(p, inp);
  UNLOCK_TCPIP_CORE();
  return ret;
#else /* LWIP_TCPIP_CORE_LOCKING_INPUT */
  //创建消息指针
  struct tcpip_msg *msg;
  //创建消息的目的就是为了发送消息邮箱,这里需要判断消息邮箱是否有效
  LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(mbox));
  //分配消息的实际内存
  msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);
  if (msg == NULL) {
    return ERR_MEM;
  }
  //消息类型属于数据消息
  msg->type = TCPIP_MSG_INPKT;
  msg->msg.inp.p = p;
  msg->msg.inp.netif = inp;
  msg->msg.inp.input_fn = input_fn;
  //发送消息到消息邮箱
  if (sys_mbox_trypost(&mbox, msg) != ERR_OK) {
    //释放消息内存
    memp_free(MEMP_TCPIP_MSG_INPKT, msg);
    return ERR_MEM;
  }
  return ERR_OK;
#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
}
3.2 API消息处理

应用程序与协议栈之间的通讯处理。两部分API函数间通过api_msg消息结构体传递消息

在这里插入图片描述

  • api_msg消息结构体定义
struct api_msg {
	/* API函数建立连接,首先会建立一个netconn结构来描述这个连接的各种属性 */
	struct netconn *conn;	//与消息相关的某个连接,可以理解为一个socket
  	err_t err;  //消息处理函数的返回值 
  	union {
    struct netbuf *b;//函数do_send的参数,用于网络数据发送
    struct {//函数do_newconn的参数
      u8_t proto;
    } n;
    struct {
      API_MSG_M_DEF_C(ip_addr_t, ipaddr);
      u16_t port;
    } bc;
    /** 获取IP地址,acpect,会返回我们客户端的IP地址以及端口号*/
    struct {
      ip_addr_t API_MSG_M_DEF(ipaddr);
      u16_t API_MSG_M_DEF(port);
      u8_t local;
    } ad;
    /** write */
    struct {
      const void *dataptr;
      size_t len;
      u8_t apiflags;
#if LWIP_SO_SNDTIMEO
      u32_t time_started;
#endif /* LWIP_SO_SNDTIMEO */
    } w;
    /** read*/
    struct {
      u32_t len;
    } r;
#if LWIP_TCP
    /* close shutdown */
    struct {
      u8_t shut;
#if LWIP_SO_SNDTIMEO || LWIP_SO_LINGER
      u32_t time_started;
#else /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
      u8_t polls_left;
#endif /* LWIP_SO_SNDTIMEO || LWIP_SO_LINGER */
    } sd;
#endif /* LWIP_TCP */
#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
    /** IGMP */
    struct {
      API_MSG_M_DEF_C(ip_addr_t, multiaddr);
      API_MSG_M_DEF_C(ip_addr_t, netif_addr);
      enum netconn_igmp join_or_leave;
    } jl;
#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
// listen(fd, 5)
#if TCP_LISTEN_BACKLOG
    struct {
      u8_t backlog;
    } lb;
#endif /* TCP_LISTEN_BACKLOG */
} msg;
  • lwip_netconn接口:用户编写socket接口时,lwip内核会针对我们API的类型进行相关处理
#if LWIP_TCP
extern u8_t netconn_aborted;
#endif /* LWIP_TCP */
void lwip_netconn_do_newconn         (void *m);
void lwip_netconn_do_delconn         (void *m);
void lwip_netconn_do_bind            (void *m);
void lwip_netconn_do_connect         (void *m);
void lwip_netconn_do_disconnect      (void *m);
void lwip_netconn_do_listen          (void *m);
void lwip_netconn_do_send            (void *m);
void lwip_netconn_do_recv            (void *m);
#if TCP_LISTEN_BACKLOG
void lwip_netconn_do_accepted        (void *m);
#endif /* TCP_LISTEN_BACKLOG */
void lwip_netconn_do_write           (void *m);
void lwip_netconn_do_getaddr         (void *m);
void lwip_netconn_do_close           (void *m);
void lwip_netconn_do_shutdown        (void *m);
#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
void lwip_netconn_do_join_leave_group(void *m);
#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
#if LWIP_DNS
void lwip_netconn_do_gethostbyname(void *arg);
#endif /* LWIP_DNS */
struct netconn* netconn_alloc(enum netconn_type t, netconn_callback callback);
void netconn_free(struct netconn *conn);
  • netconn_bind函数
err_t
netconn_bind(struct netconn *conn, const ip_addr_t *addr, u16_t port){
  //api_msg 创建
  API_MSG_VAR_ALLOC(msg);
  //api_msp 赋值
  API_MSG_VAR_REF(msg).conn = conn;
  API_MSG_VAR_REF(msg).msg.bc.ipaddr = API_MSG_VAR_REF(addr);
  API_MSG_VAR_REF(msg).msg.bc.port = port;
  //发送api消息到消息邮箱
  err = netconn_apimsg(lwip_netconn_do_bind, &API_MSG_VAR_REF(msg));
  API_MSG_VAR_FREE(msg);
  return err;
}
  • netconn_apimsg函数
static err_t netconn_apimsg(tcpip_callback_fn fn, struct api_msg *apimsg){
  err_t err;
  //发送消息到消息邮箱,并等待处理完毕后的信号量
  err = tcpip_send_msg_wait_sem(fn, apimsg, LWIP_API_MSG_SEM(apimsg));
  if (err == ERR_OK) {
    return apimsg->err;
  }
  return err;
}

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安迪西嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值