浅谈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()不能 

### 回答1: 在Socket编程中,shutdown()和close()都是用来关闭一个套接字(Socket)的方法,但二者有不同的作用。 shutdown()方法是用来关闭一个已连接的套接字,它可以在客户端和服务器端分别调用。shutdown()的调用会使得套接字不能再进行数据的发送和接收,但它可以继续进行一些管理操作,例如可以发送一些控制信息给对方,告知对方套接字已关闭。 而close()方法是用来关闭一个套接字的连接,它可以在客户端和服务器端分别调用。调用close()方法后,套接字不再使用,也不会再接收或发送数据。当客户端调用close()时,它会向服务器发送一个关闭连接的请求,服务器收到请求后也会调用close()方法关闭套接字。 ### 回答2: 在Socket编程中,shutdownclose都是用于关闭连接的方法,但它们有一些区别。 首先,shutdown是用于半关闭连接的操作,它可以关闭Socket的输入和输出流的其中一方。shutdown方法可以接受一个参数,该参数可以是常量Socket.SHUT_RD,用于关闭输入流;也可以是常量Socket.SHUT_WR,用于关闭输出流;还可以是常量Socket.SHUT_RDWR,用于关闭输入和输出流。通过这种方式,可以控制关闭连接的一方。 相比之下,close是用于完全关闭连接的操作,它会关闭Socket的输入和输出流,并释放相关的资源。调用close方法后,再次尝试使用该Socket将会抛出异常。 其次,shutdown可以在一个Socket处于连接状态时多次调用,并且可以对输入和输出流进行独立的关闭,而close方法只能在一个Socket连接被完全关闭后调用一次。 最后,通过调用shutdown方法关闭连接,可以保留Socket的状态信息和相关资源,并继续使用该Socket进行数据传输。而调用close方法关闭连接则会释放Socket相关的资源,并且无法再次使用该Socket。 综上所述,shutdownclose方法在Socket编程中都可以用于关闭连接,但shutdown方法可以控制关闭连接的一方,可以多次调用并保留Socket状态,而close方法只能完全关闭连接并释放相关资源。 ### 回答3: 在Socket编程中,shutdown()和close()是两个不同的方法,用于关闭连接和释放Socket资源。 shutdown()方法用于关闭已连接的Socket连接,它需要传入一个参数,来指定关闭的方式。参数可以是SHUT_RD、SHUT_WR或SHUT_RDWR。SHUT_RD表示禁止读取数据,SHUT_WR表示禁止写入数据,SHUT_RDWR表示同时禁止读写数据。通过调用shutdown()方法,我们可以选择关闭连接的读端、写端或者同时关闭两者,这样就可以保持双方的连接而不再传输数据。 相比之下,close()方法是用于完全关闭Socket连接,它不需要传入任何参数。调用close()方法会立即关闭Socket连接,并释放所有与该Socket相关的资源。关闭连接后,将无法再进行读取或写入操作,也无法再建立连接。 总结来说,shutdown()方法是暂时关闭Socket连接的一种手段,可以选择性地关闭读端、写端或者两者。而close()方法是永久关闭Socket连接,并释放相关资源的一种方式。关闭连接后,将无法再进行数据传输或者建立新的连接。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值