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 int
2 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 = 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 subsequent 15  lookups will be faster (we exploit locality in TCP segment 16  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 other 34  end. We then call the error callback to inform the 35  application that the connection is dead before we 36  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 the 42  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 be 48  called when new send buffer space is available, we call it 49  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專屬的conn
17   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。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM