LWIP之TCP层接收相关

本文详细介绍了lwIP中TCP数据的接收流程,重点分析了recvfrom函数的工作原理及其实现细节,包括数据如何从网络层传递到应用层。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

既然定了这么个标题,当然是要从socket的recv来讲了。这里主要涉及到lwip_recvfrom这个函数。它的大致过程是,先通过netconn_recv(sock->conn);从netconn的recvmbox中收取数据,在这里有个do_recv的调用,而do_recv又调用了tcp_recved,关于这个函数的注释如下:
* This function should be called by the application when it has
* processed the data. The purpose is to advertise a larger window
* when the data has been processed.
知道了,这里的do_recv只是起到一个知会的作用,可能的话会对接收条件做一些调整。

回到主题,lwip_recvfrom从netconn接收完数据就是要copy the contents of the received buffer into the supplied memory pointer mem,这一步是通过netbuf_copy_partial函数来完成的。

接着往下走,我们发现,数据的来源是在netconn的recvmbox,刚才提到过的。好了,那么是在什么地方,什么时候这个recvmbox被填充的呢?

在工程中搜索recvmbox,发现在recv_tcp函数有这样一句:
sys_mbox_trypost(conn->recvmbox, p)
ok,就是它了。看看函数的原型:
* Receive callback function for TCP netconns.
* Posts the packet to conn->recvmbox, but doesn't delete it on errors.
static err_t recv_tcp(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
好的,p是个输入参数,并且这是个静态函数,是作为tcp的接收回调用的。

嗯接着看recv_tcp的caller:
* Setup a tcp_pcb with the correct callback function pointers
* and their arguments.
* @param conn the TCP netconn to setup
static void setup_tcp(struct netconn *conn)
{
struct tcp_pcb *pcb;

pcb = conn->pcb.tcp;
tcp_arg(pcb, conn);
tcp_recv(pcb, recv_tcp);
tcp_sent(pcb, sent_tcp);
tcp_poll(pcb, poll_tcp, 4);
tcp_err(pcb, err_tcp);
}
哈哈,可谓是一网打尽啊。对于这个函数中的几个调用,我们看一个就好了,别的实现也差不多,就是个赋值的过程

Void tcp_recv(struct tcp_pcb *pcb,
err_t (* recv)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err))
{
pcb->recv = recv;
}
setup_tcp上面也有讲过,是在newconn的时候被调用的,创建完pcb,就是调用的这个函数,以配置连接的各个回调函数。

然而到这里似乎走了一个死胡同里了,貌似没有什么地方对pcb->recv有调用的,而唯一有的就是接收TCP事件TCP_EVENT_RECV的宏定义中。同时,其他的几个函数也是类似的情况,例如send_tcp函数。

真是“山穷水尽疑无路,柳暗花明又一村”啊。原来上面的也不只是死胡同。这里就要从tcp的三大接收处理函数说起了。
最底层的(在tcp层)就是tcp_input,它是直接被ip层调用的,该函数的定义注释是这么写的:
* The initial input processing of TCP. It verifies the TCP header, demultiplexes
* the segment between the PCBs and passes it on to tcp_process(), which implements
* the TCP finite state machine. This function is called by the IP layer (in ip_input()).

Tcp_input又调用了tcp_process函数做进一步的处理,它的定义注释如下:
* Implements the TCP state machine. Called by tcp_input. In some
* states tcp_receive() is called to receive data. The tcp_seg
* argument will be freed by the caller (tcp_input()) unless the
* recv_data pointer in the pcb is set.

是的,下面是tcp_receive函数,被tcp_process调用
* Called by tcp_process. Checks if the given segment is an ACK for outstanding
* data, and if so frees the memory of the buffered data. Next, is places the
* segment on any of the receive queues (pcb->recved or pcb->ooseq). If the segment
* is buffered, the pbuf is referenced by pbuf_ref so that it will not be freed until
* i it has been removed from the buffer.

然而光有这些调用顺序是不行的,最重要的是下面两个变量【都在tcp_in.c中】
static u8_t recv_flags;
static struct pbuf *recv_data;

在tcp_receive中有以下主要几句
if (inseg.p->tot_len > 0)
{
recv_data = inseg.p;
}

if (cseg->p->tot_len > 0)
{
/* Chain this pbuf onto the pbuf that we will pass to
the application. */
if (recv_data)
{
pbuf_cat(recv_data, cseg->p);
}
else
{
recv_data = cseg->p;
}
cseg->p = NULL;
}

下面的这个是tcp_input中的,是在tcp_process处理完之后的
if (recv_data != NULL)
{
if(flags & TCP_PSH)
{
recv_data->flags |= PBUF_FLAG_PUSH;
}

/* Notify application that data has been received. */
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
}
看最后一条语句就好了,很熟悉是吧,对了,就是上面传说中的死胡同,到此也解开了。
### LWIPTCP 接收数据的实现方式 LwIP 是一种轻量级的嵌入式 TCP/IP 协议栈,其设计目标是在资源受限的环境中提供高效的网络通信能力。对于 TCP 数据接收功能,LwIP 提供了一套完善的接口来处理数据流。 #### 1. **核心概念** 在 LwIPTCP 模块中,`tcp_recv()` 函数是一个重要的回调函数,用于通知应用程序有新的数据到达[^1]。当远程主机发送的数据包通过底驱动程序传递到 LwIP 后,这些数据会被存储在一个 PBUF(Packet Buffer)链表中,并触发 `tcp_recv()` 回调函数的通知过程。 #### 2. **典型流程** 以下是基于 LwIPTCP 数据接收的主要工作流程: - 当接收到新数据时,LwIP 将数据存放在内部缓冲区并调用用户的 `recv_callback`。 - 用户可以在回调函数中读取数据或将数据复制到应用缓存中。 - 如果不需要立即处理数据,可以暂时保留数据直到后续操作完成。 #### 3. **代码示例** 下面展示了一个简单的 TCP 客户端/服务器模型下的数据接收逻辑: ```c #include "lwip/tcp.h" err_t tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { if (err == ERR_OK) { tcp_arg(newpcb, arg); tcp_recv(newpcb, tcp_recv_callback); // 设置接收回调 } return ERR_OK; } static err_t tcp_recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if (!p) { // 对方关闭连接 tcp_close(tpcb); return ERR_OK; } u8_t data[p->tot_len]; memcpy(data, p->payload, p->len); printf("Received %d bytes of data\n", p->len); tcp_recved(tpcb, p->len); // 告诉 lwIP 已经处理了这么多字节 pbuf_free(p); // 释放 pbuf 缓冲区 return ERR_OK; } ``` 上述代码展示了如何设置一个 TCP 接受回调以及处理传入数据的过程。其中的关键点在于: - 使用 `tcp_recv()` 注册回调函数以捕获传入的数据。 - 处理完成后需调用 `tcp_recved()` 来告知协议栈已成功消费指定数量的数据[^2]。 #### 4. **注意事项** 需要注意的是,在某些情况下可能需要额外考虑错误恢复机制或者更复杂的流量控制策略。例如,如果应用无法及时消耗大量到来的数据,则可能导致内存耗尽或其他异常情况发生。因此建议开发者仔细阅读官方文档或参考其他成熟项目中的最佳实践[^3]。 另外,在实际开发过程中还需要关注特定硬件平台上的初始化细节,比如 PHY 设备复位等功能配置[^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值