一,简介
在上一章输入的tcp报文在找到了其对应的TCP控制块后,需要根据tcp控制块的不同状态,对报文有不同的处理方式。这将可能导致tcp状态的变化,使用tcp状态机来表示这样状态变化。
(图侵删)
再结合tcp连接和断开的过程了解tcp状态机
二,源码分析
tcp状态机的源码就是上图状态机的实现;
首先是处理rst,若输入报文是rst报文,则判断该rst合不合法,合法则复位tcp连接:
/*---------------------------------------优先处理rst报文----------------------------------------*/
if (flags & TCP_RST) {
//报文中带rst标志
if (pcb->state == SYN_SENT) {
//连接建立过程中,判断ackno来识别一个rst是否正确
if (ackno == pcb->snd_nxt) {
//连接建立过程。应答序号等于下一个要发送的序号,是正确的
acceptable = 1;
}
} else {
//其他状态的tcp需要检查报文的序号以判断rst是否合法
//报文序号等于期待接收的序号时,pcb复位
if (seqno == pcb->rcv_nxt) {
acceptable = 1;
} else if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
pcb->rcv_nxt + pcb->rcv_wnd)) {
//如果序号是在接收窗口内,则发送ack,等待回复重新检查报文序号
tcp_ack_now(pcb);
}
}
if (acceptable) {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n"));
LWIP_ASSERT("tcp_input: pcb->state != CLOSED", pcb->state != CLOSED);
recv_flags |= TF_RESET; //process处理结果标志为复位
pcb->flags &= ~TF_ACK_DELAY; //清除ack
return ERR_RST;
} else {
//不需要复位,忽略rst报文
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
seqno, pcb->rcv_nxt));
LWIP_DEBUGF(TCP_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
seqno, pcb->rcv_nxt));
return ERR_OK;
}
}
//本地不处于SYN_SENT或SYN_RCVD,却收到了SYN,是异常情况,可能是对方重启了
if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) {
/* Cope with new connection attempt after remote end crashed */
//应对远程端崩溃后的新连接尝试。
tcp_ack_now(pcb);
return ERR_OK;
}
//如果本地接收未关闭,就复位保活计时器
if ((pcb->flags & TF_RXCLOSED) == 0) {
/* Update the PCB (in)activity timer unless rx is closed (see tcp_shutdown) */
pcb->tmr = tcp_ticks;
}
pcb->keep_cnt_sent = 0; //清除保活计数值
tcp_parseopt(pcb); //解析出mss
现在进入状态机的具体代码
switch (pcb->state) {
/*--------------------------------客户端发起连接请求等待服务器返回syn和ack----------------------------------------*/
case SYN_SENT:
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("SYN-SENT: ackno %"U32_F" pcb->snd_nxt %"U32_F" unacked %"U32_F"\n", ackno,
pcb->snd_nxt, lwip_ntohl(pcb->unacked->tcphdr->seqno)));
//报文是ack和syn,且序号能对的上,则发送ack,并进入连接建立状态
if ((flags & TCP_ACK) && (flags & TCP_SYN)
&& (ackno == pcb->lastack + 1)) {
pcb->rcv_nxt = seqno + 1;
pcb->rcv_ann_right_edge = pcb->rcv_nxt;//?不晓得
pcb->lastack = ackno;
pcb->snd_wnd = tcphdr->wnd; //本地发送窗口为对方的通告窗口大小
pcb->snd_wnd_max = pcb->snd_wnd;
pcb->snd_wl1 = seqno - 1; //上次更新时序号
pcb->state = ESTABLISHED;
//计算ip路径下的mss
pcb->mss = tcp_eff_send_mss(pcb->mss, &pcb->local_ip, &pcb->remote_ip);
pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss); //初始化拥塞窗口
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SENT): cwnd %"TCPWNDSIZE_F
" ssthresh %"TCPWNDSIZE_F"\n",
pcb->cwnd, pcb->ssthresh));
LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0));
--pcb->snd_queuelen; //发送报文队列减1
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"TCPWNDSIZE_F"\n", (tcpwnd_size_t)pcb->snd_queuelen));
//既然收到了ack,那么就将unack队列的第一个报文删除
rseg = pcb->unacked;
//lwip将重传定时到的unacked放到了unsent,所以unacked的报文可能被放到了unsent
if (rseg == NULL) {
rseg = pcb->unsent;
LWIP_ASSERT("no segment to free", rseg != NULL);
pcb->unsent = rseg->next;
} else {
pcb->unacked = rseg->next; //重新组织unacked
}
tcp_seg_free(rseg); //释放被acked的报文
//如果接下来没有需要应答的报文,则关闭重传定时器,否则重置它
if (pcb->unacked == NULL) {
pcb->rtime = -1;
} else {
pcb->rtime = 0;
pcb->nrtx = 0;
}
//回调连接建立函数
TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
if (err == ERR_ABRT) {
return ERR_ABRT;
}
//最后发送一个ack,三次握手结束
tcp_ack_now(pcb);
}
//只收到ack报文,可能是半连接
else if (flags & TCP_ACK) {
//发送rst报文重新连接
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
ip_current_src_addr(), tcphdr->dest, tcphdr->src);
//重传次数不超限制则立即重传,包括SYN
if (pcb->nrtx < TCP_SYNMAXRTX) {
pcb->rtime = 0;
tcp_rexmit_rto(pcb);
}
}
break;
/*------------------------------服务器收到SYN并发送完syn+ack,等待客户端回答-----------------------------------*/
case SYN_RCVD:
if (flags & TCP_ACK) {
//报文是客户端的ack报文
if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {
//确认序号在发送窗口的正确范围
pcb->state = ESTABLISHED; //进入建立状态
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
#if LWIP_CALLBACK_API
LWIP_ASSERT("pcb->listener->accept != NULL",
(pcb->listener == NULL) || (pcb->listener->accept != NULL));
#endif
if (pcb->listener == NULL) {
err = ERR_VAL; //listen pcb可能已经被关闭了
} else
#endif /* LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG */
{
tcp_backlog_accepted(pcb); //无操作
//回调连接建立函数
TCP_EVENT_ACCEPT(pcb->listener, pcb, pcb->callback_arg, ERR_OK, err);
}
//如果回调函数返回错误,或者listen pcb已经被关闭,则终止tcp
if (err != ERR_OK) {
if (err != ERR_ABRT) {
tcp_abort(pcb);
}
return ERR_ABRT;
}
//报文中还有其他的数据,则交给应用层处理
tcp_receive(pcb);
//如果本地发送的数据被报文中的ack确认,则实际acked-1,因为syn占一个字节
if (recv_acked != 0) {
recv_acked--;
}
pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss); //设置阻塞窗口
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SYN_RCVD): cwnd %"TCPWNDSIZE_F
" ssthresh %"TCPWNDSIZE_F"\n",
pcb->cwnd, pcb->ssthresh));
//如果有fin标志,则回答,并进入closewait,等待应用程序
if (recv_flags & TF_GOT_FIN) {
tcp_ack_now(pcb);
pcb->state = CLOSE_WAIT;
}
} else {
//非法的ack确认序号,发送rst
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
ip_current_src_addr(), tcphdr->dest, tcphdr->src);
}
} else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {
//收到对方重复的syn,说明服务器发出的syn+ack丢失,重发
tcp_rexmit(pcb);
}
break;
case CLOSE_WAIT: //服务器不会接收数据
/* FALLTHROUGH */
case ESTABLISHED: //已经建立连接
tcp_receive(pcb); //将数据交给上层
//被动关闭连接
if (recv_flags & TF_GOT_FIN) {
/* passive close */
tcp_ack_now(pcb);
pcb->state = CLOSE_WAIT;
}
break;
/*------------------------------------客户端发送断开请求,等待回答-----------------------------------*/
case FIN_WAIT_1:
tcp_receive(pcb); //将数据交给上层
if (recv_flags & TF_GOT_FIN) {
//收到服务器的断开请求
if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt) && //同时收到本地FIN的ack,且本地没有未发送的数据,跳过wait2,进入timewait
pcb->unsent == NULL) {
LWIP_DEBUGF(TCP_DEBUG,
("TCP connection closed: FIN_WAIT_1 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_ack_now(pcb); //发送ack
tcp_pcb_purge(pcb); //释放pcb
TCP_RMV_ACTIVE(pcb); //移除链表
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb); //进入新的链表
} else {
//异常情况,可能是双方同时发起断开
tcp_ack_now(pcb);
pcb->state = CLOSING;
}
} else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt) &&
pcb->unsent == NULL) {
pcb->state = FIN_WAIT_2; //收到ack,进入FIN_WAIT_2,等待服务器的FIN
}
break;
/*----------------------------------客户端已经发送断开连接,等待服务端应用程序断开----------------------*/
case FIN_WAIT_2:
tcp_receive(pcb); //将数据交给上层
//收到服务器的断开请求,回答
if (recv_flags & TF_GOT_FIN) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_2 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_ack_now(pcb);
tcp_pcb_purge(pcb);
TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
}
break;
case CLOSING: //两端同时关闭连接
tcp_receive(pcb);
//收到了对方的ack且没有未发送的数据,进入TIME_WAIT
if ((flags & TCP_ACK) && ackno == pcb->snd_nxt && pcb->unsent == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: CLOSING %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_pcb_purge(pcb);
TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
}
break;
case LAST_ACK: //服务器上层处理完,发送断开请求,等待对方回答
tcp_receive(pcb);
//收到了对方的ack且没有未发送的数据,结束tcp一生
if ((flags & TCP_ACK) && ackno == pcb->snd_nxt && pcb->unsent == NULL) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: LAST_ACK %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
recv_flags |= TF_CLOSED; //tcp_input会对tcp进一步释放
}
break;
default:
break;
}
return ERR_OK;
}
可见tcp状态机完成的主要还是tcp状态之间的转换的逻辑。而真正的tcp报文里的数据则交给了tcp_receive()
,该函数检查tcp报文数据,并将有序的数据传递给应用层。