shutdown 与 close 的区别

最近弄一个ssl的客户端工具,客户端每条报文总是以RST结束断开连接,不是想象中的四路断开,百思不得其解,最后添加shutdown得以解决。

转了三篇文章,需要一一看下来,才会对这两个函数有个彻底的认识,特别是第三篇,得拿出源码分析才是王道!当然,结合实际验证也很重要。

转一:http://blog.csdn.net/jnu_simba/article/details/9068059

假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据,此时client调用read,如果接收到FIN 段会返回0,但client此时还是可以write 给server的,write调用只负责把数据交给TCP发送缓冲区就可以成功返回了,所以不会出错,而server收到数据后应答一个RST段,表示服务器已经不能接收数据,连接重置,client收到RST段后无法立刻通知应用层,只把这个状态保存在TCP协议层。如果client再次调用write发数据给server,由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。

有时候代码中需要连续多次调用write,可能还来不及调用read得知对方已关闭了连接就被SIGPIPE信号终止掉了,这就需要在初始化时调用sigaction处理SIGPIPE信号,对于这个信号的处理我们通常忽略即可,signal(SIGPIPE, SIG_IGN); 如果SIGPIPE信号没有导致进程异常退出(捕捉信号/忽略信号),write返回-1并且errno为EPIPE(Broken pipe)。

 #include <unistd.h>
 int close(int fd);

close 关闭了自身数据传输的两个方向。

 #include <sys/socket.h>
 int shutdown(int sockfd, int how);

shutdown 可以选择关闭某个方向或者同时关闭两个方向,shutdown how = 0 or how = 1 or how = 2 (SHUT_RD or SHUT_WR or SHUT_RDWR),后两者可以保证对等方接收到一个EOF字符(即发送了一个FIN段),而不管其他进程是否已经打开了这个套接字。而close不能保证,只有当某个sockfd的引用计数为0,close 才会发送FIN段,否则只是将引用计数减1而已。也就是说只有当所有进程(可能fork多个子进程都打开了这个套接字)都关闭了这个套接字,close 才会发送FIN 段。

所以说,如果是调用shutdown how = 1 ,则意味着往一个已经发送出FIN的套接字中写是允许的,接收到FIN段仅代表对方不再发送数据,但对方还是可以读取数据的,可以让对方可以继续读取缓冲区剩余的数据。


转载二:http://blog.chinaunix.net/uid-24532607-id-3014406.html

1,只要TCP栈的读缓冲里还有未读取(read)数据,则调用close时会直接向对端发送RST。 

  2,shutdown与socket描述符没有关系,即使调用shutdown(fd, SHUT_RDWR)也不会关闭fd,最终还需close(fd)。 

  3,可以认为shutdown(fd, SHUT_RD)是空操作,因为shutdown后还可以继续从该socket读取数据,这点也许还需要进一步证实。 

  4,在已发送FIN包后write该socket描述符会引发EPIPE/SIGPIPE。 

  5,当有多个socket描述符指向同一socket对象时,调用close时首先会递减该对象的引用计数,计数为0时才会发送FIN包结束TCP连接。shutdown不同,只要以SHUT_WR/SHUT_RDWR方式调用即发送FIN包。 

  6,SO_LINGER与close,当SO_LINGER选项开启但超时值为0时,调用close直接发送RST(这样可以避免进入TIME_WAIT状态,但破坏了TCP协议的正常工作方式),SO_LINGER对shutdown无影响。 

  7,TCP连接上出现RST与随后可能的TIME_WAIT状态没有直接关系,主动发FIN包方必然会进入TIME_WAIT状态,除非不发送FIN而直接以发送RST结束连接。

转载三:http://dev.riaos.com/?p=9006329

浅谈shutdown()和close()的区别

 

  shutdown()函数可以选择关闭全双工连接的读通道或者写通道,如果两个通道同时关闭,则这个连接不能再继续通信。close()函数会同时关闭全双工连接的读写通道,除了关闭连接外,还会释放套接字占用的文件描述符。而shutdown()只会关闭连接,但是不会释放占用的文件描述符。所以即使使用了SHUT_RDWR类型调用shutdown()关闭连接,也仍然要调用close()来释放连接占用的文件描述符。
1. close()
     close()函数对应的系统调用是sys_close(),在fs/open.c中定义。在sys_close()中,会首先根据文件描述符在进程的打开文件表中查找对应的file结构实例,然后调用filp_close()来关闭文件。关闭操作是在fput()(由filp_close()调用)中进行的,引用数减1后为零,才会调用__fput()来释放文件占用的内存。对套接字来说,__fput()中我们主要关心以下代码:

