Lwip—Sequential API编程接口

UDP的使用方法

static void udp_thread(void *arg)
{
	OS_CPU_SR cpu_sr;
	err_t err;
	static struct netconn *udpconn;
	static struct netbuf  *recvbuf;
	static struct netbuf  *sentbuf;
	struct ip_addr destipaddr;
	u32 data_len = 0;
	struct pbuf *q;
	
	LWIP_UNUSED_ARG(arg);
	udpconn = netconn_new(NETCONN_UDP);  //创建一个UDP连接
	udpconn->recv_timeout = 10;  		//接收超时为10ms
	
	if(udpconn != NULL)  //如果创建udp连接成功
	{
		err = netconn_bind(udpconn,IP_ADDR_ANY,UDP_DEMO_PORT); //服务器端绑定到熟知端口
		IP4_ADDR(&destipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],
                 lwipdev.remoteip[3]); 
		netconn_connect(udpconn,&destipaddr,UDP_DEMO_PORT); 	//连接到远端主机
      //作为服务器时,以上两个函数可以不要
		if(err == ERR_OK)//绑定成功
		{
			while(1)
			{
				if((udp_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送
				{
					sentbuf = netbuf_new();
					netbuf_alloc(sentbuf,strlen((char *)udp_demo_sendbuf));
					memcpy(sentbuf->p->payload,(void*)udp_demo_sendbuf,
                           strlen((char*)udp_demo_sendbuf));
					err = netconn_send(udpconn,sentbuf);  	//将netbuf中的数据发送出去
					if(err != ERR_OK)
					{
						printf("发送失败\r\n");
						netbuf_delete(sentbuf);      //删除buf
					}
					udp_flag &= ~LWIP_SEND_DATA;	//清除数据发送标志
					netbuf_delete(sentbuf);      	//删除buf
				}	
				
				netconn_recv(udpconn,&recvbuf); //接受数据
				if(recvbuf != NULL)             //接收到数据
				{ 
					OS_ENTER_CRITICAL(); //关中断
					memset(udp_demo_recvbuf,0,UDP_DEMO_RX_BUFSIZE);  //数据接受缓冲区清零
					for(q=recvbuf->p;q!=NULL;q=q->next)  //遍历完整个pbuf链表
					{
                      //判断要拷贝的UDP_RX_BUFFSIZE中的数据是否大于UDP_RX_BUFFSIZE的剩余空间,
                      //如果大于的话就只拷贝UDP_RX_BUFFSIZE剩余的长度,否则的话就拷贝所有的数据
						if(q->len > (UDP_DEMO_RX_BUFSIZE-data_len)) 							memcpy(udp_demo_recvbuf+data_len,q->payload,(UDP_DEMO_RX_BUFSIZE-data_len));
						else memcpy(udp_demo_recvbuf+data_len,q->payload,q->len);
						data_len += q->len;  	
						if(data_len > UDP_RX_BUFSIZE) break;
					}
					OS_EXIT_CRITICAL();  
					data_len=0;                         //复制完成后data_len要清零
					printf("%s\r\n",udp_demo_recvbuf);  //打印接收的数据
					netbuf_delete(recvbuf);             //删除buf
				}else OSTimeDlyHMSM(0,0,0,5);           //延时5ms
			}
		}else printf("UDP绑定失败\r\n);
	}else printf("UDP连接创建失败\r\n);
}


//创建UDP线程
//返回值:0 成功
//    其他 失败
INT8U udp_demo_init(void)
{
	INT8U res;
	OS_CPU_SR cpu_sr;
	
	OS_ENTER_CRITICAL();	
	res = OSTaskCreate(udp_thread,(void*)0,(OS_STK*)&UDP_TASK_STK[UDP_STK_SIZE-1],UDP_PRIO);
	OS_EXIT_CRITICAL();		
	return res;
}

使用UDP的话,首先应该创建一个UDP线程,然后要遵循udp应用的一般模板:

//这是UDP基本的发送模板
struct netconn *conn;
struct netbuf *buf;
struct ip_addr *addr;
char *sendbuf = "123";
conn = netconn_new(NETCONN_UDP);                 //新建一个UDP类型的连接结构
IP4_ADDR(addr,192.168.1.78);                     //构造目的IP地址
netconn_connect(conn,&addr,7000);                //连接目的端

buf = netbuf_new();                              //创建一个新的netbuf结构       
netbuf_alloc(buf,strlen((char *)sendbuf));       //分配数据空间
netconn_send(conn,buf);                          //发送新数据
netconn_delete(conn);                            //删除连接结构
netconn_delete(buf);                             //删除netbuf结构
//这是UDP基本的接收模板
struct netconn *conn;
struct netbuf *buf;
struct ip_addr *addr;
unsigned short port;
conn = netconn_new(NETCONN_UDP);                  //新建UDP连接
conn->recv_timeout = 10;  		                 //接收超时为10ms
netconn_bind(conn,NULL,7);                        //服务器端绑定到熟知端口7
while(1)
  {
    conn = netconn_recv(conn,&buf);                 //在连接上接收数据
  	addr = netbuf_fromaddr(buf);                  //获得数据的源IP地址
    port = netbuf_fromport(buf);                  //获得数据的源端口
    do_something(&buf->p)
    netbuf_delete(buf);
  }

第一步:创建一个指向netconn的全局指针变量;

第二步:使用netconn_new函数创建一个UDP类型的连接,返回值指向第一步创建的全局指针变量,expl:conn = netconn_new(NETCONN_UDP);

第三步:作为发送方,conn连接接口要连接到远端,netconn_connect(conn,IP地址,端口号); 而作为接收要绑定一个固定端口,netconn_bind(conn,NULL,7);

第四步:发送netconn_send(conn,buf);或者接收netconn_recv(conn,&buf);。

第五步:删除netbuf结构,如果只是发送的话,还要删除netconn结构。

TCP的使用方法

使用TCP的话,首先应该创建一个TCP线程,然后要遵循TCP应用的一般模板:

作为服务器的模板:

static void tcp_server_thread(void *arg)
{
struct netconn *conn,*newconn; 
err_t err;
conn = netconn_new(NETCONN_TCP);  //新建一个TCP连接
netconn_bind(conn,NULL,7);        //绑定服务器熟知端口7
netconn_listen(conn);             //进入侦听状态,接收客户端连接
while(1)
  {  
  	err = netconn_accept(conn,&newconn);//等待一个新连接
  	if(err == ERR_OK)                   //新连接建立成功
      {
        struct netbuf *buf;
      	void *data; u16 len;
         //在新连接上等待,循环接收数据包
        while((err = netconn_recv(newconn,&buf)) == ERR_OK){
         do
           {
             netbuf_data(buf,&data,&len);
             err = netconn_write(newconn,data,len,NETCONN_COPY);//回显
           }while(netbuf(buf)>=0);
          netbuf_delete(buf);//删除数据包
        }
      //接收数据包失败,或者到空包,则表示对端已经断开连接
      netconn_close(newconn);      //断开本地连接
      netconn_delete(newconn);     //删除连接结构
      }
  }
}
//创建TCP线程
//返回值:  0 成功
//       其他 失败
INT8U tcp_server_init(void)
{
	INT8U res;
	OS_CPU_SR cpu_sr;
	
	OS_ENTER_CRITICAL();	
	res = OSTaskCreate(tcp_server_thread,(void*)0,(OS_STK*)&TCPSERVER_TASK_STK[TCPSERVER_STK_SIZE-1],TCPSERVER_PRIO); 
	OS_EXIT_CRITICAL();		
	
	return res;
}

第一步:使用netconn_new函数创建一个TCP类型的连接,返回值指向创建的全局指针变量,expl:conn = netconn_new(NETCONN_TCP);

第二步:作为服务端要绑定一个固定端口,netconn_bind(conn,NULL,7);

第三步:使服务端处于侦听状态netconn_listen(conn);,等待一个新连接netconn_accept(conn,&newconn);

第四步:连接成功后,再新连接上等待循环接收数据包netconn_recv(newconn,&buf);

第五步:处理数据;

第六步:删除netbuf结构,如果只是发送的话,还要删除netconn结构。

作为客户端的模板:

static void tcp_client_thread(void *arg)
  {
    struct netconn *tcp_clientconn;
  	err_t err;
  	
  	while(1)
      {
        tcp_clientconn = netconn_new(NETCONN_TCP);//创建TCP连接
        err = netconn_connect(tcp_clientconn,&server_ipaddr,server_port);//连接服务器
        if(err != ERR_OK) netconn_delete(tcp_clientconn);//返回值不等于ERR_OK,删除连接
        else if(err == ERR_OK)//处理新连接的数据
        {
       		 while(1)
           	 {
				if((tcp_client_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送
				{
					err = netconn_write(tcp_clientconn,tcp_client_sendbuf,
                     strlen((char*)tcp_client_sendbuf),NETCONN_COPY); 
					tcp_client_flag &= ~LWIP_SEND_DATA;
				}
        	  }
     	  }
  	   }
   }
//创建TCP客户端线程
//返回值:0  成功
//	   其他  失败
INT8U tcp_client_init(void)
{
	INT8U res;
	OS_CPU_SR cpu_sr;
	
	OS_ENTER_CRITICAL();	
	res = OSTaskCreate(tcp_client_thread,(void*)0,(OS_STK*)&TCPCLIENT_TASK_STK[TCPCLIENT_STK_SIZE-1],TCPCLIENT_PRIO); 
	OS_EXIT_CRITICAL();
	
	return res;
}

第一步:使用netconn_new函数创建一个TCP类型的连接,返回值指向创建的全局指针变量,expl:conn = netconn_new(NETCONN_TCP);

第三步:作为客户端,conn连接接口要连接到远端,netconn_connect(conn,IP地址,端口号);

第四步:客户端这时可以用netconn_write写数据了,到时候,有内核发送出去。

第五步:删除netbuf结构,如果只是发送的话,还要删除netconn结构。

协议栈api

协议栈API的实现由两部分组成:一部分作为用户编程接口函数提供给用户,这些函数在用户进程中执行;另一部分驻留在协议栈内核进程中,这两部分通过进程通信机制(IPC)实现通信和同步,共同为应用程序提供服务。被使用到的进程通信机制包括以下三种:

  • 邮箱,例如内核邮箱mbox、连接上接收数据的邮箱recvmbox;

  • 信号量,例如op_completed,用于两部分API的同步;

  • 共享内存,例如内核消息结构tcpip_msg、API消息内容api_msg等;

两部分API之间的关系如图。API设计的核心在于让用户进程负责尽可能多的工作,而协议栈进程值负责简单的通信工作。

image

内核进程

在操作系统模拟曾的支持下,LwIP内核作为操作系统的一个任务运行,在协议栈初始化函数tcp_init中,内核进程被创建。

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) {                          /* MAIN Loop */
    sys_timeouts_mbox_fetch(&mbox, (void **)&msg); //等待一个消息
    switch (msg->type) {                           //根据消息的不同类型做处理
    case TCPIP_MSG_API:                            //API调用
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
      msg->msg.apimsg->function(&(msg->msg.apimsg->msg));//执行API函数
      break;
    case TCPIP_MSG_INPKT:                         //底层数据包输入
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
      if (msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {//若支持ARP,则
        ethernet_input(msg->msg.inp.p, msg->msg.inp.netif);                       //交给ARP层处理
      } else                                                                      //若接口不支持ARP(环回),则数据包直接交给IP层处理
      {
        ip_input(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;
    }
  }
}

void
tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
  lwip_init();                            //初始化内核

  tcpip_init_done = initfunc;             //注册用户自定义函数
  tcpip_init_done_arg = arg;              //函数参数
  if(sys_mbox_new(&mbox, TCPIP_MBOX_SIZE) != ERR_OK) {//创建内核邮箱
    LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
  }
#if LWIP_TCPIP_CORE_LOCKING
  if(sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
    LWIP_ASSERT("failed to create lock_tcpip_core", 0);
  }
#endif /* LWIP_TCPIP_CORE_LOCKING */
//创建内核进程
  sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}

全局邮箱mbox在协议栈初始化时建立,用于内核进程tcpip_thread接收消息。内核进程通过共享内存的方式与协议栈的其他各个模块进行通信,它从邮箱中获得的时一个纸箱消息结构的指针。内核消息封装在tcpip_msg结构中,tcpip_thread使用从邮箱中获得的指针到对应的内存地址处读取消息的内容,并根据消息的内容作出各种处理。上层API函数的实现及底层数据包的处理都是基于这种消息机制的。

内核进程会一直阻塞在sys_timeouts_mbox_fetch(&mbox, (void **)&msg);上 除了等待上层发送的消息,它还处理一些定时任务,比如ARP,TCP定时器。系统消息是通过消息结构tcpip_msg来描述的,内核进程识别消息的类型,并调用不同的函数处理这些消息。

struct tcpip_msg {
  enum tcpip_msg_type type;  //消息类型字段 TCPIP_MSG_API,TCPIP_MSG_INPKT,TCPIP_MSG_TIMEOUT,
  			            //TCPIP_MSG_UNTIMEOUT,TCPIP_MSG_CALLBACK,TCPIP_MSG_CALLBACK_STATIC
  sys_sem_t *sem;            //信号量指针,暂未用到
  union {
#if LWIP_NETCONN
    struct api_msg *apimsg;  //指向api_msg结构,记录API消息的具体内容
#endif /* LWIP_NETCONN */
#if LWIP_NETIF_API
    struct netifapi_msg *netifapimsg;
#endif /* LWIP_NETIF_API */ 
    struct {                  //底层数据包消息的具体内容
      struct pbuf *p;         //指向收到的数据
      struct netif *netif;    //收到数据包的网络接口
    } inp;
    struct {                      //回调执行一个函数消息的具体内容
      tcpip_callback_fn function; //函数指针
      void *ctx;                  //函数参数
    } cb;              
#if LWIP_TCPIP_TIMEOUT
    struct {                  //注册、撤销一个定时时间消息的具体内容
      u32_t msecs;            //定时时间
      sys_timeout_handler h;  //定时函数
      void *arg;              //函数参数
    } tmo;
#endif /* LWIP_TCPIP_TIMEOUT */
  } msg;
};

比如数据包消息:

err_t
tcpip_input(struct pbuf *p, struct netif *inp)
{
  struct tcpip_msg *msg;

  if (!sys_mbox_valid(&mbox)) {//系统邮箱有效的情况下
    return ERR_VAL;
  }
  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中共用体inp的pbuf字段
  msg->msg.inp.netif = inp;   //指向msg中共用体inp的netif字段
  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 */
}

协议栈API处理数据包消息流程如图: 数据包消息

(1)~(3)在中断处理程序或者单独地数据包查询接收线程中完成,(4)(5)则是由内核进程完成的。

API消息:

err_t
tcpip_apimsg(struct api_msg *apimsg)
{
  struct tcpip_msg msg;
  if (sys_mbox_valid(&mbox)) {
    msg.type = TCPIP_MSG_API;//类型API消息
    msg.msg.apimsg = apimsg; //指向API具体消息
    sys_mbox_post(&mbox, &msg);//投递邮箱
    sys_arch_sem_wait(&apimsg->msg.conn->op_completed, 0);//等待处理程序释放信号
    return apimsg->msg.err;
  }
  return ERR_VAL;
}

API消息重点在apimsg

struct api_msg {
  /** function to execute in tcpip_thread context */
  void (* function)(struct api_msg_msg *msg);
  /** arguments for this function */
  struct api_msg_msg msg;
};

struct api_msg_msg {
  struct netconn *conn; //与消息相关的当前连接,该字段对任何调用都是必须的
  err_t err;
  union {
    struct netbuf *b;  //执行do_send时需要的参数,待发送数据
    struct {          //执行do_newconn时需要的参数,连接类型
      u8_t proto;      
    } n;
    struct {           //执行do_bind和do_connect时需要的参数
      ip_addr_t *ipaddr;//IP地址
      u16_t port;       //端口号
    } bc;
    struct {            //执行do_getaddr时需要的参数
      ip_addr_t *ipaddr;
      u16_t *port;
      u8_t local;
    } ad;
    struct {           //执行do_write时需要的参数
      const void *dataptr;
      size_t len;
      u8_t apiflags;
#if LWIP_SO_SNDTIMEO
      u32_t time_started;
#endif /* LWIP_SO_SNDTIMEO */
    } w;
    struct {           //执行do_recv时需要的参数
      u32_t len;
    } r; 
    struct {            /** used for do_close (/shutdown) */
      u8_t shut;
    } sd;
#if LWIP_IGMP
    struct {            /** used for do_join_leave_group */
      ip_addr_t *multiaddr;
      ip_addr_t *netif_addr;
      enum netconn_igmp join_or_leave;
    } jl;
#endif /* LWIP_IGMP */
#if TCP_LISTEN_BACKLOG
    struct {
      u8_t backlog;
    } lb;
#endif /* TCP_LISTEN_BACKLOG */
  } msg;
};

API函数的实现,以netconn_bind为例:

image

前边说过协议栈API分两部分,一部分在用户进程中,一部分在内核中。用户使用的在在文件api_lib.c中,形式如netconn_xxx;而内核进程中的API函数在tcpip_msg中,形如do_xxx。图中,用户程序调用netconn_bind绑定一个连接,则函数函数实现时,通过向内核进程发送一个TCPIP_MSG-API类型的消息,告诉内核进程执行do_bind函数;消息发送后,函数阻塞在信号量上,等待内核处理该消息;内核在处理消息时,会根据消息内容调用do_bind,而do_bind会根据连接的类型调用内核函数udp_bind、tcp_bind、和raw_bind;当do_bind执行完后,它会释放信号量,这使被阻塞的netconn_bind得以继续执行。

协议栈接口

数据缓冲netbuf

netbuf是API描述数据的一种方式,应用程序可以使用该结构管理发送数据,接收数据的缓冲区。

struct netbuf {
  struct pbuf *p, *ptr;
  ip_addr_t addr;
  u16_t port;
};

字段p指向pbuf链表,字段ptr也指向该netbuf的pbuf链表,但p一直指向pbuf链表中的第一个pbuf结构,而ptr可能指向链表中的其他位置,与该指针调整密切相关的函数是netbuf_next和netbuf_first.还有两个字段addr和port分别记录数据发送方的IP地址和端口号。

image

操作数据缓冲

无论是UDP连接还是TCP连接,当协议栈接收到数据包后,会将数据封装在一个netbuf中,并递交给应用程序。在数据发送时,不同的类型的连接将导致不同的数据处理方式:对于TCP连接,用户只需要提供待发送数据的起始地址和长度,内核会根据实际情况将数据封装在合适大小的数据包中,并放入发送队列;对于UDP来说,用户需要自行将数据封装在netbuf结构中,当发送函数被调用时,内核直接将该数据包中的数据发送出去。

函数名 函数功能
netbuf_new 申请一个新的netbuf空间,但不分配任何数据空间,函数返回一个netbuf结构。
netbuf_delete 释放一个netbuf结构空间。
netbuf_alloc 为netbuf结构分配size大小的数据空间,返回数据空间的起始地址(pbuf的payload指向的地址)。
netbuf_free 释放netbuf结构指向的数据pbuf,若pbuf指针为空,则没有任何空间被释放。
netbuf_ref 与netbuf_alloc相似,区别在于它不会分配具体的数据区域,只会分配一个pbuf首部结构,并将pbuf的payload指针指向数据地址dataptr。
netbuf_chain 将tail中的pbuf连接到head中的pbuf之后,调用此函数,tail结构会被删除,用户不能在应用这个netbuf。
netbuf_data 取出ptr记录的pbuf中的数据。将netbuf结构中ptr指针记录的pbuf数据填入dataptr,同时将该pbuf中的数据长度填入到len中。
netbuf_next 将netbuf结构的ptr指针指向pbuf链表中的下一个pbuf结构。
netbuf_first 将netbuf结构的ptr指针指向第一个pbuf。

连接结构netconn

netconn为用户提供一个统一的编程接口,不管连接的类型是UDP还是TCP,系统API函数对各种连接类型的操作函数进行了统一的封装。用户程序直接忽略连接类型的差异,使用统一的结构和编程函数。

//枚举类型,用于描述连接类型
enum netconn_type {
  NETCONN_INVALID    = 0,            //无效类型
  NETCONN_TCP        = 0x10,         //TCP
  NETCONN_UDP        = 0x20,         //UDP
  NETCONN_UDPLITE    = 0x21,         //UDPLite
  NETCONN_UDPNOCHKSUM= 0x22,         //无校验UDP
  NETCONN_RAW        = 0x40          //原始连接
};
//枚举类型,用于描述连接状态,主要在TCP连接中使用
enum netconn_state {
  NETCONN_NONE,                     //不处于任何状态
  NETCONN_WRITE,                    //正在发送数据
  NETCONN_LISTEN,                   //侦听状态
  NETCONN_CONNECT,                  //连接状态
  NETCONN_CLOSE                     //关闭状态
};
//定义函数指针类型netconn_callback
typedef void (* netconn_callback)(struct netconn *, enum netconn_evt, u16_t len);
//连接结构
struct netconn {
  enum netconn_type type;          //描述了当前连接的类型 TCP、UDP或者RAW
  enum netconn_state state;        //当前连接的状态
  union {                          //共用体 记录与连接相关的内核控制块(UDP控制块、TCP控制块)
    struct ip_pcb  *ip;            //IP控制块
    struct tcp_pcb *tcp;           //tcp控制块
    struct udp_pcb *udp;           //udp控制块
    struct raw_pcb *raw;           //RAW控制块
  } pcb;
  err_t last_err;                  /** the last error this netconn had */
  sys_sem_t op_completed;          //信号量,用于两部分API同步
  sys_mbox_t recvmbox;             //接受数据的邮箱,也可看做时数据缓冲队列
#if LWIP_TCP
  sys_mbox_t acceptmbox;//用于TCP服务端,连接请求的缓冲队列
#endif /* LWIP_TCP */
#if LWIP_SOCKET
  int socket;           //socket描述符,实现socket API时使用到
#endif /* LWIP_SOCKET */
#if LWIP_SO_RCVBUF
  int recv_bufsize;    //数据邮箱recvmbox上可缓存的最大数据长度
  s16_t recv_avail;    //数据邮箱recvmbox中已缓存的数据长度
#endif /* LWIP_SO_RCVBUF */
  u8_t flags;         //netconn的更多状态标识,比如在发送缓冲不够时,发送是够阻塞;是否自动更新内核接收窗口等
#if LWIP_TCP
  size_t write_offset; //当调用netconn_write发送数据但缓存不够时,数据会被暂时封装在write_msg中等待下一次发送,write_offset记录下一次发送时的索引
  struct api_msg_msg *current_msg;
#endif /* LWIP_TCP */
  netconn_callback callback;   //连接相关回调函数,实现Socket API时使用到
};

内核回调接口

协议栈API时基于Raw/Callback API来实现的,它与内核交互的方式也只能通过回调。这些回调函数被默认的注册到相关控制块的相关字段。其中UDP有1个函数,TCP有6个。

  1. recv_udp UDP内核接收到数据后,将回调时的参数(数据包pbuf)组装成一个上层的数据包格式netbuf,并把这个netbuf投递到连接netconn的邮箱中。

  2. accept_function TCP服务器接受一个新的连接请求并完成三次握手后,此函数被回调,使用新连接的TCP控制块建立一个新连接结构netconn,并将该结构投递到服务器连接的acceptmbox邮箱中。

  3. sent_tcp 当本地待确认数据被对方TCP报文中的ACK确认后,TCP控制块中的sen_tcpt函数将被回调执行。它将调用do_writemore检查当前连接的netconn是否仍然有数据需要发送,若是则发送数据,并在所有数据发送完成后,释放netconn结构中的信号op_completed。

  4. recv_tcp 当TCP内核接收到关于某个TCP控制块的数据后,在控制块上注册的用户函数recv_tcp会被调用。它将数据包pbuf递交到连接结构netconn的recvmbox邮箱上。

  5. do_connected 作为TCP客户端,在向服务器发起连接请求,并完成三次握手后,TCP控制块中的do_connected函数将被回调执行。它的功能是释放信号量op_conpleted。

  6. poll_tcp TCP慢定时器周期性的调用poll_tcp(4个慢时钟时长,2S),检查连接netconn是否处于数据发送状态,若是,则调用函数do_writemore将连接上的剩余数据发送出去。

  7. err_tcp 当某个连接上出现错误时,函数会被回调执行。它会向netconn结构的两个邮箱中投递一条空消息,告诉上层,当前连接发送错误,同时函数还会判断当前是否有API函数阻塞在连接上,如果有,则释放一个信号量op_completed,让API函数解除阻塞。

API函数

函数名 函数功能
netconn_new 为新连接申请一个连接结构netconn空间,并需要指明新连接的类型,常用值为NETCONN_TCP,NETCONN_UDP。
netconn_delete 删除一个连接结构netconn。
netconn_getaddr 获得一个连接结构netconn的源IP地址和源端口号或者目的IP地址和目的端口号。
netconn_bind 将一二连接结构与本地IP地址addr(IP_ADDR_ANY代表任何一个网络接口的IP地址)和端口号port进行绑定。
netconn_connet 连接服务器,它将连接机构与目的IP地址和目的端口号port进行绑定。
netconn_disconnect 与服务器断开连接(只作用在UDP)。
netconn_listen 将连接结构netconn置为侦听状态。
netconn_accept 从acceptmbox邮箱中获得一个新建立的连接。
netconn_recv 从连接的recvmbox邮箱中接收数据包。
netconn_send 用于已经建立的UDP连接上发送数据。
netconn_write 在稳定的TCP连接上发送数据,参数dataptr和size分别指出了待发送数据的起始地址和长度,该函数并不要求用户将数据封装在netbuf中,对数据长度没有限制。
netconn_close 关闭一个TCP连接。

以上函数的执行过程一般和前面介绍的netconn_bind函数过程差不多。

posted @ 2022-07-12 17:36  不明白就去明白  阅读(893)  评论(0编辑  收藏  举报