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

原创 2014年03月10日 13:15:16
  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()不能。

linux网络编程之shutdown() 与 close()函数详解

1.close()函数 #include int close(int sockfd); //返回成功为0,出错为-1.     close 一个套接字的默认行为是把套接字标记为已关闭,然...
  • lgp88
  • lgp88
  • 2012年01月05日 20:28
  • 23234

Socket之shutdown()用法

通常来说,socket是双向的,即数据是双向通信的。但有些时候,你会想在socket上实现单向的socket,即数据往一个方向传输。 单向的socket便称为半开放Socket。要实现半开放式,需要...
  • moxiaomomo
  • moxiaomomo
  • 2012年08月23日 01:09
  • 26721

socket和shutdown

从函数调用上来分析(msdn):一旦完成了套接字的连接,应当将套接字关闭,并且释放其套接字句柄所占用的所有资源。真正释放一个已经打开的套接字句柄的资源直接调用closesocket即可,但要明白clo...
  • mjshldcsd
  • mjshldcsd
  • 2012年01月06日 13:19
  • 3231

shutdown和close的区别

当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作: close(sockfd);    你也可以调用shutdown()...
  • mafuli007
  • mafuli007
  • 2012年05月07日 23:03
  • 8266

套接字 中 shutdown与close区别

1. int shutdown ( int s ,  int how ) how=0 关闭接收端,unix下会刷新输入队列,丢弃在tcp/ip 栈中的应用程序还未读到的数据,如果还有新数据到来,tc...
  • le119126
  • le119126
  • 2015年06月04日 16:53
  • 716

linux网络编程之socket(十):shutdown 与 close 函数 的区别

假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据...
  • Simba888888
  • Simba888888
  • 2013年06月10日 22:03
  • 16762

tcp的close与shutdown的区别引发的血案

之前写的一个程序需要与flash进行通信,根据flash关于policy-request-file的描述,服务器端甚至不需要读到这个请求,只需要返回一个crossdomain的协议,然后再把连接关闭。...
  • HopingWhite
  • HopingWhite
  • 2011年04月25日 12:20
  • 3548

高性能网络编程4--TCP连接的关闭

TCP连接的关闭有两个方法close和shutdown,这篇文章将尽量精简的说明它们分别做了些什么。为方便阅读,我们可以带着以下5个问题来阅读本文:1、当socket被多进程或者多线程共享时,关闭连接...
  • russell_tao
  • russell_tao
  • 2013年10月26日 12:24
  • 16276

TCP连接中的close和shutdown

今天在看网络的书,自己想写一个
  • yushiyaogg
  • yushiyaogg
  • 2014年07月30日 16:36
  • 1629

socket中的close和shutdown区别

很明显这个两个函数是有差别的。close关闭本进程的socket id,但链接还是开着的。怎么理解?我们知道socket描述符是对内核中socket对象的引用。而close操作的正式socket描述符...
  • u011542994
  • u011542994
  • 2015年04月05日 22:15
  • 1901
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:浅谈shutdown()和close()的区别
举报原因:
原因补充:

(最多只允许输入30个字)