在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;
-
#if (NGX_HAVE_KQUEUE)
-
rev->available = 1;
-
#endif
-
}
-
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协议栈的实现部分。
阅读(2067) | 评论(0) | 转发(0) |