Socket 编程中 close() 和 shutdown() 有啥区别?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket 编程中 close() 和 shutdown() 有啥区别?相关的知识,希望对你有一定的参考价值。

Socket 编程中 用close() 和 shutdown()关掉socket 有什么实质性的区别?

一、指代不同

1、shutdown() :是指禁止在一个套接口上进行数据的接收与发送。

2、close():关闭一个套接口。更确切地说,它释放套接口描述字s,以后对s的访问均以WSAENOTSOCK错误返回。

二、原理不同

1、shutdown() :how参数为0,则该套接口上的后续接收操作将被禁止。这对于低层协议无影响。对于TCP协议,TCP窗口不改变并接收前来的数据(但不确认)直至窗口满。对于UDP协议,接收并排队前来的数据。任何情况下都不会产生ICMP错误包。



2、close():为对套接口的最后一次访问,则相应的名字信息及数据队列都将被释放。closesocket()的语义受SO_LINGER与SO_DONTLINGER选项影响。


三、特点不同

1、shutdown() :一个应用程序不应依赖于重用一个已被shutdown()禁止的套接口。特别地,一个WINDOWS套接口实现不必支持在这样的套接口上使用connect()调用。

2、close():如无错误发生,则closesocket()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。


参考资料来源:百度百科-shutdown()

参考资料来源:百度百科-closesocket()

参考技术A 下面说说close和shutdown两个函数的差别,两个函数在网络编程中都被人为是来关闭套接字的,差别如下:
1.操作本质不同
close是文件系统VFS的一个通用函数
shutdown是专门针对socket套接字设立的函数
SOCKET在unix中本就是以文件的形式呈现给大家的,在每创建一个socket时,会对应创建一个文件与之对应,返回文件描述符,
具体的结构在此不详述,在调用close时实际是关闭了减少了文件描述符,当文件描述符引用为0时,则会删除文件,关闭套接字。
而shutdown是直接对socket的操作,相当于设置了socket的属性,可以设置为只读,只写,或者直接关闭,但不会操作与此socket相关
的文件
2.两者操作的效果一同
close的操作跟设置选项LINGER有关,此选项是设置当用户调用close后是否等待把当前缓冲区的数据发送完毕后再关闭套接字
LINGER结构有两个值,一个是on,一个是time
当on为0时,默认情况,用户发送完分组,然后发送FIN分组
当on为1时,time为0,则用户直接发送RST分组
当on为1时,time为非0,则用户等待time值然后发送FIN分组,如果time到了但用户没有发送完数据,则返回错误EWOULDBLOCK
SHUTDOWN
SHUT_RD,套接字不能再发出接收请求,进程仍然可以往套接字发送数据,套接字接收缓冲区中所有数据被丢弃,再接收到的任何数据由TCP丢弃,对套接字发送缓冲区没有任何影响
SHUT_WD ,套接字不能再发出发送请求,套接字发送缓冲区的数据将被发送到对端,然后跟着正常的FIN终止序列
参考技术B close是关闭连接并释放资源。 而shutdown只是关闭发送或者接收或者两者,而并不是关闭连接。

具体参考:http://msdn.microsoft.com/zh-cn/library/windows/desktop/ms740481.aspx
参考技术C 当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:close(sockfd);
你也可以调用shutdown()函数来关闭该socket。但是该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关 闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。
int shutdown(int sockfd,int how);
Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:
SHUT_RD:关闭连接的读端。也就是该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该套接字发出任何读操作。对 TCP套接字该调用之后接受到的任何数据将被确认然后无声的丢弃掉。
SHUT_WR:关闭连接的写端,进程不能在对此套接字发出写操作
SHUT_RDWR:相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR

使用close中止一 个连接,但它只是减少描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭连接。
shutdown可直接关闭描述符,不考虑描述 符的参考数,可选择中止一个方向的连接。

