nginx源码初读(9)--让烦恼从数据结构开始(ngx_listening/ngx_connection)

在nginx中connection就是对tcp连接的封装,其中包括连接的socket,读事件,写事件。利用nginx封装的connection,我们可以很方便的使用nginx来处理与连接相关的事情,比如,建立连接,发送与接受数据等。而nginx中的http请求的处理就是建立在connection之上的,所以nginx不仅可以作为一个web服务器,也可以作为邮件服务器。当然,利用nginx提供的connection,我们可以与任何后端服务打交道。

结合一个tcp连接的生命周期,我们看看nginx是如何处理一个连接的。首先,nginx在启动时,会解析配置文件,得到需要监听的端口与ip地址,然后在nginx的master进程里面,先初始化好这个监控的socket(创建socket,设置addrreuse等选项,绑定到指定的ip地址端口,再listen),然后再fork出多个子进程出来,然后子进程会竞争accept新的连接。此时,客户端就可以向nginx发起连接了。当客户端与服务端通过三次握手建立好一个连接后,nginx的某一个子进程会accept成功,得到这个建立好的连接的socket,然后创建nginx对连接的封装,即ngx_connection_t结构体。接着,设置读写事件处理函数并添加读写事件来与客户端进行数据的交换。最后,nginx或客户端来主动关掉连接,到此,一个连接就寿终正寝了。

当然,nginx也是可以作为客户端来请求其它server的数据的(如upstream模块),此时,与其它server创建的连接,也封装在ngx_connection_t中。作为客户端,nginx先获取一个ngx_connection_t结构体,然后创建socket,并设置socket的属性( 比如非阻塞)。然后再通过添加读写事件,调用connect/read/write来调用连接,最后关掉连接,并释放ngx_connection_t。


第一点:ngx_listening
在了解connection之前,我们可以先看一下ngx_listening,他保存了跟监听有关的信息:

typedef struct ngx_listening_s  ngx_listening_t;
struct ngx_listening_s {
    ngx_socket_t        fd;     
    /* socket套接字句柄
     * ... getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1; ...
     * ... bind(s, ls[i].sockaddr, ls[i].socklen); 
           listen(s, ls[i].backlog);
           ls[i].fd = s;  ...
     */  

    struct sockaddr    *sockaddr;              // 监听sockaddr地址
    socklen_t           socklen;               // size of sockaddr
    size_t              addr_text_max_len;     // 存储ip字符串的addr_text的最大长度
    ngx_str_t           addr_text;             // 以字符串形式存储的ip地址
    /* ... ngx_create_listening中的一部分代码:
           sa = ngx_palloc(cf->pool, socklen);    // 给sockaddr分配地址空间
           if (sa == NULL) {
               return NULL;
           }
           ngx_memcpy(sa, sockaddr, socklen);     // 复制地址到sockaddr中

           ls->sockaddr = sa;
           ls->socklen = socklen;

           // 把ip转为字符串格式并存储再addr_text中,并根据具体使用情况获得max的值
           len = ngx_sock_ntop(sa, socklen, text, NGX_SOCKADDR_STRLEN, 1);
           ls->addr_text.len = len;
           switch (ls->sockaddr->sa_family) {
       #if (NGX_HAVE_INET6)
               case AF_INET6:
                   ls->addr_text_max_len = NGX_INET6_ADDRSTRLEN;
                   break;
               ...    
           }       
           ls->addr_text.data = ngx_pnalloc(cf->pool, len);
           if (ls->addr_text.data == NULL) {
               return NULL;
           }
           ngx_memcpy(ls->addr_text.data, text, len); ...
     */      

    int                 type;                 
    /* 套接字类型。types是SOCK_STREAM时,表示是tcp。与linux的一致。*/

    int                 backlog;
    /* TCP监听时的backlog队列,它表示系统所允许的已通过三次握手建立好的tcp连接、但还没有任何进程开始处理的最大数 */

    int                 rcvbuf; 
    int                 sndbuf;
    /* socket接收/发送缓冲区,是在http模块中add_listening调用create后(create为-1),单独处理给的一块内存区 */

#if (NGX_HAVE_KEEPALIVE_TUNABLE)
    int                 keepidle;
    int                 keepintvl;
    int                 keepcnt;
#endif

