说到 Server 端的 listen 操作,就有意思了。☄☺
目录
listen 是 Server 才有的动作,主要用来监听 Client 发起的连接请求,并为该连接新建 socket。为什么还要为每个连接都新建一个 socket 呢?原来,Server 端使用一个 socket 专门用来监听,每监听到一个 Client 连接,就用新建的 socket 与 Client 通信。监听 socket 只负责监听,一直持续有效。
函数原型
int lwip_listen(int s, int backlog)
参数:
s,即用来监听的 socket fd
backlog,表示能监听的连接(SYN)个数上限。在监听过程中收到 Client SYN 后就开始建立连接,该值表示监听阶段缓存的正在建立的连接 和 已经建立但还没有被应用层 accept 的连接 的个数上限,达到上限后新收到的 SYN 便不再处理,默认最大值 255。当已建立的连接被应用层 accept 后,便可以腾出缓存空间接收新的连接请求。
函数解析
注意,这里的 s 是 Server 用来监听的 socket fd,为避免混淆,我们暂且写成 Server_L_fd。
这个函数主要干了以下几件事情:
1. 新建一个如下类型的 Server_L_tcp_pcb
struct tcp_pcb_listen {
IP_PCB;
TCP_PCB_COMMON(struct tcp_pcb_listen);#if TCP_LISTEN_BACKLOG
u8_t backlog;
u8_t accepts_pending;
#endif
};
这个结构是比 struct tcp_pcb 小得多,为了节省内存只保留监听需要的东西。
还记得内存池 mempool 吗,这里的 “新建” 就是从中拿一个 struct tcp_pcb_listen 出来。
2. 原 tcp_pcb
从原 tcp_pcb 中拷贝信息到 Server_L_tcp_pcb,再把原 tcp_pcb 释放。
原有 tcp_pcb 结构包含所有用于 tcp 通信的参数,结构较大而监听时用不到。所以将其中能用到的拷贝到 Server_L_tcp_pcb 中,并将原有 tcp_pcb 从 tcp_bound_pcbs 中拿出来释放掉(还到 mempool 中)。
拷贝的变量主要有:
lpcb->local_port = pcb->local_port;
lpcb->state = LISTEN;
lpcb->prio = pcb->prio;
lpcb->so_options = pcb->so_options;
ip_addr_copy(lpcb->local_ip, pcb->local_ip);
看到没,local_ip / local_port 都没变哦。
Server_L_tcp_pcb 的状态是 LISTEN。
3. 将 Server_L_tcp_pcb 插到 tcp_listen_pcbs 的头部。
4. 原 netconn
释放掉原 netconn 中的 recvmbox,监听时用不着。
申请 acceptmbox,默认大小 6,表示最多有 6 个已完成握手的连接等待应用层 accept !注意与 backlog 的区别。
原 netconn 状态变成 NETCONN_LISTEN,并将成员 pcb.tcp 指向 Server_L_tcp_pcb(原 tcp_pcb 已释放)。
总结
lwip_listen() 把 Server 端用于监听的前期工作完成后,Server 就坐等 Client 发起连接握手。后面我们分析,收到 Client SYN 后,三次握手的过程 Server 内部都发生了什么。
本文围绕Server端的listen操作展开,介绍了lwip_listen函数。该函数用于监听Client连接请求并新建socket,解析了其函数原型及参数,详细阐述函数执行的几件事,如新建Server_L_tcp_pcb、拷贝信息、插入列表、处理netconn等,完成前期工作后Server等待Client连接。

476

被折叠的 条评论
为什么被折叠?



