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

news/2024/11/27 4:52:06/

三.传输层(主讲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和确认号acknou32_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;        // 本地IPip_addr_t remote_ip;    // 目的IPu8_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_pcbprev = 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 != NULLif (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接收任何IPif (ip_addr_cmp(&(lpcb->local_ip), &current_iphdr_dest) || ip_addr_isany(&(lpcb->local_ip))){// 找到匹配的接口之后退出循环往下运行,这时lpcb != NULLbreak;}        }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_processif (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_processif (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_dataTCP_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;                            // 期望接收到的下一个序号,注意加1npcb->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_pcbnpcb->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_RESETpcb->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;}// 保活报文计数器清0pcb->keep_cnt_sent = 0;// 处理报文首部中的选项字段(暂略)tcp_parseopt(pcb);// 根据当前所处的不同的TCP状态执行相应动作switch (pcb->state){case SYN_SENT:    // 客户端发出SYN后,就处于该状态等待服务器返回SYN+ACK// 如果收到的是SYN+ACK,且输入报文中的确认号,就是控制块中已发送,但尚未收到应答报文段中的序号+1if ((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个字节,所以减1if (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,则返回一个RSTtcp_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_ACKcase 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);                // 回复ACKtcp_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);                // 返回ACKpcb->state = CLOSING;            // 进入CLOSING状态}}// 如果只收到有效的ACKelse 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);                    // 回复ACKtcp_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);                        // 调用函数处理报文中的数据// 如果收到合法ACKif (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);                        // 调用函数处理报文中的数据// 如果收到合法ACKif (flags & TCP_ACK && ackno == pcb->snd_nxt){recv_flags |= TF_CLOSED;            // recv_flags设置为TF_CLOSED,由tcp_input函数对该控制块进行释放和清除}

http://www.ppmy.cn/news/23992.html

相关文章

NLP顶会近三年小众研究领域

ACL 2022 编码器和解码器框架、自然语言生成、知识i神经元、抽取式文本摘要、预训练语言模型、零样本神经机器翻译等。 2021 新闻标题生成任务等。跨语言命名实体识别、代码搜索、音乐生成、Hi-Transformer、预训练语言模型、语义交互等。 EMNLP 2021 代码摘要生成、隐私…

微信小程序内部那些事

微信小程序没有window、document&#xff0c;它更像是一个类似 Node.js 的宿主环境。因此在小程序内部不能使用 document.querySelector 这样的选择器&#xff0c;也不支持 XMLHttpRequest、location、localStorage 等浏览器 API&#xff0c;只能使用小程序自己提供的 API&…

shell脚本免交互与expect

目录 Here Document 定义 格式 注意 例子 统计行数 修改密码​编辑 expect 定义 基本命令 实验 免交互ssh主机 Here Document 定义 使用I/O重定向的方式将命令列表提供给交互式程序 格式 命令<< 标记....标记 注意 标记可以使用任意的合法字符&#xf…

IT人的晋升之路——关于人际交往能力的培养

对于咱们的程序员来说&#xff0c;工作往往不是最难的&#xff0c;更难的是人际交往和关系的维护处理。很多时候我们都宁愿加班&#xff0c;也不愿意是社交&#xff0c;认识新的朋友&#xff0c;拓展自己的圈子。对外的感觉就好像我们丧失了人际交往能力&#xff0c;是个呆子&a…

【c++】类和对象:让你明白“面向一个对象有多重要”:构造函数,析构函数,拷贝构造函数的深入学习

文章目录 什么是面向对象&#xff1f;一&#xff1a;类是什么&#xff1f; 1.类的访问限定符 2.封装 3.类的实例化 4.this指针二&#xff1a;类的6个默认成员函数 1.构造函数 2.析构函数 3.拷贝构造函数什么是面向对象&#xff1f; c语言是面向…

nginx相关反爬策略总结笔记

引言 互联网站点的流量一部分由人类正常访问行为产生&#xff0c;而高达30%-60%的流量则是由网络爬虫产生的&#xff0c;其中一部分包含友好网络爬虫&#xff0c;如搜索引擎的爬虫、广告程序、第三方合作伙伴程序、Robots协议友好程序等;而并非所有的网络爬虫都是友好的&#x…

SPSS数据分析软件的安装与介绍(附网盘链接)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Web 框架 Flask 快速入门(一)flask基础与模板

前言 课程地址&#xff1a;Python Web 框架 Flask 快速入门 文章目录前言&#x1f334; Flask基础和模板&#x1f337; 一个简单的flask程序&#x1f33c; 模板的使用&#x1f334; Flask基础和模板 1、web框架的作用 避免重复造轮子&#xff0c;app程序不必关心于服务器的沟…