注意:
1>. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
2>. 在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信. 如果一个进程close(sfd)将不会影响到其它进程. 得自己理解引用计数的用法了
参考技术D 从函数调用上来分析(msdn):一旦完成了套接字的连接,应当将套接字关闭,并且释放其套接字句柄所占用的所有资源。真正释放一个已经打开的套接字句柄的资源直接调用closesocket即可,但要明白closesocket的调用可能会带来负面影响,具体的影响和如何调用有关,最明显的影响是数据丢失,因此一般都要在closesocket之前调用shutdown来关闭套接字。
shutdown:为了保证通信双方都能够收到应用程序发出的所有数据,一个合格的应用程序的做法是通知接受双发都不在发送数据!这就是所谓的“正常关闭 ”套接字的方法,而这个方法就是由shutdown函数,传递给它的参数有SD_RECEIVE,SD_SEND,SD_BOTH三种,如果是 SD_RECEIVE就表示不允许再对此套接字调用接受函数。这对于协议层没有影响,另外对于tcp套接字来说,无论数据是在等候接受还是即将抵达,都要重置连接(注意对于udp协议来说,仍然接受并排列传入的数据,因此udp套接字而言shutdown毫无意义)。如果选择SE_SEND,则表示不允许再调用发送函数。对于tcp套接字来说,这意味着会在所有数据发送出并得到接受端确认后产生一个FIN包。如果指定SD_BOTH,答案不言而喻。
closesocket:对此函数的调用会释放套接字的描述,这个道理众所周知(凡是经常翻阅msdn的程序员),因此,调用此函数后,再是用此套接字就会发生调用失败,通常返回的错误是WSAENOTSOCK。此时与被closesocket的套接字描述符相关联的资源都会被释放,包括丢弃传输队列中的数据!!!!对于当前进程中的线程来讲,所有被关起的操作,或者是被挂起的重叠操作以及与其关联的任何事件,完成例程或完成端口的执行都将调用失败!另外 SO_LINGER标志还影响着closesocket的行为,但对于传统的socket程序,这里不加解释
因此可以可以看出shutdown对切断连接有着合理的完整性。
下面从tcp协议上来分析shutdown和closesocket的行为(behavior):closesocket或shutdown(使用 SD_SEND当作参数时),会向通信对方发出一个fin包,而此时套接字的状态会由ESTABLISHED变成FIN_WAIT_1,然后对方发送一个 ACK包作为回应,套接字又变成FIN_WAIT_2,如果对方也关闭了连接则对方会发出FIN,我方会回应一个ACK并将套接字置为 TIME_WAIT。因此可以看出closesocket,shutdown所进行的TCP行为是一样的,所不同的是函数部分,shutdown会确保 windows建立的数据传输队列中的数据不被丢失,而closesocket会冒然的抛弃所有的数据,因此如果你愿意closesocket完全可以取代shutdown,然而在数据交互十分复杂的网络协议程序中,最好还是shutdown稳妥一些!?有关TCP协议的连接原理清访问 http://www.rfc-editor.org第RFC793号文件

整理close 和 shutdown 的原理

http://stackoverflow.com/questions/14740852/linux-socket-close-vs-shutdown

  1. shutdown(sd, SHUT_WR)  发送FIN给对端,对端会响应一个ACK(协议层-内核所做). 未来任何尝试写这个socket会发生错误。但是对端仍然可以可以读,因为本端关闭写时,可能由于协议层的策略(滑动窗口、拥塞窗口、angle算法等)导致发送延迟,如果有待发送的消息,那么要尽力保证这些消息都发出去的。所以,会在最后一个报文中加入FIN标志,同时,关闭用于减少网络中小报文的angle算法,向连接对端发送消息。如果没有待发送的消息,则构造一个报文,仅含有FIN标志位,发送出去关闭连接;对端可以继续写数据给本端(主动端),因为对端收到FIN(收到FIN之前其实还有主动端缓冲区待发送消息)并响应ACK了(在响应ACK之前可能还有数据发送给本端),响应ACK后就进入CLOSE_WAIT状态,这个状态一般比较短暂,如果存在大量的CLOSE_WAIT,说明程序未关闭socket或者并发量太大来不及,或者你用的网络库有bug

  2. shutdown(sd, SHUT_RD) 不发送任何网络消息:他只是限制本地API对于随后的socket的读取直接返回EOS. 在已经在读操作被shutdown的socket上接收数据的行为(内核的行为, 表明对端要发送数据过来的行为)是依赖于系统的: Unix会确认它并且丢弃它; Linux会确认它并且缓冲它,最终会使得sender停止运行(发送rst给sender); Windows会发出一个RST, 此时sender继续写会报错“Connection reset by peer”

  3. 注:本端(主动端)发出fin进入FIN_WAIT1,收到对端ACK(对端发送ACK之前可能还有数据要发送给本端)进入FIN_WAIT2状态,收到对端FIN,发送ACK则进入TIME_WAIT,在2MSL内可以重发最后的ACK防止最后的ACK丢包;对端收到主动端的fin,如果有数据要发送给主动端,就发送数据,然后发送ACK,如果没有就直接发送ACK给主动端,然后对端进入CLOSE_WAIT状态,此时程序要保证及时关闭socket,不然会close_wait泄漏造成服务挂掉,当关闭socket时发送FIN给主动端,对端进入LASK_ACK状态,主动端收到FIN并ACK给对端,主动端进入TIME_WAIT,对端关闭(TIMEWAIT存在的原因就是防止最后一个ack丢包,在2MSL内可重传ACK)。

 

