nginx延迟关闭机制SO_LINGER

        经过前面文章的分析可以知道,nginx在释放一个请求时,到底要不要释放在这个请求基础上的TCP连接,是由keepalive机制与延迟关闭机制决定的。虽然http请求是被释放了, 但可能在这个TCP连接上稍后还有其他http请求到来,因此请求是被释放了,但TCP连接可能没有马上被释放。keepalive机制在上一篇文章已经分析了,现在来分析下延迟关闭的处理过程。

一、延迟关闭背景介绍

        延迟关闭的主要作用是保持更好的客户端兼容性,但是却需要消耗更多的额外资源。例如当发送数据时,由于write调用后不一定就发送完了,还有可能在缓冲区中,此时服务器在发现有异常需要关闭连接,会调用close,检查到接收缓冲区还有数据,就会reset, 对客户端不友好,导致客户端之前接收到的数据全部丢弃。另外在调用close收到客户端的数据,也会reset。 因此就需要linger延迟关闭。
        nginx的延迟关闭是自己实现的,而不是内核的SO_SINGER。为什么nginx要自己实现一套?因为linux系统的linger主要是阻塞方式实现的,当设置了linger超时时间场景时,不管使用阻塞还是非阻塞套接字,调用close都是会阻塞的, 而并不是UNP所讲到的非阻塞socket会立即返回EWOULDBLOCK。而nginx是非阻塞的,因此使用系统实现满足不了nginx的需求。

        nginx延迟关闭机制,只是为了接收来自客户端的剩余数据,接收完成后还是会马上被关闭tcp连接的, 这一点还是与keepalive机制有区别的。keepalive在一个请求结束时,tcp连接并没有关闭,可以继续处理来自客户端的剩余的http请求。要了解nginx的延迟关闭机制,需要对几个额外的知识点要有所了解。nginx通过TCP的套接字选项SO_LINGER、shutdown函数、read函数共同来实现延迟关闭机制。先来看下TCP的套接字选项SO_LINGER

        1.1 TCP的套接字选项SO_LINGER

struct linger
{
	int		l_onoff;		//延迟关闭功能是否开启,0关闭: 1开启
	int  	l_linger;       //需要延迟多长时间
}

        linux提供的套接字选项SO_LINGER可以改变在套接字上执行close函数时的默认行为。默认情况下执行close函数时,将执行四次挥手的过程结束与客户端的tcp连接。SO_LINGER选项将影响close的两种行为:

        (1)如果l_onoff字段非0, 表示开启了延迟关闭功能,并且l_linger字段为0。 那么在执行close函数时,不再是执行四次挥手的过程,结束与客户端的tcp连接。相反将丢弃接收缓冲区的所有数据并且立即终止连接,也就是发送RST复位数据包给客户端。

        (2)如果l_onoff字段非0, 表示开启了延迟关闭功能,兵器l_linger字段也非0,也就是需要延迟多长时间。那么此时执行close函数将被阻塞,直到;

               (2.1) nginx服务器发送的数据全部得到了对端的确认, 此时close返回0。       

               (2.2) 发生信号中断或者异常(比如意外收到了客户端发过来的关闭连接报文),或者超时,此时close返回值为0

        1.2 shutdown函数接口描述

#include <sys/socket.h>
int shutdown(int sockfd, int how);

        其中参数how可取值为SHUT_RD、SHUT_WR、SHUT_RDWR, 分别表示关闭读、关闭写、关闭读写。这也就比close函数只能进行关闭读写来得灵活些。在介绍完这些背景知识后,现在看下nginx是如何处理延迟关闭的。

二、nginx延迟关闭的条件

        函数ngx_http_finalize_connection是客户端请求被正常处理后的关闭函数,在资源释放前需要判断keepalive机制或者延迟关闭机制是否启用。如果keepalive机制与延迟机制都启用的情况下,优先会采用keepalive机制,因此keepalive的优先级别更高。而延迟关闭说到底只是延迟一下,最终还是要关闭这个连接。(在延迟关闭的情况下, 请求对象并没有马上释放,而是等到接收完来自客户端的所有剩余数据之后和tcp连接一块被关闭)。

//释放http请求与连接
static void ngx_http_finalize_connection(ngx_http_request_t *r)
{
	//执行到这里说明keepavlive为0,没有开启keepalive机制。是否需要关闭http请求与tcp连接,还需要判断延迟关闭功能有没开启。
	//如果指定了总是延迟关闭或者等待客户端发完数据在关闭时,则延迟关闭tcp连接与http请求
    if (clcf->lingering_close == NGX_HTTP_LINGERING_ALWAYS
        || (clcf->lingering_close == NGX_HTTP_LINGERING_ON
            && (r->lingering_close
                || r->header_in->pos < r->header_in->last
                || r->connection->read->ready)))
    {
        ngx_http_set_lingering_close(r);
        return;
    }
}

        什么情况下需要延迟关闭呢? 

        (1) 如果在nginx.conf配置文件中设置lingering_close指令的值为always,则必须延迟关闭。

        (2) 用户设置lingering_close指令的值为on时,并且接收缓冲区中收到了来自客户端的数据,也需要进行延迟关闭处理。

        (3) 用户设置lingering_close指令的值为on时,此时读事件就绪了,表示准备接收客户端发送来的数据。和上面的条件2还是有区别的,此时缓冲区中并没有数据,只是准备接收客户端的数据。

        总之,我们需要知道延迟关闭所要避免的就是在执行close后之后,却由于接收缓冲区中存放了客户端发来的数据,或者正在接收客户端的数据而导致发送RST复位报文异常终止连接所带来的负面影响。这个RST复位报文可能导致之前发送给客户端并且尚在网络或者客户端接收缓冲区的正常响应数据丢弃。

