LWIP应用开发|RAW API编程模型

RAW API编程模型

1. UDP编程模型

1.1 RAW API中与UDP相关的函数

LWIP的RAW API编程方式是基于回调机制的,当初始化应用的时候必须为内核中不同的事件注册相应的回调函数,当相应的事件发生时这些回调函数就会被调用。下表中给出了UDP的部分RAW API功能函数,可以使用这些函数来完成UDP的数据发送和接收

RAW API函数 函数说明
udp_new 新建一个 UDP 的 PCB 块
udp_remove 将一个PCB控制块从链表中删除,并释放这个控制块的内存
udp_bind 为UDP的PCB控制块绑定一个本地IP地址和端口号
udp_connect 连接到指定IP地址主机的指定端口上
udp_disconnect 断开连接,将控制块设置为非连接状态
udp_send 通过一个PCB控制块发送数据
udp_recv 需要创建的一个回调函数,当接收到数据的时候被调用
1.2 LWIP中的UDP协议函数

LWIP源码中的udp.c和udp.h这两个文件是关于UDP协议的。其中与UDP报文处理有关的函数之间的关系如下图示
在这里插入图片描述

2. TCP编程模型

2.1 RAW API中与TCP相关的函数

LWIP提供了很多关于TCP的RAW API编程函数,可以使用这些函数来完成TCP的数据发送和接收。下表列出了部分函数

函数分组 API函数 函数功能描述
TCP连接建立 tcp_new() 创建一个TCP的PCB 控制块
tcp_bind() 为TCP的PCB控制块绑定一个本地IP地址和端口号
tcp_listen() 开始TCP的PCB监听
tcp_accept() 控制块accept字段注册的回调函数,侦听到连接时被调用
tcp_accepted() 通知LWIP协议栈一个TCP连接被接受了
tcp_conect() 连接远端主机
发送TCP数据 tcp_write() 构造一个报文并放到控制块的发送缓冲队列中
tcp_sent() 控制块sent字段注册的回调函数,数据发送成功后被回调
tcp_output() 将发送缓冲队列中的数据发送出去
接收TCP数据 tcp_recv() 控制块recv字段注册的回调函数,当接收到新数据时被调 用
tcp_recved() 程序处理完数据后要调用这个函数,通知内核更新接收窗口
轮询函数 tcp_poll() 控制块poll字段注册的回调函数,该函数周期性调用
关闭和中止连接 tcp_close() 关闭一个TCP连接
tcp_err() 控制块err字段注册的回调函数,遇到错误时被调用
tcp_abort() 中断TCP连接
2.2 LWIP中的TCP协议函数

LWIP源码中的tcp.c、tcp.h、tcp_in.c、tcp_out.c这些文件是关于TCP协议的。TCP层中函数的关系如下图示
在这里插入图片描述

3. RAW API编程实例

3.1 使用RAW API实现TCP回响服务器

使用RAW API实现TCP回响服务器,实现发送任意数据即回响相同的数据。TCP回响服务器源码已经在STM32CubeMX中的F4固件包中实现,可在以下文件夹中找到源码文件tcp_echoserver.c和tcp_echoserver.h;将这两个文件移植到相应工程中即可。

C:\Users\Administrator\STM32Cube\Repository\STM32Cube_FW_F4_V1.25.2\Projects\STM324x9I_EVAL\Applications\LwIP\LwIP_TCP_Echo_Server

移植tcp_echoserver.c和相应头文件
main.c中添加tcp_echoserver_init函数
编译烧写后使用NC命令连接
发送任意数据等待回响
  • 参考不带操作系统移植LWIP将LWIP移植到F4开发板中,并能够ping通开发板。
  • 在main主函数下添加tcp_echoserver_init()函数
  /* USER CODE BEGIN 2 */
  tcp_echoserver_init();
  printf("System is Start!\r\n");
  /* USER CODE END 2 */
  • 编译烧写后使用NC命令连接开发板;发送任意字符后立即返回相同的字符

在这里插入图片描述

