Chinaunix

标题: socket中close和shutdown的区别 [打印本页]

作者: seufy88    时间: 2014-04-01 20:47
标题: socket中close和shutdown的区别
本帖最后由 seufy88 于 2014-04-01 21:01 编辑

这两个的区别在网上都有好多文章.
只是我还是有不明点.所以想请教一下大家.

这里不考虑多进程共享socket id这种情况.
单纯的讨论.

close (socket id): 马上发送FIN信号,所有的未完成发送或者接受的数据都将被丢失
close成功后,对这个socket id进行read/write都将失败.
但是从TCP层面将,close只是能控制本方发送FIN到对方,并不能控制对方何时发送FIN过来.
请问在这种情况下,如果对方还发data过来,本方的TCP层还会发送ack of data给对方吗?

      本方                  对方
       |                        |
close|-------FIN----->|
       |<--- ack of FIN -|
       |                        |
       |<----data ------ |
       |---ack of data -->|?????

在这种情况下,如果对方还发送data(而不是FIN)过来,本方是否直接返回RST?

shutdown (socket id):如果关闭写,如果输出缓冲区内有数据,则所有的数据将发送出去后将发送一个FIN信号
如果关闭写,则对这个socket的后续write会失败,但是允许后续对这个socket进行read.

本方                  对方
|                        |
|-------FIN----->|
|<--- ack of FIN -|
|                        |
|<----data ------ |
|---ack of data -->|


========
close,shutdown都会在TCP层面主动发送FIN
但是这些都只是local侧的,并不能掌控对方侧的行为(是继续发data还是发送FIN)
在这种情况下,我们local侧分别在使用close,shutdown后,如果对方侧仍有data发送过来,是否会有不同的操作?
已知的是shutdown之后,如果对方侧仍有data到达,则local侧可以发送回ack of data
但是close的情况又是怎么样的呢?

作者: timespace    时间: 2014-04-01 21:41
一言难尽,也不用网上怎么搜,找到UNPv1(UNIX网络编程第一卷),专门有讲这个问题的章节,看完就没疑问了。
作者: seufy88    时间: 2014-04-01 21:46
回复 2# timespace
小弟有空会去看.
本人并不是究结于close ,shutdown这样的API
而是对下面的TCP实现有点疑问.


   
作者: gaojl0728    时间: 2014-04-02 10:12
close和shutdown的行为取决于内核的实现。
1. close调用之后如果在local TCP buffer内如果还有数据没有读取会给对方直接回RST, 否则发送FIN,这取决于调用close时刻local TCP buffer的状态, 跟对端是不是继续发送数据无关。
2. shutdown关闭读不会给对方发FIN, 只有关闭写才会发FIN, 而且跟local TCP buffer状态没关系,只发送FIN包,从不发送RST
作者: seufy88    时间: 2014-04-02 11:16
本帖最后由 seufy88 于 2014-04-02 11:26 编辑
gaojl0728 发表于 2014-04-02 10:12
close和shutdown的行为取决于内核的实现。
1. close调用之后如果在local TCP buffer内如果还有数据没有读取 ...


恩,我之前没说明白,这里的shutdown特指W,会发送FIN。这个本人能够理解。

1. close调用之后如果在local TCP buffer内如果还有数据没有读取会给对方直接回RST, 否则发送FIN,这取决于调用close时刻local TCP buffer的状态, 跟对端是不是继续发送数据无关
=》如果close造成的是发送FIN,在这之后,对方继续发送data 过来的话呢?
按照底层TCP协议,local侧是否继续发送ack of data给对方,直到local侧buffer满了
因为TCP两方的通信通道,local侧永远只能掌控local侧出去的方向,不能控制对方侧继续发送数据。即在FIN_WAIT2状态时,local侧依然可以接收对方的data

调用close,shutdown,内核侧做了不同的guard工作:
对socket继续read的话,
close之后不允许读取对方来的数据,shutdown之后还是允许继续读取。