    ngx_connection_handler_pt   handler;
    /* handler of accepted connection(对新建立连接的处理方法)  
     * ... ls->handler = ngx_http_init_connection; ...
     */

    void               *servers;  
    /* array of ngx_http_in_addr_t, for example
     * 当前主要用于HTTP或mail等模块,用于保存当前监听端口对应的所有主机名 */

    ngx_log_t           log;
    ngx_log_t          *logp;
    /* 看了看源码,初步分析log是自身带的一个日志,logp指向相应业务core模块的日志 */

    size_t              pool_size;
    /* 要为新的tcp连接建立内存池的话,内存池的大小为pool_size  
     * ... c->pool = ngx_create_pool(ls->pool_size, ev->log); ...
     */

    size_t              post_accept_buffer_size;
    /* 服务器可以接收的头部buffer大小?超过了就不要啦~~
     * ls->post_accept_buffer_size = cscf->client_header_buffer_size;
     */ 

    ngx_msec_t          post_accept_timeout;
    /* should be here because of the deferred accept
     * TCP连接建立后,在post_accept_timeout之后仍然没有收到用户数据,则内核直接丢弃连接
     * ... if (!rev->timer_set) {    
               // 加入定时红黑树,处理超时时间
               ngx_add_timer(rev, c->listening->post_accept_timeout);
           } ...
     */      

    ngx_listening_t    *previous;
    /* 指向前一个listening结构,都统统连起来 */

    ngx_connection_t   *connection;
    /* 当前监听句柄对应的connection结构 ...  c = ls[i].connection; ...*/

    unsigned            open:1;
    /* 1:当前监听句柄有效   0:正常关闭 */

    unsigned            remain:1; 
    /* 用已有的ngx_cycle_t来初始化ngx_cycle_t结构体时,不关闭监听端口,对于运行中升级有用 */

    unsigned            ignore:1;
    /* 1:跳过设置当前ngx_listening_t结构体中的套接字    0:正常初始化 
     * ... for (i = 0; i < cycle->listening.nelts; i++) {
               if (ls[i].ignore) {
                   continue;
               }
               ...    // 使用setsockopt对socket进行设置
           } ...    
     */

    unsigned            bound:1;       // already bound   已经绑定啦 
    unsigned            inherited:1;   // inherited from previous process  当前监听句柄从上一个进程来的
    unsigned            nonblocking_accept:1;   // 没人用这个
    unsigned            listen:1;      // 当前结构体对应的socket已监听
    unsigned            nonblocking:1;          // 是否阻塞,也没用。因为nginx是异步非阻塞的
    unsigned            shared:1;    // shared between threads or processes 没人用- -,毕竟worker间没联系
    unsigned            addr_ntop:1; // 为1表示当前ip地址被转换为字符串形式,调用create_listening后自行设置

#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
    unsigned            ipv6only:1;
#endif
    unsigned            keepalive:2;

#if (NGX_HAVE_DEFERRED_ACCEPT)
    unsigned            deferred_accept:1;
    unsigned            delete_deferred:1;
    unsigned            add_deferred:1;
#ifdef SO_ACCEPTFILTER
    char               *accept_filter;
#endif
#endif
#if (NGX_HAVE_SETFIB)
    int                 setfib;
#endif

#if (NGX_HAVE_TCP_FASTOPEN)
    int                 fastopen;
#endif
};

跟listening有关的几个函数:

ngx_listening_t *ngx_create_listening(ngx_conf_t *cf, void *sockaddr,
    socklen_t socklen);                                    // 创建listening_t
ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle);   // 只有在nginx.c中用了一下,作为返回值生成方法
ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle);  // cycle_init中使用
void ngx_configure_listening_sockets(ngx_cycle_t *cycle);  // cycle_init中使用
void ngx_close_listening_sockets(ngx_cycle_t *cycle);      // 出错或者需要退出时调用

第二点:ngx_connection_t
以下就是connection结构体的定义:

