lwip之数据收发流程

2023-05-16

lwip从逻辑上看也是分为4层:链路层、网络层(IP、ARP、(ICMP、IGMP这两个协议是网络层的补充协议,并不严格属于网络层))、传输层(TCP、UDP)、应用层,基本等同TCP/IP,只是各层之间可以进行交叉存取,没有严格划分。
协议汇总:
    1. ARP协议:根据IP地址获取物理地址MAC的一个TCP/IP协议
一个典型的lwip系统包含3个进程:首先是上层应用程序进程,然后是lwip协议栈进程,最后是底层硬件数据包接收进程

动态内存管理:
    采用ucos-ii内存管理系统,即申请一块内存,分割成整数个大小相同的内存块
    
一.    链路层
    
                                                                                        
    当主机A要与主机B通信时,ARP协议可以将主机B的IP地址解析成主机B的MAC地址,工作流程如下:    
        第一步:主机A先检查自己的ARP缓冲,看是否存在主机B匹配的MAC地址,如果没有,就会向外广播一个ARP请求包
        第二步:其他主机收到后,发现请求的IP地址与自己的IP地址不匹配,则丢弃ARP请求
        第三步:主机B确定ARP请求中的IP地址与自己的IP地址匹配,则将主机A的IP地址和MAC地址映射到本地ARP缓存中
        第四步:主机B将包含其MAC地址的ARP回复发回给主机A
        第五步:主机A收到从主机B发来的ARP回复时,会将主机B的IP地址和MAC地址映射更新到本地ARP缓存中。主机B的MAC地址一旦确定,主机A就可以向主机B发送IP通信了
    
    连接链路层和网络层的纽带:以太网数据包接收进程tcpip_thread
        static void tcpip_thread(void *arg)
        {
            struct tcpip_msg *msg;        // 消息来自于网卡中断
            
            while(1)
            {
                // 该任务阻塞在这里接收要处理的消息,当有数据包到来时,网卡芯片中断函数接收数据,并post消息,中断退出后,该任务获取消息
                sys_timeouts_mbox_fetch(&mbox, (void **)&msg);    
                
                // 判断本条消息的类型,只关注数据包消息TCPIP_MSG_INPKT
                switch (msg->type)
                {
                    case TCPIP_MSG_INPKT:                                            // 数据包消息
                        if(msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET))
                            ethernet_input(msg->msg.inp.p,msg->msg.inp.netif);    // 如果支持ARP,先进行ARP处理,再判断是否递交IP层,对于IP数据包,这里2个选择最终都要调用ip_input进入IP层
                        else
                            ip_input(msg->msg.inp.p, msg->msg.inp.netif);            // 否则直接递交IP层,ip_input为IP层主要函数,解析见下文,这里直接调用ip_input存在问题,有误,需要先以太网数据包指针,使掠过包头,指向IP协议包头
                        memp_free(MEMP_TCPIP_MSG_INPKT, msg);                        // 释放消息内存
                        break;
                    case TCPIP_MSG_TIMEOUT:                                            // 超时消息
                        sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
                        memp_free(MEMP_TCPIP_MSG_API, msg);
                        break;
                    default:
                        break;
                }
            }
        }
        err_t ethernet_input(struct pbuf *p,struct netif *netif)
        {
            struct eth_hdr *ethhdr;                    // 以太网数据包头结构体
            u16_t type;
            s16_t ip_hdr_offset = SIZEOF_ETH_HDR;    // 包头固定值14字节
            
            ethhdr = (eth_hdr *)p->payload;
            type = htons(ethhdr->type);
            
            switch(type)
            {
                case ETHTYPE_IP:                                                // IP数据包
                    etharp_ip_input(netif,p);                                    // 使用收到的IP包更新ARP缓存表,详见《lwip之ARP协议》
                    pbuf_header(p, -ip_hdr_offset);                                // 调整以太网数据包指针,使掠过包头,指向IP协议包头
                    ip_input(p,netif);                                            // 提交IP层,ip_input为IP层主要函数,解析见下文
                case ETHTYPE_ARP:                                                // ARP数据包
                    etharp_arp_input(netif,(struct eth_addr *)netif->hwaddr,p);    // ARP数据包处理,第二个形参是本机MAC,详见《lwip之ARP协议》
                    break;
                default:
                    break;
            }
        }
        
        注:消息结构体struct tcpip_msg {
                            enum tcpip_msg_type type;                // 本条消息的类型:TCPIP_MSG_INPKT - 数据包消息,TCPIP_MSG_TIMEOUT - 超时消息
                            sys_sem_t *sem;                            // 事件控制块ECB
                            union{
                                struct api_msg *apimsg;
                                struct netifapi_msg *netifapimsg;
                                struct {
                                    struct pbuf *p;
                                    struct netif *netif;
                                } inp;                                // inp结构体最重要,内含数据包内容结构、网络接口结构
                                struct {
                                      tcpip_callback_fn function;
                                      void *ctx;
                                } cb;
                                struct {
                                      u32_t msecs;
                                      sys_timeout_handler h;
                                      void *arg;
                                } tmo;
                            }msg;
                        }
    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------    
