收录于:
【LWIP】LWIP协议|相关知识汇总|LWIP学习笔记
相关链接:
【LWIP】Ip4_input函数分析
——|【LWIP】pbuf_realloc函数分析
【LWIP】tcp_input()函数分析
——|【LWIP】tcp_timewait_input函数解析
——|【LWIP】tcp_listen_input函数分析
【LWIP】tcp_receive函数分析
【LWIP】udp_input函数分析
1.调用流程:
tcp_input接收IP层递交上来的数据包,获取TCP首部长度(包括选项部分),将p指针移向pbuf的有效数据部分, 根据TCP报头,遍历tcp_active_pcbs链表,tcp_tw_pcbs链表,tcp_listen_pcbs链表,查找相应TCP控制块, 若在tcp_listen_pcbs链表中匹配,调用tcp_listen_input()。
2.函数简析:
tcp_listen_input函数是对处于LISTEN状态的控制块输入报文的处理函数,处于LISTEN状态的控制块只能响应SYN握手包。
3.具体分析:
1.判断TCP报头中的RST标志是否置1,若是直接退出; 2.判断TCP报头中的ACK标志是否置1,若是返回一个RST报文(调用tcp_rst函数); 3.对RST标志置0,ACK标志置0,且SYN标志置1的输入报文进行处理(若RST,ACK有置位,不会进SYN的处理), 建立一个新的tcp_pcb(原先的为tcp_pcb_listen结构),填充结构体成员(带SYN和ACK标志), 最终调用tcp_output函数输出(这是TCP层的总输出函数,下次详细讲解);
4.源码:
static void
tcp_listen_input(struct tcp_pcb_listen *pcb)
{
struct tcp_pcb *npcb;
u32_t iss;
err_t rc;
if (flags & TCP_RST) {
/* 判断TCP报头中的RST标志是否置1,若是直接退出 */
return;
}
/* 处于listen状态的pcb只能响应SYN握手包,对含有ACK标志的输入报文返回一个RST报文 */
if (flags & TCP_ACK) {
/* 调用tcp_rst函数返回一个RST报文 */
LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_listen_input: ACK in LISTEN, sending reset\n"));
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
ip_current_src_addr(), tcphdr->dest, tcphdr->src);
/* 处于listen状态的服务器端等到了SYN握手包 */
} else if (flags & TCP_SYN) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection request %"U16_F" -> %"U16_F".\n", tcphdr->src, tcphdr->dest));
/* 这个宏配置没看出来作用,没注释... */
#if TCP_LISTEN_BACKLOG
if (pcb->accepts_pending >= pcb->backlog) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: listen backlog exceeded for port %"U16_F"\n", tcphdr->dest));
return;
}
#endif /* TCP_LISTEN_BACKLOG */
/* 建立一个新的tcp_pcb,因为处于tcp_listen_pcbs链表上的pcb是tcp_pcb_listen结构的,而其他链表上的pcb是tcp_pcb结构 */
npcb = tcp_alloc(pcb->prio);
/* 如果新建失败,往往是因为内存不够,就什么都不做,发送方会再次发送,等到那时可能就有多余的内存空间了 */
if (npcb == NULL) {
err_t err;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\n"));
TCP_STATS_INC(tcp.memerr);
TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err);
LWIP_UNUSED_ARG(err); /* err not useful here */
return;
}
#if TCP_LISTEN_BACKLOG
pcb->accepts_pending++;
npcb->flags |= TF_BACKLOGPEND;
#endif /* TCP_LISTEN_BACKLOG */
/* 为这个新建的tcp_pcb填充成员 */
ip_addr_copy(npcb->local_ip, *ip_current_dest_addr());
ip_addr_copy(npcb->remote_ip, *ip_current_src_addr());
npcb->local_port = pcb->local_port;
npcb->remote_port = tcphdr->src;
npcb->state = SYN_RCVD; //进入SYN_RCVD状态
npcb->rcv_nxt = seqno + 1; //期望接收到的下一个序号,注意加1
npcb->rcv_ann_right_edge = npcb->rcv_nxt; //初始化右侧通告窗口
iss = tcp_next_iss(npcb); //为新连接计算一个新的初始序列号
npcb->snd_wl2 = iss;
npcb->snd_nxt = iss;
npcb->lastack = iss;
npcb->snd_lbb = iss;
npcb->snd_wl1 = seqno - 1; //初始化上次窗口更新时收到的序号
npcb->callback_arg = pcb->callback_arg; //初始化用户自定义数据
#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
npcb->listener = pcb;
#endif /* LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG */
/* inherit socket options */
npcb->so_options = pcb->so_options & SOF_INHERITED; //继承socket选项
/* 将这个设置好的tcp_pcb注册到tcp_active_pcbs链表中去 */
TCP_REG_ACTIVE(npcb);
/* 从收到的SYN握手包中提取TCP头中选项字段的值,并设置到自己的tcp_pcb */
tcp_parseopt(npcb);
npcb->snd_wnd = tcphdr->wnd; //根据TCP头中对方可接收数据长度,初始化本地发送窗口大小
npcb->snd_wnd_max = npcb->snd_wnd;
npcb->ssthresh = LWIP_TCP_INITIAL_SSTHRESH(npcb); //拥塞算法相关,暂略
#if TCP_CALCULATE_EFF_SEND_MSS
npcb->mss = tcp_eff_send_mss(npcb->mss, &npcb->local_ip, &npcb->remote_ip); // 初始化mss
#endif /* TCP_CALCULATE_EFF_SEND_MSS */
MIB2_STATS_INC(mib2.tcppassiveopens);
/* 回复带有SYN和ACK标志的握手数据包 */
rc = tcp_enqueue_flags(npcb, TCP_SYN | TCP_ACK);
if (rc != ERR_OK) {
tcp_abandon(npcb, 0);
return;
}
/* TCP层的总输出函数,下次详细分析 */
tcp_output(npcb);
}
return;
}