LWIP之TCP层接收相关 tcp_recv的实现(转)

本文深入探讨了lwIP中recvfrom函数的工作原理,详细解析了数据如何从netconn的recvmbox传递到应用程序的过程,包括关键函数如tcp_recved的作用及调用流程。
AI助手已提取文章相关产品:
标签:
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。 http://bluefish.blog.51cto.com/214870/158416
既然定了这么个标题,当然是要从 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);
}
看最后一条语句就好了,很熟悉是吧,对了,就是上面传说中的死胡同,到此也解开了。
 

本文出自 “bluefish” 博客,请务必保留此出处http://bluefish.blog.51cto.com/214870/158416

您可能感兴趣的与本文相关内容

### `tcp_recv` 函数的作用和使用方法 `tcp_recv` 是 lwIP 协议栈中用于设置接收回调函数的关键接口,其作用是当远程主机发送数据到本地 TCP 连接时,触发用户指定的回调函数来处理接收到的数据。该函数允许用户在数据到达时进行异步处理,而无需轮询接收缓冲区。 #### 函数原型 ```c void tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv); ``` #### 参数说明 - **`struct tcp_pcb *pcb`** 该参数是当前 TCP 连接的控制块指针,表示与远程主机的连接状态。通过该指针,lwIP 能够识别是哪个连接接收到数据,并调用相应的回调函数处理。 - **`tcp_recv_fn recv`** 这是一个函数指针类型,指向用户定义的接收处理函数。该函数的原型如下: ```c err_t my_recv_function(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err); ``` - `void *arg`:用户自定义上下文指针,通常在设置回调时传入,用于传递状态或结构体。 - `struct tcp_pcb *pcb`:当前连接的 PCB 指针,用于操作连接状态。 - `struct pbuf *p`:指向接收到的数据缓冲区,若为 `NULL` 表示连接已关闭或发生错误。 - `err_t err`:表示接收状态,若为 `ERR_OK` 表示数据接收成功。 #### 使用方法 在连接建立后,用户需要通过 `tcp_recv` 设置接收回调函数。每当有数据到达时,lwIP 会调用该回调函数,并将接收到的数据封装在 `pbuf` 结构中传递给用户。用户处理完数据后,必须调用 `tcp_recved` 函数通知 lwIP 已经处理完数据,以便更新接收窗口大小。 示例代码如下: ```c err_t my_recv_function(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { if (err != ERR_OK || p == NULL) { // 连接关闭或发生错误 tcp_close(pcb); return err; } // 处理接收到的数据 char *data = (char *)p->payload; int length = p->len; // 假设回显数据 tcp_write(pcb, data, length, TCP_WRITE_FLAG_COPY); // 通知 lwIP 数据已处理完毕 tcp_recved(pcb, p->len); // 释放 pbuf pbuf_free(p); return ERR_OK; } // 在 accept 回调中设置接收函数 tcp_recv(pcb, my_recv_function); ``` #### 注意事项 - **必须调用 `tcp_recved`** 用户在处理完接收到的数据后,必须调用 `tcp_recved(pcb, len)` 来通知 lwIP 接收窗口可以更新。否则,接收窗口会逐渐缩小,最终导致远程主机无法继续发送数据 [^2]。 - **正确释放 `pbuf`** 接收到的数据存储在 `pbuf` 中,处理完成后必须调用 `pbuf_free(p)` 释放内存,避免内存泄漏。 - **避免在发送回调中调用 `tcp_recved`** 如果在发送回调函数中调用 `tcp_recved`,会导致接收窗口管理混乱,影响后续数据接收 [^2]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值