二. 网络层
        lwip使用一个ip_hdr的结构体来描述IP协议包头:
                                                struct ip_hdr{
                                                    u16_t _v_hl_tos;    // 包含4位版本号(IPv4 - 4,IPv6 - 6)、4位IP包头长(通常为5*4,即本结构体大小)、8位服务类型
                                                    u16_t _len;            // 整个IP数据包长度
                                                    u16_t _id;            // 16位标识用于标识IP层发出的每一份IP报文,自增
                                                    u16_t _offset;        // 包含3位标志和13位片偏移,IP数据包分片时使用
                                                    u8_t _ttl;            // TTL描述该IP数据包最多能被转发的次数,自减
                                                    u8_t _proto;        // 协议字段用于描述该IP数据包的上层协议,0x01 - ICMP,0x02 - IGMP,0x06 - TCP,0x17 - UDP
                                                    u16_t _chksum;        // 16位的IP首部校验和
                                                    ip_addr_p_t src;    // 源IP
                                                    ip_addr_p_t dest;    // 目的IP
                                                }
    
        ip_input为IP层主干函数,完成了IP层数据包处理(核心工作就是IP地址匹配;得到完整数据包),然后将合适的数据包提交给上层,这里的p->payload已经越过了14字节包头,指向了IP头
        err_t ip_input(struct pbuf *p,struct netif *inp)
        {
            struct     ip_hdr *iphdr;    // 指向IP包头的指针
            struct     netif *netif;    // 指向netif硬件网络接口设备描述符的指针
            u16_t    iphdr_hlen;        // IP包头的长度,通常是固定20字节
            u16_t    iphdr_len;        // 整个IP包长,包含IP包头、上层协议头、数据
            
            // 取出 IP数据包头
            iphdr = (struct ip_hdr *)p->payload;
            
            // 检查IP包头中的版本号字段,IPv4 - 4,IPv6 - 6
            if(IPH_V(iphdr) != 4)
            {
                pbuf_free(p);
                return ERR_OK;    
            }
            
            // 提取IP包头中的头长度字段,通常固定值20字节
            iphdr_hlen = IPH_HL(iphdr);
            iphdr_hlen *= 4;
            
            // 提取IP包头中的IP包总长度字段,确保小于递交上来的pbuf包中的总长度
            iphdr_len = ntohs(IPH_LEN(iphdr));
            if(iphdr_len > p->len || iphdr_len > p->tot_len)
            {
                pbuf_free(p);
                return ERR_OK;    
            }
            
            // 校验IP数据包头
            if (inet_chksum(iphdr, iphdr_hlen) != 0)
            {
                pbuf_free(p);
                return ERR_OK;    
            }
            
            // 对IP数据报进行截断,得到完整无冗余IP数据包
            pbuf_realloc(p, iphdr_len);
            
            // 遍历netif_list链表(系统存在2个网卡设备,意味着有2个netif分别用于描述它们,也意味着本机有2个IP地址,所以此时就需要遍历),检测IP数据包中的目的IP是否与本机相符,不符则丢弃或转发
            ip_addr_copy(current_iphdr_dest, iphdr->dest);
            ip_addr_copy(current_iphdr_src, iphdr->src);
            int first = 1;
            netif = inp;
            do{
                // 通过netif->flag标志位判断该网卡设备是否配置且使能,同时判断本机IP是否有效
                if ((netif_is_up(netif)) && (!ip_addr_isany(&(netif->ip_addr))))
                {
                    // 如果目的IP地址与本机IP地址匹配或者目的IP地址是广播类型,意味着成功匹配,退出遍历
                    if(ip_addr_cmp(&current_iphdr_dest, &(netif->ip_addr)) || ip_addr_isbroadcast(&current_iphdr_dest, netif))    
                    {
                        break;    
                    }
                }
                if (first)
                {
                    first = 0;
                    netif = netif_list;
                }
                else
                {
                    netif = netif->next;
                }
                  if (netif == inp)
                  {
                    netif = netif->next;
                  }
            }while(netif != NULL);
            
            //  如果该数据包中的源IP地址是广播IP,则直接丢弃
            if ((ip_addr_isbroadcast(&current_iphdr_src, inp)) || (ip_addr_ismulticast(&current_iphdr_src)))
            {
                pbuf_free(p);
                return ERR_OK;
            }
            
            // 遍历完成以后,如果依旧没有找到匹配的netif结构体,说明该数据包不是给本机的,转发或丢弃(这里直接丢弃)
              if (netif == NULL)
              {
                  pbuf_free(p);
                return ERR_OK;
              }
              
              // 判断该IP包是否是分片数据包
              // 如果是分片数据包,则需要将该分片包暂存,等接收完所有分片包后,统一将整个数据包提交给上层
              if ((IPH_OFFSET(iphdr) & PP_HTONS(IP_OFFMASK | IP_MF)) != 0)
              {
                  // 在这里重组接收到的分片包,如果还没接收完整,p=NULL
                  p = ip_reass(p);
                // 如果分片包还没接收完整,本函数结束
                if (p == NULL)
                {
                  return ERR_OK;
                }
                // 如果分片包接收完整,这时的p已经是一个完整的数据包结构体了
                // 再从p中获取完整的IP包
                iphdr = (struct ip_hdr *)p->payload;            
              }
              
            // 能到达这一步的数据包必然是未分片的或经过分片重组完整后的数据包
            current_netif = inp;
              current_header = iphdr;
              if (raw_input(p, inp) == 0)
              {
                  // 根据IP数据包头中的协议字段判断该数据包应该被递交给上层哪个协议
                  switch (IPH_PROTO(iphdr))
                  {
                      case IP_PROTO_UDP:    // UDP协议
                          udp_input(p, inp);    // 从这里进入传输层,解析见下文
                          break;    
                      case IP_PROTO_TCP:    // TCP协议
                          tcp_input(p, inp);    // 从这里进入传输层,解析见下文
                        break;
                    case IP_PROTO_ICMP:    // ICMP协议
                        icmp_input(p, inp);
                        break;
                    case IP_PROTO_IGMP:    // IGMP协议
                        igmp_input(p, inp, &current_iphdr_dest);
                        break;
                    default:            // 如果都不是
                        // 如果不是广播数据包,返回一个协议不可达ICMP数据包给源主机
                        if (!ip_addr_isbroadcast(&current_iphdr_dest, inp) && !ip_addr_ismulticast(&current_iphdr_dest))
                        {
                            p->payload = iphdr;
                            icmp_dest_unreach(p, ICMP_DUR_PROTO);
                        }    
                        pbuf_free(p);        
                  }    
              }
            
            current_netif = NULL;
            current_header = NULL;
            ip_addr_set_any(&current_iphdr_src);
            ip_addr_set_any(&current_iphdr_dest);
        }
                                            
        IP层的补充协议:ICMP、IGMP
            这时候主机A学到了主机B的MAC地址,就把这个MAC封装到ICMP协议中向主机B发送,报文格式如下:
                包头14字节                                        :因为ICMP协议包属于网络层协议,所以帧类型是0x0800
            +    ICMP协议头(主要是二级协议类型、源IP、目的IP)    :二级协议类型ICMP对应值0x01
            +    ICMP协议主体(主要是一个类别)                    :类别取值0x00 - 这是一条回应信息    0x03 - 目的不可达    0x08 - 请求回应信息
            
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
三.传输层(这里主要讲TCP)
        TCP连接的建立过程(三次握手):
            1. 客户端发送一个SYN标志置1的TCP数据报,握手包中指明源端口和目的端口,同时告知客户端初始序号seqno_client
            2. 当服务器接收到该数据包并解析后,也发回一个SYN标志置1的数据报作为应答,应答中包含服务器端初始序号seqno_server,同时将ACK标志置1,将确认序号设置为seqno_client+1
            3. 当客户端接收到服务端的SYN应答包,会再次产生一个握手包,包中ACK标志置1,确认序号设置为seqno_server+1
        TCP连接的断开过程(四次握手):
            1. 当客户端应用程序主动执行关闭操作时,客户端会向服务器发送一个FIN标志置1的报文段,用来关闭从客户端到服务器的数据传送,该报文段序号字段为seqno_client
            2. 当服务器接收到这个FIN报文段后,返回一个ACK报文,确认序号为seqno_client+1,当客户端收到这个ACK后,从客户端到服务器方向的连接就断开了
            3. 服务器TCP向其上层应用程序通过客户端的端口操作,这会导致服务器应用程序关闭它的连接,同样,此时一个FIN置1的报文段将被发往客户端,该报文段序号字段为seqno_server
            4. 当客户端收到这个FIN报文段后,也会返回一个ACK作为响应,确认序号为seqno_server+1,从服务器到客户端方向的连接也就被断开了
            
        ******************************************************************************************************************************************************************************************************    
        lwip一共定义了11种TCP状态:
                            enum tcp_state{
                                CLOSED        = 0,    // 没有连接
                                LISTEN        = 1,    // 服务器进入侦听状态,等待客户端的连接请求
                                SYN_SENT    = 2,    // 连接请求已发送,等待确认
                                SYN_RCVD    = 3,    // 已收到对方的连接请求
                                ESTABLISHED = 4,    // 连接已建立
                                FIN_WAIT_1    = 5,    // 程序已关闭该连接
                                FIN_WAIT_2    = 6,    // 另一端已接受关闭该连接
                                CLOSE_WAIT    = 7,    // 等待程序关闭连接
                                CLOSING        = 8,    // 两端同时收到对方的关闭请求
                                LAST_ACK    = 9,    // 服务器等待对方接受关闭操作
                                TIME_WAIT    = 10,    // 关闭成功,等待网络中可能出现的剩余数据
                            }
                            
        两条最经典的TCP状态转换路径:
            1.第一条路径描述了客户端申请建立连接与断开连接的整个过程:
                                                CLOSED ————————> SYN_SENT ————————> ESTABLISHED ————> FIN_WAIT_1 ————> FIN_WAIT_2 ——————> TIME_WAIT ——> CLOSED
                                                        主动打开/syn                syn+ack/ack                      /fin                    ack/                fin/ack
            2.第二条路径描述了服务器建立连接与断开连接的整个过程:
                                                CLOSED ————————> LISTEN ————————> SYN_RCVD ————> ESTABLISHED ————————> CLOSE_WAIT ————> LAST_ACK ————> CLOSED
                                                        被动打开/                    syn/syn+ack                ack/                    fin/ack                          /fin                ack/
        
        ******************************************************************************************************************************************************************************************************                                                    
        lwip使用一个tcp_hdr的结构体来描述tcp协议包头:
                                                struct tcp_hdr{
                                                    u16_t src;                    // 源端口
                                                    u16_t dest;                    // 目的端口
                                                    u32_t seqno;                // 序号,用来标识从TCP发送端到接收端的数据字节流
                                                    u32_t ackno;                // 确认序号,是发送确认的一段所期望收到的下一个序号
                                                    u16_t _hdrlen_rsvd_flags;    // 包含4位TCP包头长(通常为5*4,即本结构体大小)、6个标志位(URG、ACK、PSH、RST、SYN、FIN)
                                                    u16_t wnd;                    // 窗口大小字段,表示还能接收的字节数,实现流量控制
                                                    u16_t chksum;                // 16位整个TCP报文校验和,包含了TCP头和TCP数据,由发送端计算并由接收端验证
                                                    u16_t urgp;                    // 紧急指针,暂略
                                                }
                                                
        ******************************************************************************************************************************************************************************************************                                            
        lwip使用一个tcp_pcb控制块来描述一个TCP连接(lwip实际定义了2种TCP控制块,一种专门用于描述处于LISTEN状态的连接,另一种用于描述处于其他状态的连接):
                                                struct tcp_pcb{
                                                    IP_PCB;                        // 该宏描述了连接的IP相关信息,主要包含源IP、目的IP两个重要字段    
                                                    
                                                    // 这部分是2种类型TCP控制块都具有的字段                                        
                                                    struct tcp_pcb *next;        // 指向下一个tcp_pcb控制块的链表指针
                                                    enum tcp_state state;        // TCP连接的状态,如上所述共11种
                                                    u8_t prio;                    // 该控制块的优先级,可用于回收低优先级控制块
                                                    void *callback_arg;            // 指向用户自定义数据,在函数回调时使用
                                                    tcp_accept_fn accept;        // 连接accept时回调函数
                                                    u16_t local_port;            // 绑定的本地端口
                                                            
                                                    u16_t remote_port;            // 远程端口
                                                    u8_t flags;                    // 控制块状态、标志字段,描述了当前控制块的特性,各位的含义如下宏定义
                                                    #define TF_ACK_DELAY    0x01    // 延迟发送ACK
                                                    #define TF_ACK_NOW        0x02    // 立即发送ACK
                                                    #define TF_INFR            0x04    // 连接处于快重传状态
                                                    #define TF_TIMESTAMP    0x08    // 连接的时间戳选项已使能
                                                    #define TF_RXCLOSED     0x10    // 因TCP连接断开导致RX关闭
                                                    #define TF_FIN            0x20    // 应用程序已关闭该连接
                                                    #define TF_NODELAY        0x40    // 禁止Nagle算法
                                                    #define TF_NAGLEMEMERR    0x80    // 本地缓冲区溢出
                                                    
                                                    // 接收相关字段
                                                    u32_t rcv_nxt;                // 期望接收的下一个序号,也即是本地将要反馈给对方的ACK的序号,也是本地接收窗口的左边界
                                                    u16_t rcv_wnd;                // 当前接收窗口大小,会随着数据的接收与递交动态变化
                                                    u16_t rcv_ann_wnd;            // 将向对方通告的窗口大小,也会随着数据的接收与递交动态变化
                                                    u32_t rcv_ann_right_edge;    // 上一次窗口通告时窗口的右边界值
                                                    
                                                    // 时间相关字段
                                                    u32_t tmr;                    // 其它各计数器都基于tmr的值来实现        
                                                      u8_t polltmr, pollinterval;    // 这两个字段用于周期性调用一个函数,polltmr会周期性增加,当超过pollinterval时,poll函数会被调用
                                                      s16_t rtime;                // 重传定时器,当大于rto的值时则重传报文
                                                      u16_t mss;                    // 对方可接收的最大报文大小
                                                      
                                                      // RTT估计相关的参数
                                                      u32_t rttest;
                                                      u32_t rtseq;
                                                      s16_t sa, sv;
                                                      
                                                      s16_t rto;                    // 重传超时时间,使用上面3个RTT参数计算出来
                                                      u8_t nrtx;                    // 重传次数
                                                      
                                                      // 快速重传与恢复相关字段
                                                      u32_t lastack;                // 接收到的上一个确认序号,也就是最大确认序号
                                                      u8_t dupacks;                // 上述最大确认序号被重复收到的次数    
                                                      
                                                      // 阻塞控制相关参数
                                                      u16_t cwnd;                  // 连接当前的阻塞窗口大小
                                                      u16_t ssthresh;                // 拥塞避免算法启动阈值
                                                      
                                                      // 发送相关字段
                                                      u32_t snd_nxt;                // 下一个将要发送的序号
                                                      u16_t snd_wnd;                // 当前发送窗口大小
                                                      u32_t snd_wl1, snd_wl2;        // 上次窗口更新时收到的数据序号seqno和确认号ackno
                                                      u32_t snd_lbb;                 // 下一个被缓冲的应用程序数据的编号
                                                      
                                                      u16_t acked;                // 保存了被确认的已发送长度
                                                      u16_t snd_buf;                 // 可用的发送空间(以字节为单位)
                                                      u16_t snd_queuelen;            // 被占用的发送空间(以数据段pbuf为单位)
                                                      u16_t unsent_oversize;        // 尚未被发送的字节数
                                                      struct tcp_seg *unsent;        // 未发送的数据段队列,链表形式
                                                      struct tcp_seg *unacked;    // 发送了未收到确认的数据段队列,链表形式
                                                      struct tcp_seg *ooseq;        // 接收到有序序号以外的数据段队列,链表形式
                                                      
                                                      struct pbuf *refused_data;    // 指向上一次成功接收但未被应用层取用的数据pbuf
                                                      
                                                      // 回调函数
                                                      err_t (*sent)(void *arg,struct tcp_pcb *pcb,u16_t space);            // 数据成功发送后被调用        
                                                      err_t (*recv)(void *arg,struct tcp_pcb,struct pbuf *p,err_t err);    // 接收到数据后被调用
                                                      err_t (*connected)(void *arg, struct tcp_pcb *tpcb, err_t err);        // 连接建立后被调用
                                                      err_t (*poll)(void *arg, struct tcp_pcb *tpcb);                        // 该函数被内核周期性调用
                                                      void  (*errf)(void *arg, err_t err);                                // 连接发生错误时被调用
                                                      
                                                      // 心跳相关参数
                                                      u32_t keep_idle;            // 最后一个正常报文结束到保活计时器(心跳)启动的时间间隔
                                                      u32_t keep_intvl;            // 保活计时器(心跳)发送间隔
                                                      u32_t keep_cnt;                // 保活计时器(心跳)最大重发次数    ?
                                                      u32_t persist_cnt;            // 坚持定时器计数值
                                                      u8_t persist_backoff;        // 坚持定时器开关,大于0开启
                                                      u8_t keep_cnt_sent;            // 保活计时器(心跳)最大重发次数    ?
                                                }    
                                                struct tcp_pcb_listen{
                                                    IP_PCB;
                                                    struct tcp_pcb *next;        
                                                    enum tcp_state state;        
                                                    u8_t prio;                    
                                                    void *callback_arg;    
                                                    tcp_accept_fn accept;        
                                                    u16_t local_port;
                                                }
            注:#define IP_PCB     ip_addr_t local_ip;        // 本地IP
                                ip_addr_t remote_ip;    // 目的IP
                                u8_t       so_options;    // 套接字选项    可取值:    #define SOF_ACCEPTCONN    (u8_t)0x02U
                                                                                #define SOF_REUSEADDR     (u8_t)0x04U
                                                                                #define SOF_KEEPALIVE     (u8_t)0x08U
                                                                                #define SOF_BROADCAST     (u8_t)0x20U
                                                                                #define SOF_LINGER        (u8_t)0x80U
                                                                                #define SOF_INHERITED     (SOF_REUSEADDR|SOF_KEEPALIVE|SOF_LINGER)
                                u8_t      tos;            // 服务类型
                                u8_t      ttl;            // TTL
            这个TCP控制块是整个TCP协议的核心,TCP协议实现的本质就是对TCP控制块中各字段的操作,所以非常重要!!!
        
        ******************************************************************************************************************************************************************************************************    
        tcp_input是TCP层的总输入函数,它会为数据包寻找一个匹配的TCP控制块,以及调用相应的函数tcp_timewait_input,tcp_listen_input,tcp_process进行处理
        void tcp_input(struct pbuf *p,struct netif *inp)
        {
            struct tcp_pcb     *pcb,*prev;
            struct tcp_pcb_listen *lpcb;
            u8_t hdrlen;
            err_t err;
            
            // 略过IP包头,提取TCP头
            iphdr = (struct ip_hdr *)p->payload;
            tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr)*4)
            
            // 移动pbuf结构中的数据包指针,使指向TCP头
            if (pbuf_header(p, -((s16_t)(IPH_HL(iphdr) * 4))) || (p->tot_len < sizeof(struct tcp_hdr)))
            {
                pbuf_free(p);
                return;    
            }
            
            // 不处理输入的广播包
            if (ip_addr_isbroadcast(&current_iphdr_dest, inp) || ip_addr_ismulticast(&current_iphdr_dest))
            {
                pbuf_free(p);
                return;        
            }
            
            // 验证TCP校验和
            if (inet_chksum_pseudo(p, ip_current_src_addr(), ip_current_dest_addr(),IP_PROTO_TCP, p->tot_len) != 0)
            {
                pbuf_free(p);
                return;    
            }
            
            // 继续移动pbuf结构中的数据包指针,使指向TCP数据
            hdrlen = TCPH_HDRLEN(tcphdr);
            if(pbuf_header(p, -(hdrlen * 4))
            {
                pbuf_free(p);
                return;    
            }
            
            // 网络字节序转主机字节序
            tcphdr->src = ntohs(tcphdr->src);                // 源端口
            tcphdr->dest = ntohs(tcphdr->dest);                // 目的端口
            seqno = tcphdr->seqno = ntohl(tcphdr->seqno);    // 序号
            ackno = tcphdr->ackno = ntohl(tcphdr->ackno);    // 确认序号
            tcphdr->wnd = ntohs(tcphdr->wnd);                // 窗口大小

            flags = TCPH_FLAGS(tcphdr);                        // 6位标志位
            tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);    // TCP数据包中数据的总长度,对于有FIN或SYN标志的数据包,该长度要加1
            
            // 以下就是对接收到的数据包进行分类处理,也就是寻找合适的接口,根据IP,port
            // 首先在tcp_active_pcbs 链表池中找,有没有匹配的tcp_pcb
            prev = NULL;
            for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next)
            {
                if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), &current_iphdr_src) && ip_addr_cmp(&(pcb->local_ip), &current_iphdr_dest))
                {
                    // 找到匹配的接口之后,将该tcp_pcb从tcp_active_pcbs链表池中取出,然后退出循环往下运行,这时pcb != NULL
                    if (prev != NULL)
                    {
                        prev->next = pcb->next;
                        pcb->next = tcp_active_pcbs;
                        tcp_active_pcbs = pcb;
                    }    
                    break;
                }    
                prev = pcb;
            }
            
            // 如果在tcp_active_pcbs中没有找到,继续在tcp_tw_pcbs 和tcp_listen_pcbs中找
            if (pcb == NULL)
            {
                // 在tcp_tw_pcbs中找
                for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next)     
                {
                    if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), &current_iphdr_src) && ip_addr_cmp(&(pcb->local_ip), &current_iphdr_dest))
                    {
                        // 进入TIME_WAIT状态处理(解析见下文),处理完直接这里返回不再往下运行
                        tcp_timewait_input(pcb);
                        pbuf_free(p);
                        return;
                    }
                }
                
                // 在tcp_listen_pcbs中找
                prev = NULL;
                for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next)
                {
                    // 判断端口是否匹配
                      if (lpcb->local_port == tcphdr->dest)     
                      {
                          // 然后判断IP是否匹配,或者是IPADDR_ANY接收任何IP
                        if (ip_addr_cmp(&(lpcb->local_ip), &current_iphdr_dest) || ip_addr_isany(&(lpcb->local_ip)))
                        {
                            // 找到匹配的接口之后退出循环往下运行,这时lpcb != NULL
                              break;
                        }        
                      }
                      prev = (struct tcp_pcb *)lpcb;
                }
                
                // 这里是判断在tcp_listen_pcbs中是否找到
                if (lpcb != NULL)
                {
                    // 将该tcp_pcb从tcp_listen_pcbs.listen_pcbs链表池中取出
                    if (prev != NULL)
                    {
                        ((struct tcp_pcb_listen *)prev)->next = lpcb->next;
                        lpcb->next = tcp_listen_pcbs.listen_pcbs;
                        tcp_listen_pcbs.listen_pcbs = lpcb;    
                    }    
                    
                    // 进入LISTEN状态处理(解析见下文),处理完直接这里返回不再往下运行
                    tcp_listen_input(lpcb);
                    pbuf_free(p);
                    return;
                }
            }
            
            // 如果在tcp_active_pcbs中找到了,则经过处理后进入tcp_process
              if (pcb != NULL)
              {
                  inseg.next = NULL;        // 关闭报文段队列功能
                inseg.len = p->tot_len;    // 设置该报文段的数据长度
                inseg.p = p;            // 设置报文段数据链表头指针
                inseg.tcphdr = tcphdr;    // 报文段的TCP头
        
                recv_data = NULL;        // 数据接收结果被保存在该全局变量,然后往上层提交
                recv_flags = 0;            // tcp_process执行完后的结果(控制块的状态变迁)将会被保存在该全局变量,首先在这里被清0
                
                // tcp_pcb的refused_data指针上是否还记录有尚未往上层递交的数据
                if (pcb->refused_data != NULL)
                {
                    // 有的话回调用户recv函数接收未递交的数据
                    TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err);
                    
                    // 判断处理recv函数的处理结果,成功refused_data指针清空,继续往下执行tcp_process
                    if (err == ERR_OK)
                    {
                        pcb->refused_data = NULL;
                    }     
                    // 失败意味着tcp_pcb都被占用满,丢弃接收包不再处理,直接返回
                    else if ((err == ERR_ABRT) || (tcplen > 0))
                    {
                        pbuf_free(p);
                        return;
                    }
                }
                
                tcp_input_pcb = pcb;    // 记录处理当前报文的控制块
                
                // 这里就是进入tcp_process处理接收包环节了(解析见下文),该函数实现了TCP状态转换功能
                err = tcp_process(pcb);
                
                // 若返回值为ERR_ABRT,说明控制块已经被完全删除(tcp_abort()),什么也不需要做
                if (err != ERR_ABRT)
                {
                    // 返回值不为ERR_ABRT时,判断报文处理的3种结果
                    if (recv_flags & TF_RESET)             // 接收到对方的复位报文
                    {
                        // 回调用户的errf函数
                        TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_RST);
                        // 删除控制块
                        tcp_pcb_remove(&tcp_active_pcbs, pcb);
                        // 释放控制块空间
                        memp_free(MEMP_TCP_PCB, pcb);
                    }    
                    else if (recv_flags & TF_CLOSED)     // 双方连接成功断开
                    {
                        // 删除控制块
                        tcp_pcb_remove(&tcp_active_pcbs, pcb);
                        // 释放控制块空间
                        memp_free(MEMP_TCP_PCB, pcb);
                    }
                    else
                    {
                        err = ERR_OK;
                        if (pcb->acked > 0)             // 如果有被确认的已发送数据长度        
                        {
                            // 回调用户的send函数
                              TCP_EVENT_SENT(pcb, pcb->acked, err);
                              if (err == ERR_ABRT)
                              {
                                goto aborted;
                              }
                        }
                        
                        if (recv_data != NULL)            // 如果有数据被接收到
                        {
                            if (pcb->flags & TF_RXCLOSED)     // 如果本地TCP控制块已经处于TF_RXCLOSED状态,则后续接收到的数据都作废
                            {
                                pbuf_free(recv_data);
                                tcp_abort(pcb);
                                goto aborted;
                            }
                            if (flags & TCP_PSH)         // 如果TCP标志位中带有PSH
                            {
                                // 设置pbuf首部的flag字段
                                recv_data->flags |= PBUF_FLAG_PUSH;
                            }
                            
                            // 回调用户的recv函数,接收递交上去的TCP数据recv_data
                            TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
                            // 判断返回值,如果是ERR_ABRT,则丢弃,返回
                            if (err == ERR_ABRT)
                            {
                                goto aborted;
                            }
                            
                            // 除此之外,如果返回值是失败,将这部分尚未往上递交的数据暂存到refused_data指针中
                            if (err != ERR_OK)
                            {
                                pcb->refused_data = recv_data;
                            }
                        }
                        
                        if (recv_flags & TF_GOT_FIN)    // 如果收到对方的FIN请求
                        {
                            // 纠正接收窗口
                            if (pcb->rcv_wnd != TCP_WND)
                            {
                                pcb->rcv_wnd++;
                            }
                            // 用一个NULL指针回调用户的recv函数,通过这种方式用户程序可以知道对方的关闭请求
                            TCP_EVENT_CLOSED(pcb, err);
                            if (err == ERR_ABRT)
                            {
                                goto aborted;
                            }
                        }
                        
                        tcp_input_pcb = NULL;        // 当前报文到此处理完毕,清空当前报文的控制块
                        tcp_output(pcb);            // 输出报文
                    }
                }
        aborted:
                tcp_input_pcb = NULL;
                recv_data = NULL;
        
                if (inseg.p != NULL)
                {
                  pbuf_free(inseg.p);
                  inseg.p = NULL;
                }
                
              }
              else
              {
                  // 如果在3张链表里都未找到匹配的pcb,则调用tcp_rst向源主机发送一个TCP复位数据包
                  if (!(TCPH_FLAGS(tcphdr) & TCP_RST))
                  {
                      tcp_rst(ackno, seqno + tcplen,ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
                  }
                  
                  pbuf_free(p);
              }
        }
        
        ******************************************************************************************************************************************************************************************************    
        // 本函数是处于LISTEN状态的控制块对输入报文的处理函数,处于LISTEN状态的控制块只能响应SYN握手包
        err_t tcp_listen_input(struct tcp_pcb_listen *pcb)
        {
            struct tcp_pcb *npcb;
            err_t rc;
            
            // 处于listen状态的pcb只能响应SYN握手包,对含有ACK标志的输入报文返回一个RST报文
            if (flags & TCP_ACK)
            {
                tcp_rst(ackno + 1, seqno + tcplen,ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
            }
            // 处于listen状态的服务器端等到了SYN握手包
            else if (flags & TCP_SYN)
            {
                // 建立一个新的tcp_pcb,因为处于tcp_listen_pcbs链表上的pcb是tcp_pcb_listen结构的,而其他链表上的pcb是tcp_pcb结构
                npcb = tcp_alloc(pcb->prio);    
                
                // 如果新建失败,往往是因为内存不够
                if (npcb == NULL)
                {
                    TCP_STATS_INC(tcp.memerr);
                        ERR_MEM;
                }
                
                // 为这个新建的tcp_pcb填充成员
                ip_addr_copy(npcb->local_ip, current_iphdr_dest);
                npcb->local_port = pcb->local_port;
                ip_addr_copy(npcb->remote_ip, current_iphdr_src);
                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;            // 初始化右侧通告窗口
                npcb->snd_wnd = tcphdr->wnd;                        // 根据TCP头中对方可接收数据长度,初始化本地发送窗口大小
                npcb->ssthresh = npcb->snd_wnd;                        // 拥塞算法相关,暂略
                npcb->snd_wl1 = seqno - 1;                            // 初始化上次窗口更新时收到的序号
                npcb->callback_arg = pcb->callback_arg;                // 初始化用户自定义数据
                npcb->accept = pcb->accept;                            // 初始化连接accept时的回调函数    
                npcb->so_options = pcb->so_options & SOF_INHERITED;    // 继承socket选项
                
                TCP_REG(&tcp_active_pcbs, npcb);                    // 将这个设置好的tcp_pcb注册到tcp_active_pcbs链表中去
                tcp_parseopt(npcb);                                    // 从收到的SYN握手包中提取TCP头中选项字段的值,并设置到自己的tcp_pcb
                npcb->mss = tcp_eff_send_mss(npcb->mss, &(npcb->remote_ip));    // 初始化mss
                
                // 回复带有SYN和ACK标志的握手数据包
                rc = tcp_enqueue_flags(npcb, TCP_SYN | TCP_ACK);
                if (rc != ERR_OK)
                {
                      tcp_abandon(npcb, 0);
                      return rc;
                }
                
                // TCP层的总输出函数,详见下文
                   return tcp_output(npcb);
            }
            return ERR_OK;
        }
        
        ******************************************************************************************************************************************************************************************************
        // 本函数是处于TIMEWAIT状态的控制块处理输入报文的函数
        err_t tcp_timewait_input(struct tcp_pcb *pcb)
        {
            // 如果报文中含RST标志,直接丢弃
            if (flags & TCP_RST)  
            {
                return ERR_OK;    
            }    
            
            // 如果报文中含SYN标志
            if (flags & TCP_SYN)
            {
                // 如果SYN的序号在接收窗口内,返回一个RST报文
                if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd))
                {
                    tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
                      return ERR_OK;    
                }
            }
            // 如果报文中含FIN标志
            else if(flags & TCP_FIN)
            {
                pcb->tmr = tcp_ticks;
            }
            
            // 如果TCP报文中有数据
            if(tcp_len > 0)
            {
                pcb->flags |= TF_ACK_NOW;    // 将当前控制块设为TF_ACK_NOW状态
                
                // TCP层的总输出函数,详见下文
                return tcp_output(pcb);        
            }
            
            return ERR_OK;
        }
        
        ******************************************************************************************************************************************************************************************************    
        // 除了处于LISTEN、TIME_WAIT状态的其余所有状态的pcb控制块,其报文的输入处理都在这里,该函数主要实现了TCP状态转换功能
        err_t tcp_process(struct tcp_pcb *pcb)
        {
            struct     tcp_seg *rseg;
            u8_t    acceptable = 0;
            err_t    err;
            
            err    = ERR_OK;
            
            // 首先判断该报文是不是一个RST报文
            if(flags & TCP_RST)
            {
                // 判断该RST报文是否合法
                if (pcb->state == SYN_SENT)     // 第一种情况,连接处于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;
                    }    
                }
                
                // 如果RST报文合法,则需要复位当前连接的控制块,非法则直接返回不做处理
                if (acceptable)
                {
                    recv_flags |= TF_RESET;            // 表明该输入报文的处理结果中包含TF_RESET
                      pcb->flags &= ~TF_ACK_DELAY;    // 因为输入是RST报文,意味当前控制块必然不处于TF_ACK_DELAY状态
                      return ERR_RST;    
                }
                else
                {
                    return ERR_OK;
                }
            }
            
            // 然后处理握手报文SYN,在连接已经建立情况下,但还是接收到对方的握手包,说明这可能是一个超时重发的握手包,直接向对方返回一个ACK即可
            if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD))
            {
                tcp_ack_now(pcb);                    // #define tcp_ack_now(pcb)     pcb->flags |= TF_ACK_NOW    - 将当前控制块设为TF_ACK_NOW状态
                return ERR_OK;    
            }
            
            // TCP连接不处于半关闭前提下,更新控制块的活动计数器
            if ((pcb->flags & TF_RXCLOSED) == 0)
            {
                pcb->tmr = tcp_ticks;
            }
            
            // 保活报文计数器清0
              pcb->keep_cnt_sent = 0;
              
              // 处理报文首部中的选项字段(暂略)
              tcp_parseopt(pcb);
              
              // 根据当前所处的不同的TCP状态执行相应动作
              switch (pcb->state)
              {
                  case SYN_SENT:    // 客户端发出SYN后,就处于该状态等待服务器返回SYN+ACK
                      // 如果收到的是SYN+ACK,且输入报文中的确认号,就是控制块中已发送,但尚未收到应答报文段中的序号+1
                      if ((flags & TCP_ACK) && (flags & TCP_SYN) && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1)
                      {
                          pcb->snd_buf++;                            // 发出SYN被返回的ACK确认,释放1字节空间,所以可用的发送空间加1字节    
                          pcb->rcv_nxt = seqno + 1;                // 期望接收的下一个序号,即接收端向发送端ACK报文中的确认号
                          pcb->rcv_ann_right_edge = pcb->rcv_nxt;    // 初始化通告窗口的右边界值(略存疑问)
                          pcb->lastack = ackno;                    // 更新接收到的最大确认号字段,也就是更新上一个确认号字段
                          pcb->snd_wnd = tcphdr->wnd;                // 发送窗口设置为接收窗口大小,实现流量控制
                          pcb->snd_wl1 = seqno - 1;                 // 上次窗口更新时收到的数据序号
                          pcb->state = ESTABLISHED;                // 进入ESTABLISHED状态
                          
                          pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip));    // 计算并设置最大报文段
                          pcb->ssthresh = pcb->mss * 10;                                // 重设mss后,ssthresh值也要相应修改
                          pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);    // 初始化阻塞窗口
                              
                          --pcb->snd_queuelen;            // SYN被返回的ACK确认,所以占用的pbuf个数减1    
                          
                          rseg = pcb->unacked;            // 从发送了未收到确认的数据段队列中取出SYN报文,相当于删除
                          pcb->unacked = rseg->next;        // 指向下一个发送了未收到确认的数据段
                          if(pcb->unacked == NULL)        // 如果未确认的数据段为空,则停止重传定时器
                            pcb->rtime = -1;
                        else                             // 如果队列中还有报文,则复位重传定时器和重传次数
                        {
                            pcb->rtime = 0;
                            pcb->nrtx = 0;
                        }
                        
                        tcp_seg_free(rseg);                // 释放取下的SYN报文段内存空间
                        
                        TCP_EVENT_CONNECTED(pcb, ERR_OK, err);    // 回调用户的connect函数(详解见下文)
                        if (err == ERR_ABRT)
                        {
                            return ERR_ABRT;
                        }
                        
                        tcp_ack_now(pcb);                // 向服务器返回ACK,三次握手结束,具体含义见L753
                      }
                      // 如果只收到对方的ACK却没有SYN,则向对方返回RST报文
                      else if(flag & TCP_ACK)
                      {
                          tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
                      }
                      break;
                  case SYN_RCVD:    // 服务器发送SYN+ACK后,就处于该状态,等待客户端返回ACK
                      // 如果收到ACK,也就是三次握手的最后一个报文
                      if(flags & TCP_ACK)
                      {
                          // 如果ACK合法
                          if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt))
                          {
                              if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt))
                              {
                                  u16_t old_cwnd;
                                  
                                  pcb->state = ESTABLISHED;    // 进入ESTABLISHED状态
                                  
                                  TCP_EVENT_ACCEPT(pcb, ERR_OK, err);        // 回调用户的accept函数
                                  if (err != ERR_OK)                         // 如果accept函数返回错误,则关闭当前连接
                                  {
                                      if (err != ERR_ABRT)
                                      {
                                        tcp_abort(pcb);
                                    }
                                    return ERR_ABRT;
                                  }
                                  
                                  old_cwnd = pcb->cwnd;        // 保存旧的阻塞窗口
                                  
                                  tcp_receive(pcb);            // 如果该ACK报文中还携带了数据,则调用tcp_receive处理报文中的数据(解析见下文)
                                  
                                  // 调整本地未被确认的字节数,因为SYN报文占用1个字节,所以减1
                                  if (pcb->acked != 0)         
                                {
                                      pcb->acked--;                
                                }
                                
                                pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);    // 初始化阻塞窗口
                                
                                // 如果在上面的tcp_receive处理结果中包含FIN标志
                                if (recv_flags & TF_GOT_FIN)
                                {
                                      tcp_ack_now(pcb);            // 回复ACK,响应对方的FIN握手标志
                                      pcb->state = CLOSE_WAIT;    // 进入CLOSE_WAIT状态
                                }
                              }
                          }
                          else
                        {
                            // 对于不合法的ACK,则返回一个RST
                            tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
                          }    
                      }
                      // 如果收到客户端重复SYN握手包,说明SYN+ACK包丢失,需要重传
                    else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1))
                    {
                          tcp_rexmit(pcb);
                    }
                    break;
                case CLOSE_WAIT:    // 服务器处于接收关闭的半连接状态,会一直等待上层应用执行关闭指令,发出FIN,并将状态变为LASK_ACK
                case ESTABLISHED:    // 连接双方都处于稳定状态
                    tcp_receive(pcb);                // 调用函数处理报文中的数据
                    
                    // 如果在上面的tcp_receive处理结果中包含FIN标志
                    if (recv_flags & TF_GOT_FIN)
                    {
                        tcp_ack_now(pcb);            // 回复ACK,响应对方的FIN握手标志
                        pcb->state = CLOSE_WAIT;    // 进入CLOSE_WAIT状态
                    }
                    break;
                case FIN_WAIT_1:    // 上层应用主动执行关闭指令,发送FIN后处于该状态(通常对于客户端来讲)
                    tcp_receive(pcb);                        // 调用函数处理报文中的数据
                    
                    // 如果在上面的tcp_receive处理结果中包含FIN标志
                    if (recv_flags & TF_GOT_FIN)
                    {
                        // 如果该报文同时包含一个合法ACK,意味着本地端将直接跳过FIN_WAIT_2进入TIME_WAIT状态
                          if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt))
                          {
                              tcp_ack_now(pcb);                // 回复ACK
                            tcp_pcb_purge(pcb);                // 清除该连接中的所有现存数据
                            TCP_RMV(&tcp_active_pcbs, pcb);    // 从tcp_active_pcbs链表中删除该控制块
                            pcb->state = TIME_WAIT;            // 跳过FIN_WAIT_2状态,直接进入TIME_WAIT状态
                            TCP_REG(&tcp_tw_pcbs, pcb);        // 将该控制块加入tcp_tw_pcbs链表    
                          }
                          // 如果该报文不含ACK,即表示双方同时执行了关闭连接操作
                          else
                          {
                              tcp_ack_now(pcb);                // 返回ACK
                            pcb->state = CLOSING;            // 进入CLOSING状态
                          }
                    }
                    // 如果只收到有效的ACK
                    else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt))
                    {
                          pcb->state = FIN_WAIT_2;            // 进入FIN_WAIT_2状态
                    }
                    break;    
                case FIN_WAIT_2:    // 主动关闭,发送FIN握手且收到ACK后处于该状态        
                    tcp_receive(pcb);                        // 调用函数处理报文中的数据
                    
                    // 如果在上面的tcp_receive处理结果中包含FIN标志
                    if (recv_flags & TF_GOT_FIN)
                    {
                        tcp_ack_now(pcb);                    // 回复ACK
                        tcp_pcb_purge(pcb);                    // 清除该连接中的所有现存数据
                        TCP_RMV(&tcp_active_pcbs, pcb);        // 从tcp_active_pcbs链表中删除该控制块
                        pcb->state = TIME_WAIT;                // 进入TIME_WAIT状态
                        TCP_REG(&tcp_tw_pcbs, pcb);            // 将该控制块加入tcp_tw_pcbs链表
                    }
                    break;
                case CLOSING:        // 双方同时执行主动关闭,处于该状态(特殊情况)
                    tcp_receive(pcb);                        // 调用函数处理报文中的数据
                    
                    // 如果收到合法ACK
                    if (flags & TCP_ACK && ackno == pcb->snd_nxt)
                    {
                        tcp_pcb_purge(pcb);                    // 清除该连接中的所有现存数据
                        TCP_RMV(&tcp_active_pcbs, pcb);        // 从tcp_active_pcbs链表中删除该控制块
                        pcb->state = TIME_WAIT;                // 进入TIME_WAIT状态
                        TCP_REG(&tcp_tw_pcbs, pcb);            // 将该控制块加入tcp_tw_pcbs链表
                    }
                    break;
                case LAST_ACK:        // 服务器在执行被动关闭时,发送完FIN,等待ACK时处于该状态
                    tcp_receive(pcb);                        // 调用函数处理报文中的数据
                    
                    // 如果收到合法ACK
                    if (flags & TCP_ACK && ackno == pcb->snd_nxt)
                    {
                        recv_flags |= TF_CLOSED;            // recv_flags设置为TF_CLOSED,由tcp_input函数对该控制块进行释放和清除
                    }
                    break;
                default:
                    break;
              }
              return ERR_OK;
        }
        
        ******************************************************************************************************************************************************************************************************    
        // 只会被tcp_process函数调用,用于进一步完成对输入报文的处理,具体来说,该函数主要是完成输入报文的冗余截断,管理unacked、unsent、ooseq三张链表
        void tcp_receive(struct tcp_pcb *pcb)
        {
            struct tcp_seg *next;
            struct tcp_seg *prev, *cseg;
            struct pbuf *p;
            s32_t off;
            s16_t m;
            u32_t right_wnd_edge;    // 本地发送窗口右边界
            u16_t new_tot_len;
            int found_dupack = 0;    // 重复ack标志,置1表示是重复ack    
            
            // 首先检测报文是否包含ACK标志
            if (flags & TCP_ACK)
            {
                right_wnd_edge = pcb->snd_wl2 + pcb->snd_wnd;    // 获取本地发送窗口右边界
                
                // 有3种情况可以导致本地发送窗口更新
                if (TCP_SEQ_LT(pcb->snd_wl1, seqno)||                                // snd_wl1小于新seqno,说明对方有发来数据
                    (pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno))||    // snd_wl1等于新seqno且snd_wl2小于新ackno,说明对方没有发送数据,只是在收到数据后发送一个确认
                    (pcb->snd_wl2 == ackno && tcphdr->wnd > pcb->snd_wnd))             // snd_wl2等于新ackno且snd_wnd小于报文首部的窗口通告wnd,说明我方没有发数据过去,但被对方告知接收窗口变大    
                {
                    pcb->snd_wnd = tcphdr->wnd;        // 更新本地发送窗口大小    ,跟对方发来的接收窗口通告匹配
                    pcb->snd_wl1 = seqno;            // 更新接收到的序号
                    pcb->snd_wl2 = ackno;            // 更新接收到的确认号
                    
                    // 如果发送窗口非0,且探察开启
                    if (pcb->snd_wnd > 0 && pcb->persist_backoff > 0)
                    {
                        pcb->persist_backoff = 0;    // 停止窗口探察
                    }
                }
                
                // 判断是否是一个重复的ACK,需要满足5个条件
                // 1.如果ackno小于等于lastack,即没有确认新数据
                if (TCP_SEQ_LEQ(ackno, pcb->lastack))                         
                {
                    pcb->acked = 0;        // 没有确认新数据,那么acked为0
                    
                    // 2.如果报文段中没有数据
                    if (tcplen == 0)
                    {
                        // 3.本地发送窗口没有更新
                        if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge)
                        {
                            // 4.如果重传定时器正在运行,即本地有数据正等待被确认
                            if (pcb->rtime >= 0)
                            {
                                // 5.如果ackno等于lastack
                                if (pcb->lastack == ackno)
                                {
                                    // 此时可以确定这是一个重复的ack,说明报文发生了丢失
                                    found_dupack = 1;
                                    // 该ack被重复收到的次数自增
                                    if (pcb->dupacks + 1 > pcb->dupacks)
                                        ++pcb->dupacks;
                                    // 如果该ack重复收到超过3次,说明发生了拥塞
                                    if (pcb->dupacks > 3)
                                    {
                                        if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd)
                                        {
                                            pcb->cwnd += pcb->mss;
                                        }
                                    }
                                    // 如果该ack重复第3次收到,执行快速重传算法
                                    else if (pcb->dupacks == 3)
                                    {
                                        tcp_rexmit_fast(pcb);
                                    }
                                }
                            }
                        }
                    }
                    
                    // 如果没有确认新数据但又不属于重复ack
                    if (!found_dupack)
                    {
                        pcb->dupacks = 0;        // 将ack重复收到的次数清0
                      }
                }
                // 如果是正常情况的ACK,lastack+1<=ackno<=snd_nxt
                else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt))
                {
                    // 如果控制块处于快速重传状态    ,则关闭重传状态、拥塞功能    
                    if (pcb->flags & TF_INFR)
                    {
                        pcb->flags &= ~TF_INFR;
                        pcb->cwnd = pcb->ssthresh;
                    }
                    
                    pcb->nrtx = 0;                                // 重传次数清0
                    pcb->rto = (pcb->sa >> 3) + pcb->sv;        // 复位重传超时时间
                    pcb->acked = (u16_t)(ackno - pcb->lastack);    // 更新acked字段为被确认的已发送数据长度
                    pcb->snd_buf += pcb->acked;                    // 更新可用的发送空间
                    pcb->dupacks = 0;                            // 将ack重复收到的次数清0
                    pcb->lastack = ackno;                        // 更新接收到的ackno
                    
                    // 如果处于TCP连接已经建立状态,调整拥塞算法功能模块
                    if (pcb->state >= ESTABLISHED)
                    {
                        if (pcb->cwnd < pcb->ssthresh)
                        {
                              if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd)
                            {
                                pcb->cwnd += pcb->mss;
                              }
                        }
                        else
                        {
                              u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);
                              if (new_cwnd > pcb->cwnd)
                            {
                                pcb->cwnd = new_cwnd;
                              }
                        }
                    }
                    
                    // 遍历unacked队列,将所有数据编号小于等于ackno的报文段移除
                    while (pcb->unacked != NULL && TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked), ackno))
                    {      
                        // 将满足要求的报文从unacked链表取出
                        next = pcb->unacked;
                        pcb->unacked = pcb->unacked->next;
                        
                        // 如果该报文包含FIN标志,意味着当前收到的ACK对FIN做了确认,则acked字段减1,即不需要提交上层使知道FIN被对方成功接收
                        if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0))
                        {
                              pcb->acked--;
                        }
                        
                        pcb->snd_queuelen -= pbuf_clen(next->p);        // 释放被该报文占用的发送空间
                        tcp_seg_free(next);                                // 释放被该报文占用的tcp报文段
                    }
                    
                    // 当所有满足要求的报文段移除成功后,判断unacked队列是否为空
                    if(pcb->unacked == NULL)
                        pcb->rtime = -1;    // 若为空,关闭重传定时器
                    else
                        pcb->rtime = 0;        // 否则复位重传定时器
        
                    pcb->polltmr = 0;        // 复位轮询定时器
                }        
                // 如果该ACK既不是重复ACK,又不是正常ACK,则acked字段清0,即该ACK不确认任何已发送数据
                else
                {
                    pcb->acked = 0;
                }
                
                // 遍历unsent队列,将所有数据编号小于等于ackno的报文段移除
                // 这是因为对于需要重传的报文段,lwip直接将它们挂在unsent队列上,所以收到的ACK可能是对已超时报文段的确认
                while (pcb->unsent != NULL && TCP_SEQ_BETWEEN(ackno, ntohl(pcb->unsent->tcphdr->seqno) + TCP_TCPLEN(pcb->unsent), pcb->snd_nxt))
                {
                    // 将满足要求的报文从unsent链表取出
                    next = pcb->unsent;
                    pcb->unsent = pcb->unsent->next;
                    
                    // 如果该报文包含FIN标志,意味着当前收到的ACK对FIN做了确认,则acked字段减1,即不需要提交上层使知道FIN被对方成功接收
                    if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0))
                    {
                        pcb->acked--;
                    }
                    
                    pcb->snd_queuelen -= pbuf_clen(next->p);        // 释放被该报文占用的发送空间
                    tcp_seg_free(next);                                // 释放被该报文占用的tcp报文段
                }
                
                // RTT计算,暂略
                if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno))
                {
                    m = (s16_t)(tcp_ticks - pcb->rttest);
                    m = m - (pcb->sa >> 3);
                    pcb->sa += m;
                    if (m < 0) {
                    m = -m;
                    }
                    m = m - (pcb->sv >> 2);
                    pcb->sv += m;
                    pcb->rto = (pcb->sa >> 3) + pcb->sv;    
                    pcb->rttest = 0;
                }
            }
            
            // 如果该输入报文还包含了数据,则要继续对数据进行处理
            if (tcplen > 0)
            {
                // 如果seqno + 1 <= rcv_nxt <= seqno + tcplen - 1,意味着收到的数据区域头部有无效数据(收到的数据有部分处于本地左侧接收窗口外),需要截断数据头
                if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1))
                {
                    off = pcb->rcv_nxt - seqno;                            // 需要截掉的数据长度
                    p = inseg.p;                                        // 获取收到的报文段的pbuf链表头
                    
                    // 判断需要截断的长度是否超出了第一个pbuf中存储的数据长度
                    if (inseg.p->len < off)
                    {
                        new_tot_len = (u16_t)(inseg.p->tot_len - off);    // 截断重复数据后的有效数据长度

                        // 如果超出,则需要遍历pbuf链表,依次摘除数据,直到最后一个包含摘除数据的pbuf
                        while (p->len < off)
                        {
                            off -= p->len;                                // 剩余摘除长度
                            p->tot_len = new_tot_len;                    // 更新当前pbuf中的数据总长,
                            p->len = 0;                                    // 因为数据被摘除,所以当前pbuf中的数据分长清0
                            p = p->next;                                // 指向下一个pbuf
                        }
                        
                        // 处理最后一个包含摘除数据的pbuf,就是调整数据指针略过摘除数据
                        pbuf_header(p, (s16_t)-off);
                    }
                    else
                    {
                        // 如果未超出,则调整第一个pbuf中的数据指针略过摘除数据
                        pbuf_header(inseg.p, (s16_t)-off);
                    }
                    
                    inseg.len -= (u16_t)(pcb->rcv_nxt - seqno);    // 更新TCP报文段数据总长
                    inseg.tcphdr->seqno = seqno = pcb->rcv_nxt;    // 更新TCP头中的seqno,指向接收窗口头位置
                }
                else
                {
                    // 如果seqno < rcv_nxt,意味着seqno+tcplen-1 < rcv_nxt,说明这是个完全重复的报文段
                    if (TCP_SEQ_LT(seqno, pcb->rcv_nxt))
                    {
                        tcp_ack_now(pcb);        // 只回复一个ACK给对方(这里是否应该直接返回不再运行下去)
                    }
                }
                
                // 如果数据起始编号在接收窗口内
                if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd - 1))
                {
                    // 如果该报文数据处于接收起始位置,意味着该报文是连续到来的
                    if (pcb->rcv_nxt == seqno)
                    {
                        tcplen = TCP_TCPLEN(&inseg);        // 更新该报文的总数据长度

                        // 如果总长大于接收窗口大小,就需要做尾部截断处理,这里包含对FIN和SYN两种标志的不同处理结果,注意体会
                        if (tcplen > pcb->rcv_wnd)
                        {
                            // 如果TCP头中带FIN标志,清除FIN标志,因为对方还有数据要发过来
                            if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN)
                            {
                                TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) &~ TCP_FIN);
                            }
                            
                            inseg.len = pcb->rcv_wnd;        // 根据接收窗口调整数据长度

                            // 如果TCP头中带SYN标志,报文段数据长度减1
                            if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN)
                            {
                                inseg.len -= 1;
                            }
                            
                            pbuf_realloc(inseg.p, inseg.len);    //  因为数据被截断,pbuf中的参数需要相应调整
                            tcplen = TCP_TCPLEN(&inseg);        // 再次更新该报文的总数据长度
                        }
                        
                        // 如果无序报文段队列ooseq上存在报文段
                        if (pcb->ooseq != NULL)
                        {
                            // 判断当前有序报文段的TCP头中是否带FIN标志
                            if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN)
                            {
                                // 如果该有序报文段带FIN标志,意味着单向TCP连接结束
                                // 不可能再从对方收到新的报文段,ooseq队列中的报文段没有成为有序报文段可能,只能作废
                                while (pcb->ooseq != NULL)
                                {
                                  struct tcp_seg *old_ooseq = pcb->ooseq;
                                  pcb->ooseq = pcb->ooseq->next;
                                  tcp_seg_free(old_ooseq);
                                }
                            }
                            else
                            {
                                next = pcb->ooseq;
                                // 遍历ooseq链表,删除序号被当前有序报文段完全覆盖的报文段
                                while (next && TCP_SEQ_GEQ(seqno + tcplen,next->tcphdr->seqno + next->len))
                                {
                                    // 如果这些即将被删除的报文段带FIN标志且当前有序报文段不带SYN标志
                                    if (TCPH_FLAGS(next->tcphdr) & TCP_FIN &&(TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) == 0)
                                    {
                                        TCPH_SET_FLAG(inseg.tcphdr, TCP_FIN);    // 在当前有效报文段的TCP头中添加FIN标志
                                        tcplen = TCP_TCPLEN(&inseg);            // 再次更新该报文的总数据长度
                                    }
                                    prev = next;
                                    next = next->next;
                                    tcp_seg_free(prev);
                                }
                                
                                // 如果当前有序报文段尾部与ooseq中的报文段存在部分重叠    
                                if (next && TCP_SEQ_GT(seqno + tcplen,next->tcphdr->seqno))
                                {
                                    inseg.len = (u16_t)(next->tcphdr->seqno - seqno);    // 截断当前有序报文段尾部的重叠部分,得到有效部分长度
                                    
                                    // 如果当前有序报文段TCP头中带SYN标志,报文段数据长度减1
                                    if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN)
                                    {
                                        inseg.len -= 1;
                                    }
                                    
                                    pbuf_realloc(inseg.p, inseg.len);            //  因为数据被截断,pbuf中的参数需要相应调整
                                    tcplen = TCP_TCPLEN(&inseg);                // 再次更新该报文的总数据长度
                                }
                                pcb->ooseq = next;
                            }
                        }
                        
                        pcb->rcv_nxt = seqno + tcplen;    // 更新下一个期望接收到的序号,也就是接收窗口左边界
                        pcb->rcv_wnd -= tcplen;            // 更新当前可用接收窗口

                        tcp_update_rcv_ann_wnd(pcb);    // 更新公告窗口
                        
                        // 如果该有序报文段中存在数据
                        if (inseg.p->tot_len > 0)
                        {
                            recv_data = inseg.p;        // 将全局指针recv_data指向报文段中的数据pbuf
                            inseg.p = NULL;
                        }
                        
                        // 如果该有序报文段的TCP头中带FIN标志
                        if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN)
                        {
                          recv_flags |= TF_GOT_FIN;        // 则在报文处理结果变量recv_flags添加TF_GOT_FIN标志
                        }
                        
                        // 遍历ooseq队列,取出所有有序的报文段
                        // (通过比较ooseq队列中报文段的seqno和当前TCP控制块中保存的rcv_nxt来判定该报文段是否有序)
                        while (pcb->ooseq != NULL && pcb->ooseq->tcphdr->seqno == pcb->rcv_nxt)
                        {
                            cseg = pcb->ooseq;
                            seqno = pcb->ooseq->tcphdr->seqno;    // 更新序号
                            pcb->rcv_nxt += TCP_TCPLEN(cseg);    // 更新下一个期望接收到的序号
                            pcb->rcv_wnd -= TCP_TCPLEN(cseg);    // 更新当前可用接收窗口
                            tcp_update_rcv_ann_wnd(pcb);        // 更新公告窗口
                            
                            // 如果该有序报文段中存在数据,则通过全局指针recv_data向上层提交数据
                            if (cseg->p->tot_len > 0)
                            {
                                // 判断全局指针recv_data是否为空
                                if (recv_data)
                                {
                                    // 如果不为空,意味着有更早的数据准备向上提交
                                    pbuf_cat(recv_data, cseg->p);    // 将当前数据pbuf挂到recv_data指向的数据链表的尾部
                                }
                                else
                                {
                                    // 如果为空,直接将当前数据pbuf赋给recv_data
                                    recv_data = cseg->p;
                                }
                                cseg->p = NULL;
                            }
                            
                            // 如果该有序报文段的TCP头中带FIN标志
                            if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN)
                            {
                                recv_flags |= TF_GOT_FIN;        // 则全局变量recv_flags添加TF_GOT_FIN标志

                                // 如果当前TCP处于ESTABLISHED状态,则变成CLOSE_WAIT状态
                                if (pcb->state == ESTABLISHED)
                                {
                                    pcb->state = CLOSE_WAIT;
                                }
                            }
                            
                            pcb->ooseq = cseg->next;
                            tcp_seg_free(cseg);
                        }
                        
                        // 以上都执行完毕后,向源端返回一个ACK,此处其实只是先在TCP控制块中添加ACK标志
                        tcp_ack(pcb);
                    }
                    // 如果该报文数据不处于接收起始位置,意味着该报文不是有序的
                    else
                    {
                        // 首先向源端返回一个立即ACK
                        tcp_send_empty_ack(pcb);
                        
                        // 然后将该报文段放入ooseq队列
                        if (pcb->ooseq == NULL)
                        {
                            // 如果ooseq为空,则拷贝该报文段到新开辟的报文段空间,并将新开辟报文段作为ooseq起始单元
                              pcb->ooseq = tcp_seg_copy(&inseg);
                        }
                        else
                        {
                            prev = NULL;    // 定义为ooseq链表中上一个报文段,这里首先清空
                            // 遍历ooseq队列,选择合适位置插入该报文段
                            for(next = pcb->ooseq; next != NULL; next = next->next)
                            {
                                // 依次比较两个报文段的起始序号seqno,如果相等
                                if (seqno == next->tcphdr->seqno)
                                {
                                    // 继续比较两个报文段的数据长度
                                    if (inseg.len > next->len)
                                    {
                                        // 如果输入报文段数据长度更长
                                        // 拷贝该报文段到新开辟的报文段空间
                                        cseg = tcp_seg_copy(&inseg);
                                        
                                        // 插入ooseq链表
                                        if (cseg != NULL)
                                        {
                                            // 如果不是ooseq上的第一个报文段
                                            if (prev != NULL)
                                            {
                                                prev->next = cseg;    // 插入ooseq链表的上一个报文段之后
                                            }
                                            // 如果是第一个
                                            else
                                            {
                                                pcb->ooseq = cseg;    // 直接替换原有的第一个
                                            }
                                            
                                            tcp_oos_insert_segment(cseg, next);    // 处理好插入后与原有的下一个报文段的影响,简单来说,就是切掉冗余,释放内存
                                        }
                                        break;    // 退出循环                            
                                    }
                                    else
                                    {
                                        // 如果输入报文段数据长度更短,则直接丢弃,并退出循环
                                        break;
                                    }
                                }
                                // 如果不相等
                                else
                                {
                                    // 如果是ooseq上的第一个报文段
                                    if (prev == NULL)
                                    {
                                        // 如果该报文段的起始序号大于要插入的报文段起始序号
                                        if (TCP_SEQ_LT(seqno, next->tcphdr->seqno))
                                        {
                                            cseg = tcp_seg_copy(&inseg);            // 拷贝要插入的报文段到新开辟的报文段空间
                                            if (cseg != NULL)
                                            {
                                                pcb->ooseq = cseg;                    // 将新报文段插到ooseq第一个位置
                                                tcp_oos_insert_segment(cseg, next);    // 处理好插入后与原有的第一个报文段的影响
                                            }
                                            break;        // 退出循环
                                        }
                                    }
                                    // 如果不是第一个
                                    else
                                    {
                                        // 如果待插入报文段起始序号在前一个和后一个报文段起始序号之间
                                        if (TCP_SEQ_BETWEEN(seqno, prev->tcphdr->seqno+1, next->tcphdr->seqno-1))
                                        {
                                            cseg = tcp_seg_copy(&inseg);    // 拷贝要插入的报文段到新开辟的报文段空间
                                            if (cseg != NULL)
                                            {
                                                // 如果与前一个报文段有数据重合
                                                if (TCP_SEQ_GT(prev->tcphdr->seqno + prev->len, seqno))
                                                {
                                                    prev->len = (u16_t)(seqno - prev->tcphdr->seqno);    // 截断前一个报文段尾部
                                                    pbuf_realloc(prev->p, prev->len);                    // 因为数据被截断,pbuf中的参数需要相应调整
                                                }
                                                
                                                prev->next = cseg;                    // 将新报文段插入前一个报文段之后
                                                tcp_oos_insert_segment(cseg, next);    // 处理好插入后与原有的下一个报文段的影响
                                            }
                                            break;
                                        }
                                    }
                                    
                                    // 如果已经是ooseq上的最后一个报文段
                                    // 且待插入的报文段起始序号大于该报文起始序号(其实函数运行到这里该条件必然成立)
                                    if (next->next == NULL && TCP_SEQ_GT(seqno, next->tcphdr->seqno))
                                    {
                                        // 如果该报文的TCP头中有FIN标志,则直接丢弃待插入的报文段,退出循环
                                        if (TCPH_FLAGS(next->tcphdr) & TCP_FIN)
                                        {
                                            break;
                                        }
                                        
                                        next->next = tcp_seg_copy(&inseg);    // 拷贝要插入的报文段到新开辟的报文段空间,并插在队列尾部

                                        // 如果新插入的报文段不为空
                                        if (next->next != NULL)
                                        {
                                            // 如果与前一个报文段有数据重合
                                            if (TCP_SEQ_GT(next->tcphdr->seqno + next->len, seqno))
                                            {
                                                next->len = (u16_t)(seqno - next->tcphdr->seqno);    // 截断前一个报文段尾部
                                                pbuf_realloc(next->p, next->len);                    // 因为数据被截断,pbuf中的参数需要相应调整
                                            }
                                            
                                            // 如果新插入的报文段数据长度超出了当前接收窗口大小
                                            if ((u32_t)tcplen + seqno > pcb->rcv_nxt + (u32_t)pcb->rcv_wnd)
                                            {
                                                // 如果新插入的报文段的TCP头中有FIN标志
                                                if (TCPH_FLAGS(next->next->tcphdr) & TCP_FIN)
                                                {
                                                    TCPH_FLAGS_SET(next->next->tcphdr, TCPH_FLAGS(next->next->tcphdr) &~ TCP_FIN);    // 去掉TCP头中的FIN标志
                                                }
                                                
                                                next->next->len = pcb->rcv_nxt + pcb->rcv_wnd - seqno;    // 根据接收窗口大小调制新插入的报文段数据长度
                                                pbuf_realloc(next->next->p, next->next->len);            // 因为数据被截断,pbuf中的参数需要相应调整
                                                tcplen = TCP_TCPLEN(next->next);                        // 再次更新该报文的总数据长度
                                            }
                                            
                                        }
                                        break;
                                    }
                                }
                                
                                prev = next;    // 以上都不满足,则遍历ooseq链表中下一个
                            }
                        }
                    }
                }
                // 如果数据不在接收范围内
                else
                {
                    tcp_send_empty_ack(pcb);    // 直接向源端返回一个立即确认ACK
                }
            }
            // 如果输入的报文段中不包含数据
            else
            {
                // 且序号位于接收窗口之内
                if(!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd-1))
                {
                      tcp_ack_now(pcb);        // 回一个ACK
                }
            }
        }
        
        err_t tcp_output(struct tcp_pcb *pcb)
        {
            struct tcp_seg *seg,*useg;
            u32_t wnd,snd_nxt;
            
            if(tcp_input_pcb == pcb)
            {
                return ERR_OK;    
            }
            
            wnd = LWIP_MIN(pcb->snd_wnd,pcb->cwnd);
            seg = pcb->unsent;
            
            if(pcb->flags & TF_ACK_NOW && (seg = NULL || ntohl(seg->tcphdr->seqno)  ))
            {
                    
            }
        }
        
        
        
       
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

