协议栈 状态机 java_TCP状态机《LwIP协议栈源码详解——TCP/IP协议的实现》

服务器端接收到SYN握手包,向客户端返回带SYN和ACK的握手包,并将相应TCP控制块置为SYN_RCVD状态,并挂在tcp_active_pcbs链表上。以后,继续等待客户端发送过来的握手包,这次,服务器期望的是接收一个ACK包以完成建立连接要求的三次握手操作。

还是和前几次一样,数据包进来通过ip_input传递给tcp_input,后者在三个链表中查找一个匹配的连接控制块。这次进来的是客户端发送的ACK握手包,服务器端相应的tcp控制块一定是在tcp_active_pcbs链表上。接下来,以查找到的tcp_pcb结构为参数,调用TCP状态机函数tcp_process处理输入数据段。在讲解tcp_process函数之前,先来看看这个状态机函数要用到的一些重要的全局变量,这些变量是在tcp_input函数中,通过接收数据段的各个字段的值进行设置的。全局变量tcphdr指向收到的数据段的TCP头部;全局变量seqno记录了TCP头部中的数据序号字段;全局变量ackno存储确认序号字段;全局变量flags表示TCP首部中的各个标志字段;全局变量tcplen表示数据报中数据的长度,对于SYN包和FIN包,该长度为1;全局变量inseg用于描述收到的数据内容,它是tcp_seg类型的,tcp_seg这个结构体后面再讲;全局变量recv_flags用来标识tcp_process函数对数据段的处理结果,初始化为

0。

tcp_process函数首先判断该数据段是不是一个复位数据段,若是则进行相应的处理,这里先跳过这小部分。直接到达tcp_process函数状态机部分,它就是对TCP状态转换图的简单代码诠释。必须要贴一大段代码上来了,这比任何语言更能说清楚问题,注意下面的代码已经被我去掉了相关注释和编译输出部分。

switch (pcb->state) {

case

SYN_SENT: // 客户端将SYN发送到服务器等待握手包返回

if ((flags & TCP_ACK) &&

(flags & TCP_SYN) //实际收到ACK和SYN包

&& ackno ==

ntohl(pcb->unacked->tcphdr->seqno) + 1) {

pcb->snd_buf++;

pcb->rcv_nxt = seqno + 1;

pcb->lastack = ackno;

pcb->snd_wnd = tcphdr->wnd;

pcb->snd_wl1 = seqno - 1;

pcb->state = ESTABLISHED;

tcp_parseopt(pcb); // 处理选项字段

#if TCP_CALCULATE_EFF_SEND_MSS //

根据需要设置有效发送mss字段

pcb->mss =

tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip));

#endif

pcb->ssthresh = pcb->mss *

10;//由于重新设置了mss字段值,所以要重设ssthresh值

pcb->cwnd = ((pcb->cwnd == 1) ?

(pcb->mss * 2) : pcb->mss); // 设置阻塞窗口

--pcb->snd_queuelen; //要发送的数据段个数减1??

rseg =

pcb->unacked; //取下要被确认的字段

pcb->unacked = rseg->next;

if(pcb->unacked == NULL)

//没有字段需要被确认,则停止定时器

pcb->rtime = -1;

else

{ //否则重新开启定时器

pcb->rtime = 0;

pcb->nrtx = 0;

}

tcp_seg_free(rseg); //释放已经被确认了的段

TCP_EVENT_CONNECTED(pcb, ERR_OK,

err);

tcp_ack_now(pcb); //返回ACK握手包

}

else if (flags & TCP_ACK)

{ //仅仅只有ACK而无SYN标志

tcp_rst(ackno, seqno + tcplen,

&(iphdr->dest), &(iphdr->src),

tcphdr->dest,

tcphdr->src);//不支持半打开状态,所以返回一个复位包

}

break;

case

SYN_RCVD: //

服务器端发送出SYN+ACK后便处于该状态

if (flags & TCP_ACK && //

在SYN_RCVD状态接收到ACK返回包

!(flags & TCP_RST))

