学习《嵌入式网络那些事lwip协议 深度剖析与实战演练》过程中
对tcp_process这个函数的理解

连接建立过程中的握手分为三个报文分别为①② ③
连接断开过程中的握手分为④ ⑤ ⑥ ⑦
此外还有双方同时关闭的情况需要考虑
在这里插入图片描述
函数前面部分属于对输入的报文进行判断,是否合法,是否为存在RST标志置位,并判断收到的握手包为超时重发

static err_t
tcp_process(struct tcp_pcb *pcb)
 struct tcp_seg *rseg;
 u8_t acceptable = 0;
 err_t err;
 err = ERR_OK;
 /* Process incoming RST segments. */
 if (flags & TCP_RST) {
   /* First, determine if the reset is acceptable. */
   if (pcb->state == SYN_SENT) {
     if (ackno == pcb->snd_nxt) {
       acceptable = 1;
   } else {
     if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
                         pcb->rcv_nxt+pcb->rcv_wnd)) {
       acceptable = 1;
   if (acceptable) {
     LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n"));
     LWIP_ASSERT("tcp_input: pcb->state != CLOSED", pcb->state != CLOSED);
     recv_flags |= TF_RESET;
     pcb->flags &= ~TF_ACK_DELAY;
     return ERR_RST;
   } else {
     LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
      seqno, pcb->rcv_nxt));
     LWIP_DEBUGF(TCP_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
      seqno, pcb->rcv_nxt));
     return ERR_OK;
 if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) {
   /* Cope with new connection attempt after remote end crashed */
   tcp_ack_now(pcb);
   return ERR_OK;
 if ((pcb->flags & TF_RXCLOSED) == 0) {
   /* Update the PCB (in)activity timer unless rx is closed (see tcp_shutdown) */
   pcb->tmr = tcp_ticks;
 pcb->keep_cnt_sent = 0;
 tcp_parseopt(pcb);
switch (pcb->state)

下面是实现TCP状态机的具体部分

  case SYN_SENT:
    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("SYN-SENT: ackno %"U32_F" pcb->snd_nxt %"U32_F" unacked %"U32_F"\n", ackno,
     pcb->snd_nxt, ntohl(pcb->unacked->tcphdr->seqno)));
    /* received SYN ACK with expected sequence number? */
    if ((flags & TCP_ACK) && (flags & TCP_SYN)
        && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) {
      pcb->snd_buf++;
      pcb->rcv_nxt = seqno + 1;
      pcb->rcv_ann_right_edge = pcb->rcv_nxt;
      pcb->lastack = ackno;
      pcb->snd_wnd = tcphdr->wnd;
      pcb->snd_wnd_max = tcphdr->wnd;
      pcb->snd_wl1 = seqno - 1; /* initialise to seqno - 1 to force window update */
      pcb->state = ESTABLISHED;
#if TCP_CALCULATE_EFF_SEND_MSS
      pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip));
