TCP 之 FIN_WAIT_2状态处理流程
概述
在主动关闭方发送了FIN之后,进入FIN_WAIT_1状态,在此状态收到了ACK,则进入FIN_WAIT_2状态,而FIN_WAIT_2后续要做的工作是等待接收对端发过来的FIN包,并且发送ACK,进而进入到TIME_WAIT状态;本文主要关注从FIN_WAIT_1进入FIN_WAIT_2状态,以及在FIN_WAIT_2状态来包或者定时器触发后的处理流程;
进入FIN_WAIT_2
tcp_rcv_state_process函数中对于ack的处理步骤中,假如连接处于FIN_WAIT_1,且数据均已经被确认完,则进入TIME_WAIT_2状态;如果无需在该状态等待(linger2<0),或者收到了乱序数据段,则直接关闭连接;如果需要等待,则需要判断等待时间与TIMEWAIT时间的大小关系,若>TIMEWAIT_LEN,则添加TIME_WAIT_2定时器,否则直接进入TIME_WAIT接管(其子状态仍然是FIN_WAIT_2),接管之后会添加TIME_WAIT定时器;
另,tcp_close函数调用时,如果当前状态是FIN_WAIT_2也会用相似方式进入TIME_WAIT接管,不再单独介绍;
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
/* step 5: check the ACK field */
acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
FLAG_UPDATE_TS_RECENT) > ; switch (sk->sk_state) {
case TCP_FIN_WAIT1: {
int tmo; /* If we enter the TCP_FIN_WAIT1 state and we are a
* Fast Open socket and this is the first acceptable
* ACK we have received, this would have acknowledged
* our SYNACK so stop the SYNACK timer.
*/
if (req) {
/* Return RST if ack_seq is invalid.
* Note that RFC793 only says to generate a
* DUPACK for it but for TCP Fast Open it seems
* better to treat this case like TCP_SYN_RECV
* above.
*/
if (!acceptable)
return ;
/* We no longer need the request sock. */
reqsk_fastopen_remove(sk, req, false);
tcp_rearm_rto(sk);
} /* 发送数据未确认完毕 */
if (tp->snd_una != tp->write_seq)
break; /* 进入FIN_WAIT_2状态 */
tcp_set_state(sk, TCP_FIN_WAIT2); /* 关闭发送端 */
sk->sk_shutdown |= SEND_SHUTDOWN; /* 路由缓存确认 */
sk_dst_confirm(sk); /* 套接口不是DEAD状态,状态发生变化,唤醒等待进程 */
if (!sock_flag(sk, SOCK_DEAD)) {
/* Wake up lingering close() */
sk->sk_state_change(sk);
break;
} /* linger2<0,无需在FIN_WAIT_2等待 */
if (tp->linger2 < ) {
/* 关闭连接 */
tcp_done(sk);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
return ;
} /* 收到期望序号以后的数据段(data, fin) */
if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
/* Receive out of order FIN after close() */
if (tp->syn_fastopen && th->fin)
tcp_fastopen_active_disable(sk);
/* 关闭连接 */
tcp_done(sk);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
return ;
} /* 获取FIN_WAIT_2等待时间 */
tmo = tcp_fin_time(sk); /* > TIMEWAIT_LEN,加入FIN_WAIT_2定时器 */
if (tmo > TCP_TIMEWAIT_LEN) {
inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
}
/* 有fin?? 或者 被用户进程锁定,加入FIN_WAIT_2定时器 */
else if (th->fin || sock_owned_by_user(sk)) {
/* Bad case. We could lose such FIN otherwise.
* It is not a big problem, but it looks confusing
* and not so rare event. We still can lose it now,
* if it spins in bh_lock_sock(), but it is really
* marginal case.
*/
inet_csk_reset_keepalive_timer(sk, tmo);
}
/* 正常等待时间< TIMEWAIT_LEN,进入TIMEWAIT接管状态 */
else {
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto discard;
}
break;
}
}
状态转换触发
FIN_WAIT_2状态的走向有以下几个流程触发点,(1)TIME_WAIT_2定时器未超时时间内,收到数据段触发; (2)TIME_WAIT_2定时器超时触发; (3)TIME_WAIT定时器未超时时间内,收到数据段触发; (4)TIME_WAIT定时器超时触发;
(1) TIME_WAIT_2定时器未超时时间内,收到数据段触发,如果设置FIN标记,则直接进入TIME_WAIT状态;
在函数tcp_rcv_state_process处理数据段的过程中,FIN_WAIT_2状态最终会调用tcp_data_queue来处理数据段;
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
/* step 7: process the segment text */
switch (sk->sk_state) {
case TCP_CLOSE_WAIT:
case TCP_CLOSING:
case TCP_LAST_ACK:
if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
break;
case TCP_FIN_WAIT1:
case TCP_FIN_WAIT2:
/* RFC 793 says to queue data in these states,
* RFC 1122 says we MUST send a reset.
* BSD 4.4 also does reset.
*/
if (sk->sk_shutdown & RCV_SHUTDOWN) {
if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
tcp_reset(sk);
return ;
}
}
/* Fall through */
case TCP_ESTABLISHED:
tcp_data_queue(sk, skb);
queued = ;
break;
}
}
tcp_data_queue在处理数据段的时候,有对FIN标记的检查,如果有该标记,则进入tcp_fin函数;
static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
{
/* ... */
if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
tcp_fin(sk);
/* ... */
}
tcp_fin函数中,如果此时连接状态为FIN_WAIT_2,则发送ACK,并且直接进入TIME_WAIT状态;在tcp_time_wait函数处理中,会删除当前控制块,所以FIN_WAIT_2定时器也就不存在了;
void tcp_fin(struct sock *sk)
{
/* ... */
switch (sk->sk_state) {
case TCP_FIN_WAIT2:
/* Received a FIN -- send ACK and enter TIME_WAIT. */
tcp_send_ack(sk);
tcp_time_wait(sk, TCP_TIME_WAIT, );
break;
}
/* ... */
}
(2)TIME_WAIT_2定时器超时触发,如果linger2<0,或者等待时间<=TIMEWAIT_LEN,直接发送reset关闭连接;如果linger2>=0,且等待时间>TIMEWAIT_LEN,则进入TIME_WAIT接管;
static void tcp_keepalive_timer (unsigned long data)
{
/*...*/
/* 处于fin_wait2且socket即将销毁,用作FIN_WAIT_2定时器 */
if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) { /* 停留在FIN_WAIT_2的停留时间>=0 */
if (tp->linger2 >= ) {
/* 获取时间差值 */
const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN; /* 差值>0,等待时间>TIME_WAIT时间,则进入TIME_WAIT状态 */
if (tmo > ) {
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
goto out;
}
} /* 发送rst */
tcp_send_active_reset(sk, GFP_ATOMIC);
goto death;
}
/*...*/
}
(3)TIME_WAIT定时器未超时时间内,收到数据段触发,若收到合法的FIN,则进入真正的TIME_WAIT状态;
tcp_v4_rcv收入数据段过程中,会对TIME_WAIT状态做特别处理,而对于TIME_WAIT子状态的处理在函数tcp_timewait_state_process中;
int tcp_v4_rcv(struct sk_buff *skb)
{
/*...*/
do_time_wait:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
inet_twsk_put(inet_twsk(sk));
goto discard_it;
} /* 校验和错误 */
if (tcp_checksum_complete(skb)) {
inet_twsk_put(inet_twsk(sk));
goto csum_error;
} /* TIME_WAIT入包处理 */
switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) { /* 收到syn */
case TCP_TW_SYN: {
/* 查找监听控制块 */
struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
&tcp_hashinfo, skb,
__tcp_hdrlen(th),
iph->saddr, th->source,
iph->daddr, th->dest,
inet_iif(skb)); /* 找到 */
if (sk2) {
/* 删除tw控制块 */
inet_twsk_deschedule_put(inet_twsk(sk));
/* 记录监听控制块 */
sk = sk2;
refcounted = false; /* 进行新请求的处理 */
goto process;
}
/* Fall through to ACK */
} /* 发送ack */
case TCP_TW_ACK:
tcp_v4_timewait_ack(sk, skb);
break;
/* 发送rst */
case TCP_TW_RST:
tcp_v4_send_reset(sk, skb);
/* 删除tw控制块 */
inet_twsk_deschedule_put(inet_twsk(sk));
goto discard_it;
/* 成功*/
case TCP_TW_SUCCESS:;
}
goto discard_it;
}
tcp_timewait_state_process函数处理流程中,如果TIME_WAIT的子状态为FIN_WAIT_2,并且收到了合法的FIN之后,会进入真正的TIME_WAIT状态,即子状态也为TIME_WAIT,并且设置TIME_WAIT定时器;
enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,
const struct tcphdr *th)
{
/*...*/
/* 子状态是FIN_WAIT2 */
if (tw->tw_substate == TCP_FIN_WAIT2) {
/* Just repeat all the checks of tcp_rcv_state_process() */ /* Out of window, send ACK */
/* 序号回绕或者数据超出窗口范围,发送ack */
if (paws_reject ||
!tcp_in_window(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq,
tcptw->tw_rcv_nxt,
tcptw->tw_rcv_nxt + tcptw->tw_rcv_wnd))
return tcp_timewait_check_oow_rate_limit(
tw, skb, LINUX_MIB_TCPACKSKIPPEDFINWAIT2); /* rst,则停止调度,销毁tw控制块 */
if (th->rst)
goto kill; /* syn && 序号>= 期望接收序号??? */
if (th->syn && !before(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt))
return TCP_TW_RST; /* Dup ACK? */
/* 非ack || 以前的ack || 新的无数据ack */
if (!th->ack ||
!after(TCP_SKB_CB(skb)->end_seq, tcptw->tw_rcv_nxt) ||
TCP_SKB_CB(skb)->end_seq == TCP_SKB_CB(skb)->seq) {
inet_twsk_put(tw);
return TCP_TW_SUCCESS;
} /* New data or FIN. If new data arrive after half-duplex close,
* reset.
*/
/* 不是fin,或者fin有数据 */
if (!th->fin ||
TCP_SKB_CB(skb)->end_seq != tcptw->tw_rcv_nxt + )
return TCP_TW_RST; /* FIN arrived, enter true time-wait state. */
/* fin包,进入真正的TIME_WAIT */
tw->tw_substate = TCP_TIME_WAIT; /* 设置下一次接收序号 */
tcptw->tw_rcv_nxt = TCP_SKB_CB(skb)->end_seq; /* 设置时间戳 */
if (tmp_opt.saw_tstamp) {
tcptw->tw_ts_recent_stamp = get_seconds();
tcptw->tw_ts_recent = tmp_opt.rcv_tsval;
} /*重新设置tw定时器 */
inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN); /* 发送ack */
return TCP_TW_ACK;
}
/*...*/
}
(4)TIME_WAIT定时器超时触发,定时器超时,将tw控制块从ehash和bhash中删除,在收到数据段会发送reset;
定时器超时会进入到tw_timer_handler处理函数,该函数在统计信息之后,调用inet_twsk_kill;
static void tw_timer_handler(unsigned long data)
{
struct inet_timewait_sock *tw = (struct inet_timewait_sock *)data; if (tw->tw_kill)
__NET_INC_STATS(twsk_net(tw), LINUX_MIB_TIMEWAITKILLED);
else
__NET_INC_STATS(twsk_net(tw), LINUX_MIB_TIMEWAITED);
inet_twsk_kill(tw);
}
inet_twsk_kill从ehash和bhash中把tw控制块删除,并且释放之;
static void inet_twsk_kill(struct inet_timewait_sock *tw)
{
struct inet_hashinfo *hashinfo = tw->tw_dr->hashinfo;
spinlock_t *lock = inet_ehash_lockp(hashinfo, tw->tw_hash);
struct inet_bind_hashbucket *bhead; spin_lock(lock);
sk_nulls_del_node_init_rcu((struct sock *)tw);
spin_unlock(lock); /* Disassociate with bind bucket. */
bhead = &hashinfo->bhash[inet_bhashfn(twsk_net(tw), tw->tw_num,
hashinfo->bhash_size)]; spin_lock(&bhead->lock);
inet_twsk_bind_unhash(tw, hashinfo);
spin_unlock(&bhead->lock); atomic_dec(&tw->tw_dr->tw_count);
inet_twsk_put(tw);
}
TCP 之 FIN_WAIT_2状态处理流程的更多相关文章
- TCP/IP连接状态
1.建立连接协议(三次握手)(1)客户端发送一个带SYN标志的TCP报文到服务器.这是三次握手过程中的报文1.(2) 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN ...
- 动手学习TCP:客户端状态变迁
上一篇文章中介绍了TCP连接的建立和终止. 通过实际操作了解到,在TCP协议工作过程中,客户端和服务端都会接收或者发送特定标志的TCP数据包,然后进入不同的状态. 也就是说,TCP协议就是一个包含多种 ...
- TCP连接的状态详解以及故障排查
我们通过了解 TCP各个状态 ,可以排除和定位网络或系统故障时大有帮助. 一.TCP状态 LISTENING :侦听来自远方的TCP端口的连接请求 . 首先服务端需要打开一个 socket 进行监听, ...
- 转载:TCP连接的状态详解以及故障排查
FROM:http://blog.csdn.net/hguisu/article/details/38700899 该博文的条理清晰,步骤明确,故复制到这个博文中收藏,若文章作者看到且觉得不能装载,麻 ...
- TCP连接的状态与关闭方式及其对Server与Client的影响
TCP连接的状态与关闭方式及其对Server与Client的影响 1. TCP连接的状态 首先介绍一下TCP连接建立与关闭过程中的状态.TCP连接过程是状态的转换,促使状态发生转换的因素包括用户调用. ...
- TCP/IP TIME_WAIT状态
百度运维部二面面试官问我这个 我直接懵逼了 TIME_WAIT状态是通信双方简历TCP连接后, 主动关闭的一方就会进入TIME_WAIT状态 1.client向server发送FIN(M),clien ...
- FIN_WAIT_2状态解释
关于网络设备的FIN_WAIT_2状态解释出处:http://hi.baidu.com/netdemon1981/blog/item/584bfbb2aeb1d4acd9335ad9.html 在HT ...
- TCP连接的状态与关闭方式,及其对Server与Client的影响
1. TCP连接的状态 首先介绍一下TCP连接建立与关闭过程中的状态.TCP连接过程是状态的转换,促使状态发生转换的因素包括用户调用.特定数据包以及超时等,具体状态如下所示: CLOSED:初始状态, ...
- 网络的FIN_WAIT_2状态解释和分析
关于网络设备的FIN_WAIT_2状态解释出处:http://hi.baidu.com/netdemon1981/blog/item/584bfbb2aeb1d4acd9335ad9.html 在HT ...
随机推荐
- PHP扩展之 Imagick安装
最近的PHP项目中,需要用到切图和缩图的效果,在本地windows开发环境,安装过程遇到好多问题,在此与大家分享. php官网里,一大群老外也看不懂这玩意怎么装,主要原因在于,php版本庞杂,还有x8 ...
- form表单中的enctype 属性以及post请求里Content-Type方式
对于form表单中的enctype 属性之前理解的一般,就知道是类似于一种编码形式.后来公司做一个form表单提交数据的时候,重点是这个form表单里有文件上传,而我又要用vue来模拟form表单提交 ...
- JavaScript中with不推荐使用,为什么总是出现在面试题中?
with的基本使用 尴尬的with关键字 一.with的基本使用 with是用来扩展语句作用域的,什么意思呢?先来看看语法和示例: 语法: with(expression){ statement } ...
- JavaWeb【七、JSP状态管理】
http协议无状态性 当提交请求,服务器返回响应.但当同一用户同一浏览器再次提交请求,服务器并不知道与刚才的请求是同一用户浏览器发起. 保存用户状态的两大机制 Session-保存在服务器端 Cook ...
- deep_learning_backprop
反向传播理解–从抽象到具体 神经网络从计算的角度看,数据是从底层输入,经过每一层,根据与该层之间的权重计算以一个中间结果,这个中间结果再经过一个非线性激活函数作用,得到该层的输出结果,然后把该层的输出 ...
- three.js之创建一条直线
<!DOCTYPE html> <html> <head> <meta charset=utf-8> <title>My first thr ...
- dubbo框架-学习-dubbo原理
博客:Dubbo原理和源码解析之服务暴露 博客:dubbo实现原理简单介绍
- Python twisted事件驱动网络框架 源码剖析
一.Twisted简介 Twisted是一个事件驱动的网络框架,其中包含了诸多功能,例如:网络协议.线程.数据库管理.网络操作.电子邮件等. 事件驱动简而言之,事件驱动分为二个部分:第一,注册事件:第 ...
- View相关面试问题-View绘制面试问题详解
View树的绘制流程: measure() --> layout() --> onDraw(),具体使用这个可以参考之前实现的自定义的博客:[http://www.cnblogs.co ...
- js 深浅拷贝 笔记总结
一.js 数据类型 javaScritp的数据类型有:数值类型.字符串类型.布尔类型.null.undefined.对象(数组.正则表达式.日期.函数),大致分成两种:基本数据类型和引用数据类型, 其 ...