之前提到调用clsoe会关闭bidirection,这里所谓的关闭bidirection其实是否是内核自己提供的guard动作,和TCP协议本身“不一致”
因为从TCP层面看,一方发送FIN到对方后,并不能真正关闭bidirection,需要对方也发送FIN之后,TCP connection才算真正关闭bidirection

不知道我的想法是否有误?
作者: timespace    时间: 2014-04-02 13:06
回复 3# seufy88
如果是看API,我肯定推荐man shutdown或man close,既然是看书,那就不全是API的事情,网络API才有几个,值得UNPv1用近千页的篇幅来论述?UNPv1讲解API的同时,会有大量TCP/IP方面的解释,以助于理解本质问题,作者写TCP/IP详解三卷的功力也体现在UNPv1中。
你的问题不外乎就是shutdown/close/SO_LINGER三者的功能与区别,看完SO_LINGER就解释你剩下的疑问了。



   
作者: timespace    时间: 2014-04-02 13:47
不过多数来论坛提问的人是没耐心看书的,简单说下我的理解:

首先,close对TCP协议栈的影响可以由setsockopt(SOL_SOCKET, SO_LINGER, struct linger *optval; socklen_t optlen)控制。
struct linger {
    int onoff; /* 0=off, nonzero=on */
    int l_linger; /* linger time, POSIX specifies units as seconds */
};

但是从TCP层面将,close只是能控制本方发送FIN到对方,并不能控制对方何时发送FIN过来.
请问在这种情况下,如果对方还发data过来,本方的TCP层还会发送ack of data给对方吗?

1. close后,默认情况与使用l_onoff=0效果一致,本方继续接收数据并ACK,但会丢弃这些数据,直到对方FIN过来,完成连接终止过程;
2. close后,l_onoff非0且l_linger=0,立即丢弃发送缓存区数据并给对方发送一个RST,也就是不会有正常的四分组终止过程,也没有TIME_WAIT,对方收到RST后,肯定不能继续发数据了;
3. close后,l_onoff非0且l_linger>0,close会等待l_linger(阻塞情况下),如果数据发完,执行#1,否则执行#2;
灵活运用上面三种方式,本地socket足以应付不知进退的对方socket,排除共享fd和关闭fd问题外,close相当于shutdown(fd, SHUT_RDWR)。


作者: gaojl0728    时间: 2014-04-02 13:54
回复 5# seufy88


看了下linux-3.12.6的实现,内核的实现还是有点小复杂的。

1. close调用因为会关闭文件句柄, 因此close之后既不能读也不能继续写,shutdown_write之后句柄还在,还能继续读
2. 调用close和shutdown_write效果是一样的,都会给对方发送FIN, local端进入FIN_WAIT1, 之后能和等待对端回复ACK,
    (1)如果此后收到的是不带ACK纯数据包, 内核会暴力回复RST, 所以一种可能的情况就是虽然我调的是shutdown_write, 但最终双向都关闭了,因为网络中的包很难说什么时候到达。
    (2)如果此后收到的是带ACK的纯数据包,会有所不同,
        a. 如果前面是close调用, local端进入FIN_WAIT2, 同时启动time wait定期器,最终会关闭local端的TCP链接.
        b. 如果前面是shutdown_write, 数据会进入local TCP buffer, 上层应用可以继续收数据。
3. close对于local端来说是bidirection, 但对于对端来说是unidirection的, 对端根本就不知道你本端是close还是shutdown_write, 对端可以选择不close socket, 继续发送数据,这就会触发上面的第2条的逻辑,对端会收到RST, 对端的内核会关闭TCP链接。
4. 以上讨论忽略了linger选项,如果加上他就更复杂了。
作者: gaojl0728    时间: 2014-04-02 14:08
回复 7# timespace


    我给你纠正一下, 第2条,
