# Linux Socket Close 原理

我们将会在使用完 socket fd 后,将其关闭,此时将会调用 close 函数完成该动作,close 进而调用 sys_close 系统调用完成关闭。本文将详细描述 close 关闭原理。

int serverFD = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr; // 端口绑定信息
addr.sin_family = AF_INET; // 指定协议簇
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8080); // 将端口信息转为网络字节序(大端序)
bind(serverFD, (sockaddr*)&addr, sizeof(addr)); // 开始绑定
listen(serverFD,100); // 绑定后开启监听,同时指定接收队列长度为 100
struct sockaddr_in clientSocket; // 客户端信息结构
int sin_size = sizeof(struct sockaddr_in); // 指定 客户端信息结构大小
int clientFD = accept(serverFD, (struct sockaddr*)&clientSocket,(socklen_t*)&sin_size)) // 从 backlog 队列(accept队列)中获取客户端连接
close(clientFD); // 关闭客户端连接
close(serverFD); // 关闭服务端连接
1
2
3
4
5
6
7
8
9
10
11
12

sys_close 函数

该函数用于关闭 fd 所表示的 file 文件,我们知道对于 socket 而言,将会调用 socket 中设置的回调函数完成实际关闭。可以看到首先将 fd 下标置为未使用状态,随后调用 filp_close 回调 关闭函数。而在 filp_close 函数中,若注册了 flush 刷新函数,那么回调,否则 调用 fput 函数,在该函数中回调 release 方法,随后将分配的 file 结构相关内存回收。源码描述如下。