lwip之数据收发流程 的相关文章

  • LWIP 双IP实现

    LwIP是Light Weight 轻型 IP协议 xff0c 有无操作系统的支持都可以运行 LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用 xff0c 它只需十几KB的RAM和40K左右的ROM就可以运行 xff
  • LwIP多TCP连接问题

    多个TCP连接的问题困扰了我很久 xff0c 前段时间解决了这个问题 xff0c 现在写下我的感受 xff1a 多个TCP可以绑定多个端口 xff0c 这里我是绑定一个端口 xff0c 这样更加复合实际应用 xff08 我的多个TCP的功能
  • LwIP在stm32上的无操作系统移植

    LwIP是一个轻型IP协议 xff0c 有无操作系统的支持都可以运行 这里的移植是无操作系统移植 LwIP虽然是一个轻型的IP协议 xff0c 但是TCP IP基本功能都有 而且占用的资源不多 xff0c 非常适合用于嵌入式系统 移植的平台
  • LWIP中断接收调试

    在POLL模式转为中断模式的时候发现ping时间很久而且经常超时 xff0c 屏蔽掉如下耗时的打印代码就好了 PRINT INFO 34 receive frame d len buffer s n 34 len buffer
  • uIP和LwIP背后的那个牛逼男人

    在公众号给大家介绍过Uip和LwIP xff0c 如果使用过这两种TCP IP协议栈 xff0c 那么你一定会熟悉一个人Adam Dunkels亚当 邓克尔 瑞典计算机科学院的教授 xff0c 这两种开源的协议栈都出自他手 xff0c 现在
  • 一张表看懂uIP和lwIP的区别

    我们给大家介绍过目前比较流行的开源TCP IP开源协议栈uIP和lwIP 这两种都是由瑞典计算机科学研究院开发的 xff0c 广泛应用于嵌入式系统中 因为全功能的TCP IP协议是很庞大的 xff0c 在资源紧张的嵌入式上是很难实现的 xf
  • uIP与LWIP的区别

    uIP是专门为8位和16位控制器设计的一个非常小的TCP IP栈 完全用C编写 xff0c 因此可移植到各种不同的结构和操作系统上 xff0c 一个编译过的栈可以在几KB ROM或几百字节RAM中运行 uIP中还包括一个HTTP服务器作为服
  • 用Stm32CubeMX在STM32F107上移植LWIP(PHY:DM9161A)

    背景 有一块吃灰7年的神州IV号开发板 xff0c 主控芯片STM32F107VCT6 xff0c PHY芯片DM9161A xff0c 配套的资料都是当年ST的标准库 这个开发板应该是因为当年上市太匆忙 xff0c 资料和代码的细节部分做
  • STM32以太网通信-LWIP简介

    LwIP全名 xff1a Light weight IP xff0c 意思是轻量化的TCP IP协议 xff0c 是瑞典计算机科学院 SICS 的Adam Dunkels 开发的一个小型开源的TCP IP协议栈 LwIP的设计初衷是 xff
  • LWIP_MDNS

    一 xff0e mdns1 什么是mdns xff1f mDNS协议适用于局域网内没有DNS服务器时的域名解析 xff0c 设备通过组播的方式交互DNS记录来完成域名解析 xff0c 约定的组播地址是 xff1a 224 0 0 251 x
  • STM32移植lwip之建立web服务器 linux下的web服务器搭建

    stm32作为web服务器 完善底层网络接口层的驱动程序 并将LwIP协议栈移植到 C OS 操作系统上来 结合网络技术 使用LwIP的应用程序接口程序LwIP API编写相关的HTTP应用程序 在应用程序中 嵌入式web服务器会处理来自浏
  • 基于STM32CUBE的LWIP+FREERTOS+Socket构成的TCP服务器

    本实验基于STM32F767制作 一 实验目的 本实验主要目的是通过Socket网络编程方法完成通过以太网控制两个LED的亮灭的 xff0c 同理可用到继电器上控制其它高电压设备 二 实验步骤 1 首先先生成一个带有LWIP和FreeRTO
  • LWIP之lwip_select函数

    代码如下 xff1a Processing exceptset is not yet implemented int lwip select int maxfdp1 fd set readset fd set writeset fd set
  • LwIP之套接字接口

    套接字结构体 struct lwip sock API连接指针 struct netconn conn 前一次读剩下的数据 void lastdata 前一次读数据的偏移量 u16 t lastoffset 接收数据的次数 s16 t rc
  • ESP32 之 ESP-IDF 教学(十二)WiFi篇—— LwIP 之 TCP 通信

    本文章 来自原创专栏 ESP32教学专栏 基于ESP IDF xff0c 讲解如何使用 ESP IDF 构建 ESP32 程序 xff0c 发布文章并会持续为已发布文章添加新内容 xff01 每篇文章都经过了精打细磨 xff01 通过下方对
  • 【STM32】HAL库-以太网外设-LAN8720A-LWIP-无操作系统

    开发环境 KEIL MDK ARM 5 27MCU STM32F429IGT6PHY IC LAN8720ALWIP LWIP2 1 2STM32CUBEMX 6 6 1HAL V1 27 1 LAN8720A使用RMII接口与STM32的
  • STM32 基础系列教程 33 - Lwip_tcp_client

    前言 学习stm32 以太网接口使用 及LWIP使用 用LWIP快速实现一个TCP client网络通信功能 学会基本LWIP的网络数据接收与发送功能 让初学者了解lwip 关于Lwip更多的功能介绍将会在后期的STM32 中级教程中介绍
  • 基于stm32f107 stm32cube 和 LWIP 协议实现 udp 组播通信

    最近在做一个基于stm32f107 实现 UDP 组播通信的项目 项目基于 stm32cube 配置生成 如下图 UDP组播头文件 ifndef MULTICAST H define MULTICAST H include lwip udp
  • 【lwIP(第十三章)】WebServer协议

    目录 一 WebServer简介 二 Web服务器工作原理 三 CGI技术简介 四 CGI工作原理 五 SSI技术简介 六 SSI工作原理 一 WebServer简介 Web Server 就是提供 Web 服务的 Server 主要功能是
  • ESP32 - 具有本机 LwIP 库的 UDP 广播器/接收器

    我正在使用 ESP32 顺便说一句 这是一个很棒的平台 构建一个分布式应用程序 所有参与者都应该以最简单的形式通过 UDP 进行通信 通过广播发送消息并监听周围所有的消息 每个参与者自行过滤相关消息 到目前为止 我有以下初始化例程 int