close后,l_onoff非0且l_linger=0, 只有在此时TCP的读队列没有数据的情况下, 才会暴力发送RST, 否则内核还是会四次握手的。
作者: timespace    时间: 2014-04-02 16:00
回复 9# gaojl0728
多谢纠正。没研究过内核源码,不过有个疑问,close后,l_onoff非0且l_linger=0,如果本地TCP读取队列有数据且内核不RST,内核会逐步丢掉这些数据,一旦对方不发FIN,怎么终止连接?FIN_WAIT_2有超时?


   
作者: seufy88    时间: 2014-04-02 16:07
gaojl0728 发表于 2014-04-02 14:08
回复 7# timespace

应该是:

“close后,l_onoff非0且l_linger=0, 只有在此时TCP的读队列数据的情况下, 才会暴力发送RST, 否则内核还是会四次握手的。”
作者: gaojl0728    时间: 2014-04-02 16:12
回复 10# timespace


不好意思是我回复有点问题, 说的不太全面:
调用close后, 内核会先检查TCP读取队列,
1. 如果有数据没有读完, 不管linger选项是任何值, 内核总是会发送RST给对端,
2. 如果没有数据可读,内核接着检查linger选项, 如果l_onoff非0且l_linger=0, 也会发送RST
3. 其他情况会发送FIN进行四次挥手。同时如果l_onoff非0且l_linger>0,  close调用会阻塞在这里等待l_linger的时间。

所以你说的情况也会发送RST的。
作者: gaojl0728    时间: 2014-04-02 16:19
回复 11# seufy88


        那我回复写的有问题,他说的是对的。close完整的流程看我楼上的回复。
作者: seufy88    时间: 2014-04-02 16:25
回复 9# gaojl0728


    请问,如果socket的引用数>2, 此时close(socket),在减少引用数的基础上,应该不会发送FIN吧,因为其他共享这个socket 的进程需要使用


(2)如果此后收到的是带ACK的纯数据包,会有所不同,
        a. 如果前面是close调用, local端进入FIN_WAIT2, 同时启动time wait定期器,最终会关闭local端的TCP链接.
==》进入FIN_WAIT2后,就不接收对方发来的data 了吗?
我的理解是,local侧还是可以继续接收数据并返回ack, 只是这些数据丢弃掉了。application对这个socket当然肯定也不能再read


作者: seufy88    时间: 2014-04-02 16:41
@timespace
@gaojl0728
感谢两位的解答。
作者: gaojl0728    时间: 2014-04-02 16:56
回复 14# seufy88


我前面说“同时启动time wait定期器,最终会关闭local端的TCP链接”是基于一般情况下local调用close发送了FIN给对端之后, 对端这时应该会发送FIN+ACK包回来的情况,但如果对端来的还不是FIN+ACK包,而是数据包,
1. 如果数据包带ACK,  这些数据包还是会进入TCP收队列,
2. 如果数据包不带ACK, 直接丢掉。

不过Linux这块代码好像有点问题, 如果对端发来的是纯FIN包, 而不是FIN+ACK包,内核也会丢掉。
也就是说对端回过来FIN+ACK, 没问题。但是先回FIN, 再回ACK就不行了, 很奇怪。不太符合协议。
作者: gaojl0728    时间: 2014-04-02 17:09
回复 16# gaojl0728


完整的过程是本端调用close发送FIN给对方, 此时本端进入FIN_WAIT_1,
如果此时对端发过来不带ACK的包(包括纯FIN包和纯数据包),直接丢掉, 如果是带ACK的数据包, 本端会进入FIN_WAIT_2, 同时这个数据包也会进入TCP队列, 此后如果来的还是带ACK的数据包, 数据包还能进入TCP队列。
直到FIN+ACK为止, 其他包都丢掉了。

作者: gaojl0728    时间: 2014-04-02 17:09
回复 16# gaojl0728


