Nginx:双向队列(Queue)

源码文件

src/core/ngx_queue.h
src/core/ngx_queue.c

设计思路

Nginx双向链表ngx_queue_t是采用"寄宿"在元素中的包含prev和next的ngx_queue_s来实现的。Linux内核中的page管理同样也使用到了这样的链接结构。linux内核情景分析 这样描述道:linux内核作者将prev和next从具体的“宿主”数据结构中抽象成为一个结构体list_head,这样list_head就可以成为该“宿主”的“连接件”。(kernel:include/linux/list.h , include/linux/mm.h )

        采用ngx_quque_t来构建双向链表,可以将链表的链接操作相关的数据结构抽象出来,这样有利于进行链表操作函数的编写。其次,用ngx_queue_t结构串接起来的链表可以是不同类型的数据类型(只要这个数据类型包含ngx_quque_t这个数据结构)。打个不恰当的比喻,不管什么样的物品(数据类型),只要物品上有个孔(ngx_quque_t)我们就能用线(ngx_queue_t构成的链)将这些物品串起来。再者,对于链表而言,进行排序,移动元素等操作只需要修改ngx_queue_t中的相关指针即可,所以也称Nginx的双向链表结构为轻量级链表。


Nginx提供一个非常简单的侵入式双向队列(双向链表),也即向每个元素嵌入数据结构的链接结点。这样做的好处在于:
1. 省去对数据结构所用内存的管理,进一步减少内存碎片;
2. 降低代码复杂度。

另外,通过使用额外的哨兵结点机制,简化了代码。

数据结构

nginx的队列是由具有头节点的双向循环链表实现的,每一个节点结构为ngx_queue_t,定义如下。

    typedef struct ngx_queue_s  ngx_queue_t;  
    struct ngx_queue_s {          //队列结构  
        ngx_queue_t  *prev;  
        ngx_queue_t  *next;  
    };  

其中,sizeof(ngx_queue_t)=8

从队列结构定义可以看出,nginx的队列结构里并没有其节点的数据内容。


  /--------------------------------------------------------------------\
  |                                                                    |
  |     ngx_http_location_queue_t          ngx_http_location_queue_t   |
  |     +-----------------------+          +-----------------------+   |
  |     | ngx_queue_t queue     |          | ngx_queue_t queue     |   |
  |  /--+> +--------------------+ <-\  /---+> +--------------------+ <-/
  \--|--+- | prev               |   \--|---+- | prev               |
     |  |  +--------------------+      |   |  +--------------------+
     |  |  | next               | -----/   |  | next               | --\
     |  +--+--------------------+          +--+--------------------+   |
     |  | ...                   |          | ...                   |   |
     |  |                       |          |                       |   |
     |  |                       |          |                       |   |
     |  +-----------------------+          +-----------------------+   |
     |                                                                 |
     \-----------------------------------------------------------------/

  注意点:
    1. 图中最左边的queue即为哨兵结点。

如何获取队列节点数据

由队列基本结构和以上操作可知,nginx的队列操作只对链表指针进行简单的修改指向操作,并不负责节点数据空间的分配。

因此,用户在使用nginx队列时,要自己定义数据结构并分配空间,且在其中包含一个ngx_queue_t的指针或者对象

当需要获取队列节点数据时,使用ngx_queue_data宏,其定义如下。

    #define ngx_queue_data(q, type, link)                 \  
        (type *) ((u_char *) q – offsetof(type, link))  

其实ngx_queue_t只是一个幌子,真正存储在队列中的元素是包含ngx_queue_t成员的结构。
换句话说:如果一个结构想使用队列的方式存储,那么这个结构就要包含ngx_queue_t成员。

offset
这里用到了C语言的一个技巧:offsetof 函数:根据结构的成员的指针定位结构的指针:理解了这个,ngx_queue 就明白了。
例如
struct x_s{
 int a;
 int b;
} x;
则 &x.b = &x + (&x.b - &x) = &x + offsetof(struct x_s, b);
所以只要一个结构里有ngx_queue成员,就可以根据ngx_queue成员的指针获得这个结构的指针。

