第一次挥手
在 TCP 中有两个函数可以关闭连接,分别是close
和shutdown
tcp_close
close 函数对应的底层实现函数是 tcp_close ,具体实现如下:
void tcp_close(struct sock *sk, long timeout)
{
lock_sock(sk); // 对sock进行锁定
__tcp_close(sk, timeout); // 实际作用函数
release_sock(sk);
sock_put(sk);
}
void __tcp_close(struct sock *sk, long timeout)
{
struct sk_buff *skb;
int data_was_unread = 0;
int state;
sk->sk_shutdown = SHUTDOWN_MASK; // 修改shutdown标志为3,代表同时关闭读写端
if (sk->sk_state == TCP_LISTEN) { // 如果处于listen状态,在此状态还没有接收缓冲,因此处理较为简单
tcp_set_state(sk, TCP_CLOSE); // 修改状态为关闭
/* Special case. */
inet_csk_listen_stop(sk);
goto adjudge_to_death;
}
/* 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->sk_receive_queue)) != NULL) { // 当接收队列不为空,注意这些未读取的包是直接丢弃!!!
u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq; // 这个包体包含的序列号范围长度
if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) // 如果这个包是FIN包,那么长度-1,难道-1之后就是0?TODO
len--;
data_was_unread += len; // 未读取的序列号长度
__kfree_skb(skb); // 释放掉这个包
}
/* If socket has been already reset (e.g. in tcp_reset()) - kill it. */
if (sk->sk_state == TCP_CLOSE) // 已经关闭
goto adjudge_to_death;
if (unlikely(tcp_sk(sk)->repair)) { // 不知是什么标志,源码中没有注释TODO
sk->sk_prot->disconnect(sk, 0);
} else if (data_was_unread) { // 如果有未处理的数据包被清除了,可见这里是一种暴力的操作,不遵循四次挥手
/* Unread data was tossed, zap the connection. */
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE); // 设置为关闭状态
tcp_send_active_reset(sk, sk->sk_allocation); // 给对端发送一个RST包
} else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) { // linger状态的处理
/* Check zero linger _after_ checking for unread data. */
sk->sk_prot->disconnect(sk, 0);
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
} else if (tcp_close_state(sk)) { // 根据tcp当前状态修改为下一个状态
// TCP_ESTABLISHED -> TCP_FIN_WAIT1
// TCP_CLOSE_WAIT -> TCP_LAST_ACK
tcp_send_fin(sk); // 在修改完状态之后下发FIN,这个FIN包可能是第一次挥手也可能是第三次挥手,看具体语境
}
sk_stream_wait_close(sk, timeout); // 阻塞等待sock状态的变化为!(FIN_WAIT_1、CLOSING、LAST_ACK)里面使用的是一个 do{}while() 的循环
adjudge_to_death:
// 设置套接字状态为DEAD状态,使之成为孤儿套接字,同时更新系统的孤儿套接字数
state = sk->sk_state;
sock_hold(sk);
sock_orphan(sk); // 这里会把sock设置为DEAD状态
local_bh_disable();
bh_lock_sock(sk);
/* remove backlog if any, without releasing ownership. */
__release_sock(sk);
this_cpu_inc(tcp_orphan_count);
/* Have we already been destroyed by a softirq or backlog? */
if (state != TCP_CLOSE && sk->sk_state == TCP_CLOSE)
goto out;
// 处理FIN_WAIT2状态
if (sk->sk_state == TCP_FIN_WAIT2) {
struct tcp_sock *tp = tcp_sk(sk);
if (tp->linger2 < 0) { // TCP_LINGER2设置为小于0,说明不需要等待FIN_WAIT_2状态,因此立即转换为CLOSED状态
tcp_set_state(sk, TCP_CLOSE); // 设置为关闭状态
tcp_send_active_reset(sk, GFP_ATOMIC); // 发送RST
__NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPABORTONLINGER);
} else {
const int tmo = tcp_fin_time(sk); // 计算要维持FIN_WAIT2状态的时长,如果设置了TCP_LINGER2,那么读取该值,否则读取tcp_fin_timeout(默认60s)参数,且最小值是 rto<<2 - rot>>1
if (tmo > TCP_TIMEWAIT_LEN) { // 如果等待时长大于1分钟,TCP_TIMEWAIT_LEN==60,因为TIME_WAIT状态要刚好是1分钟,因此这里要利用保活定时器消耗掉这个差值
inet_csk_reset_keepalive_timer(sk,
tmo - TCP_TIMEWAIT_LEN); // 重置keepalive定时器,这个定时器是保活机制和FIN_WAIT_2共用的
} else {
tcp_time_wait(sk, TCP_FIN_WAIT2, tmo); // 进入TIME_WAIT状态,这里就会释放掉重量的sock结构,转而使用inet_timewait_sock结构,因此TIME_WAIT状态占用的内存会少很多
goto out;
}
}
}
if (sk->sk_state != TCP_CLOSE) {
if (tcp_check_oom(sk, 0)) { // 看是否是OOM了,如果是说明网络还是没问题的,那么发出RST报文
tcp_set_state(sk, TCP_CLOSE); // 关闭
tcp_send_active_reset(sk, GFP_ATOMIC); // RST报文
__NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPABORTONMEMORY);
} else if (!check_net(sock_net(sk))) { // 网络有问题,直接关闭吧
/* Not possible to send reset; just close */
tcp_set_state(sk, TCP_CLOSE); // 关闭
}
}
if (sk->sk_state == TCP_CLOSE) { // 关闭成功,一些收尾工作
struct request_sock *req;
req = rcu_dereference_protected(tcp_sk(sk)->fastopen_rsk,
lockdep_sock_is_held(sk)); // 如果有request_sock那么要回收
/* We could get here with a non-NULL req if the socket is
* aborted (e.g., closed with unread data) before 3WHS
* finishes.
*/
if (req)
reqsk_fastopen_remove(sk, req, false); // 回收req
inet_csk_destroy_sock(sk); // 销毁sock结构体
}
/* Otherwise, socket is reprieved until protocol close. */
out:
bh_unlock_sock(sk);
local_bh_enable();
}
tcp_shutdown
void tcp_shutdown(struct sock *sk, int how)
{
/* We need to grab some memory, and put together a FIN,
* and then put it into the queue to be sent.
* Tim MacKenzie(tym@dibbler.cs.monash.edu.au) 4 Dec '92.
*/
if (!(how & SEND_SHUTDOWN)) // 如果不是关闭SEND_SHUTDOWN或者SHUTDOWN_MASK,即没有关闭发送端;这里可以看到RCV_SHUTDOWN不会改变tcp状态。经过测试,当主动调用shutdown(fd, RCV_SHUTDOWN)后,如果此时没有数据了,那么会立即返回0,如果是放在IO多路复用里面,那么会一直有可读事件,而每次读取获得都是长度都是0;而如果后续又收到了数据,还是可以成功读取。
return;
/* If we've already sent a FIN, or it's a closed state, skip this. */
if ((1 << sk->sk_state) &
(TCPF_ESTABLISHED | TCPF_SYN_SENT |
TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) {
/* Clear out any half completed packets. FIN if needed. */
if (tcp_close_state(sk)) // 进入下一个状态
tcp_send_fin(sk); // 发送FIN报文
}
}
tcp_send_fin
不管是在 tcp_close 还是 tcp_shutdown 中,要发送FIN
报文,都是通过 tcp_send_fin 函数进行的,具体实现如下:
void tcp_send_fin(struct sock *sk)
{
struct sk_buff *skb, *tskb, *tail = tcp_write_queue_tail(sk); // write_queue存储的是未发送出去的数据包
struct tcp_sock *tp = tcp_sk(sk);
/* Optimization, tack on the FIN if we have one skb in write queue and
* this skb was not yet sent, or we are under memory pressure.
* Note: in the latter case, FIN packet will be sent after a timeout,
* as TCP stack thinks it has already been transmitted.
*/
tskb = tail;
if (!tskb && tcp_under_memory_pressure(sk)) // 如果没有未发送的数据包,但是内存紧张
tskb = skb_rb_last(&sk->tcp_rtx_queue); // rtx_queue存储的是已经发送但是还没有收到回复的数据包,以红黑树根据序列号进行组织
if (tskb) { // 有可以利用的数据包
TCP_SKB_CB(tskb)->tcp_flags |= TCPHDR_FIN; // 给这个包打上FIN标志
TCP_SKB_CB(tskb)->end_seq++;
tp->write_seq++;
if (!tail) { // 证明这里是因为内存紧张把一个已经发送过的数据包打上FIN标签之后再利用,需要修改发送序号
/* This means tskb was already sent.
* Pretend we included the FIN on previous transmit.
* We need to set tp->snd_nxt to the value it would have
* if FIN had been sent. This is because retransmit path
* does not change tp->snd_nxt.
*/
WRITE_ONCE(tp->snd_nxt, tp->snd_nxt + 1);
return;
}
} else {
skb = alloc_skb_fclone(MAX_TCP_HEADER, sk->sk_allocation); // 没有可以再利用的数据包,那么需要重新构建一个
if (unlikely(!skb))
return;
INIT_LIST_HEAD(&skb->tcp_tsorted_anchor);
skb_reserve(skb, MAX_TCP_HEADER);
sk_forced_mem_schedule(sk, skb->truesize);
/* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */
tcp_init_nondata_skb(skb, tp->write_seq,
TCPHDR_ACK | TCPHDR_FIN); // 打上FIN标志
tcp_queue_skb(sk, skb); // 放入到write_queue尾部
}
__tcp_push_pending_frames(sk, tcp_current_mss(sk), TCP_NAGLE_OFF); // 立即发送
}
第一次挥手丢失,会发生什么?
当客户端发出 FIN 报文之后,会进入 FIN_WAIT1 状态,由于没有接收到服务端的 ACK 报文,因此会进行重发。数据包的重发依然是由 tcp_retransmit_timer 负责,其中在 tcp_write_timeout 函数中就有对重发次数的判断:
static int tcp_write_timeout(struct sock *sk)
{
// 忽略无关代码
if (sock_flag(sk, SOCK_DEAD)) { // 当前sock已经处于DEAD状态,说明已经发出了FIN报文
const bool alive = icsk->icsk_rto < TCP_RTO_MAX;
retry_until = tcp_orphan_retries(sk, alive); // 获取处于DEAD状态时的最大重发次数,读取配置tcp_orphan_retries(默认是0,但是里面会处理为8)
do_reset = alive ||
!retransmits_timed_out(sk, retry_until, 0); // 如果连接还活跃但是已经耗尽了最大重发次数,那么在关闭时就可尝试发送一个RST报文,然后重置为CLOSE状态,否则直接关闭
if (tcp_out_of_resources(sk, do_reset))
return 1;
}
}
// ...
}
当 FIN 报文丢失之后,会经过tcp_orphan_retries(默认值为0)
次数的重试之后才会直接关闭 TCP,虽然默认值为 0,但是实际在代码中会被处理为 8 次。
rxsi@VM-20-9-debian:~$ cat /proc/sys/net/ipv4/tcp_orphan_retries
0 // 虽然是0,但是实际代码中值是8
这里要注意, FIN_WAIT_1 状态是没有超时时间的,所以当迟迟没有收到 ACK 回复时,只会依赖于 FIN 包达到最大重传次数而关闭。这与 FIN_WAIT_2 状态是不同的。