close函数:

  参数sockfd为套接字描述符,成功返回0,失败返回-1;该函数的功能是关闭套接字描述符引用计数,当计数大于0时什么都不干,当计数等于0时触发TCP/IP的四次挥手,即主动关闭方发送FIN。

  如果在调用close以前,TCP协议栈的发送队列中有已排队等候发送的数据,则协议栈尝试将数据发送出去,发送完毕后根据套接字描述引用计数来决定是否关闭连接。
      如果在调用close以后,且套接字描述符引用计数为0,则在其上调用write或者read函数则会产生错误码为9即EBADF(bad file descriptor 和Broken Pipe不一样,Broken Pipe表示文件描述符还合法,但是连接状态非法)的错误。

  调用close函数时,TCP协议栈对发送队列中已排队等候发送数据的处理流程受SO_LINGER选项的影响,该选项关联的数据结构如下:
          struct linger{
              int l_onoff;    //0=off,nonzero=on
              int l_linger;    //linger time
          };
      l_onoff是选项开关,如果是0则关闭选项并且忽略参数l_linger,如果是非零则打开选项,默认是0;l_linger是延迟关闭连接时间,只有l_onoff打开时即为非零时,该参数的值才有效。这几个参数与close时TCP协议栈对待发数据的处理流程如下表:

  表1 待发数据处理流程

l_onoff

l_linger

close行为

发送队列

TCP协议栈

忽略

立即返回

保持直至发送完成

接管套接字并保证将数据发送至对端

非零

立即返回

立即放弃

直接发送RST,自身立即复位,不用经过2MSL状态,对端收到复位错误码

非零

非零

阻塞直到l_linger时间超时或数据发送完成(套接字必须设置为阻塞)

在超时时间段内保持尝试发送,若超时则立即放弃

超时则同第二种情况,若发送完成则皆大欢喜

 

  当应用程序在调用close()函数关闭TCP连接时,Linux内核的默认行为是将套接口发送队列里的原有数据(比如之前残留的数据)以及新加入 的数据(比如函数close()产生的FIN标记,如果发送队列没有残留之前的数据,那么这个FIN标记将单独产生一个新数据包)发送出去并且销毁套接口 (并非把相关资源全部释放,比如只是把内核对象sock标记为dead状态等,这样当函数close()返回后,TCP发送队列的数据包仍然可以继续由内 核协议栈发送,但是一些相关操作就会受到影响和限制,比如对数据包发送失败后的重传次数)后立即返回。这需要知道两点:

  第一,当应用程序获得 close()函数的返回值时,待发送的数据可能还处在Linux内核的TCP发送队列里,因为当我们调用write()函数成功写出数据时,仅表示这些 数据被Linux内核接收放入到发送队列,如果此时立即调用close()函数返回后,那么刚才write()的数据限于TCP本身的拥塞控制机制(比如 发送窗口、接收窗口等等),完全有可能还呆在TCP发送队列里而未被发送出去;当然也有可能发送出去一些,毕竟在调用函数close()时,进入到 Linux内核后有一次数据包的主动发送机会,即:
tcp_close() -> tcp_send_fin() -> __tcp_push_pending_frames() -> tcp_write_xmit()

  第二,所有这些数据的发送,最终却并不一定能全部被对端确认(即数据包到了对端TCP协议栈的接收队列),只能做到TCP协议本身提供的一定程度的 保证,比如TCP协议的重传机制(并且受close()函数影响,重传机制弱化,也就是如果出现类似系统资源不足这样的问题,调用过close()函数进 行关闭的套接口所对应的这些数据会优先丢弃)等,因为如果网络不好可能导致TCP协议放弃继续重传或在意外收到对端发送过来的数据时连接被重置导致未成功 发送的数据全部丢失(后面会看到这种情况)。

 

 

 



 

以上是关于Socket 编程中 close() 和 shutdown() 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

socket.shutdown 与 socket.close

整理close 和 shutdown 的原理

Socket 编程中 close() 和 shutdown() 有啥区别?

linux下socket编程中close()函数??

socket 编程 : shutdown vs close

linux下socket编程中close()函数??