三、nginx设置延迟关闭。

        不管怎样,一旦需要对套接字进行延迟关闭,都会调用ngx_http_set_lingering_close函数进行设置。

static void ngx_http_set_lingering_close(ngx_http_request_t *r)
{
	//设置读时间的回调。用于接收来自客户端发来的剩余数据
    rev = c->read;
    rev->handler = ngx_http_lingering_close_handler;

	//设置延迟关闭的时间,并加入到定时器中。定时器时间到后将会关闭http请求与tcp连接
    r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000);
    ngx_add_timer(rev, clcf->lingering_timeout);

	//都需要延迟关闭了,则不需要往客户端写入数据了。因此写事件的回调设置为不做任何事情
    wev = c->write;
    wev->handler = ngx_http_empty_handler;

	//都需要延迟关闭了,则不需要往客户端写入数据了,因此关闭写端
    ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN);
}

        函数内部将设置读事件的回调为ngx_http_lingering_close_handler,后续不管是超时还是发生可读数据,都会调用这个函数进行处理。因为延迟关闭的作用就是为了接收客户端发送来的剩余数据,  接收完数据后在关闭tcp连接与释放http请求,因此不会在给发送任何的响应了。因此可以设置写事件为不做任何事情,并调用shutdown关闭写入端。

        具体实现过程:先调用shutdown关闭写端,此时会给客户端发送fin包,表示服务器不会给客户端再发送数据了,但可以接收数据,后续接收到数据就不会造成reset。

四、nginx处理延迟关闭

        在收到来自客户端的数据或者发生超时情况都会调用ngx_http_lingering_close_handler, 来看下这个函数做了些什么?

static void ngx_http_lingering_close_handler(ngx_event_t *rev)
{
	//超时了,过了好长时间客户端都没有发送数据。则需要释放http请求,关闭tcp连接
    if (rev->timedout)
	{
        ngx_http_close_request(r, 0);
        return;
    }

	//都已经超过延迟关闭的总时间了,则需要释放http请求,关闭tcp连接
    timer = (ngx_msec_t) (r->lingering_time - ngx_time());
    if (timer <= 0) 
	{
        ngx_http_close_request(r, 0);
        return;
    }

    do 
	{
		//阻塞,直到接收到了客户端发送来的所有数据,或者超时了。
		//在这两种情况下recv返回0
        n = c->recv(c, buffer, NGX_HTTP_LINGERING_BUFFER_SIZE);
        if (n == NGX_ERROR || n == 0)
		{
            ngx_http_close_request(r, 0);
            return;
        }
    } while (rev->ready);

    timer *= 1000;
	//将延迟关闭时间设置小些,并在此插入到定时器中
    if (timer > clcf->lingering_timeout) 
	{
        timer = clcf->lingering_timeout;
    }
    ngx_add_timer(rev, timer);
}

        函数内容调用recv阻塞的接收来自客户端的剩余数据,如果接收完成了所有的剩余数据,则会释放http请求与关闭tcp连接。当然如果超时了,recv也会返回,然后也会释放http请求与关闭tcp连接。因此不管是发生了超时事件,还是读取完来自客户端的剩余的数据,都会调用ngx_http_close_request函数。这个函数内会释放http请求与关闭tcp连接,来看下这个函数与延迟关闭有什么关联。

static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
{
    ngx_http_free_request(r, rc);
    ngx_http_close_connection(c);
}

        函数内部一共调用了两个函数,一个用于释放http请求,一个用于关闭tcp连接。先看下释放http请求的过程。

static void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc)
{
	//超时后,设置延迟关闭选项
	if (r->connection->timedout) 
	{
		if (clcf->reset_timedout_connection) 
		{
			linger.l_onoff = 1;
			linger.l_linger = 0;
			//超时后设置延迟关闭,后续调用close时不会进行四次挥手操作,而是给客户端发送rst复位报文
			setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,(const void *) &linger, sizeof(struct linger));
		}
	}
}

        只有在发生超时的情况下才会设置套接字选项SO_LINGER,后续调用close关闭tcp连接时,将会给客户端发送rst复位报文。而如果不是发生超时,而是接收完来自客户端所有的剩余数据,则不会设置套接字选项SO_LINGER, 因此后续调用close函数关闭tcp连接,则会进行四次挥手过程,结束与客户端的tcp连接。

ngx_http_close_connection

    --->ngx_close_connection

void ngx_close_connection(ngx_connection_t *c)
{
	//关闭socket
	//如果超时了,则被设置了延迟关闭选项,因此执行close将发送rst复位报文给客户端
	//如果接受完客户端的剩余数据,则没有设置延迟关闭选项,此时执行close将执行四次挥手过程,
	//结束与客户端tcp连接
	ngx_close_socket(fd)
}

        可以看出,在开启了延迟关闭机制与没有开启的情况下,调用close函数关闭TCP连接,给客户端发送的报文是不同的。开启延迟关闭情况下将会给客户端发送RST复位报文,而没有开启的情况下,将会执行正常的四次挥手过程,结束与客户端的tcp连接。 nginx延迟关闭机制,只是为了接收来自客户端的剩余数据,接收完成后还是会马上被关闭tcp连接的, 这一点还是与keepalive机制有区别的。keepalive在一个请求结束时,tcp连接并没有关闭,可以继续处理来自客户端的剩余的http请求。

        到此为止,nginx服务如何处理延迟关闭功能已经分析完成了。下一篇文章将分析子请求的处理过程。
 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值