VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 网络工程 > 网络工程师 >
  • 如何优雅地断开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.  
    // Close a waiting recv()
  2.  
    Time
  3.  
    |
  4.  
    | thread-1 | thread-2 | tcpdump
  5.  
    | | |
  6.  
    | recv(sock_fd | |
  7.  
    | <unfinished ...> | |
  8.  
    1| | close(sock_fd) = 0 |
  9.  
    | | | // Some data arrived
  10.  
    | | | // after close()
  11.  
    2| | | < seq 1:36 ... length 35
  12.  
    | | | > ack 36 ...
  13.  
    | // Data was received. | |
  14.  
    3| <... recv resumed>) = 35 | |
  15.  
    4| | | > FIN sent
  16.  
    | | | < ack of FIN received
  17.  
    | | | ...
  18.  
    | // Can't be used any more | |
  19.  
    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()


  1.  
    // Shutdown a waiting recv()
  2.  
    Time
  3.  
    |
  4.  
    | thread-1 | thread-2 | tcpdump
  5.  
    | | |
  6.  
    | recv(sock_fd | |
  7.  
    | <unfinished ...> | |
  8.  
    1| | shutdown(sock_fd) = 0 | > FIN sent
  9.  
    | | | < ack of FIN received
  10.  
    | | | ...
  11.  
    | // Woken up by shutdown() | |
  12.  
    | // no errno set | |
  13.  
    2| <... recv resumed>) = 0 | |
  14.  
    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也会被唤醒:


  1.  
    // Shutdown a waiting accept()
  2.  
    Time
  3.  
    |
  4.  
    | thread-1 | thread-2