之前的文章已经分析了tcp的建立过程以及tcp读和写,下面我们继续看下shutdown方法。
代码语言:javascript复制// net/socket.c
SYSCALL_DEFINE2(shutdown, int, fd, int, how)
{
int err, fput_needed;
struct socket *sock;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock != NULL) {
...
if (!err)
err = sock->ops->shutdown(sock, how);
...
}
return err;
}
该方法先根据fd找到对应的socket,再调用sock->ops->shutdown指向的方法继续执行shutdown逻辑。
由第一篇文章我们可以知道,sock->ops->shutdown指向的方法是inet_shutdown。
代码语言:javascript复制// net/ipv4/af_inet.c
int inet_shutdown(struct socket *sock, int how)
{
struct sock *sk = sock->sk;
...
switch (sk->sk_state) {
...
default:
sk->sk_shutdown |= how;
if (sk->sk_prot->shutdown)
sk->sk_prot->shutdown(sk, how);
break;
...
}
/* Wake up anyone sleeping in poll. */
sk->sk_state_change(sk);
...
return err;
}
EXPORT_SYMBOL(inet_shutdown);
方法描述
1. 将shutdown类型how标记到sk->sk_shutdown字段。
2. 调用sk->sk_prot->shutdown指向的方法继续执行shutdown逻辑。
3. 调用sk->sk_state_change方法,唤醒那些在监听sock状态变化的阻塞线程。
由第一篇文章可以知道,sk->sk_prot->shutdown指向的方法为tcp_shutdown。
代码语言:javascript复制// net/ipv4/tcp.c
void tcp_shutdown(struct sock *sk, int how)
{
...
if (!(how & SEND_SHUTDOWN))
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);
}
}
EXPORT_SYMBOL(tcp_shutdown);
方法描述
1. 如果shutdown类型how中,不包括SEND_SHUTDOWN,即只包括RCV_SHUTDOWN,则不用再执行其他逻辑,直接返回就好。
当我们在调用shutdown方法时,如果只指定RCV_SHUTDOWN,最终结果只是标记sk->sk_shutdown字段,使其值包含RCV_SHUTDOWN,并不会再执行其他tcp逻辑。
2. 检查sk->sk_state字段状态,判断是否已经发送了fin消息,或者是否已经是closed状态,如果是,也不用再继续了。
3. 调用tcp_close_state方法,根据当前sk->sk_state的状态,设置其下一状态,比如,如果当前状态为TCP_ESTABLISHED,则下一状态应该为TCP_FIN_WAIT1,之后该方法返回是否需要发送fin消息。
4. 如果需要,则发送fin消息给对方。
完。