首页 > 代码库 > lwip socket探秘之recv

lwip socket探秘之recv

一个基本的socket建立顺序是
Server端:
  • socket()
  • bind()
  • listen()
  • accept()
  • recv()
Client端:
  • socket()
  • connect()
  • send()
 
本文着重介绍Server端的recv()过程。
 
前一篇文章中,accept()生成了一个新的socket,作为server端socket,用于某一个特定client和server通信。本文中我们把它叫做专属socket,和用作listen的socket以示区别。
下面用户就要使用这个新socket来接收client端数据了。接收的方法是recv()函数,即lwip_recv()。
1 int2 lwip_recv(int s, void *mem, size_t len, int flags)3 {4   return lwip_recvfrom(s, mem, len, flags, NULL, NULL);5 }
注意上面的s都是专属socket。
 1 int 2 lwip_recvfrom(int s, void *mem, size_t len, int flags, 3         struct sockaddr *from, socklen_t *fromlen) 4 { 5 ............. 6     do{ 7 ............. 8         sock->lastdata = http://www.mamicode.com/buf = netconn_recv(sock->conn); // 专属socket->conn 9 .............10     }11 }
 1 /** 2 * Receive data (in form of a netbuf containing a packet buffer) from a netconn 3 * 4 * @param conn the netconn from which to receive data 5 * @return a new netbuf containing received data or NULL on memory error or timeout 6 */ 7 struct netbuf * 8 netconn_recv(struct netconn *conn) 9 {10 .............11     if (sys_arch_mbox_fetch(conn->recvmbox, (void *)&p, conn->recv_timeout)==SYS_ARCH_TIMEOUT) { // 可见recv()函数实际上是从专属sock->conn->recvmbox上取数据。12       memp_free(MEMP_NETBUF, buf);13       conn->err = ERR_TIMEOUT;14       return NULL;15     }16 ..............17 }
那么这个数据是怎么被放上来的呢?也就是tcp层收到数据后,是怎么区分不同的socket并放到某一个socket->conn上的呢?
我们还是从tcp层的入口,即tcp_input()中看。
 1 void 2 tcp_input(struct pbuf *p, struct netif *inp) 3 { 4 ........ 5   for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) { // 当client端发TCP segment过来时,根据client端和server端的ip地址和port号,显然在active pcbs链表里可以找到对应的pcb,这也是一个此client专属的pcb 6     LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED); 7     LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT); 8     LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN); 9     if (pcb->remote_port == tcphdr->src &&10        pcb->local_port == tcphdr->dest &&11        ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)) &&12        ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest))) {13 14       /* Move this PCB to the front of the list so that subsequent15          lookups will be faster (we exploit locality in TCP segment16          arrivals). */17       LWIP_ASSERT("tcp_input: pcb->next != pcb (before cache)", pcb->next != pcb);18       if (prev != NULL) {19         prev->next = pcb->next;20         pcb->next = tcp_active_pcbs;21         tcp_active_pcbs = pcb;22       }23       LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb);24       break;25     }26     prev = pcb;27   }28 .........29     err = tcp_process(pcb); //我们之前分析过的函数30 .........31     if (err != ERR_ABRT) {32       if (recv_flags & TF_RESET) {33         /* TF_RESET means that the connection was reset by the other34            end. We then call the error callback to inform the35            application that the connection is dead before we36            deallocate the PCB. */37         TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_RST);38         tcp_pcb_remove(&tcp_active_pcbs, pcb);39         memp_free(MEMP_TCP_PCB, pcb);40       } else if (recv_flags & TF_CLOSED) {41         /* The connection has been closed and we will deallocate the42            PCB. */43         tcp_pcb_remove(&tcp_active_pcbs, pcb);44         memp_free(MEMP_TCP_PCB, pcb);45       } else {46         err = ERR_OK;47         /* If the application has registered a "sent" function to be48            called when new send buffer space is available, we call it49            now. */50         if (pcb->acked > 0) {51           TCP_EVENT_SENT(pcb, pcb->acked, err);52         }53      54         if (recv_data != NULL) {55           if(flags & TCP_PSH) {56             recv_data->flags |= PBUF_FLAG_PUSH;57           }58 59           /* Notify application that data has been received. */60           TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err); // 这里把接收到的数据recv_data通过这个宏进行处理,实际上是调用了函数recv_tcp()61 62 ...............63         }64       }65     }66 ..............67 }
我们来看一下函数recv_tcp(),这里就有答案了:
 1 /** 2 * Receive callback function for TCP netconns. 3 * Posts the packet to conn->recvmbox, but doesn‘t delete it on errors. 4 * 5 * @see tcp.h (struct tcp_pcb.recv) for parameters and return value 6 */ 7 static err_t 8 recv_tcp(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) // 注意这里的pcb是client专属pcb,p就是上面的recv_data,指向TCP层收到并处理好的数据 9 {10   struct netconn *conn;11   u16_t len;12 13   LWIP_UNUSED_ARG(pcb);14   LWIP_ASSERT("recv_tcp must have a pcb argument", pcb != NULL);15   LWIP_ASSERT("recv_tcp must have an argument", arg != NULL);16   conn = arg; // 这个arg就是专属pcb->conn,也是client专属的conn17   LWIP_ASSERT("recv_tcp: recv for wrong pcb!", conn->pcb.tcp == pcb);18 19   if ((conn == NULL) || (conn->recvmbox == SYS_MBOX_NULL)) {20     return ERR_VAL;21   }22 23   conn->err = err;24   if (p != NULL) {25     len = p->tot_len;26     SYS_ARCH_INC(conn->recv_avail, len);27   } else {28     len = 0;29   }30 31   if (sys_mbox_trypost(conn->recvmbox, p) != ERR_OK) { // 原来如此!这里把p指向的数据最终挂到了专属pcb->conn的recvmbox上,而取走它的就是上文分析的lwip_recv()函数,lwip_recv()函数通过新socket号来到这个recvmbox上取32     return ERR_MEM;33   } else {34     /* Register event with callback */35     API_EVENT(conn, NETCONN_EVT_RCVPLUS, len);36   }37 38   return ERR_OK;39 }
总结一下,listen socket listen到的client的连接请求后,会在server端开辟一个新的pcb、新的conn和新的socket。
当有一个tcp_input()来到后,根据tcp segment的ip address和port,找到pcb,从pcb找到conn,放到conn的recvmbox上;
另一方面,当用户通过socket调用recv()函数时,recv()函数通过socket找到conn,并到conn的recvmbox上取tcp segment。
 

lwip socket探秘之recv