nginx 的socket 选项处理--TCP_DEFER_ACCEPT

在nginx listen配置项的说明中有一个选项:

  deferred -- indicates to use that postponed accept(2) on Linux with. the aid of option TCP_DEFER_ACCEPT.

  我们知道通常影响服务端性能的四大原因有:内存拷贝、内存分配、系统调用,进程切换。而nginx的deffered选项的作用是用来降低服务端进行epoll_ctl、epoll_wait(linux下)的次数(系统调用)和降低服务端保持的连接句柄数,从而提高服务端处理性能的设置。设置这个选项以后只有客户端有数据到达时才被epoll_wait监听到,再去accept和处理连接数据。
void
ngx_event_accept(ngx_event_t *ev)
{
    ......
        if (ev->deferred_accept) {
            rev->ready = 1;
        }
        ls->handler(c);
    ......
}

而ls->handler在ngx_http_add_listening中被初始化为 ls->handler = ngx_http_init_connection,那么继续看

void
ngx_http_init_connection(ngx_connection_t *c)
{
    ......
    if (rev->ready) {
        /* the deferred accept(), rtsig, aio, iocp */
        if (ngx_use_accept_mutex) {
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }
    ......
}

  在ngx_http_init_connection中就将接收到的事件放到posted队列中等待线程的处理。而在未设置deferred 选项时,这个操作是要我们在将接收到的连接epoll_ctl(ADD)之后再epoll_wait探测到EPOLLIN事件时才做这个处理的,从这里看我们就减少了一次epoll_wait该事件的过程,而且等到有数据时才accept,那么和未设置deffered选项时相比减少了accept到有数据之间这段事件服务器维护的一条连接。

  但是这个过程时如何实现的呢?我们知道TCP建立连接时进行三次握手协议,即客户端发送sync,服务端sync、ack,客户端ack即连接建立,这个时候服务端就对这个连接句柄进行accept。而查查TCP_DEFER_ACCEPT这个选项,可以了解到在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)
        ......
}
  TCP在处理链接请求时,服务器发送sync,ack后,处理客户端来的数据时,如果客户端只是对服务端的sync的ack那么则丢弃对这个裸ack,而直到有数据一起到来时再将tcp状态转换为ESTABLISHED,服务器端才对这个监听的句柄进行accept处理。
 
当然在服务端丢弃裸ack后就破坏了tcp的三次握手协议的过程,针对这个内核使用的是一个 超时重传sync、ack的机制,其以一个2的等比数列的方式进行重传。这部分具体的处理,请参考内核tcp协议栈的实现部分。

posted on 2014-06-26 20:50  tgates  阅读(2192)  评论(0编辑  收藏  举报

导航