zynq Lwip学习笔记-tcp_input函数

前言

最近在学习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功能中起了重要作用。

  • 33
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值