随机推荐

  • R语言中mean函数

    mean函数是求算术平均值 用法 xff1a mean x trim 61 0 na rm 61 FALSE x是数值型 逻辑向量 trim表示截尾平均数 xff0c 0 0 5之间的数值 xff0c 如 xff1a 0 10表示丢弃最大1
  • 使用Spring @DependsOn控制bean加载顺序

    使用Spring 64 DependsOn控制bean加载顺序 spring容器载入bean顺序是不确定的 xff0c spring框架没有约定特定顺序逻辑规范 但spring保证如果A依赖B 如beanA中有 64 Autowired B
  • 使用R中merge()函数合并数据

    使用R中merge 函数合并数据 在R中可以使用merge 函数去合并数据框 xff0c 其强大之处在于在两个不同的数据框中标识共同的列或行 如何使用merge 获取数据集中交叉部分 merge 最简单的形式为获取两个不同数据框中交叉部分
  • 介绍java中Pair

    介绍java中Pair 在这篇文章中 xff0c 我们讨论了一个非常有用的编程概念 xff0c 配对 Pair 配对提供了一种方便方式来处理简单的键值关联 xff0c 当我们想从方法返回两个值时特别有用 在核心Java库中可以使用配对 Pa
  • python numpy 中linspace函数

    python numpy 中linspace函数 numpy提供linspace函数 有时也称为np linspace 是python中创建数值序列工具 与Numpy arange函数类似 xff0c 生成结构与Numpy 数组类似的均匀分
  • Spring框架学习重点概要笔记(一)

    spring框架的注解实现 xff1a xff08 1 xff09 首先在spring的核心配置文件中开启注解配置 xff1a xff08 2 xff09 64 Component 目前Spring中提供了四个注解 xff0c 相当于时Co
  • 做个爱读书的程序员

    很多时候我们抱怨工作过于忙碌 xff0c 疲于应付各种事情 但是往往即使有时间也总是会无所事事地浪费掉 xff0c 我们可以找很多理由去给自己解脱 xff0c 固然是想在忙碌之外给自己放松一下 xff0c 大多却是过于可惜的时候 今天跟一个
  • xming Error: Can‘t open display:

    运行 xclock xhost 报错 xff1a xming Error Can 39 t open display 需要安装xorg x11 xauth在运行 xclock 或 xhost root 64 stone yum y inst
  • onItemClickListener不起作用解决办法2(原创)

    写这边文章之前 xff0c 犹豫再三 xff0c 不知道会不会冒犯一位朋友 xff0c 他给我之前的一篇文章提出的意见 但我声明真心无意 xff0c 只是想把问题拿出来分析一下 xff0c 希望获得理解 listview在android开发
  • 关于STM32系列串口打印乱码原因解析

    STM32打印串口乱码 xff0c 如果你的配置确认没有问题 xff0c 那么最有可能是 HSE VALUE 这个值不对 xff0c 你可能使用的是一份demo程序 xff0c 而demo程序的时钟晶振和你的时钟晶振不一定都是一样 xff0
  • node.js提取html中的内部引入css和js代码,并修改为外部引入

    前提 xff1a 配置好node环境 xff0c 能够使用终端 xff08 cmd或Windows PowerShell xff09 node命令运行js文件 nodejs安装教程 某位大佬整理的nodejs入门教程 node xxx sp
  • Ffplay视频播放流程

    背景说明 FFmpeg是一个开源 xff0c 免费 xff0c 跨平台的视频和音频流方案 xff0c 它提供了一套完整的录制 转换以及流化音视频的解决方案 而ffplay是有ffmpeg官方提供的一个基于ffmpeg的简单播放器 学习ffp
  • 统计机器学习方法 for NLP:基于LSA的主题模型

    前言 最近在重刷李航老师的 统计机器学习方法 尝试将其与NLP结合 xff0c 通过具体的NLP应用场景 xff0c 强化对书中公式的理解 xff0c 最终形成 统计机器学习方法 for NLP 的系列 这篇将介绍潜在语义分析LSA xff
  • 知识图谱最新权威综述论文解读:时序知识图谱部分

    从最近一两年有关知识图谱的顶会论文中可以发现 xff0c 越来越多的国内外研究者开始关注动态时序知识图谱 xff0c 可见时序知识图谱已经成为了一大研究趋势 xff0c 相信之后会有更多相关研究出来 因此 xff0c 这期我们对综述论文的时
  • RabbitMQ简介、概念、安装、启动、工作模式

    RabbitMQ简介 概念 安装 启动 工作模式 1 RabbitMQ简介 RabbitMQ是支持多种消息协议 xff0c 易于部署和使用的开源消息代理服务器 xff0c 用于在分布式系统中存储转发消息 由以高性能 健壮以及可伸缩性出名的E
  • 递归删除某一文件夹下某一后缀名的所有文件,包括嵌套文件(例如删除.cfg文件)

    递归删除某一文件夹下某一后缀名的所有文件 xff0c 包括嵌套文件 xff08 例如删除 cfg文件 xff09 有时候我的电脑中会莫名生成很多 cfg文件 xff0c 一个一个的删除太繁琐了 xff0c 所有就写了这个函数 当然这个函数的
  • SQL 基础篇 SQL 4 查询结果限制返回行数

    SQL基础篇 汇总 刷题传送门 题目链接 题面 样例输入 drop table if exists user profile CREATE TABLE 96 user profile 96 96 id 96 int NOT NULL 96
  • RabbitMQ(一):工作模型

    RabbitMQ实现了AMQP协议 xff0c 所以RabbitMQ的工作模型也是基于AMQP的 Broker xff1a RabbitMQ宿主机 默认端口是5672 Connection xff1a 无论是生产者发送消息 xff0c 还是
  • 从零实现 USB转虚拟串口 CDC

    上一次调试是USB读卡器 43 FATFS xff0c 这次是USB转虚拟串口 说来也巧了 xff0c 这是我大学的毕设 xff0c 那个时候用STC芯片实现USB转串口 xff0c 差点儿没把我给搞死 多年后遇见故人 xff0c 甚是感慨
  • lwip之数据收发流程

    lwip从逻辑上看也是分为4层 xff1a 链路层 网络层 IP ARP xff08 ICMP IGMP这两个协议是网络层的补充协议 xff0c 并不严格属于网络层 xff09 传输层 TCP UDP 应用层 xff0c 基本等同TCP I