3.2 TCP回响服务器源码分析
static struct tcp_pcb *tcp_echoserver_pcb;
/* ECHO protocol states */
enum tcp_echoserver_states{
    
    
  ES_NONE = 0,
  ES_ACCEPTED,
  ES_RECEIVED,
  ES_CLOSING
};
/* structure for maintaing connection infos to be passed as argument 
   to LwIP callbacks*/
struct tcp_echoserver_struct{
    
    
  u8_t state;             /* current connection state */
  u8_t retries;
  struct tcp_pcb *pcb;    /* pointer on the current tcp_pcb */
  struct pbuf *p;         /* pointer on the received/to be transmitted pbuf */
};

static err_t tcp_echoserver_accept(void *arg, struct tcp_pcb *newpcb, err_t err);
static err_t tcp_echoserver_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
static void tcp_echoserver_error(void *arg, err_t err);
static err_t tcp_echoserver_poll(void *arg, struct tcp_pcb *tpcb);
static err_t tcp_echoserver_sent(void *arg, struct tcp_pcb *tpcb, u16_t len);
static void tcp_echoserver_send(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es);
static void tcp_echoserver_connection_close(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es);
/* 初始化tcp echo服务器 */
void tcp_echoserver_init(void){
    
    
  /* 创建新的tcp内存块 */
  tcp_echoserver_pcb = tcp_new();
  if (tcp_echoserver_pcb != NULL){
    
    
    err_t err;    
    /* 将创建的tcp控制块绑定到本机的ip和端口7上 */
    err = tcp_bind(tcp_echoserver_pcb, IP_ADDR_ANY, 7);    
    if (err == ERR_OK){
    
    
      /* 开启监听,若监听成功会返回一个新的tcp控制块并释放掉之前的控制块 */
      tcp_echoserver_pcb = tcp_listen(tcp_echoserver_pcb);      
      /* 等待客户端连接 */
      tcp_accept(tcp_echoserver_pcb, tcp_echoserver_accept);
    }
    else{
    
    
      /* 若绑定失败,则释放掉tcp控制块 */
      memp_free(MEMP_TCP_PCB, tcp_echoserver_pcb);
    }
  }
}
/**
  * @brief  tcp_accept函数的回调函数的实现
  * @param  arg: not used
  * @param  newpcb: 指向客户端的tcp的控制块
  * @param  err: not used 
  * @retval err_t: error status
  */
static err_t tcp_echoserver_accept(void *arg, struct tcp_pcb *newpcb, err_t err){
    
    
  err_t ret_err;
  struct tcp_echoserver_struct *es;
  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(err);
  /* 设置客户端的tcp的优先级为最低 */
  tcp_setprio(newpcb, TCP_PRIO_MIN);
  /* 动态分配tcp_echoserver_struct */
  es = (struct tcp_echoserver_struct *)mem_malloc(sizeof(struct tcp_echoserver_struct));
  if (es != NULL){
    
    	//只要涉及到动态分配,都需要判断返回地址是否正确
    es->state = ES_ACCEPTED;	//当前server的状态为客户接入完成
    es->pcb = newpcb;	//pcb指向客户的tcp控制块
    es->retries = 0;	//重复次数赋值为0
    es->p = NULL;    	//数据接收/发送的缓冲区指向空
    /* 将参数传入到pcb控制块中 */
    tcp_arg(newpcb, es);    
    /* 初始化接收的回调函数tcp_echoserver_recv */ 
    tcp_recv(newpcb, tcp_echoserver_recv);   
    /* 初始化错误产生后的回调函数  */
    tcp_err(newpcb, tcp_echoserver_error);  
    /* 初始化轮询的回调函数,若intercal为0,则不启用轮询功能 */
    tcp_poll(newpcb, tcp_echoserver_poll, 0);   
    ret_err = ERR_OK;
  }
  else{
    
    	//若创建tcp_echoserver_struct失败
    /* 关闭tcp连接 */
    tcp_echoserver_connection_close(newpcb, es);
    /* 返回内存分配失败 */
    ret_err = ERR_MEM;
  }
  return ret_err;  
}
/**
  * @brief  接收回调函数的实现
  * @param  arg: 这个就是tcp_echoserver_struct
  * @param  tpcb: 当前使用的tcp控制块
  * @param  pbuf: 指向了接收到数据的缓冲区,这个pbuf是lwip内部接收到数据后,自动分配的
  * @param  err: 错误信息
  * @retval err_t: 错误代码
  */
