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

lwip socket探秘之accept

一个基本的socket建立顺序是
Server端:
  • socket()
  • bind()
  • listen()
  • accept()
  • recv()
Client端:
  • socket()
  • connect()
  • send()
 
本文着重介绍Server端的accept()过程。
 
上一篇我们已经分析了listen()过程,listen()过程新建了pcb并把它放到了tcp_listen_pcbs这个链表里。
接下来,Client端通过Server绑定的地址和端口号(通过bind绑定),给Server发包。Server收到了Client过来的TCP包后,如何记住这个Client,并且接下来会做什么呢?这些就是这篇小文分析的内容。
 
首先Server端TCP层接到了Client来的TCP segment。我们从lwip中TCP层rx的入口开始讲起。
tcp_input()是TCP层rx数据的入口,如下面代码段红色注释部分所示。
注意传进来的参数p,虽然这是TCP层入口,但它内部的p->payload是没有剥离ip头的,原因就是,TCP层处理socket时,是需要remote端的ip的。这一点从tcp_input()函数内部就能看出来。
代码段如下:
  1 /**  2 * The initial input processing of TCP. It verifies the TCP header, demultiplexes  3 * the segment between the PCBs and passes it on to tcp_process(), which implements  4 * the TCP finite state machine. This function is called by the IP layer (in  5 * ip_input()).  6 *  7 * @param p received TCP segment to process (p->payload pointing to the IP header)  8 * @param inp network interface on which this segment was received  9 */ 10 void 11 tcp_input(struct pbuf *p, struct netif *inp) 12 { 13   struct tcp_pcb *pcb, *prev; 14   struct tcp_pcb_listen *lpcb; 15   u8_t hdrlen; 16   err_t err; 17  18   PERF_START; 19  20   TCP_STATS_INC(tcp.recv); 21   snmp_inc_tcpinsegs(); 22  23   iphdr = p->payload; // 得到了IP header 24   tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4); // 得到了TCP header 25  26 ............ 27  28   /* Convert fields in TCP header to host byte order. */ 29   tcphdr->src = http://www.mamicode.com/ntohs(tcphdr->src); // 把tcp header的一些内容从网络字节序转成主机字节序。可以猜测ip header已经在ip层被转过了。 30   tcphdr->dest = ntohs(tcphdr->dest); 31   seqno = tcphdr->seqno = ntohl(tcphdr->seqno); 32   ackno = tcphdr->ackno = ntohl(tcphdr->ackno); 33   tcphdr->wnd = ntohs(tcphdr->wnd); 34  35 .................. 36  37 //下面出现了3个pcb链表,分别是tcp_active_pcbs、tcp_tw_pcbs和tcp_listen_pcbs。 38   for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) { 39     LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED); 40     LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT); 41     LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN); 42     if (pcb->remote_port == tcphdr->src && 43        pcb->local_port == tcphdr->dest && 44        ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)) && 45        ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest))) { 46  47       /* Move this PCB to the front of the list so that subsequent 48          lookups will be faster (we exploit locality in TCP segment 49          arrivals). */ 50       LWIP_ASSERT("tcp_input: pcb->next != pcb (before cache)", pcb->next != pcb); 51       if (prev != NULL) { 52         prev->next = pcb->next; 53         pcb->next = tcp_active_pcbs; 54         tcp_active_pcbs = pcb; 55       } 56       LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb); 57       break; 58     } 59     prev = pcb; 60   } 61  62   if (pcb == NULL) { 63     /* If it did not go to an active connection, we check the connections 64        in the TIME-WAIT state. */ 65     for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) { 66       LWIP_ASSERT("tcp_input: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT); 67       if (pcb->remote_port == tcphdr->src && 68          pcb->local_port == tcphdr->dest && 69          ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)) && 70          ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest))) { 71         /* We don‘t really care enough to move this PCB to the front 72            of the list since we are not very likely to receive that 73            many segments for connections in TIME-WAIT. */ 74         LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for TIME_WAITing connection.\n")); 75         tcp_timewait_input(pcb); 76         pbuf_free(p); 77         return; 78       } 79     } 80  81 //我们暂时只关心tcp_listen_pcbs这个链表。这个链表包含了所有处在listen状态的pcb。一个listen状态的pcb是什么时候把自己注册进tcp_listen_pcbs这个链表的?这个待会讲到。 82   /* Finally, if we still did not get a match, we check all PCBs that 83      are LISTENing for incoming connections. */ 84     prev = NULL; 85     for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) { 86       if ((ip_addr_isany(&(lpcb->local_ip)) || // 如果pcb->local_ip是0或者NULL,意味着接收任何ip的连接请求 87         ip_addr_cmp(&(lpcb->local_ip), &(iphdr->dest))) &&  // 或者pcb->local_ip和接收到的tcp segment的ip地址一致,并且pcb->local_port和接收到的tcp segment的port号也一致,认为找到了之前注册的、并且是remote client发过来的这个tcp segment的目标的pcb。 88         lpcb->local_port == tcphdr->dest) { 89         /* Move this PCB to the front of the list so that subsequent 90            lookups will be faster (we exploit locality in TCP segment 91            arrivals). */ 92         if (prev != NULL) { 93           ((struct tcp_pcb_listen *)prev)->next = lpcb->next; 94                 /* our successor is the remainder of the listening list */ 95           lpcb->next = tcp_listen_pcbs.listen_pcbs; 96                 /* put this listening pcb at the head of the listening list */ 97           tcp_listen_pcbs.listen_pcbs = lpcb; 98         } 99      100         LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for LISTENing connection.\n"));101         tcp_listen_input(lpcb); // 得到server正在listen状态的pcb后,用刚收到的remote tcp segment更新pcb,马上会分析这个函数102         pbuf_free(p);103         return;104       }105       prev = (struct tcp_pcb *)lpcb;106     }107   }
 
tcp_listen_input()如下:
 1 /** 2 * Called by tcp_input() when a segment arrives for a listening 3 * connection (from tcp_input()). 4 * 5 * @param pcb the tcp_pcb_listen for which a segment arrived 6 * @return ERR_OK if the segment was processed 7 *         another err_t on error 8 * 9 * @note the return value is not (yet?) used in tcp_input()10 * @note the segment which arrived is saved in global variables, therefore only the pcb11 *       involved is passed as a parameter to this function12 */13 static err_t14 tcp_listen_input(struct tcp_pcb_listen *pcb)15 {16   struct tcp_pcb *npcb;17   err_t rc;18 19   /* In the LISTEN state, we check for incoming SYN segments,20      creates a new PCB, and responds with a SYN|ACK. */21   if (flags & TCP_ACK) {22     /* For incoming segments with the ACK flag set, respond with a23        RST. */24     LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_listen_input: ACK in LISTEN, sending reset\n"));25     tcp_rst(ackno + 1, seqno + tcplen,26       &(iphdr->dest), &(iphdr->src),27       tcphdr->dest, tcphdr->src);28   } else if (flags & TCP_SYN) {29     LWIP_DEBUGF(TCP_DEBUG, ("TCP connection request %"U16_F" -> %"U16_F".\n", tcphdr->src, tcphdr->dest));30 #if TCP_LISTEN_BACKLOG31     if (pcb->accepts_pending >= pcb->backlog) {32       LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: listen backlog exceeded for port %"U16_F"\n", tcphdr->dest));33       return ERR_ABRT;34     }35 #endif /* TCP_LISTEN_BACKLOG */36     npcb = tcp_alloc(pcb->prio); // allocate一个新的pcb,待会会放入active pcb链表37     /* If a new PCB could not be created (probably due to lack of memory),38        we don‘t do anything, but rely on the sender will retransmit the39        SYN at a time when we have more memory available. */40     if (npcb == NULL) {41       LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\n"));42       TCP_STATS_INC(tcp.memerr);43       return ERR_MEM;44     }45 #if TCP_LISTEN_BACKLOG46     pcb->accepts_pending++;47 #endif /* TCP_LISTEN_BACKLOG */48     /* Set up the new PCB. */49     ip_addr_set(&(npcb->local_ip), &(iphdr->dest)); // pcb里存好server端自己的ip50     npcb->local_port = pcb->local_port; // pcb里存好server端自己的port号51     ip_addr_set(&(npcb->remote_ip), &(iphdr->src)); // pcb里指定client端的ip52     npcb->remote_port = tcphdr->src; // pcb里指定client端的port号53     npcb->state = SYN_RCVD; // pcb的状态变成了SYN_RCVD54     npcb->rcv_nxt = seqno + 1;55     npcb->rcv_ann_right_edge = npcb->rcv_nxt;56     npcb->snd_wnd = tcphdr->wnd;57     npcb->ssthresh = npcb->snd_wnd;58     npcb->snd_wl1 = seqno - 1;/* initialise to seqno-1 to force window update */59     npcb->callback_arg = pcb->callback_arg;60 #if LWIP_CALLBACK_API61     npcb->accept = pcb->accept;62 #endif /* LWIP_CALLBACK_API */63     /* inherit socket options */64     npcb->so_options = pcb->so_options & (SOF_DEBUG|SOF_DONTROUTE|SOF_KEEPALIVE|SOF_OOBINLINE|SOF_LINGER);65     /* Register the new PCB so that we can begin receiving segments66        for it. */67     TCP_REG(&tcp_active_pcbs, npcb); // 把新pcb加入active pcbs链表,以后这个pcb专门为server端与remote ip对应的这个client端之间的通信服务68 69     /* Parse any options in the SYN. */70     tcp_parseopt(npcb);71 #if TCP_CALCULATE_EFF_SEND_MSS72     npcb->mss = tcp_eff_send_mss(npcb->mss, &(npcb->remote_ip));73 #endif /* TCP_CALCULATE_EFF_SEND_MSS */74 75     snmp_inc_tcppassiveopens();76 77     /* Send a SYN|ACK together with the MSS option. */78     rc = tcp_enqueue(npcb, NULL, 0, TCP_SYN | TCP_ACK, 0, TF_SEG_OPTS_MSS   // tx一个SYN|ACK79 #if LWIP_TCP_TIMESTAMPS80       /* and maybe include the TIMESTAMP option */81      | (npcb->flags & TF_TIMESTAMP ? TF_SEG_OPTS_TS : 0)82 #endif83       );84     if (rc != ERR_OK) {85       tcp_abandon(npcb, 0);86       return rc;87     }88     return tcp_output(npcb);89   }90   return ERR_OK;91 }

 