完整的过程是本端调用close发送FIN给对方, 此时本端进入FIN_WAIT_1,
如果此时对端发过来不带ACK的包(包括纯FIN包和纯数据包),直接丢掉, 如果是带ACK的数据包, 本端会进入FIN_WAIT_2, 同时这个数据包也会进入TCP队列, 此后如果来的还是带ACK的数据包, 数据包还能进入TCP队列。
直到FIN+ACK为止, 其他包都丢掉了。

作者: seufy88    时间: 2014-04-02 17:16
回复 12# gaojl0728


    如此说来,对于以close方式主动关闭的一侧,是有很大程度会造成RST,而不是正常的四次BYBY
  timing很重要。直接close的时候,并不能保证buffer队列里没有要读的数据。


作者: seufy88    时间: 2014-04-02 17:20
gaojl0728 发表于 2014-04-02 17:09
回复 16# gaojl0728


完整的过程是本端调用close发送FIN给对方, 此时本端进入FIN_WAIT_1,

=》 本端发送完FIN后,如果之前本端发送的数据segment有丢失,对方会一直请求这个丢失的segment的序列号,些时,本端还是可以重发的吧?
直到对方对这个segment进行ACK(也可能对方重新收到丢失的segment后,直接发送FIN的ACK)


作者: gaojl0728    时间: 2014-04-02 17:25
回复 19# seufy88


一般用TCP编程在应用层都会设计自己的协议来保证对端最后一个包已经发完才会调close, 要不然就RST了。
这一点需要应用层自己保证, 内核不管。
作者: gaojl0728    时间: 2014-04-02 17:26
回复 20# seufy88


    这个就有点复杂了, 不太容易理清楚这个过程,不确定啊。
作者: seufy88    时间: 2014-04-02 17:40
gaojl0728 发表于 2014-04-02 17:25
回复 19# seufy88


所以这也是大家推荐的不要“暴力”close,而应该更优雅的方式关闭socket了。
比如
主动关闭方先调用shutdown (W)
过一段时间后read返回0(对方也发FIN了)后再调用close,真正关闭。