struct ngx_connection_s {
    void               *data;
    /* 连接未使用时,data用于充当连接池中空闲链表中的next指针。
     * 连接使用时由模块而定,HTTP中,data指向ngx_http_request_t。*/

    ngx_event_t        *read;
    ngx_event_t        *write;
    /* 连接对应的读写事件 */

    ngx_socket_t        fd;
    /* 连接对应的socket */

    ngx_recv_pt         recv;
    ngx_send_pt         send;
    /* 直接接收/发送网络字节流的方法 */

    ngx_recv_chain_pt   recv_chain;
    ngx_send_chain_pt   send_chain;
    /* 以ngx_chain接收/发送网络字节流的方法 */

    ngx_listening_t    *listening;
    /* 这个连接对应的ngx_listening_t监听对象,此连接由listening监听端口的事件建立 */

    off_t               sent;
    /* 已发送字节数 */

    ngx_log_t          *log;
    /* 对应的日志对象 */

    ngx_pool_t         *pool;
    /* 在accept一个新连接的时候,会创建一个内存池,而这个连接结束时候,会销毁一个内存池。  
       这里所说的连接是成功建立的tcp连接.内存池的大小由pool_size决定。  
       所有的ngx_connect_t结构体都是预分配的。*/

    struct sockaddr    *sockaddr;
    socklen_t           socklen;
    ngx_str_t           addr_text;
    /* 连接客户端的socket结构体,socket结构体的长度,以字符串表示的ip */

    ngx_str_t           proxy_protocol_addr;
    /* 在1.5.12新增,代理地址,用于http代理模块 */

#if (NGX_SSL)
    ngx_ssl_connection_t  *ssl;
    /* 根据安装时的配置,选择是否建立ssl连接 */
#endif

    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;
    /* 本机监听端口对应的sockaddr结构体,实际上就是listening监听对象的sockaddr成员 */

    ngx_buf_t          *buffer;
    /* 用户接受、缓存客户端发来的字符流,buffer是由连接内存池分配的,大小自由决定 */

    ngx_queue_t         queue;
    /* 将当前连接添加到ngx_cycle_t核心结构体的reuseable_connection_queue双向链表中,表示可以重用 */

    ngx_atomic_uint_t   number;
    /* 连接使用次数。ngx_connection_t每建立一条来自client的连接或主动向server发起连接时,number都会加1 */

    ngx_uint_t          requests;
    /* 处理请求的次数 */

    unsigned            buffered:8;
    /* 缓存中的业务类型,各个模块有自己的一些定义 */

    unsigned            log_error:3;     
    /* ngx_connection_log_error_e */
    /* 本连接的日志级别,只定义了5个值,由ngx_connection_log_error_e枚举表示 */

    unsigned            unexpected_eof:1;       // 不期待字符流结束
    unsigned            timedout:1;             // 连接已超时
    unsigned            error:1;                // 处理过程出现了错误
    unsigned            destroyed:1;            // 标识此连接已销毁,pool/socket等不可用 

    unsigned            idle:1;                 // 连接处于空闲状态,如keepalive两次请求中间的状态
    unsigned            reusable:1;             // 连接可重用,与上面的queue字段对应使用
    unsigned            close:1;                // 连接关闭

    unsigned            sendfile:1;             // 正在发送文件数据
    unsigned            sndlowat:1;
    /* 表示只有连接套接字对应的发送缓冲区必须满足最低设置的大小阀值时,事件驱动模块才会分发该事件。
     * 这与ngx_handle_write_event方法中的lowat参数是对应的 */

    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e,枚举定义有3个 */
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e,枚举定义有3个 */

    unsigned            need_last_buf:1;         // 新加进来的,用得地方比较少,是否需要最后一块buf?

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

#if (NGX_HAVE_AIO_SENDFILE)
    unsigned            busy_count:2;
#endif

#if (NGX_THREADS)
    ngx_thread_task_t  *sendfile_task;
#endif
};

我们可以看到connection结构体中包含了与连接有关的各种参数,还有控制连接和信息传输的一些规则。梳理完这些东西,下一节就可以开始看漫长的cycle了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章