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()
// Close a waiting recv()
Time
|
| thread-1 | thread-2 | tcpdump
| | |
| recv(sock_fd | |
| | |
1| | close(sock_fd) = 0 |
| | | // Some data arrived
| | | // after close()
2| | | < seq 1:36 ... length 35
| | | > ack 36 ...
| // Data was received. | |
3| <... recv resumed>) = 35 | |
4| | | > FIN sent
| | | < ack of FIN received
| | | ...
| // Can't be used any more | |
5v recv(sock_fd)= -1 | |
在上面的例子里:
(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()
// Shutdown a waiting recv()
Time
|
| thread-1 | thread-2 | tcpdump
| | |
| recv(sock_fd | |
| | |
1| | shutdown(sock_fd) = 0 | > FIN sent
| | | < ack of FIN received
| | | ...
| // Woken up by shutdown() | |
| // no errno set | |
2| <... recv resumed>) = 0 | |
v | |
在上面的例子里:
(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也会被唤醒:
// Shutdown a waiting accept()
Time
|
| thread-1 | thread-2