不知道实际生产中是如何做的(本人没写过网络程序
作者: seufy88    时间: 2014-04-02 19:30
回复 10# timespace


    理论上讲,对方不发FIN,那么local侧处在FIN_WAIT_2,对方处在CLOSE_WAIT,这么一样保持这样下去.
直到应用层决定进行关闭.

但是在berkely版本中,会防止永远在FIN_WAIT_2无限等待下去.而是会设置一个TIMER,到点了就会进入CLOSE状态.
但这个实现本身是违背TCP协议的.
作者: timespace    时间: 2014-04-02 20:23
回复 24# seufy88
貌似主流系统都支持这个超时,否则一直等下去不是办法,RFC有提到不能设超时吗?


   
作者: linux_c_py_php    时间: 2014-04-03 10:08
握手是4次单向通讯完成的, shutdown WR只是第一步而已。
作者: seufy88    时间: 2014-04-03 22:12
本帖最后由 seufy88 于 2014-04-03 22:18 编辑
linux_c_py_php 发表于 2014-04-03 10:08
握手是4次单向通讯完成的, shutdown WR只是第一步而已。


正好想请教你.
我们平常所说的server并发处理,这里所谓的并发处理client侧来的连接请求.
以nginx来说,其实就是epoll同时监听多个socket,如果有需要处理的socket,就还是"循环"依次处理各个"有效"的socket.
如此一来,如果每个处理过程花费的时间相对级别上很多的话,这个"并发处理"是不是感觉就不像是并发处理了,因为process就这么一个,
epoll_wait后还是要依序处理各个socket的

刚开始接触时,一直没明白nginx这样单process,不使用线程(出于开稍原因),单process处理几W并发,所谓的并发是什么回事.

能不能说说nginx的非阻塞异步,为什么被称为是并发处理?
因为讲给初学者"并发",脑子里第一反应就是"fork多process,或是以多线程的方式1对1处理client请求"
看完nginx单process模型后,还是有点疑问,怎么epoll多路后就成了所谓的并发了,不也还是一个一个请求依次执行的嘛(站在只有一个process的角度)

本人没写过服务器程序,理解或有偏差请见谅
作者: seufy88    时间: 2014-04-05 09:10
本帖最后由 seufy88 于 2014-04-05 09:14 编辑

回复 21# gaojl0728

一般用TCP编程在应用层都会设计自己的协议来保证对端最后一个包已经发完才会调close, 要不然就RST了。
这一点需要应用层自己保证, 内核不管。
==>local侧只需要保证队列里没有要read的数据就可以调用close,发送FIN了吧.
因为如果应用层要保证对对端最后一个包已经发完,那只能read返回 0(对端主动FIN),此情况下local再close,岂不是一直是被动FIN?


完整的过程是本端调用close发送FIN给对方, 此时本端进入FIN_WAIT_1,
如果此时对端发过来不带ACK的包(包括纯FIN包和纯数据包),直接丢掉, 如果是带ACK的数据包, 本端会进入FIN_WAIT_2, 同时这个数据包也会进入TCP队列, 此后如果来的还是带ACK的数据包, 数据包还能进入TCP队列。
直到FIN+ACK为止, 其他包都丢掉了。
===>我想问一下,本端调用close发送FIN时,有没有可能本端之前还有未被ack的数据存在?即TCP协议中,是否一定要所有发出的数据被ack后才能FIN?
关于这一点,书中并没有提到.据我所知,最后一个数据包可以一并带上FIN的标示.
这里我想带出另一个问题,应用层调用send返回后,数据是交由内核发送了,但实际上,应该没有被ACK吧?




   
作者: winshining    时间: 2014-04-12 15:12
回复 27# seufy88


    nginx会开子进程处理的吧,很久以前看过nginx的父子进程通信的代码,分为master和worker的,其实就是父进程和子进程,worker的个数还可以配置的。
作者: seufy88    时间: 2014-04-13 09:43
回复 29# winshining

我说的(并发)就是子进程的事.没关父进程什么事.

   
作者: seufy88    时间: 2014-04-20 21:21
回复 7# timespace

你好,再追问一下.
一般调用CLOSE后
"1. close后,默认情况与使用l_onoff=0效果一致,本方继续接收数据并ACK,但会丢弃这些数据,直到对方FIN过来,完成连接终止过程;"
你在这句中提到,本方会继续接收对方来的数据并ACK,

这个是否和SIGPIPE有出入?因为SIGPIPE是对已经收到RST的SOCKET,
什么情况下会收到RST,站在方面的角度,就是它向本方一个已经CLOSE的SOCKET继续发送数据,本方y"应该"发送RST给对方,对方二次调用WRITE时,就会检测到SIGPIPE.

但是根据描述"1"来看,对方继续发送数据到本方,本方并不会发送RST,而是ACK.

请问这样说来,是否有出入?

而你其他的描述("2","3")中也没有提到和这一情况相关的.

TKS
   
作者: timespace    时间: 2014-04-21 10:05
回复 31# seufy88
三个办法从易到难:
1. 看参考书,上面推荐过。
2. 写demo,tcpdump抓包分析。
3. 研究内核代码。
TCP/IP是实践者为实践所写的网络协议,如果想做凭空推理或想象,请移步OSI模型,谢谢。


   
作者: seufy88    时间: 2014-04-21 12:05
回复 32# timespace


    我自问自答一下吧。
    SOCKET状态为CLOSE时,收到DATA才会发送RST
    如果调用close()后,该socket并不处于CLOSE状态,而是FIN_WATI1/2等,所以继续收到DATA后,并不会发送RST,而是如你所说:"1. close后,默认情况与使用l_onoff=0效果一致,本方继续接收数据并ACK,但会丢弃这些数据,直到对方FIN过来,完成连接终止过程;"







欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2