前言
最近在学习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)
- 参数
struct pbuf *p: 指向接收到的 TCP 数据包的 pbuf 结构。
struct netif *inp: 网络接口,表示数据包是在哪个网络接口上接收到的 - 作用
该函数是TCP数据输入的总入口,负责处理IP层递交的TCP数据包。以下是关于tcp_input函数的主要作用的一些详细描述:
1,接收IP层递交的数据包:IP层在接收到数据后,会经过一系列的处理,然后将TCP数据包传递给tcp_input函数。tcp_input函数会从IP数据包中提取TCP报文,并检验报文的正确性。
2,查找并关联TCP控制块:在接收到TCP报文后,tcp_input函数会查找与这个报文相关联的TCP控制块(TCB,TCP Control Block)。TCP控制块是lwIP中用于管理TCP连接的数据结构,它包含了TCP连接的状态信息和其他相关参数。
3,根据TCP连接状态处理报文:tcp_input函数会根据TCP控制块所处的状态,调用不同的函数来处理TCP报文。例如,对于处于LISTEN状态和TIME_WAIT状态的连接,它会分别调用tcp_listen_input和tcp_timewait_input函数来处理报文。而对于其他状态的连接,它会调用tcp_process函数来进行处理。
4,TCP状态机的处理:tcp_process函数是TCP状态机的实现,它会根据TCP协议的状态转移规则,更新TCP控制块的状态,并执行相应的操作。例如,对于接收到的SYN报文,它可能会将连接状态从LISTEN转换为SYN_RECEIVED,并发送SYN+ACK报文作为响应。
5,进一步的数据处理:在完成TCP状态机的处理后,tcp_input函数可能还会调用其他函数(如tcp_receive)来完成进一步的数据处理。这包括将TCP报文中的有效数据传递给应用层,以及处理TCP头部的选项数据等。
总的来说,tcp_input函数在lwIP协议栈中起到了连接IP层和TCP层,以及处理TCP数据输入的关键作用。它确保了TCP数据能够正确地被接收、解析和处理,从而实现了TCP协议的可靠传输功能。
二、函数体
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功能中起了重要作用。