介绍下TCP协议中的半关闭状态。
1,对close的正确理解
一般关闭一个socket,是调用close函数。close函数首先将socket fd的reference减一,若reference依旧大于0,则该socket端口的状态保持不变;若reference等于0,则首先将sender buffer中的数据全部发送出去,并将receive buffer中的数据全部丢弃,最后发送FIN,执行主动关闭。
对当前process而言,当一个socket fd执行close后,再对其做read,会返回EOF;执行write,会返回SIGPIPE.
socket提供了SO_LINGER选项,允许我们改变close的行为,从而到达某些特别的操纵目的。
默认情况下,close会立即返回,tcp协议栈首先会将send buffer中的数据先发送出去,然后开始执行主动关闭。
(a) l_onoff = 0,l_linger参数将被忽略,等同于close的默认方式;
(b) l_onoff = 1, l_linger = 0; close会立即返回,tcp协议栈将send buffer和receive buffer中的数据全部情况,同时立即发送RST。采用这种方式将绕过正常的4次握手关闭,可避免进入2MSL TIME_WAIT状态。
(c) l_onoff = 1, l_linger = xx; close会被block住,当send buffer里面的数据全部被发送并被ack,则close返回,否则直到xx超时。若超时,则close返回EWOULDBLOCK,同时send buffer里面的所有数据将被丢弃。这个功能只对blocked fd有效,对non-blocked fd无效。
第3种方式相对于第1种而言,优点是可以确切直到send buffer里面的数据有没有被tcp协议栈完整的发送出去。因为第1种的方式,close立即返回,应用层无法知晓是否send buffer中的数据已经被对方协议栈完整收到。采用第3种,应用层可以清楚直到对方协议栈是否完整收到了数据。虽然3比1进步了一些,但依旧无法确保对方应用层已经完整收到了数据。要想确认对方应用端收到了数据,方案只有两个:
(1) 在应用层上设计协议,比如对方收到完整的数据后,发送一个确认的数据包过来。那么发送方只有在收到这个确认数据包之后,才会调用close。
(2) 采用shutdown做半关闭,并通过read等待读取到EOF。在正常情况下,对方只有在数据完整收到后才会执行close操作,发送方才能读取到EOF。
2,close与shutdown的区别
(1) shutdown与socket描述符没有关系,即使调用shutdown(fd, SHUT_RDWR)也不会关闭fd,最终还需close(fd)。
(2) shutdown无论当前socket fd的reference值是否为0,都会对该socket执行主动关闭
(3) 在调用了SHUT_WR后再执行write操作,会触发SIGPIPE,但可以对该fd正常read;close之后对当前process无论read&write都不允许;
3,半关闭的API支持
A:
B:
4,其它
blocked read:
n=read(fd, buf, MAXLINE)
n > 0,表示这次成功读到了多少个字节。
若n < 0 errno = EINTR,代表数据没有读完,是中断导致的,需要再次读。
n < 0 && errno != EINTR,则代表出错,比如收到RST,或其它各种错误。
n=0,代表对方端发送FIN。由于TCP是流式的,因此n=0仅会在对方开始关闭链接close或shutdown时才出现。