SO_LINGER 是相当复杂的选项之一,它主要影响 close 和 shutdown 函数的行为(参考 man 手册),为了方便讨论,后面我以 close 为例。
在不同的平台上,SO_LINGER 选项表现行为也不一样,不同的类 unix 系统不一样,windows 和 linux 也不一样,这实在是太麻烦了。本文将在 centos7 上对此选项的行为进行分析,然后不加证明的引用一篇针对 SO_LINGER 在不同平台上进行交叉实验的结果。
1. 程序路径
代码托管在 gitos 上,请使用下面的命令获取:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull
更新一下。本节程序所使用的程序路径是 unp/program/options/opt
。
2. SO_LINGER 的作用
该选项只对面向连接的(connection-oriented)的协议有用,比如 TCP 协议。它的目的是改变 close 函数的行为。
SO_LINGER 的数据类型是结构体:
struct linger { l_onoff; l_linger; }
默认情况下,SO_LINGER 选项是被关闭的,此时成员 l_onoff = 0,这种情况下 l_linger 是没有用的。通过前面的 showopts 程序我们可以查看到该默认值,如图 1.
图1 查看 SO_LINGER 选项的默认值
3. SO_LINGER 如何影响 close
这里分三种情况讨论(只针对 linux,我当前的测试内核是 3.10),前提条件是对端 TCP 程序正常,套接字描述符是阻塞的。
- 1)
l_onoff == 0
, {off, ~}
这是系统的默认情况。此时 l_linger 被忽略,close 立即返回,如果发送缓冲还有残留数据,系统会在后台将这些数据发送给对端,最后发送 FIN 给对端。
- 2)
l_onff != 0 && l_linger == 0
, {on, 0}
close 函数立即返回,将放弃发送缓冲区中残留数据,立即发送 RST 给对端。
- 3)
l_onff != 0 && l_linger > 0
, {on, >0}
close 会阻塞,直到下面两种情况中的任何一种发生就返回,返回值是 0:
a) 所有数据都已发送完(包含 FIN),且得到对端的 ack,这种情况为提前返回;
b) 延滞时间到(l_linger 指定的时间,单位为秒)。
在 linux 系统中,无论有没有提前返回,系统会在后台将发送缓冲区中所有残留数据(包含 FIN)发送给对端。该行为与具体的系统实现有关。具体请参考图 2. unp 一书对此描述并不是很清晰,只是说对于非阻塞的套接字来说,会 Abort(它表示放弃发送缓冲区数据并丢一个 RST 给对端)。
图2 引用自 Cross-Platform Testing of SO_LINGER
我们关注的是第 3 列,即 linger 超时后,是否要 Abort. 对于 Linux 来说,并没有 Abort,这和我前面叙述的结果一样。待会我会用实验验证。
4. 实验
- 实验环境:
准备一份 295740 字节大小的文件,只要比较大,能够把对端接收缓冲区占满就行了。我这里采用的是 input2 这个文件。
- 关于 opt 的 slowread 选项说明
这个选项可以让 opt 程序进入慢读取模式,每隔 2 秒读取 size 字节,size 由你指定。这种情况下,opt 程序只读不发。
4.1 {off, ~} 情况
图3 close 立即返回,并在后台继续发送数据
图3中, 在接收方 flower 慢读取模式下,接收方的接收缓冲区很快被占满,导致接收窗口变成了 0.
图4 发送方 sun 在后台持续发送,直到最后一个 FIN 被确认
图4, 接收方 flower 将缓冲区中数据读取完后,缓冲区有了空闲位置,发送非 0 接收窗口给对方 sun,通知对方 sun 可以继续发送数据。
图5 接收方 flower 接收到了所有数据,并进行了关闭,发送 FIN,可是客户端进程已经不存在
图5, 接收方 flower 读取完数据发送 FIN 给发送方,但是发送方 sun 进程已经关闭。
4.2 {on, 0}
图6 发送方sun close 立即返回并 Abort
我们可以利用这种方法实现发送方主动发送 RST,比如有人就用这种方法来避免出现 TIME_WAIT 状态。
图7 接收方 flower 并没有接收到所有数据,只收到了 117040 字节
4.3 {on, 5}
图8 发送方 sun 阻塞在 close 上,5 秒等待
图9 发送方 close 返回,后台仍然在发送数据,并没有 Abort
图 9, 这里很关键,尽管 sun 的数据还没有发送完(发送缓冲区还有数据),连接也没有 Abort,系统在后台继续发送数据。
图10 发送方 sun 在后台发送完了所有数据
图 10,即便开启了 linger,close 也返回了,后台仍然将数据发送完毕,并发磅了 FIN 段给 flower.
图11 接收方接收到了所有 295740 字节的数据
5. 分析
对于 linger 的前两种情况,一般和书上,别的博客上介绍的一致。只是最后一种情况,即 {on, >0} 的情况,结论就不太一样,SO_LINGER 严重依赖于具体实现。
有的系统实现可能会在 close 超时后进行 abort,比如说 windows,而对于 linux 来说,close 超时后并不会 abort. 具体情况请参考第 4.3 节的实验,以及图 2,以及这篇博文Cross-Platform Testing of SO_LINGER.
6. 总结
- 掌握 linger 的对 close 的影响以及 close 的行为
- 知道平台相关性