赞
踩
最近在学习zynq中的lwip协议族,找不到很好的记笔记的地方,所以就用csdn记录一下自己的学习过程。现在对lwip不熟悉,只是把官方的lwip echo server例程跑了一下,能跑通就一点点的照着学了,笔记都是根据自己的理解写的,而且部分内容可能也只针对lwip echo server例程有效,笔记可以供有缘人参考,但不敢保证全对,有不对的地方也期待有高人指点一二。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_40356705/article/details/136824649
void tcp_input(struct pbuf *p, struct netif *inp)
tcp_input函数的函数体特别长,这里分段进行注释
void tcp_input(struct pbuf *p, struct netif *inp) { struct tcp_pcb *pcb, *prev; // 用于遍历 PCB 链表的指针 struct tcp_pcb_listen *lpcb; // 指向监听状态的 PCB 的指针 #if SO_REUSE // 如果启用了地址重用 struct tcp_pcb *lpcb_prev = NULL; // 监听 PCB 的前一个 PCB struct tcp_pcb_listen *lpcb_any = NULL; // 监听任意地址的 PCB #endif /* SO_REUSE */ u8_t hdrlen_bytes; // TCP 头部长度(字节为单位) err_t err; // 错误码 // LWIP_UNUSED_ARG 是一个宏,用于标记未使用的参数,避免编译器警告 LWIP_UNUSED_ARG(inp); // 确保核心已锁定,防止并发访问 LWIP_ASSERT_CORE_LOCKED(); // 断言 pbuf 不为空,否则后续操作可能出错 LWIP_ASSERT("tcp_input: invalid pbuf", p != NULL); // 开始性能统计 PERF_START; // 增加接收到的 TCP 数据包的统计计数 TCP_STATS_INC(tcp.recv); // 增加 TCP 输入段的 MIB2 统计计数 MIB2_STATS_INC(mib2.tcpinsegs); // 从 pbuf 的 payload 中提取 TCP 头部 tcphdr = (struct tcp_hdr *)p->payload; #if TCP_INPUT_DEBUG // 如果启用了 TCP 输入调试 // 打印 TCP 头部信息用于调试 tcp_debug_print(tcphdr); #endif // 检查 TCP 头部是否适合放在 payload 中 if (p->len < TCP_HLEN) { // 如果 pbuf 的长度小于 TCP 头部最小长度,则丢弃该数据包 // 并输出调试信息 LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: short packet (%"U16_F" bytes) discarded\n", p->tot_len)); // 增加 TCP 长度错误的统计计数 TCP_STATS_INC(tcp.lenerr); // 跳转到丢弃数据包的标签 goto dropped; } // 检查接收到的数据包是否是广播或多播包 if (ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif()) || ip_addr_ismulticast(ip_current_dest_addr())) { // 如果是,则丢弃数据包并增加 TCP 协议错误的统计计数 TCP_STATS_INC(tcp.proterr); // 跳转到丢弃数据包的标签 goto dropped; } #if CHECKSUM_CHECK_TCP // 如果启用了 TCP 校验和检查 // 检查网络接口是否启用了校验和 IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_TCP) { // 计算 TCP 校验和 u16_t chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len, ip_current_src_addr(), ip_current_dest_addr()); if (chksum != 0) { // 如果校验和不正确,则丢弃数据包并输出调试信息 LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packet discarded due to failing checksum 0x%04"X16_F"\n", chksum)); // 打印出错的 TCP 头部信息用于调试 tcp_debug_print(tcphdr); // 增加 TCP 校验和错误的统计计数 TCP_STATS_INC(tcp.chkerr); // 跳转到丢弃数据包的标签 goto dropped; } } #endif /* CHECKSUM_CHECK_TCP */ // 计算 TCP 头部的实际长度(以字节为单位) hdrlen_bytes = TCPH_HDRLEN_BYTES(tcphdr); // 检查计算出的头部长度是否合法 if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) { // 如果头部长度小于最小 TCP 头部长度或大于整个 pbuf 的长度,则记录错误并丢弃数据包 LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: invalid header length (%"U16_F")\n", (u16_t)hdrlen_bytes)); TCP_STATS_INC(tcp.lenerr); goto dropped; }
可以看出,这段代码主要还是来鉴别数据是不是有效数据,无论是通过长度,地址还是校验和,都是为了判断数据是否有效,无效数据就丢掉。
/* 将 pbuf 的 payload 指针移动到 TCP 数据部分,而不是 TCP 头部。 */ tcphdr_optlen = (u16_t)(hdrlen_bytes - TCP_HLEN); tcphdr_opt2 = NULL; // 检查 TCP 头部和选项是否都在第一个 pbuf 中 if (p->len >= hdrlen_bytes) { // 如果都在第一个 pbuf 中 tcphdr_opt1len = tcphdr_optlen; // 移除 pbuf 的头部,使其指向 TCP 数据部分(此操作不会失败) pbuf_remove_header(p, hdrlen_bytes); /* cannot fail */ } else { u16_t opt2len; // TCP 头部在第一个 pbuf 中,但选项不在 - 数据在下一个 pbuf 中 // 由于之前的 hdrlen_bytes 检查,这里必须存在下一个 pbuf LWIP_ASSERT("p->next != NULL", p->next != NULL); // 移除第一个 pbuf 的 TCP 头部(此操作不会失败) pbuf_remove_header(p, TCP_HLEN); // 确定第一个 pbuf 和第二个 pbuf 中选项的长度 tcphdr_opt1len = p->len; opt2len = (u16_t)(tcphdr_optlen - tcphdr_opt1len); // 选项继续在下一个 pbuf 中:将第一个 pbuf 长度设为 0,并隐藏选项在下一个 pbuf 中(调整 p->tot_len) pbuf_remove_header(p, tcphdr_opt1len); // 检查选项是否适合放在第二个 pbuf 中 if (opt2len > p->next->len) { // 如果不适合,记录错误并丢弃数据包 LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: options overflow second pbuf (%"U16_F" bytes)\n", p->next->len)); TCP_STATS_INC(tcp.lenerr); goto dropped; } // 记录指向第二个 pbuf 中选项部分的指针 tcphdr_opt2 = (u8_t *)p->next->payload; // 将下一个 pbuf 的头部移动到选项之后,并手动调整 p->tot_len 以保持与改变后的 p->next 一致 pbuf_remove_header(p->next, opt2len); p->tot_len = (u16_t)(p->tot_len - opt2len); // 断言确保第一个 pbuf 的长度为 0 LWIP_ASSERT("p->len == 0", p->len == 0); // 断言确保第一个 pbuf 的总长度与下一个 pbuf 的总长度相同 LWIP_ASSERT("p->tot_len == p->next->tot_len", p->tot_len == p->next->tot_len); } /* 将TCP头部的字段从网络字节序转换为主机字节序 */ /* Convert fields in TCP header to host byte order. */ tcphdr->src = lwip_ntohs(tcphdr->src); /* 转换源端口 */ tcphdr->dest = lwip_ntohs(tcphdr->dest); /* 转换目标端口 */ seqno = tcphdr->seqno = lwip_ntohl(tcphdr->seqno); /* 转换序列号 */ ackno = tcphdr->ackno = lwip_ntohl(tcphdr->ackno); /* 转换确认号 */ tcphdr->wnd = lwip_ntohs(tcphdr->wnd); /* 转换窗口大小 */ /* 提取TCP标志位,并处理TCP数据长度 */ flags = TCPH_FLAGS(tcphdr); /* 获取TCP标志位 */ tcplen = p->tot_len; /* 获取TCP数据总长度 */ if (flags & (TCP_FIN | TCP_SYN)) { /* 如果包含FIN或SYN标志 */ tcplen++; /* 长度加1,以考虑TCP头部中的伪头部 */ if (tcplen < p->tot_len) { /* 如果长度发生u16_t溢出 */ /* u16_t overflow, cannot handle this */ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: length u16_t overflow, cannot handle this\n")); TCP_STATS_INC(tcp.lenerr); /* 统计长度错误 */ goto dropped; /* 跳转到dropped标签,丢弃数据包 */ } } /* 根据TCP头部信息,查找匹配的TCP连接 */ /* Demultiplex an incoming segment. First, we check if it is destined for an active connection. */ prev = NULL; /* 初始化prev为NULL,用于记录上一个PCB节点 */ for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) { /* 断言检查,确保PCB状态不是CLOSED、TIME-WAIT或LISTEN */ LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED); LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT); LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN); /* 检查PCB是否绑定到特定的网络接口 */ if ((pcb->netif_idx != NETIF_NO_INDEX) && (pcb->netif_idx != netif_get_index(ip_data.current_input_netif))) { prev = pcb; /* 记录上一个PCB节点 */ continue; /* 继续遍历下一个PCB */ } /* 检查PCB的远程端口、本地端口、远程IP地址和本地IP地址是否与TCP头部匹配 */ if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) && ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) { /* 找到匹配的PCB,将其移到链表前端以优化后续查找 */ /* Move this PCB to the front of the list so that subsequent lookups will be faster (we exploit locality in TCP segment arrivals). */ LWIP_ASSERT("tcp_input: pcb->next != pcb (before cache)", pcb->next != pcb); if (prev != NULL) { /* 如果prev不为NULL,说明PCB不是链表头 */ prev->next = pcb->next; /* 更新prev节点的next指针,跳过当前PCB */ pcb->next = tcp_active_pcbs; /* 将当前PCB的next指针指向链表头 */ tcp_active_pcbs = pcb; /* 更新链表头为当前PCB */ } else { TCP_STATS_INC(tcp.cachehit); /* 如果prev为NULL,说明PCB已在链表头,命中缓存 */ } LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb); break; /* 找到匹配项,跳出循环 */ } prev = pcb; /* 更新prev为当前PCB */ }
这段代码是 TCP 输入处理中的关键部分,它负责解析 TCP 头部信息,并根据头部信息查找匹配的 TCP 连接。一旦找到匹配项,后续的处理就可以基于这个连接进行。如果没有找到匹配项,数据包可能是一个新的连接请求,或者是发往一个已关闭或不存在的连接的数据包,需要根据具体情况进行进一步处理。
if (pcb == NULL) { /* 如果数据包没有匹配到任何活跃的连接,我们检查处于TIME-WAIT状态的连接 */ for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) { /* 断言检查,确保TIME-WAIT状态的PCB的状态确实为TIME-WAIT */ LWIP_ASSERT("tcp_input: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT); /* 检查PCB是否绑定到特定的网络接口 */ if ((pcb->netif_idx != NETIF_NO_INDEX) && (pcb->netif_idx != netif_get_index(ip_data.current_input_netif))) { continue; /* 如果PCB绑定到不同的网络接口,则继续检查下一个PCB */ } /* 检查PCB的远程端口、本地端口、远程IP地址和本地IP地址是否与TCP头部匹配 */ if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) && ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) { /* 对于TIME-WAIT状态的PCB,我们并不特别关心将其移到链表前端, 因为TIME-WAIT状态的连接不太可能接收到太多段。 */ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: 收到TIME_WAIT状态连接的数据包.\n")); #ifdef LWIP_HOOK_TCP_INPACKET_PCB /* 如果有定义LWIP_HOOK_TCP_INPACKET_PCB宏,则调用该宏处理数据包 */ if (LWIP_HOOK_TCP_INPACKET_PCB(pcb, tcphdr, tcphdr_optlen, tcphdr_opt1len, tcphdr_opt2, p) == ERR_OK) #endif { /* 调用tcp_timewait_input函数处理TIME-WAIT状态的PCB */ tcp_timewait_input(pcb); } /* 释放数据包占用的内存 */ pbuf_free(p); /* 处理完TIME-WAIT状态的PCB后,直接返回 */ return; } } }
这段程序说明了当数据包没有匹配到任何活跃的连接时,如何检查处于TIME-WAIT状态的连接。代码遍历了TIME-WAIT状态的PCB链表,检查每个PCB的接口索引和地址、端口是否与TCP数据包的相应信息匹配。如果匹配,则调用tcp_timewait_input函数处理该PCB,并释放数据包占用的内存。如果定义了LWIP_HOOK_TCP_INPACKET_PCB宏,则还会调用该宏进行额外的处理。
#if SO_REUSE /* 首先尝试特定的本地IP地址 */ if (lpcb == NULL) { /* 如果没有找到特定的本地IP地址,则尝试ANY类型 */ lpcb = lpcb_any; prev = lpcb_prev; } #endif /* SO_REUSE */ if (lpcb != NULL) { /* 将这个PCB移到列表的前面,以便后续的查找能够更快(我们利用TCP段的到达局部性)。 */ if (prev != NULL) { /* 将前一个PCB的next指向当前PCB的下一个PCB */ ((struct tcp_pcb_listen *)prev)->next = lpcb->next; /* 我们的后继是监听列表的其余部分 */ lpcb->next = tcp_listen_pcbs.listen_pcbs; /* 将这个监听PCB放在监听列表的头部 */ tcp_listen_pcbs.listen_pcbs = lpcb; } else { /* 如果没有前一个PCB,说明这是第一个匹配的PCB,因此缓存命中 */ TCP_STATS_INC(tcp.cachehit); } /* 调试输出:为正在监听的连接找到了匹配的PCB */ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: 找到了正在监听的连接的匹配PCB。\n")); #ifdef LWIP_HOOK_TCP_INPACKET_PCB /* 如果有定义LWIP_HOOK_TCP_INPACKET_PCB宏,则调用用户定义的钩子函数 */ if (LWIP_HOOK_TCP_INPACKET_PCB((struct tcp_pcb *)lpcb, tcphdr, tcphdr_optlen, tcphdr_opt1len, tcphdr_opt2, p) == ERR_OK) #endif { /* 调用tcp_listen_input函数处理这个监听PCB */ tcp_listen_input(lpcb); } /* 释放接收到的pbuf */ pbuf_free(p); /* 结束处理 */ return; }
在这段代码首先检查了是否定义了SO_REUSE宏。如果定义了,则首先尝试找到具有特定本地IP地址的PCB。如果没有找到这样的PCB,那么会尝试使用ANY类型的PCB(如果有的话)。如果找到了匹配的PCB(lpcb不为空),则执行一系列操作来优化后续的查找速度。这包括将找到的PCB移到监听列表的头部,以便在下次查找时能够更快地访问它。此外,如果PCB是第一个找到的匹配项,则记录一个缓存命中统计。然后,如果存在LWIP_HOOK_TCP_INPACKET_PCB宏,则调用用户定义的钩子函数来处理该PCB。最后,调用tcp_listen_input函数来处理该PCB,并释放接收到的pbuf,然后结束处理
/* 最后,如果我们仍然没有找到匹配的PCB,我们将检查所有正在监听传入连接的PCB。 */ prev = NULL; /* 初始化prev为NULL,用于记录上一个遍历到的PCB */ for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) { /* 检查PCB是否绑定到特定的网络接口 */ if ((lpcb->netif_idx != NETIF_NO_INDEX) && (lpcb->netif_idx != netif_get_index(ip_data.current_input_netif))) { prev = (struct tcp_pcb *)lpcb; /* 如果PCB不是绑定到当前网络接口,更新prev为当前PCB */ continue; /* 继续检查下一个PCB */ } /* 检查PCB的本地端口是否与TCP头部的目标端口匹配 */ if (lpcb->local_port == tcphdr->dest) { /* 如果PCB的本地IP地址是ANY类型(IPv4/IPv6) */ if (IP_IS_ANY_TYPE_VAL(lpcb->local_ip)) { /* 找到了ANY TYPE(IPv4/IPv6)的匹配 */ #if SO_REUSE lpcb_any = lpcb; /* 保存找到的ANY TYPE PCB */ lpcb_prev = prev; /* 保存上一个PCB */ #else /* SO_REUSE */ break; /* 如果没有定义SO_REUSE,则直接跳出循环 */ #endif /* SO_REUSE */ } else if (IP_ADDR_PCB_VERSION_MATCH_EXACT(lpcb, ip_current_dest_addr())) { /* 检查PCB的IP地址版本是否与当前目标地址匹配 */ if (ip_addr_cmp(&lpcb->local_ip, ip_current_dest_addr())) { /* 找到了精确的匹配 */ break; /* 跳出循环 */ } else if (ip_addr_isany(&lpcb->local_ip)) { /* 如果PCB的本地IP地址是ANY */ /* 找到了ANY的匹配 */ #if SO_REUSE lpcb_any = lpcb; /* 保存找到的ANY PCB */ lpcb_prev = prev; /* 保存上一个PCB */ #else /* SO_REUSE */ break; /* 如果没有定义SO_REUSE,则直接跳出循环 */ #endif /* SO_REUSE */ } } } prev = (struct tcp_pcb *)lpcb; /* 更新prev为当前PCB */ }
这段代码是处理在没有找到匹配PCB的情况下,如何遍历所有正在监听传入连接的PCB。它检查PCB的网络接口索引、本地端口和本地IP地址是否与TCP数据包的信息匹配。如果找到匹配,它会根据是否定义了SO_REUSE宏来决定是直接跳出循环还是保存找到的PCB。如果PCB的本地IP地址是ANY类型,则会根据是否定义了SO_REUSE宏来决定是否继续搜索或保存找到的ANY PCB。如果找到了精确匹配或ANY匹配,则会跳出循环。在每次循环迭代中,它都会更新prev变量以记录上一个遍历到的PCB。
#if TCP_INPUT_DEBUG /* 如果启用了TCP输入调试 */ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("+-+-+-+-+-+-+-+-+-+-+-+-+-+- tcp_input: flags ")); /* 打印TCP头部的标志位 */ tcp_debug_print_flags(TCPH_FLAGS(tcphdr)); LWIP_DEBUGF(TCP_INPUT_DEBUG, ("-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n")); #endif /* TCP_INPUT_DEBUG */ #ifdef LWIP_HOOK_TCP_INPACKET_PCB /* 如果定义了用户定义的钩子函数LWIP_HOOK_TCP_INPACKET_PCB */ if ((pcb != NULL) && LWIP_HOOK_TCP_INPACKET_PCB(pcb, tcphdr, tcphdr_optlen, tcphdr_opt1len, tcphdr_opt2, p) != ERR_OK) { /* 如果PCB不为空,并且钩子函数返回非ERR_OK值 */ pbuf_free(p); /* 释放接收到的pbuf */ return; /* 提前返回,不再继续处理 */ } #endif if (pcb != NULL) { /* 如果找到了与数据包匹配的PCB */ #if TCP_INPUT_DEBUG /* 如果启用了TCP输入调试 */ tcp_debug_print_state(pcb->state); /* 打印PCB的当前状态 */ #endif /* TCP_INPUT_DEBUG */ /* 设置tcp_seg结构体的字段 */ inseg.next = NULL; /* 设置下一个段为NULL */ inseg.len = p->tot_len; /* 设置段的长度为pbuf的总长度 */ inseg.p = p; /* 设置段的pbuf */ inseg.tcphdr = tcphdr; /* 设置段的TCP头部 */ recv_data = NULL; /* 初始化接收数据指针 */ recv_flags = 0; /* 初始化接收标志 */ recv_acked = 0; /* 初始化已确认接收的字节数 */ if (flags & TCP_PSH) { /* 如果TCP头部标志中包含PSH(Push) */ p->flags |= PBUF_FLAG_PUSH; /* 设置pbuf的标志位,表示这是一个PUSH操作 */ }
在这段代码首先进行了TCP输入调试信息的输出(如果定义了TCP_INPUT_DEBUG)。接着,如果存在用户定义的钩子函数LWIP_HOOK_TCP_INPACKET_PCB,则调用该函数并检查其返回值。如果返回值不是ERR_OK,则释放接收到的pbuf并提前返回,不再继续处理。
如果找到了匹配的PCB(pcb不为NULL),则进行了一系列初始化操作,包括设置tcp_seg结构体和相关的接收参数。此外,如果TCP头部标志中包含PSH(Push)标志,则设置pbuf的标志位,表示这是一个需要被上层应用尽快处理的PUSH操作
/* 如果上层应用之前“拒绝”了某些数据 */ if (pcb->refused_data != NULL) { /* 尝试处理被拒绝的数据 */ if ((tcp_process_refused_data(pcb) == ERR_ABRT) || ((pcb->refused_data != NULL) && (tcplen > 0))) { /* PCB已被中止,或者拒绝的数据仍然被拒绝,并且新接收的段包含数据 */ if (pcb->rcv_ann_wnd == 0) { /* 这是一个零窗口探测,我们用当前的RCV.NXT回应它并丢弃数据段 */ tcp_send_empty_ack(pcb); } TCP_STATS_INC(tcp.drop); MIB2_STATS_INC(mib2.tcpinerrs); goto aborted; } } /* 将当前处理的PCB赋值给全局变量tcp_input_pcb */ tcp_input_pcb = pcb; /* 处理TCP数据段 */ err = tcp_process(pcb); //注意,这里出现了tcp处理的主函数 /* 如果返回值为ERR_ABRT,表示tcp_abort()被调用并且PCB已被释放,此时不执行任何操作 */ if (err != ERR_ABRT) { if (recv_flags & TF_RESET) { /* TF_RESET表示连接被对方重置。我们调用错误回调函数通知应用连接已断开, 然后删除并释放PCB。 */ TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST); tcp_pcb_remove(&tcp_active_pcbs, pcb); tcp_free(pcb); } else { err = ERR_OK; /* 如果应用注册了"sent"函数,以便在发送缓冲区有新的空间可用时调用,我们现在调用它 */ if (recv_acked > 0) { u16_t acked16; #if LWIP_WND_SCALE /* recv_acked是u32_t类型,但sent回调函数只接受u16_t, 所以可能需要多次调用回调函数。 */ u32_t acked = recv_acked; while (acked > 0) { acked16 = (u16_t)LWIP_MIN(acked, 0xffffu); acked -= acked16; #else { acked16 = recv_acked; #endif /* 调用sent回调函数通知应用有新的发送缓冲区空间 */ TCP_EVENT_SENT(pcb, (u16_t)acked16, err); if (err == ERR_ABRT) { goto aborted; } } recv_acked = 0; } /* 延迟关闭TCP连接 */ if (tcp_input_delayed_close(pcb)) { goto aborted; } #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE /* 如果启用了乱序接收队列和窗口缩放 */ while (recv_data != NULL) { struct pbuf *rest = NULL; /* 分割pbuf以确保其大小不超过64KB */ pbuf_split_64k(recv_data, &rest); #else /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ /* 如果没有启用乱序接收队列或窗口缩放 */ if (recv_data != NULL) { #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ /* 处理接收到的数据 */
该程序段主要作用如下
检查是否有之前被上层应用拒绝的数据,并尝试处理它们。如果数据仍然被拒绝或者PCB被中止,且当前是一个零窗口探测,则发送一个空ACK并丢弃数据段。
调用tcp_process函数处理接收到的TCP数据段。
如果处理过程中没有发生错误中止(即返回值不是ERR_ABRT),检查是否收到了TCP RESET标志。如果收到,则调用错误回调函数并释放PCB。
如果应用注册了"sent"回调函数,则调用该函数通知应用发送缓冲区有了新的空间。
检查是否需要进行TCP连接的延迟关闭。
最后,处理接收到的数据(根据是否启用了乱序接收队列和窗口缩放。
/* 确保pcb->refused_data为空,如果不为空则触发断言错误 */ LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL); /* 如果PCB的接收通道已经关闭 */ if (pcb->flags & TF_RXCLOSED) { /* 尽管已关闭,仍然收到数据 -> 中止连接(发送RST),通知远端主机并非所有数据都已处理 */ pbuf_free(recv_data); #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE /* 如果启用了乱序接收队列和窗口缩放,且存在剩余数据 */ if (rest != NULL) { pbuf_free(rest); } #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ /* 中止TCP连接 */ tcp_abort(pcb); goto aborted; } /* 通知应用层接收到数据 */ TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err); if (err == ERR_ABRT) { #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE /* 如果启用了乱序接收队列和窗口缩放,且存在剩余数据 */ if (rest != NULL) { pbuf_free(rest); } #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ goto aborted; } /* 如果上层无法接收这些数据,则存储它们 */ if (err != ERR_OK) { #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE /* 如果启用了乱序接收队列和窗口缩放,且存在剩余数据 */ if (rest != NULL) { /* 将剩余数据和当前数据合并 */ pbuf_cat(recv_data, rest); } #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ /* 将接收到的数据存储在PCB的refused_data中 */ pcb->refused_data = recv_data; /* 调试输出:因为PCB“满了”,所以保留传入的数据包 */ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n")); #if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE /* 如果启用了乱序接收队列和窗口缩放,则跳出循环 */ break; #else /* 如果没有启用乱序接收队列或窗口缩放,且还有剩余数据 */ } else { /* 上层接收了数据,继续处理剩余数据(如果大于64K) */ recv_data = rest; #endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */ } /* 如果接收到了FIN标志,则调用回调函数,以NULL缓冲区指示EOF */ if (recv_flags & TF_GOT_FIN) { if (pcb->refused_data != NULL) { /* 如果存在被拒绝的数据,则延迟处理 */ /* 将FIN标志添加到被拒绝的数据包中 */ pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN; } else { /* 如果没有被拒绝的数据,则正确调整接收窗口大小,因为应用层不会为FIN的序列号调用tcp_recved() */ if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) { pcb->rcv_wnd++; } /* 调用事件回调,通知应用层连接已关闭 */ TCP_EVENT_CLOSED(pcb, err); if (err == ERR_ABRT) { goto aborted; } } } /* 重置tcp_input_pcb为NULL */ tcp_input_pcb = NULL; /* 检查是否需要进行TCP连接的延迟关闭 */ if (tcp_input_delayed_close(pcb)) { goto aborted; }
该程序段调用了·一个宏函数TCP_EVENT_RECV,该函数是echo功能的入口
验证pcb->refused_data是否为空,以确保状态一致性。
检查是否关闭了接收通道,如果是,则释放接收到的数据并发送RST信号以通知远端主机。
通知应用层数据已接收,如果应用层不能接收数据,则存储这些数据以备后续处理。
检查是否接收到了FIN标志,如果是,则调整接收窗口大小并通知应用层连接已关闭。
重置tcp_input_pcb为NULL,以便下一次处理新的TCP输入数据时使用。
检查是否需要进行TCP连接的
/* 尝试发送一些数据出去。 */ tcp_output(pcb); #if TCP_INPUT_DEBUG #if TCP_DEBUG /* 如果开启了TCP调试,打印当前PCB的状态 */ tcp_debug_print_state(pcb->state); #endif /* TCP_DEBUG */ #endif /* TCP_INPUT_DEBUG */ /* 如果在回调函数中调用了tcp_abort()来中止PCB,则跳转到此处。 在这行以下,'pcb'可能已经被释放,不应再被引用! */ aborted: tcp_input_pcb = NULL; recv_data = NULL; /* 放弃我们对inseg.p的引用 */ if (inseg.p != NULL) { pbuf_free(inseg.p); inseg.p = NULL; } } else { /* 如果没有找到匹配的PCB,则向发送方发送一个TCP RST(重置)消息。 */ LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_input: 没有找到匹配的PCB,正在重置。\n")); if (!(TCPH_FLAGS(tcphdr) & TCP_RST)) { /* 如果TCP头部中的标志位没有设置RST,则增加TCP协议错误统计数和丢弃统计数 */ TCP_STATS_INC(tcp.proterr); TCP_STATS_INC(tcp.drop); /* 发送TCP RST消息 */ tcp_rst(NULL, ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(), tcphdr->dest, tcphdr->src); } /* 释放数据包 */ pbuf_free(p); } /* 断言检查,确保TCP PCB链表的状态是正常的 */ LWIP_ASSERT("tcp_input: tcp_pcbs_sane()", tcp_pcbs_sane()); /* 停止性能测量 */ PERF_STOP("tcp_input"); /* 返回,结束处理 */ return; /* 如果在处理过程中丢弃了数据包,则跳转到此处 */ dropped: /* 增加TCP丢弃统计数 */ TCP_STATS_INC(tcp.drop); /* 增加MIB2 TCP输入错误统计数 */ MIB2_STATS_INC(mib2.tcpinerrs); /* 释放数据包 */ pbuf_free(p); }
被ip4_input调用,调用的函数较多其中TCP_EVENT_RECV宏函数在echo功能中起了重要作用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。