static err_t tcp_echoserver_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err){
    
    
  struct tcp_echoserver_struct *es;
  err_t ret_err;
  LWIP_ASSERT("arg != NULL",arg != NULL);  
  es = (struct tcp_echoserver_struct *)arg;  //获取到当前tcp_echoserver_struct
  /* 若接收到的数据为空 */
  if (p == NULL){
    
    
    /* 改变状态为关闭 */
    es->state = ES_CLOSING;
    if(es->p == NULL){
    
    	//判断是否还需要发送数据
      /* 不发送数据,则直接关闭tcp连接 */
      tcp_echoserver_connection_close(tpcb, es);
    }
    else{
    
    
      /* 创建发送数据完成后的回调函数 */
      tcp_sent(tpcb, tcp_echoserver_sent);
      /* 触发发送,并发送到网卡上 */
      tcp_echoserver_send(tpcb, es);
    }
    ret_err = ERR_OK;
  }   
  /* 收到数据但是产生了错误 */
  else if(err != ERR_OK){
    
    
    /* 释放接收缓冲区 */
    if (p != NULL){
    
    
      es->p = NULL;
      pbuf_free(p);
    }
    ret_err = err;
  }
  else if(es->state == ES_ACCEPTED){
    
    
    /* 改变状态为接收 */
    es->state = ES_RECEIVED;  
    /* 把接收的pbuf的地址存放到我们的本地结构体里 */
    es->p = p; 
    /* 进行应答 */
    tcp_sent(tpcb, tcp_echoserver_sent);   
    /* 发送数据到网卡 */
    tcp_echoserver_send(tpcb, es);    
    ret_err = ERR_OK;
  }
  else if (es->state == ES_RECEIVED){
    
    
    /* 判断发送缓冲区里是否有未发送的数据,若没有 */
    if(es->p == NULL){
    
    
      es->p = p;  //把pbuf进行存储 
      /* 进行发送 */
      tcp_echoserver_send(tpcb, es);
    }
    else{
    
    	//有数据需要发送
      struct pbuf *ptr;
      /* 由于之前数据没有发送完成,需要进行数据连接 */
      ptr = es->p;	//获取之前待发送的地址
      pbuf_chain(ptr,p);	//进行首尾连接
    }
    ret_err = ERR_OK;
  }		
  else if(es->state == ES_CLOSING){
    
    	//处于关闭状态
    /* 释放pbuf */
    tcp_recved(tpcb, p->tot_len);
    es->p = NULL;
    pbuf_free(p);
    ret_err = ERR_OK;
  }
  else{
    
    
    /* 释放pbuf */
    tcp_recved(tpcb, p->tot_len);
    es->p = NULL;
    pbuf_free(p);
    ret_err = ERR_OK;
  }
  return ret_err;
}
/**
  * @brief  当产生错误时,lwip调用此函数 
  * @param  arg: 指向我们 tcp_echoserver_struct 
  * @param  err: not used
  * @retval None
  */
static void tcp_echoserver_error(void *arg, err_t err){
    
    
  struct tcp_echoserver_struct *es;
  LWIP_UNUSED_ARG(err);
  es = (struct tcp_echoserver_struct *)arg;
  if (es != NULL){
    
    
    /* 释放tcp_echoserver_struct */
    mem_free(es);
  }
}
/**
  * @brief  This function implements the tcp_poll LwIP callback function
  * @param  arg: pointer on argument passed to callback
  * @param  tpcb: pointer on the tcp_pcb for the current tcp connection
  * @retval err_t: error code
  */
