Linux 内核网络之 Shutdown 的实现

shutdown 系统调用关闭连接的读通道、写通道或者读写通道。
首页 新闻资讯 行业资讯 Linux 内核网络之 Shutdown 的实现

shutdown 系统调用关闭连接的读通道、写通道或者读写通道。

018982694707c75fbca986479218b79296e7c1.jpg

对于读通道,shutdown 丢弃所有进程还没有读取的数据以及调用 shutdown 之后到达的数据。

对于写通道,shutdown 使用协议作相应的处理。对于 TCP,所有剩余的数据将被发送,返送完后发送 fin 报文。

以上就是 TCP 的半关闭特点。


复制

asmlinkage long sys_shutdown(int fd, int how)
{int err, fput_needed;struct socket *sock;sock = sockfd_lookup_light(fd, &err, &fput_needed);if (sock != NULL) {err = security_socket_shutdown(sock, how);if (!err)err = sock->ops->shutdown(sock, how); // inet_shutdownfput_light(sock->file, fput_needed);
}return err;
}int inet_shutdown(struct socket *sock, int how)
{struct sock sk = sock->sk;int err = 0;/ This should really check to make sure• the socket is a TCP socket. (WHY AC...)///how 增1 是为了利用how变量进行为操作how++; / maps 0->1 has the advantage of making bit 1 rcvs and1->2 bit 2 snds.2->3 /if ((how & ~SHUTDOWN_MASK) || !how) / MAXINT->0 /return -EINVAL;lock_sock(sk);//根据传输控制块的状态重新设置套接口状态,使套接口状态在完成关闭前只有2种if (sock->state == SS_CONNECTING) {if ((1 << sk->sk_state) &(TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_CLOSE))sock->state = SS_DISCONNECTING;elsesock->state = SS_CONNECTED;
}//若传输控制块处于其他状态,则设置shutdown关闭方式后,调用传输层接口shutdown,进行具体传输层关闭switch (sk->sk_state) {case TCP_CLOSE:err = -ENOTCONN;/ Hack to wake up other listeners, who can poll forPOLLHUP, even on eg. unconnected UDP sockets -- RR /default:sk->sk_shutdown |= how; // 把关闭信息设置到sk_shutdown 中if (sk->sk_prot->shutdown)sk->sk_prot->shutdown(sk, how); // tcp_shutdownbreak;/ Remaining two branches are temporary solution for missing• close() in multithreaded environment. It is not a good idea,• but we have no choice until close() is repaired at VFS level.///若处于TCP_LISTEN,则需要判断关闭方式,若有接收方向的关闭操作,则和TCP_SYN_SENT处理一样case TCP_LISTEN:if (!(how & RCV_SHUTDOWN))break;/ Fall through ///若处于连接状态过程中(LISTEN或者SYN_SENT状态),则不允许在继续连接,调用disconnect断开连接操作case TCP_SYN_SENT:err = sk->sk_prot->disconnect(sk, O_NONBLOCK); // tcp_disconnectsock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;break;
}/ Wake up anyone sleeping in poll. */// 调用sk_state_change,唤醒在传输控制块的等待队列上的进程。sk_state_change 在 sock_init_data 中被初始化sk->sk_state_change(sk);release_sock(sk);return err;
}
  • 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.

把关闭方式 设置到套接口 sk_shutdown 中。

若传输控制块处于其他状态,调用 tcp_shutdown 。

复制

若处于正在连接过程中,则调用 tcp_disconnect 断开连接。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.// 不含有SEND_SHUTDOWN,返回,接收方关闭,不发fin */if (!(how & SEND_SHUTDOWN))return;/* If we've already sent a FIN, or it's a closed state, skip this. // 以下这几个状态发fin /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)) / 设置新状态,发送fin */tcp_send_fin(sk);
}
}对于关闭读通道,不需要发送 FIN 报文,直接返回。
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

对于关闭写通道,把未发送出去的数据发送出去之后,发送 FIN 报文。

复制

int tcp_disconnect(struct sock *sk, int flags)
{struct inet_sock *inet = inet_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);struct tcp_sock *tp = tcp_sk(sk);int err = 0;int old_state = sk->sk_state;/* 不是close状态则设置为close,从hash中删除控制块 */if (old_state != TCP_CLOSE)tcp_set_state(sk, TCP_CLOSE);/* ABORT function of RFC793 // LISTEN状态,停止监听 /if (old_state == TCP_LISTEN) {//删除keepalive定时器,对全连接上的每个socket调用tcp_disconnectinet_csk_listen_stop(sk);/ 根据状态确定是否需要发送rst|| 下一个发送序号并不是最后一个队列数据段序号&& 是被动关闭的结束状态 /} else if (tcp_need_reset(old_state) ||(tp->snd_nxt != tp->write_seq &&(1 << old_state) & (TCPF_CLOSING | TCPF_LAST_ACK))) {/ The last check adjusts for discrepancy of Linux wrt. RFC• states// 发送rst */tcp_send_active_reset(sk, gfp_any());sk->sk_err = ECONNRESET;
} else if (old_state == TCP_SYN_SENT)sk->sk_err = ECONNRESET;/* 清除定时器 /tcp_clear_xmit_timers(sk);/ 释放接收队列中的skb /__skb_queue_purge(&sk->sk_receive_queue);/ 释放发送队列中的skb */sk_stream_writequeue_purge(sk);/*释放未按顺序达到的skb */__skb_queue_purge(&tp->out_of_order_queue);#ifdef CONFIG_NET_DMA__skb_queue_purge(&sk->sk_async_wait_queue);#endif/* 其他各种清理工作 */inet->dport = 0;...return err;
}
  • 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.

对于监听,全连接上的每个 socket 调用 tcp_disconnect,最终会对每个socket 发送 RST 报文。也即是若 shutdown 的是半打开的连接,则发出 RST 来关闭连接。

若 shutdown 的是正常连接,那么关闭读其实与对端是没有关系的。只要本机把接收掉的消息丢掉,其实就等价于关闭读了,并不一定非要对端关闭写的。实际上,shutdown 正是这么干的。若参数中的标志位含有关闭读,只是标识下,当我们调用 read 等方法时这个标识就起作用了,会使进程读不到任何数据。

若参数中有标志位为关闭写,则发出 FIN 包,告诉对方,本机不会再发消息了。

当关闭写通道后,再次调用 send 发送数据时,直接返回失败。

复制

int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,size_t size)
{...err = -EPIPE;if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))goto do_error;...}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

同理,关闭读通道后,再次调用 tcp_recvmsg 时不会读取数据,直接返回。

12    2023-03-01 23:53:30    Linux shutdown 进程