asmlinkage long sys_close(unsigned int fd)
{
    struct file * filp;
    struct files_struct *files = current->files;
    // 上自旋锁,将 fd 下标设置为 未使用状态
    spin_lock(&files->file_lock);
    if (fd >= files->max_fds)
        goto out_unlock;
    filp = files->fd[fd];
    if (!filp)
        goto out_unlock;
    files->fd[fd] = NULL;
    FD_CLR(fd, files->close_on_exec);
    __put_unused_fd(files, fd);
    spin_unlock(&files->file_lock);
    return filp_close(filp, files); // 完成关闭
out_unlock:
    spin_unlock(&files->file_lock);
    return -EBADF;
}// 完成实际关闭操作
int filp_close(struct file *filp, fl_owner_t id)
{
    int retval;
    retval = filp->f_error;
    if (retval)
        filp->f_error = 0;
    if (!file_count(filp)) { // 文件必须之前已经被打开
        printk(KERN_ERR "VFS: Close: file count is 0\n");
        return retval;
    }
    // 首先执行刷新操作,根据前面的内容,我们看到对于 socket 的file 来说 不存在 flush 操作
    if (filp->f_op && filp->f_op->flush) {
        int err = filp->f_op->flush(filp);
        if (!retval)
            retval = err;
    }
    ...
    fput(filp); // 开始释放file
    return retval;
}void fput(struct file *file){
    if (atomic_dec_and_test(&file->f_count)) // 引用计数为0,那么可以释放 file
        __fput(file);
}// 可以看到回调了 release 方法
void __fput(struct file *file)
{
    ...
    if (file->f_op && file->f_op->release)
        file->f_op->release(inode, file);
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

sock_close 函数

我们在前面描述 socket 创建原理时,看到将会把 sock_close 函数注册为 file_opreation 的 release 回调函数。在该函数中将会调用 sock_release 函数完成进一步关闭操作,在 sock_release 函数中,回调 socket 的release函数,该函数我们也看到将会调用 inet_release 函数完成关闭。源码如下。

int sock_close(struct inode *inode, struct file *filp)
{
    if (!inode)
    {
        printk(KERN_DEBUG "sock_close: NULL inode\n");
        return 0;
    }
    sock_fasync(-1, filp, 0);
    sock_release(SOCKET_I(inode));
    return 0;
}
​
​
void sock_release(struct socket *sock)
{
    if (sock->ops) { // 调用 socket 的 release 操作完成 inet 的释放
        struct module *owner = sock->ops->owner;
        sock->ops->release(sock);
        sock->ops = NULL;
        module_put(owner);
    }
    ...
    sock->file = NULL; // 解绑 VFS 与 socket 的关联
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

inet_release 函数

该函数将会进一步调用 tcp_close 函数完成 TCP 的关闭,在其中涉及到 SOCK_LINGER 选项,该选项用于设置 socket 在关闭时对于未发送到对端(接收到对端 ack 时)时的处理方式(如何处理?我们直接跟进 tcp 的关闭代码),可以看到如果设置了linger 参数且 正常调用 close 关闭,那么将会把 timeout 参数 设置为 用户设置的 sk_lingertime 。源码如下。

int inet_release(struct socket *sock)
{
    struct sock *sk = sock->sk;
    if (sk) {
        long timeout;
        ...
        timeout = 0;
        if (sock_flag(sk, SOCK_LINGER) && // socket 设置了 linger 参数
            !(current->flags & PF_EXITING)) // socket 关闭不是由于进程关闭导致的,而是直接调用 close 函数,那么将关闭 超时时间 设置为 sk_lingertime 时间
            timeout = sk->sk_lingertime;
        sock->sk = NULL;
        sk->sk_prot->close(sk, timeout); // 调用 tcp_close 关闭socket
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

tcp_close 函数

该函数实现了 TCP 四次挥手的过程,这里有一个特殊情况,当前 socket 若接收缓冲区存在数据,那么当关闭该socket时,将会直接发送RST报文通知对端,当前发生了异常退出。同时,若我们设置了 SOCK_LINGER 标志位,同时指定了TIME_OUT为0,那么这时不管发送缓冲区是否存在数据,直接关闭并发送RST,所以我们可以利用该特性来迅速关闭当前socket,但是将会给对端一个 RST 报文的异常信息,但当前socket不会进入TIME_WAIT阶段,因此可以快速回收当前socket。若一切正常,那么将会进入标准的 TCP 四次挥手阶段: FIN_WAIT_1(当前 socket 发送 FIN 报文)、FIN_WAIT_2(当前 socket 接收到对端的 FIN 报文)、TIME_WAIT(当前 socket 发送对端 FIN 的 ack)、CLOSE(当前 socket 过了 TIME_WAIT 阶段)。源码描述如下。

void tcp_close(struct sock *sk, long timeout)
{
    struct sk_buff *skb;
    int data_was_unread = 0;
    lock_sock(sk);
    sk->sk_shutdown = SHUTDOWN_MASK;
    if (sk->sk_state == TCP_LISTEN) { // 当前socket 为 server socket 且处于 监听连接状态,此时较为见到那,只需要修改状态为 TCP_CLOSE,同时释放对应内存即可
        tcp_set_state(sk, TCP_CLOSE);
        tcp_listen_stop(sk); // 清理 syn 半同步队列 与 完成 三次握手 的接收队列
        goto adjudge_to_death;
    }
    
    // 清空接收缓冲区,因为 socket 已经关闭,接收缓冲区中的数据已不在重要
    while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {
        u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq -
              skb->h.th->fin;
        data_was_unread += len; // 增加未读取数据计数
        __kfree_skb(skb);
    }
    tcp_mem_reclaim(sk); // 尝试回收 TCP 占用内存
    if (data_was_unread) { // 存在未读取数据,那么发送 RST 给对端(告知出现异常关闭)
        NET_INC_STATS_USER(TCPAbortOnClose);
        tcp_set_state(sk, TCP_CLOSE);
        tcp_send_active_reset(sk, GFP_KERNEL);
    } else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) { // 设置了SOCK_LINGER标志位,同时指定了 sk_lingertime 为0,那么这时调用 disconnect(调用注册的 tcp_disconnect 函数) ,该回调函数将直接丢掉缓冲区中的数据并发送 RST 报文结束
        sk->sk_prot->disconnect(sk, 0);
        NET_INC_STATS_USER(TCPAbortOnData);
    } else if (tcp_close_state(sk)) { // 否则发送 FIN 报文,进入 FIN_WAIT_1 阶段
        tcp_send_fin(sk);
    }
    if (timeout) { // 设置了超时时间,那么超时时间后,有以下两个条件唤醒:1、时间超时 2、发送的数据全部被对端接收
        struct task_struct *tsk = current;
        DEFINE_WAIT(wait);do {
            prepare_to_wait(sk->sk_sleep, &wait,
                    TASK_INTERRUPTIBLE);
            if (!closing(sk))
                break;
            release_sock(sk);
            timeout = schedule_timeout(timeout);
            lock_sock(sk);
        } while (!signal_pending(tsk) && timeout);
        finish_wait(sk->sk_sleep, &wait);
    }
    
adjudge_to_death: // 释放 当前 socket 的资源
    release_sock(sk);
    local_bh_disable();
    bh_lock_sock(sk);
    BUG_TRAP(!sock_owned_by_user(sk));
    sock_hold(sk);
    sock_orphan(sk);
    if (sk->sk_state == TCP_FIN_WAIT2) { // 若此时到达 FIN_WAIT_2 那么表明 接收到了对端发送的 FIN 报文(请参考TCP四次挥手原理)
        struct tcp_opt *tp = tcp_sk(sk);
        if (tp->linger2 < 0) { // 若设置 linger2 小于0,那么发送 RST
            tcp_set_state(sk, TCP_CLOSE);
            tcp_send_active_reset(sk, GFP_ATOMIC);
            NET_INC_STATS_BH(TCPAbortOnLinger);
        } else { // 否则根据设置的TIME_WAIT 的时间 tmo,进入 TIME_WAIT 阶段
            int tmo = tcp_fin_time(tp);
            if (tmo > TCP_TIMEWAIT_LEN) { // 默认 60 S ,60 S 后结束 TIME_WAIT 等待
                tcp_reset_keepalive_timer(sk, tcp_fin_time(tp));
            } else { // 小于 最大 60 s,那么以设置值为准
                atomic_inc(&tcp_orphan_count);
                tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
                goto out;
            }
        }
    }
    if (sk->sk_state != TCP_CLOSE) { // 若此时TCP状态仍未关闭,那么表示 TCP 占用内存过多,系统无法继续支撑连接的继续运行,那么此时关闭该socket并输出错误信息
        tcp_mem_reclaim(sk);
        if (atomic_read(&tcp_orphan_count) > sysctl_tcp_max_orphans ||
            (sk->sk_wmem_queued > SOCK_MIN_SNDBUF &&
             atomic_read(&tcp_memory_allocated) > sysctl_tcp_mem[2])) {
            if (net_ratelimit())
                printk(KERN_INFO "TCP: too many of orphaned "
                       "sockets\n");
            tcp_set_state(sk, TCP_CLOSE);
            tcp_send_active_reset(sk, GFP_ATOMIC);
            NET_INC_STATS_BH(TCPAbortOnMemory);
        }
    }
    atomic_inc(&tcp_orphan_count);
    if (sk->sk_state == TCP_CLOSE) // 清理socket,释放占用内存
        tcp_destroy_sock(sk);
out:
    bh_unlock_sock(sk);
    local_bh_enable();
    sock_put(sk);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91