lwip-数据收发流程2-传输层

news/2024/5/17 19:25:23 标签: 网络, tcp, tcp_input

三.传输层(主讲TCP协议)(tcp_input)

  • 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,从服务器到客户端方向的连接也就被断开了

  • 两条最经典的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/

  • 11种TCP状态

        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协议包头

        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;                    // 紧急指针,暂略
                                                }
  • 非常非常重要的结构体struct tcp_pcb

lwip使用一个tcp_pcb控制块来描述一个TCP连接(lwip实际定义了2种TCP控制块,一种专门用于描述处于LISTEN状态的连接,另一种用于描述处于其他状态的连接):

这个TCP控制块是整个TCP协议的核心,TCP协议实现的本质就是对TCP控制块中各字段的操作,所以非常重要!!!

                                                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_input函数

  
        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;
        }
        
        ******************************************************************************************************************************************************************************************************    
             break;
                default:
                    break;
              }
              return ERR_OK;
        }
        
        ************************************************************************************
 

  • tcp_process( )函数

除了处于LISTEN、TIME_WAIT状态的其余所有状态的pcb控制块,其报文的输入处理都在tcp_process( )里,该函数主要实现了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函数对该控制块进行释放和清除
                    }
         

http://www.niftyadmin.cn/n/55357.html

相关文章

别让猴子跳回背上

1.管理者的贡献来自于他们的判断力与影响力&#xff0c;而非他们所投入的个人时间与埋头苦干 2.管理者的绩效表现则是许多人群策群力的结果 3.管理者的时间管理: 老板占用的时间;组织占用的时间;自己占用的时间;外界占用的时间; 4.管理者的策略在于增加自己的时间&#xff0c…

Fragment切换有两种方 可能引发的问题

我们都知道fragment切换有两种方式&#xff1a; replace方式 transaction.replace(R.id.content, IndexFragment); 2. add&#xff0d;hide&#xff0d;show方式 transaction.add(R.id.content, IndexFragment); transaction.hide(otherfragment); transaction.show(thisfra…

c语言递归 累和 ,累乘积,斐波那契数列,字符串长度

目录 递归使用场景 1:使用递归的方式计算 Sn123..100 2&#xff1a;计算 n&#xff01;n*(n-1)*(n-2)*......*1; 3:计算输出斐波那契数列前20项&#xff0c;并按每行4个数的格式输出(2019年&#xff09; 4&#xff1a; 用递归和非递归两种方式编写函数strlength()。该函数…

C# PictureEdit 加载图片

方法一&#xff1a; 如果要加载的图片的长宽比不是太过失衡&#xff0c; 1.可以改变picturebox的SizeMode属性为 PictureBoxSizeMode.StretchImage&#xff0c; 2.或者Dev控件 PictureEdit的SizeMode属性为Zoom。&#xff08;zoom:缩放&#xff1b;clip剪短&#xff1b;stret…

基于live555源码的rtsp服务器

下载live555源码 http://www.live555.com/liveMedia/public/ 在Linux系统的自定义目录下输入&#xff0c;下载源码&#xff1a; wget http://www.live555.com/liveMedia/public/live.2023.01.19.tar.gz解压源码&#xff1a; tar -xvf live.2023.01.19.tar.gz当前目录下运行&…

每天10个前端小知识 【Day 11】

前端面试基础知识题 1. 浏览器的垃圾回收机制有哪些&#xff1f; JS会在创建变量时自动分配内存&#xff0c;在不使用的时候会自动周期性的释放内存&#xff0c;释放的过程就叫 “垃圾回收”。 一方面自动分配内存减轻了开发者的负担&#xff0c;开发者不用过多的去关注内存…

flea-msg使用之JMS初识

JMS初识 1. JMS 基本概念 1.1 什么是 JMS &#xff1f; Java 消息服务【Java Message Service】&#xff0c;又简称 JMS&#xff0c;它是 Java 平台上有关面向消息中间件(MOM)的技术规范。 1.2 JMS 规范 JMS 中定义了 Java 中访问消息中间件的接口&#xff0c;并没有给予实…

Java高级-常用类-String、Date、Compare、Other

本篇讲解java常用类 String类 String:字符串&#xff0c;使用一对""引起来表示。 String类被声明为final的&#xff0c;不可被继承。 String实现了Serializable接口&#xff1a;表示字符串是支持序列化的。 ​ 实现了Comparable接口&#xff1a;表示String可以比较…