至此,产生了一个新pcb,这个pcb处在SYN_RCVD状态。这个pcb被加入了active pcbs链表。
如果刚才的client又发送了tcp segment过来,那么接收的流程又跟上面讲的有所不同。还是从tcp_input开始分析:
 1 void 2 tcp_input(struct pbuf *p, struct netif *inp) 3 { 4   struct tcp_pcb *pcb, *prev; 5   struct tcp_pcb_listen *lpcb; 6   u8_t hdrlen; 7   err_t err; 8  9   PERF_START;10 11   TCP_STATS_INC(tcp.recv);12   snmp_inc_tcpinsegs();13 14   iphdr = p->payload; // 得到了IP header15   tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4); // 得到了TCP header16 17 ............18 19   /* Convert fields in TCP header to host byte order. */20   tcphdr->src = http://www.mamicode.com/ntohs(tcphdr->src); // 把tcp header的一些内容从网络字节序转成主机字节序。可以猜测ip header已经在ip层被转过了。21   tcphdr->dest = ntohs(tcphdr->dest);22   seqno = tcphdr->seqno = ntohl(tcphdr->seqno);23   ackno = tcphdr->ackno = ntohl(tcphdr->ackno);24   tcphdr->wnd = ntohs(tcphdr->wnd);25 26 ..................27 28 //下面出现了3个pcb链表,分别是tcp_active_pcbs、tcp_tw_pcbs和tcp_listen_pcbs。29 // 这一次,tcp segment对应的client-server专属pcb能够在 tcp_active_pcbs链表里找到。30   for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {31     LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED);32     LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT);33     LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN);34     if (pcb->remote_port == tcphdr->src && // 这个tcp segment对应的client和server信息都能和某个pcb一致,则找到了这个pcb35        pcb->local_port == tcphdr->dest &&36        ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)) &&37        ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest))) {38 39       /* Move this PCB to the front of the list so that subsequent40          lookups will be faster (we exploit locality in TCP segment41          arrivals). */42       LWIP_ASSERT("tcp_input: pcb->next != pcb (before cache)", pcb->next != pcb);43       if (prev != NULL) {44         prev->next = pcb->next;45         pcb->next = tcp_active_pcbs;46         tcp_active_pcbs = pcb;47       }48       LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb);49       break;50     }51     prev = pcb;52   }53 54 .............55 56   if (pcb != NULL) {57        err = tcp_process(pcb);58   }

 

tcp_process():
 1 static err_t 2 tcp_process(struct tcp_pcb *pcb) 3 { 4 .............. 5   case SYN_RCVD: 6     if (flags & TCP_ACK) { 7       /* expected ACK number? */ 8       if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) { 9         u16_t old_cwnd;10         pcb->state = ESTABLISHED;11         LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));12 #if LWIP_CALLBACK_API13         LWIP_ASSERT("pcb->accept != NULL", pcb->accept != NULL);14 #endif15         /* Call the accept function. */16         TCP_EVENT_ACCEPT(pcb, ERR_OK, err); // 这里会调到accept_function函数17         if (err != ERR_OK) {18           /* If the accept function returns with an error, we abort19            * the connection. */20           tcp_abort(pcb);21           return ERR_ABRT;22         }23         old_cwnd = pcb->cwnd;24         /* If there was any data contained within this ACK,25          * we‘d better pass it on to the application as well. */26         tcp_receive(pcb);27 28         /* Prevent ACK for SYN to generate a sent event */29         if (pcb->acked != 0) {30           pcb->acked--;31         }32 33         pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);34 35         if (recv_flags & TF_GOT_FIN) {36           tcp_ack_now(pcb);37           pcb->state = CLOSE_WAIT;38         }39       }40       /* incorrect ACK number */41       else {42         /* send RST */43         tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src),44                 tcphdr->dest, tcphdr->src);45       }46     } else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {47       /* Looks like another copy of the SYN - retransmit our SYN-ACK */48       tcp_rexmit(pcb);49     }50     break;51 ....................52 }
 1 /** 2 * Accept callback function for TCP netconns. 3 * Allocates a new netconn and posts that to conn->acceptmbox. 4 * 5 * @see tcp.h (struct tcp_pcb_listen.accept) for parameters and return value 6 */ 7 // 注意accept_function的参数是 8 // arg:server端负责listen的那个sock的sock->conn, 9 // newpcb:listen到tcp segment后为特定的client-server创建的pcb10 // err:err11 static err_t12 accept_function(void *arg, struct tcp_pcb *newpcb, err_t err)13 {14   struct netconn *newconn;15   struct netconn *conn;16 17 #if API_MSG_DEBUG18 #if TCP_DEBUG19   tcp_debug_print_state(newpcb->state);20 #endif /* TCP_DEBUG */21 #endif /* API_MSG_DEBUG */22   conn = (struct netconn *)arg;23 24   LWIP_ERROR("accept_function: invalid conn->acceptmbox",25              conn->acceptmbox != SYS_MBOX_NULL, return ERR_VAL;);26 27   /* We have to set the callback here even though28    * the new socket is unknown. conn->socket is marked as -1. */29   newconn = netconn_alloc(conn->type, conn->callback); // 新建一个netconn30   if (newconn == NULL) {31     return ERR_MEM;32   }33   newconn->pcb.tcp = newpcb;34   setup_tcp(newconn); // 这个函数很重要,我们要记住它把newconn和newpcb绑定起来了35   newconn->err = err;36 37   if (sys_mbox_trypost(conn->acceptmbox, newconn) != ERR_OK) { // 把newconn丢到了旧conn(即负责listen的pcb的conn)的acceptmbox这个mailbox上38     /* When returning != ERR_OK, the connection is aborted in tcp_process(),39        so do nothing here! */40     newconn->pcb.tcp = NULL;41     netconn_free(newconn);42     return ERR_MEM;43   } else {44     /* Register event with callback */45     API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);46   }47 48   return ERR_OK;49 }

 

现在我们已知一个新的newconn产生了,并且它放在负责listen的pcb的conn的acceptmbox里。那么谁来取走这个newconn呢?
就是lwip_accept()函数。
lwip_accept()函数就是用户使用socket过程中调用的accept()。
 1 /* Below this, the well-known socket functions are implemented. 2 * Use google.com or opengroup.org to get a good description :-) 3 * 4 * Exceptions are documented! 5 */ 6  7 int 8 lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen) // 这里的s是负责listen的socket 9 {10   struct lwip_socket *sock, *nsock;11   struct netconn *newconn;12   struct ip_addr naddr;13   u16_t port;14   int newsock;15   struct sockaddr_in sin;16   err_t err;17 18   LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d)...\n", s));19   sock = get_socket(s); // 从用户可见的int型socket得到协议栈自己维护的socket descriptor20   if (!sock)21     return -1;22 23   if ((sock->flags & O_NONBLOCK) && (sock->rcvevent <= 0)) {24     LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d): returning EWOULDBLOCK\n", s));25     sock_set_errno(sock, EWOULDBLOCK);26     return -1;27   }28 29   newconn = netconn_accept(sock->conn);  // 传入的是listen socket指向的conn30 ..................31 }

 

