三.传输层(主讲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(¤t_iphdr_dest, inp) || ip_addr_ismulticast(¤t_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), ¤t_iphdr_src) && ip_addr_cmp(&(pcb->local_ip), ¤t_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), ¤t_iphdr_src) && ip_addr_cmp(&(pcb->local_ip), ¤t_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), ¤t_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函数对该控制块进行释放和清除}