一个基本的socket建立顺序是
Server端:
- socket()
- bind()
- listen()
- accept()
- recv()
Client端:
- socket()
- connect()
- send()
本文着重介绍Server端的listen()过程。
用户使用socket,调用listen()时,实际调用的是lwip里的lwip_listen()。代码如下
1 /** 2 * Set a socket into listen mode. 3 * The socket may not have been used for another connection previously. 4 * 5 * @param s the socket to set to listening mode 6 * @param backlog (ATTENTION: need TCP_LISTEN_BACKLOG=1) 7 * @return 0 on success, non-zero on failure 8 */
9 int
10 lwip_listen(int s, int backlog) 11 { 12 struct lwip_socket *sock; 13 err_t err; 14 ............. 15 sock = get_socket(s); // 根据socket号(面向用户的socket标识)得到lwip内部的socket descriptor
16 if (!sock) 17 return -1; 18 ............... 19 err = netconn_listen_with_backlog(sock->conn, backlog); // 接下来看这个函数
20 ............... 21 return 0; 22 }
netconn_listen_with_backlog本身内容很少,主要是向下一连串调用:
netconn_listen_with_backlog
=>do_listen
=>tcp_listen
=>tcp_listen_with_backlog
tcp_listen_with_backlog这个函数才是真正做了重要工作的地方。
1 /** 2 * Set the state of the connection to be LISTEN, which means that it 3 * is able to accept incoming connections. The protocol control block 4 * is reallocated in order to consume less memory. Setting the 5 * connection to LISTEN is an irreversible process. 6 * 7 * @param pcb the original tcp_pcb 8 * @param backlog the incoming connections queue limit 9 * @return tcp_pcb used for listening, consumes less memory. 10 * 11 * @note The original tcp_pcb is freed. This function therefore has to be 12 * called like this: 13 * tpcb = tcp_listen(tpcb); 14 */
15 struct tcp_pcb *
16 tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog) 17 { 18 struct tcp_pcb_listen *lpcb; 19
20 LWIP_UNUSED_ARG(backlog); 21 LWIP_ERROR("tcp_listen: pcb already connected", pcb->state == CLOSED, return NULL); 22
23 /* already listening? */
24 if (pcb->state == LISTEN) { 25 return pcb; 26 } 27 lpcb = memp_malloc(MEMP_TCP_PCB_LISTEN); // 新建了一个pcb,后面会把原来的pcb free掉
28 if (lpcb == NULL) { 29 return NULL; 30 } 31 lpcb->callback_arg = pcb->callback_arg; // 新pcb继承旧pcb的一些内容
32 lpcb->local_port = pcb->local_port; 33 lpcb->state = LISTEN; // 因为用户调用了listen(),所以这个新pcb的状态是LISTEN
34 lpcb->so_options = pcb->so_options; 35 lpcb->so_options |= SOF_ACCEPTCONN; 36 lpcb->ttl = pcb->ttl; 37 lpcb->tos = pcb->tos; 38 ip_addr_set(&lpcb->local_ip, &pcb->local_ip); 39 TCP_RMV(&tcp_bound_pcbs, pcb); 40 memp_free(MEMP_TCP_PCB, pcb); // free掉旧的pcb
41 #if LWIP_CALLBACK_API
42 lpcb->accept = tcp_accept_null; 43 #endif /* LWIP_CALLBACK_API */
44 #if TCP_LISTEN_BACKLOG
45 lpcb->accepts_pending = 0; 46 lpcb->backlog = (backlog ? backlog : 1); 47 #endif /* TCP_LISTEN_BACKLOG */
48 TCP_REG(&tcp_listen_pcbs.listen_pcbs, lpcb); // 把新pcb挂到tcp_listen_pcbs这个链表里
49 return (struct tcp_pcb *)lpcb; 50 }
注意阅读函数上方的注释,这些注释简要的介绍了函数的作用,写的都非常有用。
tcp_listen_with_backlog这个函数主要是重新开辟了一个pcb代替旧的pcb(出于节省空间的考虑),将新pcb的状态设置为LISTEN,并将其挂在lwip里的tcp_listen_pcbs这个链表里。