我们先来看一下netconn_accept函数:
 1 /** 2 * Accept a new connection on a TCP listening netconn. 3 * 4 * @param conn the TCP listen netconn 5 * @return the newly accepted netconn or NULL on timeout 6 */ 7 struct netconn * 8 netconn_accept(struct netconn *conn) 9 {10   struct netconn *newconn;11 12 .............13 14 #if LWIP_SO_RCVTIMEO15   if (sys_arch_mbox_fetch(conn->acceptmbox, (void *)&newconn, conn->recv_timeout) == SYS_ARCH_TIMEOUT) {16     newconn = NULL;17   } else18 #else19   sys_arch_mbox_fetch(conn->acceptmbox, (void *)&newconn, 0); // 这里从listen socket->conn的acceptmbox取出1个conn,当然就是socket用户在调用listen()后,listen()通过accept_function()函数放入的新conn,这是和listen socket->conn不同的新conn,这里名字是newconn20 #endif /* LWIP_SO_RCVTIMEO*/21 ..................22 23   return newconn;24 }

 

所以,netconn_accept取出了client端连接server后server为这一对peer生成的newconn。
我们接着回到lwip_accept()函数:
 1 /* Below this, the well-known socket functions are implemented. 2 * Use google.com or opengroup.org to get a good description :-) 3 * 4 * Exceptions are documented! 5 */ 6  7 int 8 lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen) // 这里的s是负责listen的socket 9 {10 .................11   newconn = netconn_accept(sock->conn);  // 传入的是listen socket指向的conn12   if (!newconn) {13     LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d) failed, err=%d\n", s, sock->conn->err));14     sock_set_errno(sock, err_to_errno(sock->conn->err));15     return -1;16   }17 18   /* get the IP address and port of the remote host */19   err = netconn_peer(newconn, &naddr, &port); // 从newconn里得到client端ip addr和port号,实际是从newconn绑定的pcb里获得20   if (err != ERR_OK) {21     netconn_delete(newconn);22     sock_set_errno(sock, err_to_errno(err));23     return -1;24   }25 26   /* Note that POSIX only requires us to check addr is non-NULL. addrlen must27    * not be NULL if addr is valid.28    */29   if (NULL != addr) { // 如果实参addr地址不是NULL,就把client端的ip地址和port写入addr指向的地址,作为这个函数的返回值之一。但是很多时候这个addr参数用户都会设置为NULL,不需要这个信息。30     LWIP_ASSERT("addr valid but addrlen NULL", addrlen != NULL);31     memset(&sin, 0, sizeof(sin));32     sin.sin_len = sizeof(sin);33     sin.sin_family = AF_INET;34     sin.sin_port = htons(port);35     sin.sin_addr.s_addr = naddr.addr;36 37     if (*addrlen > sizeof(sin))38       *addrlen = sizeof(sin);39 40     MEMCPY(addr, &sin, *addrlen);41   }42 43   newsock = alloc_socket(newconn); // 对于一个client和server的peer,现在有了新的pcb、新的conn,还需要一个新的socket给用户用!并且这个函数还把newconn和socket绑定起来了,从新socket能得到newconn。44   if (newsock == -1) {45     netconn_delete(newconn);46     sock_set_errno(sock, ENFILE);47     return -1;48   }49   LWIP_ASSERT("invalid socket index", (newsock >= 0) && (newsock < NUM_SOCKETS));50   newconn->callback = event_callback;51   nsock = &sockets[newsock];52   LWIP_ASSERT("invalid socket pointer", nsock != NULL);53 54   sys_sem_wait(socksem);55   /* See event_callback: If data comes in right away after an accept, even56    * though the server task might not have created a new socket yet.57    * In that case, newconn->socket is counted down (newconn->socket--),58    * so nsock->rcvevent is >= 1 here!59    */60   nsock->rcvevent += -1 - newconn->socket;61   newconn->socket = newsock; // 从newconn能找到new socket62   sys_sem_signal(socksem);63 64   LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_accept(%d) returning new sock=%d addr=", s, newsock));65   ip_addr_debug_print(SOCKETS_DEBUG, &naddr);66   LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F"\n", port));67 68   sock_set_errno(sock, 0);69   return newsock;70 }
lwip_accept()函数返回了一个new socket,这个socket从listen socket而来,是server专为listen到的client准备的一个socket,可以认为是为这一对通路单独服务的server端socket。我们先把它叫做专属socket。
 
 accept()过程结束,返回的socket用于接下来的recv()。
 
 
 

lwip socket探秘之accept