#endif /* TCP_CALCULATE_EFF_SEND_MSS */
      /* Set ssthresh again after changing pcb->mss (already set in tcp_connect
       * but for the default value of pcb->mss) */
      pcb->ssthresh = pcb->mss * 10;
      pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
      LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0));
      --pcb->snd_queuelen;
      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"U16_F"\n", (u16_t)pcb->snd_queuelen));
      rseg = pcb->unacked;
      pcb->unacked = rseg->next;
      tcp_seg_free(rseg);
      /* If there's nothing left to acknowledge, stop the retransmit
         timer, otherwise reset it to start again */
      if(pcb->unacked == NULL)
        pcb->rtime = -1;
      else {
        pcb-




    
>rtime = 0;
        pcb->nrtx = 0;
      /* Call the user specified function to call when sucessfully
       * connected. */
      TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
      if (err == ERR_ABRT) {
        return ERR_ABRT;
      tcp_ack_now(pcb);
    /* received ACK? possibly a half-open connection */
    else if (flags & TCP_ACK) {
      /* send a RST to bring the other side in a non-synchronized state. */
      tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),
        tcphdr->dest, tcphdr->src);
    break;

SYN_SENT (此状态是客户端发送完SYN握手请求后的状态,等待SYN+ACK)
一、如果收到②SYN+ACK,且对确认序号ackno进行校验,符合预期。设置出口参数,
控制块进入ESTABLISHED状态,对控制块中unacked进行更新。调用connected函数(回调用tcp_enqueue_flags将握手报文段放到TCP控制块的unsent队列上。调用tcp_output(会调用tcp_output_segment判断报文是否在允许的发送窗口内,并将报文段从unsent转移到unacked队列上))发送ACK,结束三次握手。
二、如果收到的是ACK,返回一个RST

case SYN_RCVD:
    if (flags & TCP_ACK) {
      /* expected ACK number? */
      if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {
        u16_t old_cwnd;
        pcb->state = ESTABLISHED;
        LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
#if LWIP_CALLBACK_API
        LWIP_ASSERT("pcb->accept != NULL", pcb->accept != NULL);
#endif
        /* Call the accept function. */
        TCP_EVENT_ACCEPT(pcb, ERR_OK, err);
        if (err != ERR_OK) {
          /* If the accept function returns with an error, we abort
           * the connection. */
          /* Already aborted? */
          if (err != ERR_ABRT) {
            tcp_abort(pcb);
          return ERR_ABRT;
        old_cwnd = pcb->cwnd;
        /* If there was any data contained within this ACK,
         * we'd better pass it on to the application as well. */
        tcp_receive(pcb);
        /* Prevent ACK for SYN to generate a sent event */
        if (pcb->acked != 0) {
          pcb->acked--;
        pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
        if (recv_flags & TF_GOT_FIN) {
          tcp_ack_now(pcb);
          pcb->state = CLOSE_WAIT;
      } else {
        /* incorrect ACK number, send RST */
        tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),
                tcphdr->dest, tcphdr->src);
    } else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {
      /* Looks like another copy of the SYN - retransmit our SYN-ACK */
      tcp_rexmit(pcb);
    break;

SYN_RCVD(此状态是服务器发送完SYN+ACK后的状态,等待ACK)
一、 如果收到③ACK,且确认序号ackno合法
控制块进入ESTABLISHED状态,开始接收数据。调用用户的accept函数(调用出错就关闭当前连接,返回出错),保持旧的阻塞窗口,调用函数tcp_receive处理报文中的数据,同时当本地的unacked被报文中的ACK确认时,重新设置阻塞窗口。
数据传输过程中,当接收到④FIN握手,响应对方的FIN握手,并回复一个ACK⑤,并且控制块进入CLOSE_WAIT状态。
二、 如果收到①SYN握手报文,说明自己发的②SYN+ACK丢失,重传②SYN+ACK

  case CLOSE_WAIT:
    /* FALLTHROUGH */

CLOSE_WAIT (此状态是服务器处于半关闭状态)

 case ESTABLISHED:
    tcp_receive(pcb);
    if (recv_flags & TF_GOT_FIN) { /* passive close */
      tcp_ack_now(pcb);
      pcb->state = CLOSE_WAIT;
    break;

ESTABLISHED (握手建立后,传输数据的过程中)
一、接收到④FIN握手,响应对方的FIN握手,并回复一个ACK⑤,并且控制块进入CLOSE_WAIT状态。

  case FIN_WAIT_1:
    tcp_receive(pcb);
    if (recv_flags & TF_GOT_FIN) {
      if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {
        LWIP_DEBUGF(TCP_DEBUG,
          ("TCP connection closed: FIN_WAIT_1 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
        tcp_ack_now(pcb);
        tcp_pcb_purge(pcb);
        TCP_RMV_ACTIVE(pcb);
        pcb->state = TIME_WAIT;
        TCP_REG(&tcp_tw_pcbs, pcb);
      } else {
        tcp_ack_now(pcb);
        pcb->state = CLOSING;
    } else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {
      pcb->state = FIN_WAIT_2;
    break;

FIN_WAIT1 (上层应用调用tcp_close函数,并发送FIN后的状态)
继续调用tcp_receive函数处理报文中的数据
一、 收到一个⑥FIN握手,且包含有效ACK。则响应此FIN握手,回复一个ACK,开始消除连接中的所有数据。控制块从tcp_active_pcb链表中删除,控制块进入TIME_WAIT状态,控制块加入到tcp_tw_pcbs链表中
收到的这个FIN握手也可能是④,即双方同时执行关闭操作,则此时返回ACK,进入CLOSING状态
二、 收到一个只收到⑤ACK,则进入FIN_WAIT2状态

case FIN_WAIT_2:
    tcp_receive(pcb);
    if (recv_flags & TF_GOT_FIN) {
      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_2 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
      tcp_ack_now(pcb);
      tcp_pcb_purge(pcb);
      TCP_RMV_ACTIVE(pcb);
      pcb->state = TIME_WAIT;
      TCP_REG(&tcp_tw_pcbs, pcb);
    break;

FIN_WAIT2 (主动关闭,发送FIN握手且接受到ACK后的状态)
继续调用tcp_receive函数处理报文中的数据
一、收到一个⑥FIN握手,则响应此FIN握手,回复一个ACK,开始消除连接中的所有数据。控制块从tcp_active_pcb链表中删除,控制块进入TIME_WAIT状态,控制块加入到tcp_tw_pcbs链表中

case CLOSING:
    tcp_receive(pcb);
    if (flags & TCP_ACK && ackno == pcb->snd_nxt) {
      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: CLOSING %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
      tcp_pcb_purge(pcb);
      TCP_RMV_ACTIVE(pcb);
      pcb->state = TIME_WAIT;
      TCP_REG(&tcp_tw_pcbs, pcb);
    break;

CLOSING (双方同时执行主动关闭后的状态)
继续调用tcp_receive函数处理报文中的数据
当收到有效ACK,开始消除连接中的所有数据。控制块从tcp_active_pcb链表中删除,控制块进入TIME_WAIT状态,控制块加入到tcp_tw_pcbs链表中

  case LAST_ACK:
    tcp_receive(pcb);
    if (flags & TCP_ACK && ackno == pcb->snd_nxt) {
      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: LAST_ACK %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
      /* bugfix #21699: don't set pcb->state to CLOSED here or we risk leaking segments */
      recv_flags |= TF_CLOSED;
    break;
  default:
    break;

LAST_ACK
继续调用tcp_receive函数处理报文中的数据
当收到有效ACK,设置recv_flags为TF_CLOSED,返回tcp_input对该控制块进行释放和清除工作。

一、三次握手TCP状态。客户端:1.SYN_SENT 2.ESTABLISHED 服务端:1.SYN_RECVD 2.ESTABLISHED. 1.首先由客户端发起TCP连接,第一个报文段,TCP头部SYN位置为1,随机生成一个ISN, MSS数值(最大报文段 <= 1460),客户端窗口大小,客户端状态变为SYN_SENT。 2.服务端接收到SYN报文段,状... lwip从逻辑上看也是分为4层:链路层、网络层(IP、ARP、(ICMP、IGMP这两个协议是网络层的补充协议,并不严格属于网络层))、传输层(TCP、UDP)、应用层,基本等同TCP/IP,只是各层之间可以进行交叉存取,没有严格划分。 协议汇总:     1. ARP协议:根据IP地址获取物理地址MAC的一个TCP/IP协议 一个典型的lwip系统包含3个进程:首先是上层应用程序进程,然后 这里只讲raw编程,如果你使用LWIP-socket或netconn,那就不存在这个问题,这些高级函数会自己在合适的地方调用tcp_recved函数。 问题总是重复的。对于初学者而言,有不少网络上流传的源码,一个典型的例子是tcp_echoserver。各个教学的资料中也都提供源码。但很可惜,这些教学板提供的源码缺乏可移植性。一具最常见的错误在于,大家对于什么时候调用tcp_reved没什么概念... (tcp的收发与接收窗口/发送窗口/通告窗口关联比较紧密,接收/发送过程在《TCP/IP传输层协议实现 - TCP接收窗口/发送窗口/通告窗口(lwip)》https://blog.csdn.net/arm7star/article/details/117153533都有介绍,本文对收发过程进行更详细一步介绍。) 1、滑动窗口 1.1、接收窗口(接收滑动窗口) 接收窗口是本地可以接收数据的窗口,接收端只接收窗口内的数据,窗口外的丢弃。 接收到数据,接收窗口左边沿右移,接收窗口减小。 文章目录一,简介二,代码流程1,更新发送窗口2,快速重传与恢复3,拥塞控制算法4,更新unacked队列5,更新unsent队列6,rtt测试7,裁剪接收的数据a,接收的数据一部分已经接受过,另一部分是新数据b,数据正好在接收窗口左边界c,数据不在左边界 一个有着接近900行代码的函数,庞大的函数,需要我们有庖丁解牛的耐心 tcp_receive(struct tcp_pcb *pcb)用于处理接收到的一个报文,更新发送窗口,执行快速重传,拥塞控制算法,更新三个队列,由接收窗口来裁剪出有效数据   服务器端接收到SYN握手包,向客户端返回带SYN和ACK的握手包,并将相应TCP控制块置为SYN_RCVD状态,并挂在tcp_active_pcbs链表上。以后,继续等待客户端发送过来的握手包,这次,服务器期望的是接收一个ACK包以完成建立连接要求的三次握手操作。   还是和前几次一样,数据包进来通过ip_input传递给tcp_input,后者在三个链表中查找一个匹配的连接控制块。这次进来的是客户端发送的ACK握手包,服务器端相应的tcp控制块一定是在tcp_active_pcbs链表上。接下来,以查 接上文:TCP是传输层协议,它为应用程序提供可靠字节流服务。TCP相比于其他协议更加复杂,TCP代码量是整个LWIP协议栈代码的50%。基本的TCP处理被分为6个函数来完成(如图9所示)。函数tcp_input()、tcp_process()以及tcp_receive()与TCP输入处理相关,相应的tcp_write()、tcp_enqueue()以及tcp_output()处理输出相关。 当应用程序想要发送TCP数据时,调用tcp_write()。函数tcp_write()传输控制给tcp_enqueue