由该宏定义可以看出,一般定义队列节点结构(该结构类型为type)时,需要将真正的数据放在前面,

ngx_queue_t结构放在后面,故该宏使用减法计算整个节点结构的起始地址(需要进行类型转换)

例如:

struct ngx_connection_s {
    void               *data;
    ngx_event_t        *read;
    ngx_event_t        *write;

    ngx_socket_t        fd;

    ngx_recv_pt         recv;
    ngx_send_pt         send;
    ngx_recv_chain_pt   recv_chain;
    ngx_send_chain_pt   send_chain;

    ngx_listening_t    *listening;

    off_t               sent;

    ngx_log_t          *log;

    ngx_pool_t         *pool;

    struct sockaddr    *sockaddr;
    socklen_t           socklen;
    ngx_str_t           addr_text;

    ngx_str_t           proxy_protocol_addr;

#if (NGX_SSL)
    ngx_ssl_connection_t  *ssl;
#endif

    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;

    ngx_buf_t          *buffer;

    <span style="color:#FF0000;">ngx_queue_t         queue;</span>

    ngx_atomic_uint_t   number;

    ngx_uint_t          requests;

    unsigned            buffered:8;

    unsigned            log_error:3;     /* ngx_connection_log_error_e */

    unsigned            unexpected_eof:1;
    unsigned            timedout:1;
    unsigned            error:1;
    unsigned            destroyed:1;

    unsigned            idle:1;
    unsigned            reusable:1;
    unsigned            close:1;

    unsigned            sendfile:1;
    unsigned            sndlowat:1;
    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e */

    unsigned            need_last_buf:1;

#if (NGX_HAVE_IOCP)
    unsigned            accept_context_updated:1;
#endif

#if (NGX_HAVE_AIO_SENDFILE)
    unsigned            aio_sendfile:1;
    unsigned            busy_count:2;
    ngx_buf_t          *busy_sendfile;
#endif

#if (NGX_THREADS)
    ngx_atomic_t        lock;
#endif
};

    ngx_queue_t       *q;
    ngx_connection_t  *c;

c = ngx_queue_data(q, ngx_connection_t, queue);




接口函数

以h结点为哨兵的队列称之为h队列。

双向队列的各个操作都很简单,函数名即操作意图:
1. ngx_queue_init(q)初始化哨兵结点,令prev字段和next字段均指向其自身;
2. ngx_queue_empty(q)检查哨兵结点的prev字段是否指向其自身,以判断队列是否为空;
3. ngx_queue_insert_head(h, x)在哨兵结点和第一个结点之间插入新结点x;
4. ngx_queue_insert_after(h, x)是ngx_queue_insert_head的别名;
5. ngx_queue_insert_tail(h, x)在最后一个结点和哨兵结点之间插入新结点;
6. ngx_queue_head(h)获取第一个结点;
7. ngx_queue_last(h)获取最后一个结点;
8. ngx_queue_sentinel(h)获取哨兵结点(即参数h);
9. ngx_queue_next(q)获取下一个结点;
10. ngx_queue_prev(q)获取上一个结点;
11. ngx_queue_remove(x)将结点x从队列中移除;
12. ngx_queue_split(h, q, n)将h为哨兵结点的队列中q结点开始到队尾结点的整个链拆分、链接到空的n队列中,h队列中的剩余结点组成新队列;
13. ngx_queue_add(h, n)将n队列中的所有结点按顺序链接到h队列末尾,n队列清空;
14. ngx_queue_middle(queue)使用双倍步进算法寻找queue队列的中间结点;
15. ngx_queue_sort(queue, cmd)使用插入排序算法对queue队列进行排序,完成后在next方向上为升序,prev方向为降序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值