1. 设置TCP_DEFER_ACCEPT
int val = 10; // time_out
if (setsockopt(sock_descriptor, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val))== -1)
{perror("setsockopt");
exit(1);}
2. TCP_DEFER_ACCEPT的效果 正常的tcp三次握手过程:
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
设置TCP_DEFER_ACCEPT后
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,连接并不进入ESTABLISHED状态,而是在第一个真正有数据的包到达后才进入ESTABLISHED,完成连接的建立。TCP_DEFER_ACCEPT的超时在第三次握手时候,如果客户段迟迟不发送数据,服务器 连接将一直处于syn_recv状态。此时内核会重传 syn_ack ,重传的次数可以通过 sysctl -w net.ipv4.tcp_synack_retries=3来设置,如果3次重传后,客户端依然没有数据,在等待 设置TCP_DEFER_ACCEPT时候指定的超时时间后(这个时间单位为s,可是测试看来并不精准的执行),系统将回收连接,并不对客户端发出rst或者fin包。
内核中源码如下:
第三次握手会调用函数tcp_v4_hnd_req:
-
static struct sock *tcp_v4_hnd_req(struct sock*sk, struct sk_buff*skb)
-
{
-
......
-
struct request_sock *req = inet_csk_search_req(sk,&prev, th->source,
-
iph->saddr, iph->daddr);//查找半连接队列,返回req
-
if (req)
-
return tcp_check_req(sk, skb, req, prev);//ack的处理
-
......
-
}
我们看函数tcp_check_req
-
struct sock *tcp_check_req(struct sock*sk,struct sk_buff*skb,
-
struct request_sock *req,
-
struct request_sock **prev)
-
{
-
......
-
/*If TCP_DEFER_ACCEPT is set, drop bare ACK.*/
-
if (inet_csk(sk)->icsk_accept_queue.rskq_defer_accept&&
-
TCP_SKB_CB(skb)->end_seq== tcp_rsk(req)->rcv_isn+ 1) {//如果选项设置了,并且是裸
-
ack,丢弃该ack;选项值得默
-
认为1
-
inet_rsk(req)->acked= 1;
-
return NULL;
-
}
-
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb,
-
req, NULL);//如果非裸ack或没设置选项则建立连接(req从半连接
-
队列到连接队列及tcp状态变为ESTABLISHED)
-
......
-
}
我们在用户层写socket程序时,可以通过setsockopt来设置TCP_DEFER_ACCEPT选项:
-
val = 5;
-
setsockopt(srv_socket->fd, SOL_TCP, TCP_DEFER_ACCEPT,&val, sizeof(val));
-
里面 val 的单位是秒,注意如果打开这个功能,kernel 在 val 秒之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。
在内核空间会调用:
-
static int do_tcp_setsockopt(struct sock*sk, int level,
-
int optname, char __user*optval, int optlen)
-
{
-
struct tcp_sock *tp = tcp_sk(sk);
-
struct inet_connection_sock *icsk = inet_csk(sk);
-
int val;
...... if (get_user(val, (int __user *)optval))//拷贝用户空间数据
return -EFAULT;
-
......
-
case TCP_DEFER_ACCEPT:
-
icsk->icsk_accept_queue.rskq_defer_accept= 0;
-
if (val> 0) {//如果setsockopt中设置val为0,则不开始TCP_DEFER_ACCEPT选项
-
/* Translate valuein seconds to number of
-
* retransmits */
-
while (icsk->icsk_accept_queue.rskq_defer_accept< 32 &&
-
val > ((TCP_TIMEOUT_INIT / HZ) <<
-
icsk->icsk_accept_queue.rskq_defer_accept))//根据设置的val决定重传次数,譬
-
如val=10,重传次数为3;后面我们可以看到,只有
-
/proc/sys/net/ipv4/tcp_synack_retries的
-
值小于等于通过val算出的重传次数时,这个val才
-
起作用
-
icsk->icsk_accept_queue.rskq_defer_accept++;
-
icsk->icsk_accept_queue.rskq_defer_accept++;
-
}
-
break;
-
......
-
}
内核是通过函数inet_csk_reqsk_queue_prune进行重传synack:
-
void inet_csk_reqsk_queue_prune(struct sock*parent,
-
const unsigned long interval,
-
const unsigned long timeout,
-
const unsigned long max_rto)
-
{
-
struct inet_connection_sock *icsk = inet_csk(parent);
-
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
-
struct listen_sock *lopt = queue->listen_opt;
-
int max_retries = icsk->icsk_syn_retries? : sysctl_tcp_synack_retries;//默认synack
-
重传次数为5
-
int thresh = max_retries;
-
unsigned long now = jiffies;
-
struct request_sock **reqp,*req;
-
int i, budget;
-
......
-
if (queue->rskq_defer_accept)
-
max_retries = queue->rskq_defer_accept;//设定支持选项时候的重传次数
-
-
budget = 2 * (lopt->nr_table_entries/ (timeout / interval));
-
i = lopt->clock_hand;
-
-
do {
-
reqp=&lopt->syn_table[i];
-
while ((req =*reqp) != NULL){
-
if (time_after_eq(now, req->expires)){
-
if ((req->retrans< thresh ||
-
(inet_rsk(req)->acked&& req->retrans< max_retries))
-
&&!req->rsk_ops->rtx_syn_ack(parent, req, NULL)){//如果重传次数小于设定
-
的重传次数,就重传synack;这里可以看出两个并列的判断条件:req->retrans < thres
-
h和(inet_rsk(req)->acked && req->retrans < max_retries),第一个是当前req
-
的重传次数小于设定的最大重传次数,这里是5;第二个则是TCP_DEFER_ACCEPT;inet_rs
-
k(req)->acked则是在函数tcp_check_req中设定的,上面讨论过了,而max_retries则
-
为通过val计算的值,默认为1。这个重传次数决定了synack包的重传次数及最长超时时间,
-
显然两者中较大者起到决定性的作用。譬如,默认重传为2,通过val计算出的max_retries
-
值为3,则将发送3次重传的synack及超时时间为12秒后,关闭连接
-
unsigned long timeo;
-
-
if (req->retrans++== 0)
-
lopt->qlen_young--;
-
timeo = min((timeout<< req->retrans), max_rto);
-
req->expires= now + timeo;//每重传一次,超时值就按初始值
-
timeout(TCP_TIMEOUT_INIT)比值为2的等比
-
数列增加,如3 6 12 24 48 96
-
reqp = &req->dl_next;
-
continue;//继续循环
-
}
-
-
/* Drop this request*/
-
如果超时,如超过例子中的96秒,就将req从半连接队列里删除,丢弃连接
-
inet_csk_reqsk_queue_unlink(parent, req, reqp);
-
reqsk_queue_removed(queue, req);
-
reqsk_free(req);
-
continue;
-
}
-
reqp = &req->dl_next;
-
}
-
-
i = (i+ 1) & (lopt->nr_table_entries- 1);
-
-
} while(--budget> 0);
-
-
lopt->clock_hand= i;
-
-
if (lopt->qlen)
-
inet_csk_reset_keepalive_timer(parent, interval);
-
}
那么TCP_DEFER_ACCEPT选项有什么好处呢,我们知道服务端处于监听时,客户端connect;服务端会收到syn包,并发送synack;当客户端收到synack并发送裸ack时,服务端accept创建一个新的句柄,这是不支持TCP_DEFER_ACCEPT选项下的流程。如果支持TCP_DEFER_ACCEPT,收到裸ack时,不会建立连接,操作系统不会Accept,也不会创建IO句柄。操作系统应该在若干秒后,会释放相关的链接;但没有同时关闭相应的端口,所以客户端会一直以为处于链接状态,如果Connect后面马上有后续的发送数据,那么服务器会调用Accept接收这个连接。
函数inet_csk_reqsk_queue_prune是通过tcp_synack_timer,是它在定时器中起作用的
-
static void tcp_synack_timer(struct sock*sk)
-
{
-
inet_csk_reqsk_queue_prune(sk, TCP_SYNQ_INTERVAL,
-
TCP_TIMEOUT_INIT, TCP_RTO_MAX);
-
}
阅读(997) | 评论(0) | 转发(0) |