TCP头部的选项部分是为了TCP适应复杂的网络环境和更好地为应用层服务而进行设计的。
通常一个端口释放后会等待两分钟(TIME_WAIT时间)之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。
SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用。server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。在TCP连接中,主动关闭方会进入TIME_WAIT状态,因此这个功能也就是主动方收到被动方发送的FIN后,发送ACK后就可以断开连接,不再去处理该ACK丢失等情况。
也就是说,这个套接字选项向内核传达了该消息:如果这个端口被占用但是TCP状态位于 TIME_WAIT ,则可以重用端口。而如果端口忙,而TCP状态位于其他状态,那么重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果想要让服务程序停止后就可以立即重用,并且使用的端口还是这个端口,那么设置SO_REUSEADDR 选项将非常有必要。总体来说,对于需要维持大量TCP连接的服务器可以设置这个选项,因为可能会存在大量的TIME_WAIT状态(尤其是HTTP服务通常是服务器充当主动断开一方),设置该选项就很有必要,毕竟最糟糕的情况下基本是客户端的断开慢一些,影响也不是非常大(客户机通常不需要大量的TCP连接需要)
SO_REUSEPORT则与之类似,主要是该选项允许TCP完全重复绑定,只要这些socket都开启了SO_REUSEPORT即可。
Nagle算法
Nagle算法针对的是需要连续发送多个小数据包的情况下,发送的有效数据相较于头部太小,因此发送的效率过低,甚至可能导致网络阻塞。Nagle算法可以减少网络中小的数据包的数量,从而降低网络的拥塞程度。最常见的Nagle算法的例子就是Telnet程序,用户在控制台的每次击键都会发送一个数据包,这个包通常包含41个字节,然而只有一个字节是有效负载,其余40个字节都是报头,如果每次击键都发送一个报文,那就会造成了巨大的开销。为了减小这种开销,Nagle算法规定,当TCP发送了一个小的segment(小于MSS),它必须等到接收到对方的ACK之后,才能继续发送另一个小的segment,因此发送方将第一个小包发出去后,将后面到达的少量字符数据都缓存起来而不立即发送,直到收到上一个数据包的ACK 或者 当前字符属于紧急数据 或者 缓存的字符数据已经到达一定的长度。Nagle算法并非灵丹妙药,它会增加TCP发送数据的延迟。在一些要求低延迟的应用程序中(例如即时通讯应用),Nagle算法的规定是不易被接受的,因此需要设置TCP_NODELAY或者TCP_CHORK关闭Nagle算法。
TIPS: TCP的延迟ACK与Nagle算法有异曲同工之妙,延迟ACK:当TCP接收到数据时,并不会立即发送ACK给对方,相反,它会等待应用层产生数据,然后将ACK和数据一起发送,并且发送的ACK序号为将可能会是更大的ACK序号(在Linux的最多等待40毫秒)(ACK序号表示该序号之前的报文已经收到,故等待期间可能又收到了部分报文,最终只需要发送一个最大的ACK序号即可表示之前的都收到了)
TCP_NODELAY/TCP_CHORK
TCP_NODELAY 和 TCP_CHORK 都禁掉了Nagle算法,行为需求有些不同:
SO_LINGER :指定函数close对面向连接的协议如何操作,内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
struct linger {
int l_onoff; /* 0 = off, nozero = on */
int l_linger; /* linger time */
};
四次挥手断开连接主动关闭方会进入TIME_WAIT状态,TIME_WAIT状态非常多的话会导致系统负载较大(TIME_WAIT本身不占用资源,但是处理TIME_WAIT需要耗费资源),故可以通过设置打开linger则直接发送RST分组,这种情况不会产生TIME_WAIT。
推迟接收,设置该选项后,服务器接收到第一个数据后,才会建立连接。(可以用来防范空连接攻击)
当设置该选项后,服务器收到connect完成3次握手后,服务器仍然是SYN_RECV,而不是ESTABLISHED状态,操作系统不会接收数据,直至收到一个数据才会进行ESTABLISHED状态。因此如果客户一直没有发送数据,则服务器会重传SYN/ACK报文,会有重传次数和超时值的限制。
SO_KEEPALIVE 保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。
设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方 发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:
有关SO_KEEPALIVE的三个参数详细解释如下:
SO_RCVTIMEO和SO_SNDTIMEO ,它们分别用来设置socket接收数据超时时间和发送数据超时时间。
因此,这两个选项仅对与数据收发相关的系统调用有效。接收超时会影响read、readv、recv、recvfrom、recvmsg的状态,发送超时会影响write、writev、send、sendto和sendmsg的状态。
每个TCP套接字都有一个发送缓存区和一个接收缓存区,每个UDP套接字都有一个接收缓存区。
调大缓存理论上是会提升Socket传输速率的,但是,并不是总是这样。缓存是一个只有在发送方和接收方的性能差异比较大时缓存会产生影响。还要补充的是,对于TCP连接而言,真正的传输过程是依赖于发送方和接收方两方的窗口大小的,因此单单调大发送方缓存并不会对整体产生明显影响。
在大延时网络上的带宽利用率低,主要原因是延时变大之后,发送方发的数据不能及时到达接收方。导致发送缓存满之后,不能再持续发送数据。接收方则因为TCP通告窗口受到接收方剩余缓存大小的影响。接收缓存小的话,则会通告对方发送窗口变小。进而影响发送方不能以大窗口发送数据。所以,这里的调优思路应该是,发送方调大tcp_wmem,接收方调大tcp_rmem。
如果网络的环境比较差,即丢包率比较高,那么会发现哪怕调高发送和接收双方的缓存大小,也无法提升整体的传输速率,主要原因是由于丢包比较高,因此网络比较拥塞,会频繁引发慢启动,使得真正的窗口瓶颈并不在于缓存的大小,甚至一直难以到达一个较大的值。这种情况下,可以考虑采用不同的拥塞控制算法,bbr算法对丢包不敏感,因此在这种情况下如果采用bbr拥塞控制算法将使得整个网络性能较高。如果网络的丢包很少延时很大,那么调整拥塞控制算法也不会由明显提升,此时主要瓶颈就是缓存。