void  __fput( struct  file
*file)
{
    ……
      if  (file - >f_op
&&  file - >f_op - >release)
        file - >f_op - >release(inode, file);
    …..
     dput(dentry);
     ……
}
  file->f_op指向的是文件操作实例,套接字的文件操作由socket_file_ops提供。socket_file_ops属于socket层,socket层是vfs和底层协议栈连接的桥梁,真正的操作还是由协议栈来提供。在这里,file->f_op->release指向sock_close()函数。在socket层下面,接着是协议族,在这个层,不同的传输层协议都会提供自己的操作接口。在协议族层,TCP和UDP协议提供的接口都是inet_release(),这个函数最终会调用到不同的传输层协议提供的close接口。TCP协议提供的是tcp_close()函数,UDP协议提供的是udp_lib_close()。
  tcp_close()中会首先将套接字的sk_shutdown标志设置为SHUTDOWN_MASK,表示双向关闭。然后检查接收缓冲区是否有数据未读(不包括FIN包),如果有数据未读,协议栈会发送RST包,而不是FIN包。如果套接字设置了SO_LINGER选项,并且lingertime设置为0,这种情况下也会发送RST包来终止连接。其他情况下,会检查套接字的状态,只有在套接字的状态是TCP_ESTABLISHED、TCP_SYN_RECV和TCP_CLOSE_WAIT的状态下,才会发送FIN包。在决定了是否发包以及发送什么类型的包之后,协议栈会进行套接字占用的资源的清理,包括sock结构、缓冲区和错误队列占用的内存等,并进行状态的变更。如果是发送FIN包进行正常关闭,后续会进行四次关闭操作,这个过程是在协议栈中完成的,和用户进程没有关系,用户进程也不能再操作这个套接字。
  udp_lib_close()中只是简单地调用了sk_common_release()函数,sk_common_release()中会调用udp_destroy_sock()来释放发送队列中占用的内存。如果UDP套接字已绑定本地端口,会添加到udp_table哈希表中,所以套接字如果已经被添加到哈希表中,udp_lib_unhash()中会将套接字从哈希表中移除。接下来会调用sock_orphan()解除进程和套接字的关系,然后释放sock结构占用的资源。
  socket结构实例占用的内存,是在dput()调用到的sock_destroy_inode()函数来释放的, sock_destroy_inode()中只是简单地调用kmem_cache_free()释放占用的内存。
2. shutdown()
  shutdown()函数对应的系统调用是sys_shutdown(),在net/socket.c中定义。由于close()不仅可以用于关闭套接字,也可以关闭普通文件、字符设备文件等类型,为了处理不同类型文件的关闭,操作比较复杂。而shutdown()只能用于套接字类型的文件,处理也比较简单。
  sys_shutdown()中首先调用sockfd_lookup_light()来查找描述符对应的socket结构,然后调用套接字对应的协议族层中提供的shutdown接口。UDP和TCP协议提供的接口都是inet_shutdown()函数,主要处理如下所示:

int  inet_shutdown( struct  socket
*sock,   int  how)
{
    ……
      switch  (sk - >sk_state) {
      case  TCP_CLOSE :
        err   =   -ENOTCONN;
          /* Hack to wake up other listeners, who can poll for
           POLLHUP, even on eg. unconnected UDP sockets — RR */

      default :
        sk - >sk_shutdown
|=  how;
          if  (sk - >sk_prot - >shutdown)
            sk - >sk_prot - >shutdown(sk, how);
          break;

    /* 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.
     */

    case TCP_LISTEN:
        if (!(how
& RCV_SHUTDOWN))
            break;
        /* Fall through */
    case TCP_SYN_SENT:
        err = sk->sk_prot->disconnect(sk, O_NONBLOCK);
        sock->state
= err ? SS_DISCONNECTING
: SS_UNCONNECTED;
        break;
    }

    /* Wake up anyone sleeping in poll. */
    sk->sk_state_change(sk);
    ……
}

  在说明代码的处理之前,先来了解一下UDP套接字的状态。UDP的传输是没有状态的,内核中在描述UDP套接字的状态时,借用了TCP的状态。UDP套接字只有两种状态,TCP_CLOSE和TCP_ESTABLISHED。在套接字刚创建时,不管是UDP还是TCP,状态都是TCP_CLOSE。UDP在调用connect()后,状态改变为TCP_ESTABLISHED。
  如果套接字的状态TCP_CLOSE,套接字要么是刚创建的,要么连接已经关闭,所以调用shutdown()是不合适的,此时要返回ENOTCONN错误。

   接下来的代码会处理TCP_LISTEN和TCP_SYN_SENT状态以外的情况。将用户设置的关闭选项设置到套接字的sk_shutdown标志,然后调用传输层协议提供的shutdown接口。TCP协议提供的是tcp_shutdown()函数,而UDP并没有提供任何函数。
  在tcp_shutdown()中,首先检查是否是否关闭了写通道,如果不是,则直接返回。如果关闭了写通道,并且状态是TCP_ESTABLISHED、TCP_SYN_SENT、TCP_SYN_RECV或TCP_CLOSE_WAIT,会调用tcp_close_state()来进行状态的变更。如果变更状态后需要发送FIN包,则调用tcp_send_fin()来发送。
  由于UDP没有TCP_LISTEN和TCP_SYN_SENT状态,所以 sk - > sk_prot - > disconnect只会调用调用tcp_disconnect()函数。如果是套接字状态是TCP_LISTEN状态,并且是关闭读通道,内核会停止套接字的监听状态,释放sock结构占用的资源。如果是TCP_SYN_SENT状态,会发送RST包来终止连接的创建过程,释放sock结构占用的资源。
  最后会调用套接字的sk_state_change接口(通常是 sock_def_wakeup()),通知用户进程状态已经发生改变。
3. 总结
  现在总结一下shutdown()和close()的主要区别:
     1)对应的系统调用不同
     2)shutdown()只能用于套接字文件,close()可以用于所有文件类型
     3)shutdown()只是关闭连接,并没有释放文件描述符,close()可以
     4)shutdown()不能用于TCP_CLOSE状态的套接字,否则会返回 ENOTCONN 错误
     5)shutdown()可以选择关闭读通道或写通道,close()不能。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值