-
如何优雅地断开TCP连接?
socket关闭: close()和shutdown()的差异
对于一个tcp连接,在c语言里一般有2种方法可以将其关闭:
close(sock_fd);
或者
shutdown(sock_fd, ...);
多数情况下这2个方法的效果没有区别,可以互换使用。除了:
- close() 是针对file的操作
- shutdown() 是针对socket的操作
nix系统里socket是1个文件,但文件不1定是1个socket;
所以在进入系统调用后和达到协议层前(发出FIN包这一段), close()和shutdown()的行为会有1点差异。
到达协议层以后,close()和shutdown()没有区别。
举几个栗子示范下close()和shutdown()的差异
下面通过几个例子演示下close()和shutdown()在多线程并发时的行为差异, 我们假设场景是:
-
sock_fd
是一个blocking mode的socket。 -
thread-1 正在对
sock_fd
进行阻塞的recv(),还没有返回。 -
thread-2 直接对
sock_fd
调用close() 或 shutdown()。 - 不考虑linger。
栗子1: socket阻塞在recv()上, 调用close()
在上面的例子里:
-
(1) thread-2 调用close()立即成功返回,这时recv()还在使用
sock_fd
。这里因为有另外1个线程thread-1正在使用
sock_fd
, 所以只是标记这个sock_fd
为要关闭的。 socket并没有真正关闭。这时recv()还继续处于阻塞读取状态。
-
(2) close()之后,有些数据到了,recv可以读取并返回了。
-
(3) recv()收到数据, 正确退出。
-
(4) rece()结束调用,释放socket的引用,这时底层开始关闭socket的流程。
-
(5) 再次调用recv()就会得到错误。
可以看到,close()没有立即关闭socket的连接,也没有打断等待的recv()。
栗子2: socket阻塞在recv()上, 调用shutdown()
在上面的例子里:
-
(1) thread-1还在等待
sock_fd
, thread-2调用shutdown(), 立即开始关闭socket的流程,发FIN 包等。然后, 内核中
tcp_shutdown
中会调用sock_def_wakeup 唤醒阻塞在recv()上的thread-1。 -
(2) 这时recv()阻塞的线程被唤醒等待并立即返回。 返回码是0,表示socket已经关了。
可以看到,shutdown()和close()不同, 会立即关闭socket的连接,并唤醒等待的recv()。
以上2个例子的代码
close-or-shutdown-recv
栗子3: socket阻塞在accept()上, 调用shutdown()
类似的,对阻塞在accept()上的socket调用shutdown(),accept也会被唤醒: