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 ...
随机推荐
- 点击登录页面成功后,后端返回数据需要保存,在另外一个页面,发送ajax请求的时候需要登录返回数据的其中的一部分当做参数然后拿到新的数据
对于这个怎么操作首先我们要在登录的ajax请求中把后端的数据保存到sessionstorage中,代码如下 登录ajax $.ajax({ type:'post', url:xxxxxxxxx, da ...
- 百度 Ueditor 使用及规则
UMeditor 官网::https://ueditor.baidu.com/website/download.html#ueditor文档::http://fex.baidu.com/ueditor ...
- 不错的abap技术网站
http://www.saptechnical.com/index.htm https://sapcodes.com/
- jboss日志的自定义
最近由于想着每次调试socket接收数据情况都需要源码debug好麻烦,要是能把接收到的数据输出到一个单独的日志文件,那出问题的时候,查看问题就方便多了. log4j的日志是可以很方便自定义的,只是这 ...
- Python 之 random模块
Python中的random模块用于生成随机数.1.random.random() #用于生成一个0到1的随机浮点数:0<= n < 1.0>>> random.ran ...
- xtrabackup备份恢复过程
备份 1.全备 innobackupex --user=root --password=123456 --no-timestamp /backup/full 增加数据 mysql> insert ...
- IDEA springboot maven 项目部署
- linux命令详解——crontab
基本格式 : * * * * * command 分 时 日 月 周 命令 第1列表示分钟1-59 每分钟用*或者 */1表示 第2列表示小时1-23(0表示0点) 第3列表示日期1-31 第4列表示 ...
- python 2.7.5 获取文本关键字符所在行
#!/usr/bin/env python #-*- coding: UTF- -*- from __future__ import print_function import time,os,sys ...
- redis整合Spring入门
首先 衷心感谢这篇博客给我入门时的启发 三颗心脏 你需要知道,spring的官方文档中已经注明,与redis整合时,spring的jar包版本不能低于4.2.6,否则不支持,会报错的哟 测试的时候请 ...