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
- 参考不带操作系统移植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);
}