static err_t tcp_echoserver_poll(void *arg, struct tcp_pcb *tpcb){
    
    
  err_t ret_err;
  struct tcp_echoserver_struct *es;
  es = (struct tcp_echoserver_struct *)arg;
  if (es != NULL){
    
    
    if (es->p != NULL){
    
    	  //有数据需要发送
      tcp_sent(tpcb, tcp_echoserver_sent);
      /* there is a remaining pbuf (chain) , try to send data */
      tcp_echoserver_send(tpcb, es);
    }
    else{
    
    
      /* 判断状态释放为关闭  */
      if(es->state == ES_CLOSING){
    
    
        /* 关闭tcp连接 */
        tcp_echoserver_connection_close(tpcb, es);
      }
    }
    ret_err = ERR_OK;
  }
  else{
    
    
    /* 若没有创建tcp_echoserver_struct */
    tcp_abort(tpcb);	//终止tcp任务
    ret_err = ERR_ABRT;
  }
  return ret_err;
}
/**
  * @brief  发送完成以后,调用该回调函数
  * @param  None
  * @retval None
  */
static err_t tcp_echoserver_sent(void *arg, struct tcp_pcb *tpcb, u16_t len){
    
    
  struct tcp_echoserver_struct *es;
  LWIP_UNUSED_ARG(len);
  es = (struct tcp_echoserver_struct *)arg;
  es->retries = 0;  
  if(es->p != NULL){
    
    
    /* 还有数据需要继续发送 */
    tcp_sent(tpcb, tcp_echoserver_sent);
    tcp_echoserver_send(tpcb, es);
  }
  else{
    
    
    /* 需要关闭,才关闭 */
    if(es->state == ES_CLOSING)
      tcp_echoserver_connection_close(tpcb, es);
  }
  return ERR_OK;
}
/**
  * @brief  把数据发送到网卡
  * @param  tpcb: pointer on the tcp_pcb connection
  * @param  es: pointer on echo_state structure
  * @retval None
  */
static void tcp_echoserver_send(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es){
    
    
  struct pbuf *ptr;
  err_t wr_err = ERR_OK;
  while ((wr_err == ERR_OK) &&	//tcp任务没有出错
         (es->p != NULL) && 	//有待发送的数据
         (es->p->len <= tcp_sndbuf(tpcb)))	//要发送的数据,其长度小于tcp发送长度
  {
    
       
    /* 获取待发送的buf的指针 */
    ptr = es->p;
    /* 调用write发送数据到网卡上 */
    wr_err = tcp_write(tpcb, ptr->payload, ptr->len, 1); 
    if (wr_err == ERR_OK){
    
    
      u16_t plen;
      u8_t freed;
      plen = ptr->len;   //获取剩余长度  
      /* 若pbuf链表还有其他,再次发送 */
      es->p = ptr->next;      
      if(es->p != NULL){
    
    
        /* 刷新计数值 */
        pbuf_ref(es->p);
      }      
     /* chop first pbuf from chain */
      do
      {
    
    
        /* 释放pbuf */
        freed = pbuf_free(ptr);
      }while(freed == 0);
     /* 进行接收 */
     tcp_recved(tpcb, plen);
   }
   else if(wr_err == ERR_MEM){
    
    
      /* 进行重发 */
     es->p = ptr;
   }
   else{
    
    
     /* other problem ?? */
   }
  }
}
/**
  * @brief  This functions closes the tcp connection
  * @param  tcp_pcb: pointer on the tcp connection
  * @param  es: pointer on echo_state structure
  * @retval None
  */
static void tcp_echoserver_connection_close(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es){
    
      
  /* 移植所有的回调函数 */
  tcp_arg(tpcb, NULL);
  tcp_sent(tpcb, NULL);
  tcp_recv(tpcb, NULL);
  tcp_err(tpcb, NULL);
  tcp_poll(tpcb, NULL, 0);  
  /* 释放tcp_echoserver_struct内存空间 */
  if (es != NULL){
    
    
    mem_free(es);
  }    
  /* 关闭tcp连接,这里会释放pcb控制块的内存 */
  tcp_close(tpcb);
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Chuangke_Andy/article/details/113116197