2009-05-12   LWIP TCP 层接收相关

 

       既然定了这么个标题,当然是要从 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);

}

看最后一条语句就好了,很熟悉是吧,对了,就是上面传说中的死胡同,到此也解开了。