linux下tcp选项TCP_DEFER_ACCEPT详解
TCP_DEFER_ACCEPT这个选项可能大家都知道,不过我这里会从源码和数据包来详细的分析这个选项。要注意,这里我所使用的内核版本是3.0.
首先看man手册中的介绍(man 7 tcp):
TCP_DEFER_ACCEPT (since Linux 2.4)
Allow a listener to be awakened only when data arrives on the socket. Takes an integer value (seconds), this can bound the maximum number of attempts TCP will make to complete the connection. This option should not be used in code intended to be portable.
我先来简单介绍下,这个选项主要是针对server端的服务器,一般来说我们三次握手,当客户端发送syn,然后server端接收到,然后发送syn + ack,然后client接收到syn+ack之后,再次发送ack(client进入establish状态),最终server端收到最后一个ack,进入establish状态。
而当正确的设置了TCP_DEFER_ACCEPT选项之后,server端会在接收到最后一个ack之后,并不进入establish状态,而只是将这个socket标记为acked,然后丢掉这个ack。此时server端这个socket还是处于syn_recved,然后接下来就是等待client发送数据, 而由于这个socket还是处于syn_recved,因此此时就会被syn_ack定时器所控制,对syn ack进行重传,而重传次数是由我们设置TCP_DEFER_ACCEPT传进去的值以及TCP_SYNCNT选项,proc文件系统的tcp_synack_retries一起来决定的(后面分析源码会看到如何来计算这个值).而我们知道我们传递给TCP_DEFER_ACCEPT的是秒,而在内核里面会将这个东西转换为重传次数.
这里要注意,当重传次数超过限制之后,并且当最后一个ack到达时,下一次导致超时的synack定时器还没启动,那么这个defer的连接将会被加入到establish队列,然后通知上层用户。这个也是符合man里面所说的(Takes an integer value (seconds), this can bound the maximum number of attempts TCP will make to complete the connection.) 也就是最终会完成这个连接.
我们来看抓包,这里server端设置deffer accept,然后客户端connect并不发送数据,我们来看会发生什么:
1 |
|
这里要注意,close的原因是当synack超时之后,nginx接收到了这条连接,然后读事件超时,最终导致close这条连接。
接下来就来看内核的代码。
先从设置TCP_DEFER_ACCEPT开始,设置TCP_DEFER_ACCEPT是通过setsockopt来做的,而传递给内核的值是秒,下面就是内核中对应的do_tcp_setsockopt函数,它用来设置tcp相关的option,下面我们能看到主要就是将传递进去的val转换为将要重传的次数。
1 |
|
这里可以看到通过调用secs_to_retrans来将秒转换为重传次数。接下来就来看这个函数,它有三个参数,第一个是将要转换的秒,第二个是RTO的初始值,第三个是RTO的最大值。 可以看到这里都是依据RTO来计算的,这是因为这个重传次数是syn_ack的重传次数。
这个函数实现很简单,就是一个定时器退避的计算过程(定时器退避可以看我前面的blog的介绍),每次乘2,然后来计算重传次数。
1 |
|
然后来看当server端接收到最后一个ack的处理,这里只关注defer_accept的部分,这个函数是tcp_check_req,它主要用来检测SYN_RECV状态接收到包的校验。
req->retrans表示已经重传的次数。
acked标记主要是为了syn_ack定时器来使用的。
1 |
|
而当tcp_check_req返回之后,在tcp_v4_do_rcv中会丢掉这个包,让socket继续保存在半连接队列中。
然后来看syn ack定时器,这个定时器我以前有分析过(http://simohayha.iteye.com/admin/blogs/481989)
,因此我这里只是简要的再次分析下。如果需要更详细的分析,可以看我上面的链接,这个定时器会调用inet_csk_reqsk_queue_prune函数,在这个函数中做相关的处理。
这里我们就主要关注重试次数。其中icsk_syn_retries是TCP_SYNCNT这个option设置的。这个值会比sysctl_tcp_synack_retries优先.然后是rskq_defer_accept,它又比icsk_syn_retries优先.
1 |
|