
tcp四次挥手源码解析(服务器角度)
发布日期:2021-05-09 16:03:11
浏览次数:14
分类:精选文章
本文共 5052 字,大约阅读时间需要 16 分钟。
第一次第二次挥手
服务端收到客户端的fin包进入TCP_CLOSE_WAIT状态。即第一个挥手完成,然后服务器发送ack给客户端,即第二次挥手完成。
static int tcp_fin(struct sk_buff *skb, struct sock *sk, struct tcphdr *th){ sk->fin_seq = th->seq + skb->len + th->syn + th->fin; if (!sk->dead) { sk->state_change(sk); sock_wake_async(sk->socket, 1); } switch(sk->state) { case TCP_SYN_RECV: case TCP_SYN_SENT: case TCP_ESTABLISHED: /* * move to CLOSE_WAIT, tcp_data() already handled * sending the ack. */ tcp_set_state(sk,TCP_CLOSE_WAIT); if (th->rst) sk->shutdown = SHUTDOWN_MASK; break; case TCP_CLOSE_WAIT: case TCP_CLOSING: /* * received a retransmission of the FIN, do * nothing. */ break; case TCP_TIME_WAIT: /* * received a retransmission of the FIN, * restart the TIME_WAIT timer. */ reset_msl_timer(sk, TIME_CLOSE, TCP_TIMEWAIT_LEN); return(0); case TCP_FIN_WAIT1: /* * This case occurs when a simultaneous close * happens, we must ack the received FIN and * enter the CLOSING state. * * This causes a WRITE timeout, which will either * move on to TIME_WAIT when we timeout, or resend * the FIN properly (maybe we get rid of that annoying * FIN lost hang). The TIME_WRITE code is already correct * for handling this timeout. */ if(sk->ip_xmit_timeout != TIME_WRITE) reset_xmit_timer(sk, TIME_WRITE, sk->rto); tcp_set_state(sk,TCP_CLOSING); break; case TCP_FIN_WAIT2: /* * received a FIN -- send ACK and enter TIME_WAIT */ reset_msl_timer(sk, TIME_CLOSE, TCP_TIMEWAIT_LEN); sk->shutdown|=SHUTDOWN_MASK; tcp_set_state(sk,TCP_TIME_WAIT); break; case TCP_CLOSE: /* * already in CLOSE */ break; default: tcp_set_state(sk,TCP_LAST_ACK); /* Start the timers. */ reset_msl_timer(sk, TIME_CLOSE, TCP_TIMEWAIT_LEN); return(0); } return(0);}
第三次挥手
接着服务器调用close函数关闭本端的写端。
// 关闭一个socketstatic void tcp_close(struct sock *sk, int timeout){ /* * We need to grab some memory, and put together a FIN, * and then put it into the queue to be sent. */ sk->inuse = 1; // 监听型的socket要关闭建立的连接 if(sk->state == TCP_LISTEN) { /* Special case */ tcp_set_state(sk, TCP_CLOSE); // 关闭已经建立的连接 tcp_close_pending(sk); release_sock(sk); return; } sk->keepopen = 1; sk->shutdown = SHUTDOWN_MASK; if (!sk->dead) sk->state_change(sk); if (timeout == 0) { struct sk_buff *skb; /* * We need to flush the recv. buffs. We do this only on the * descriptor close, not protocol-sourced closes, because the * reader process may not have drained the data yet! */ // 销毁未处理的数据 while((skb=skb_dequeue(&sk->receive_queue))!=NULL) kfree_skb(skb, FREE_READ); /* * Get rid off any half-completed packets. */ // 有小数据包则发送 if (sk->partial) tcp_send_partial(sk); } /* * Timeout is not the same thing - however the code likes * to send both the same way (sigh). */ if(timeout) { tcp_set_state(sk, TCP_CLOSE); /* Dead */ } else { if(tcp_close_state(sk,1)==1) { tcp_send_fin(sk); } } release_sock(sk);}
状态的处理逻辑在tcp_close_state函数中。由下面代码可知,当前是TCP_CLOSE_WAIT状态,即将进入LAST_ACK状态,然后发送一个fin包给客户端,即完成了第三次挥手。
// 根据socket的当前状态修改下一个状态 static int tcp_close_state(struct sock *sk, int dead){ // 默认状态是关闭 int ns=TCP_CLOSE; // 默认不需要发送fin包 int send_fin=0; switch(sk->state) { // 还没有建立连接,直接转为关闭状态 case TCP_SYN_SENT: /* No SYN back, no FIN needed */ break; // 收到syn包并且发出了ack,对方也可能已经收到了ack把状态置为已建立,所以需要发送fin包,并把状态置为fin_wait1 case TCP_SYN_RECV: case TCP_ESTABLISHED: /* Closedown begin */ ns=TCP_FIN_WAIT1; send_fin=1; break; // 本端已关闭,即已经发送了fin包,不需要再发送了,状态不变 case TCP_FIN_WAIT1: /* Already closing, or FIN sent: no change */ case TCP_FIN_WAIT2: case TCP_CLOSING: ns=sk->state; break; // 直接置为关闭状态 case TCP_CLOSE: case TCP_LISTEN: break; // 对端已关闭,现在是本端准备关闭,需要发送fin包,然后进入last_ack状态 case TCP_CLOSE_WAIT: /* They have FIN'd us. We send our FIN and wait only for the ACK */ ns=TCP_LAST_ACK; send_fin=1; } tcp_set_state(sk,ns); /* * This is a (useful) BSD violating of the RFC. There is a * problem with TCP as specified in that the other end could * keep a socket open forever with no application left this end. * We use a 3 minute timeout (about the same as BSD) then kill * our end. If they send after that then tough - BUT: long enough * that we won't make the old 4*rto = almost no time - whoops * reset mistake. */ // 如果是本端已经关闭,在等待对端关闭的状态,则设置一个定时器,如果超时还没有收到对端的fin包则强行关闭 if(dead && ns==TCP_FIN_WAIT2) { int timer_active=del_timer(&sk->timer); if(timer_active) add_timer(&sk->timer); else reset_msl_timer(sk, TIME_CLOSE, TCP_FIN_TIMEOUT); } return send_fin;}
第四次挥手
这时候服务器socket的状态是LAST_ACK,收到客户端发送的最后ack包的时候即完成了第四次挥手,处理代码在tcp_ack中。
if (sk->state == TCP_LAST_ACK) { if (!sk->dead) sk->state_change(sk); if(sk->debug) printk("rcv_ack_seq: %lX==%lX, acked_seq: %lX==%lX\n", sk->rcv_ack_seq,sk->write_seq,sk->acked_seq,sk->fin_seq); if (sk->rcv_ack_seq == sk->write_seq /*&& sk->acked_seq == sk->fin_seq*/) { flag |= 1; tcp_set_state(sk,TCP_CLOSE); sk->shutdown = SHUTDOWN_MASK; } }
发表评论
最新留言
路过,博主的博客真漂亮。。
[***.116.15.85]2025年04月24日 07时42分59秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
android 开机启动流程分析(13)Zygote的分裂
2021-05-10
简单理解vuex原理
2021-05-10
ES6之函数的扩展
2021-05-10
发布订阅模式与观察者模式
2021-05-10
java.lang.NoSuchMethodError 错误的原因及解决方法
2021-05-10
马王堆出土道德经原文(非删改本)
2021-05-10
java从入门到精通----servlet11
2021-05-10
MySQL复习09-触发器
2021-05-10
JS实现禁止浏览器后退返回上一页
2021-05-10
redis ③ -java客户端连接redis
2021-05-10
torch.meshgrid()函数解析
2021-05-10
Halcon1:灰度投影的运用思路
2021-05-10
java获取的NTLM电脑用户名并解码(python解码)
2021-05-10
Vue 中 hash 模式和 history 模式的区别
2021-05-10
运行 Webpack 项目图片和favicon.ico找不到, 图片404错误
2021-05-10
TiDB 单机安装测试集群(Docker Compose)
2021-05-10
Python:条件判断语句类型介绍及场景应用
2021-05-10
Python:Mac Pro让终端默认运行python3.x
2021-05-10
Python:逻辑运算符的基本使用
2021-05-10