{ // 判断ACK序号是否合法

if (TCP_SEQ_BETWEEN(ackno,

pcb->lastack+1, pcb->snd_nxt)) {

u16_t old_cwnd;

pcb->state = ESTABLISHED; //

进入ESTABLISHED状态

old_cwnd =

pcb->cwnd; // 保存旧的阻塞窗口

accepted_inseq = tcp_receive(pcb); //

若包含数据则接收数据段

pcb->cwnd = ((old_cwnd == 1) ?

(pcb->mss * 2) : pcb->mss);//重新设置阻塞窗口

if ((flags & TCP_FIN) &&

accepted_inseq) { // 如果ACK包同时含有FIN位且

tcp_ack_now(pcb); //已经接收完了最后的数据,则响应FIN

pcb->state =

CLOSE_WAIT;//进入CLOSE_WAIT状态

}

}

else { //不合法的ACK序号则返回一个复位包

tcp_rst(ackno, seqno + tcplen,

&(iphdr->dest), &(iphdr->src),

tcphdr->dest, tcphdr->src);

}

}

break;

case CLOSE_WAIT:

//服务器,TCP处于半打开状态,在该方向上不会再接收到数据包

//服务器在此状态下会一直等待上层应用执行关闭命令tcp_close,并将状态变为LAST_ACK

case

ESTABLISHED://稳定状态,客户端会一直保持稳定状态直到上层应用调用tcp_close函数关闭连接,将状态变为FIN_WAIT_1

accepted_inseq = tcp_receive(pcb);

//直接接收数据

if ((flags & TCP_FIN) &&

accepted_inseq) { //同时数据包有FIN标志,且接收到了最后

tcp_ack_now(pcb); 的数据,则响应FIN

pcb->state = CLOSE_WAIT; //

进入CLOSE_WAIT状态

}

break;

case FIN_WAIT_1://客户端独有的状态

tcp_receive(pcb); //还可以接收来自服务器的数据

if (flags & TCP_FIN) {

//如果收到FIN包

if (flags & TCP_ACK &&

ackno == pcb->snd_nxt) {//且还有ACK,则进入TIME_WAIT

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链表

} else {//无ACK,则表示两端同时关闭的情况发生

tcp_ack_now(pcb); //

发送ACK

pcb->state = CLOSING; //

进入CLOSING状态

}

} else if (flags & TCP_ACK

&& ackno == pcb->snd_nxt) {//不是FIN包,而是有效ACK

pcb->state = FIN_WAIT_2;

//则进入FIN_WAIT_2状态

}

break;

case FIN_WAIT_2: //客户端在该状态等待服务器返回的FIN

tcp_receive(pcb); //还可以接收来自服务器的数据

if (flags & TCP_FIN)

{//如果收到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); //还可以接收对方的数据

if (flags & TCP_ACK &&

ackno == pcb->snd_nxt) { //若是有效ACK

tcp_ack_now(pcb); // 与上面类似的操作,不解释了

tcp_pcb_purge(pcb);

TCP_RMV(&tcp_active_pcbs, pcb);

pcb->state = TIME_WAIT;

TCP_REG(&tcp_tw_pcbs, pcb);

}

break;

case LAST_ACK://服务器端(被动关闭端)能出现该状态

tcp_receive(pcb); //还可以接收对方的数据

if (flags & TCP_ACK &&

ackno == pcb->snd_nxt) {//接收到有效ACK

recv_flags = TF_CLOSED;//

全局变量recv_flags用于标识该数据段进行了哪些处理

}//

以便tcp_input的后续处理,此时并没有把pcb->state的状态设置为CLOSED状态

break;

default:

break;

}

这就是TCP状态机的大致流程图,如果对照着TCP状态转换图来看,你会觉得它是如此的简单。不过还有很多搞不清楚的地方,比如控制块中各个字段的值特别是阻塞窗口和发送接收窗口等的值,它们代表了什么意义,为什么要如此设置,等等。

注意TCP状态转换图中的双方同时打开的情况,即从LISTEN状态到SYN_SENT状态,SYN_SENT状态到SYN_RCVD状态,没有被实现。许多其他TCP/IP实现,如BSD中也是这样的,没有实现这部分功能。因为在实际应用中这种情况几乎不可见,所以实现并没有严格按照协议来实行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值