最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

nginx源碼之epoll事件循環(huán)處理方式

 更新時間:2025年07月02日 09:33:12   作者:dai1396734  
這篇文章主要介紹了nginx源碼之epoll事件循環(huán)處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

要說明的幾點

一、為了方便調試及跟蹤代碼所以采用了單進程的模式運行程序

二、自備一份源碼

三、在閱讀函數之前,我覺得要帶有幾個問題去看

  • 事件循環(huán)什么時候開始?
  • 怎么添加事件?
  • 事件如何分發(fā)處理?

一、事件循環(huán)的創(chuàng)建

當我們啟動程序,會進入下面函數,顧名思義就是處理單進程循環(huán)的函數

void
ngx_single_process_cycle(ngx_cycle_t *cycle)
{
    ngx_uint_t  i;

    if (ngx_set_environment(cycle, NULL) == NULL) {
        /* fatal */
        exit(2);
    }
	//調用每個模塊的初始化
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->init_process) {
            if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }

    for ( ;; ) {
        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
		//處理定時器事件及網絡事件
        ngx_process_events_and_timers(cycle);

       ...
    }
}

在調用模塊的初始化時就會調用ngx_event_core_module模塊的ngx_event_process_init函數

在這里插入圖片描述

ngx_event_process_init實現如下,隱藏部分不需要的代碼過程

static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
	...
    for (m = 0; cycle->modules[m]; m++) {
    	//只有ngx_epoll_module是NGX_EVENT_MODULE類型,epoll模塊
        if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
            continue;
        }

        if (cycle->modules[m]->ctx_index != ecf->use) {
            continue;
        }

        module = cycle->modules[m]->ctx;
        //初始化epoll ,創(chuàng)建epoll
        if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
            /* fatal */
            exit(2);
        }

        break;
    }
}

當類型是NGX_EVENT_MODULE的模塊只有ngx_epoll_module和ngx_event_core_module,而只有ngx_epoll_module模塊的actions.init接口不為空,因此會調用ngx_epoll_init,在該函數里面掉了epoll_create 創(chuàng)建了epoll,并將值賦給全局的ep

static int                  ep = -1;

在這里插入圖片描述

二、連接的創(chuàng)建

2.1 創(chuàng)建管理連接及其讀寫事件的數組

依然在ngx_event_process_init函數中,數組大小為配置文件中 worker_connections 的值

static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
	...
	cycle->connections =
        ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
    if (cycle->connections == NULL) {
        return NGX_ERROR;
    }

    c = cycle->connections;
    //分配讀事件
    cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
                                   cycle->log);
    if (cycle->read_events == NULL) {
        return NGX_ERROR;
    }

    rev = cycle->read_events;
    for (i = 0; i < cycle->connection_n; i++) {
        rev[i].closed = 1;
        rev[i].instance = 1;
    }
    //分配寫事件
    cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
                                    cycle->log);
    if (cycle->write_events == NULL) {
        return NGX_ERROR;
    }

    wev = cycle->write_events;
    for (i = 0; i < cycle->connection_n; i++) {
        wev[i].closed = 1;
    }   
}

2.2 創(chuàng)建一個空閑連接的鏈表

依然在ngx_event_process_init函數中,表示沒有使用的連接

static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
	...
   do {
        i--;

        c[i].data = next;
        c[i].read = &cycle->read_events[i];
        c[i].write = &cycle->write_events[i];
        c[i].fd = (ngx_socket_t) -1;

        next = &c[i];
    } while (i);

    cycle->free_connections = next;
    cycle->free_connection_n = cycle->connection_n;
    ...
}

此時數據結構如下

在這里插入圖片描述

2.3 遍歷所有的cycle->listening元素,并為其添加讀事件

static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
	...
    ls = cycle->listening.elts;
	for (i = 0; i < cycle->listening.nelts; i++) {
		...
		rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
                                                : ngx_event_recvmsg;
        ...                                        
 		if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }
        ...
 	}
 	...
}

這里特別的說明下cycle->listening在哪里設置?設置了什么信息?后面會有用。

三、http服務器端口的設置

在進入事件循環(huán)之前,nginx會先對配置文件做解析處理,也就是ngx_conf_parse中,當解析到http配置項時,會調用ngx_http_module模塊的配置項處理函數ngx_http_block,在其中做了很多http相關的設置

這是一個配置文件的讀取過程

3.1 cycle->listening元素的添加

函數調用過程如下:

ngx_http_block -> ngx_http_add_listening ->ngx_create_listening

最終調用ngx_create_listening

static ngx_listening_t *
ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
    ngx_listening_t           *ls;
	...
    ls = ngx_create_listening(cf, addr->opt.sockaddr, addr->opt.socklen);
    if (ls == NULL) {
        return NULL;
    }

    ls->addr_ntop = 1;
	//設置ngx_listening_t的 接口handler
    ls->handler = ngx_http_init_connection;
	...
}

該函數做了2件事

  1. 將監(jiān)聽信息放入cycle->listening數組
  2. 設置ngx_listening_t的 接口handler為ngx_http_init_connection(后面會用到)

下面是ngx_create_listening 函數,用來說明1

ngx_listening_t *
ngx_create_listening(ngx_conf_t *cf, struct sockaddr *sockaddr,
    socklen_t socklen)
{
	...
 	ls = ngx_array_push(&cf->cycle->listening);
 	...
}

3.2 開啟了服務器端口的監(jiān)聽

調用了ngx_open_listening_sockets函數,函數調用過程如下: 

ngx_init_cycle -> ngx_open_listening_sockets

看到bind 和listen就應該很快的反應過來這是一個服務器端的常用代碼,所以是在這里開啟了服務器端口的監(jiān)聽

ngx_int_t
ngx_open_listening_sockets(ngx_cycle_t *cycle)
{	

	...
		for (i = 0; i < cycle->listening.nelts; i++) {
			if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
				...			
			}
		    if (listen(s, ls[i].backlog) == -1) {
		    	...
		    }	
		}
	...
}

至此,服務器監(jiān)聽設置已經完成了,同時每個需要監(jiān)聽的端口到放入了cycle->listening。

我們再回到2.3,epoll開始將監(jiān)聽端口的連接放入了epoll管理,當客戶端來連接請求時,就會觸發(fā)epoll讀事件,這里要注意的是,其中rev代表的是nginx定義的事件類。

rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept: ngx_event_recvmsg;

四、執(zhí)行事件循環(huán)

讓我們繼續(xù)回到ngx_single_process_cycle函數,當模塊調用完init_process后,開始進入了一個死循環(huán)的過程,顯然這里應該會是一個事件循環(huán)入口,而我們使用epoll,那代碼最終肯定會定格到 epoll_wait,那接下來的目的就是找到這個代碼段,繼續(xù)往下看代碼

for ( ;; ) {
	ngx_process_events_and_timers(cycle);
}

ngx_process_events_and_timers函數如下:其中ngx_process_events 就是ngx_epoll_process_events函數

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    
    ...
    //epoll 等待
    (void) ngx_process_events(cycle, timer, flags);
   
    delta = ngx_current_msec - delta;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "timer delta: %M", delta);

    ngx_event_process_posted(cycle, &ngx_posted_accept_events);

    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

    ngx_event_expire_timers();

    ngx_event_process_posted(cycle, &ngx_posted_events);
}

ngx_epoll_process_events函數如下,去掉了大部分跟邏輯無關的代碼,做了以下工作

  • epoll_wait等待所有IO事件來臨
  • 遍歷觸發(fā)的事件列表,調用其handler接口,在這里因為我們是單進程,固沒有進程鎖,flags 始終為0
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
   	
    events = epoll_wait(ep, event_list, (int) nevents, timer);
    ...
    for (i = 0; i < events; i++) {
        c = event_list[i].data.ptr;

        instance = (uintptr_t) c & 1;
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
        rev = c->read;

        revents = event_list[i].events;

        if ((revents & EPOLLIN) && rev->active) {
            rev->ready = 1;
            rev->available = -1;

            if (flags & NGX_POST_EVENTS) {
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;

                ngx_post_event(rev, queue);

            } else {
                rev->handler(rev);
            }
        }
        wev = c->write;
        if ((revents & EPOLLOUT) && wev->active) {
            if (flags & NGX_POST_EVENTS) {
                ngx_post_event(wev, &ngx_posted_events);

            } else {
                wev->handler(wev);
            }
        }
    }
    return NGX_OK;
}

這就很明顯了,epoll事件循環(huán)體就在此,根據事件的觸發(fā)方式來區(qū)分讀寫事件,因為flags為0,所以這里對事件的處理都是調用了nginx定義的事件結構體ngx_event_t的handler接口,而不會調用ngx_post_event,因此接下來根據handler接口實現的不同來說明連接的建立過程及讀寫事件的處理

五、客戶端的連接的接入

先引入3.2節(jié)中描述的代碼,其中 c->type 就是SOCK_STREAM ,在其他地方賦值了,不在過多描述

rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept: ngx_event_recvmsg;

所以調用了ngx_event_accept,那該函數做了哪些事?下面貼出一段代碼來說明(忽略一些socket 的配置代碼)

void
ngx_event_accept(ngx_event_t *ev)
{
	ngx_listening_t   *ls;
	ngx_connection_t  *c, *lc;
	...
	lc = ev->data;
    ls = lc->listening;
	...
	c = ngx_get_connection(s, ev->log);
	...
	    c->recv = ngx_recv;
        c->send = ngx_send;
        c->recv_chain = ngx_recv_chain;
        c->send_chain = ngx_send_chain;

        c->log = log;
        c->pool->log = log;

        c->socklen = socklen;
        c->listening = ls;
        c->local_sockaddr = ls->sockaddr;
        c->local_socklen = ls->socklen;
	...
   	if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
            if (ngx_add_conn(c) == NGX_ERROR) {
                ngx_close_accepted_connection(c);
                return;
            }
   	}
	ls->handler(c);
	...
}

5.1 取出一個未使用的連接,并將該連接納入epoll去管理

從連接池中拿出一個連接,在調用ngx_add_conn 宏,此時該連接已經將socket的描述符fd設置進去了其中ngx_add_conn 定義如下

#define ngx_add_conn         ngx_event_actions.add_conn

而ngx_event_actions是一個全局的變量,在ngx_epoll_init函數 賦值

static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
	...
    ngx_event_actions = ngx_epoll_module_ctx.actions;
    ...
}

因此ngx_add_conn就是ngx_epoll_add_connection函數,就是將客戶端的連接納入epoll的管理,如下

static ngx_int_t
ngx_epoll_add_connection(ngx_connection_t *c)
{
    struct epoll_event  ee;

    ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP;
    ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);

    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
                   "epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);

    if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
        return NGX_ERROR;
    }

    c->read->active = 1;
    c->write->active = 1;

    return NGX_OK;
}

注意看,在上面函數中,epoll事件的data已經指向了 nginx里面定義的連接類ngx_connection_t 了,這也就將epoll中的事件與nginx定義的ngx_connection_t 關聯起來了

在這里插入圖片描述

至此,epoll開始循環(huán)監(jiān)聽每一個連接的事件了。

5.2 設置了連接的發(fā)送和接收函數

	    c->recv = ngx_recv;
        c->send = ngx_send;

這里的ngx_recv

//recv 函數宏定義
#define ngx_recv             ngx_io.recv

//ngx_io為全局定義
ngx_os_io_t  ngx_io;

//被該結構體賦值,接收發(fā)送函數就很明顯了
ngx_os_io_t ngx_os_io = {
    ngx_unix_recv,
    ngx_readv_chain,
    ngx_udp_unix_recv,
    ngx_unix_send,
    ngx_udp_unix_send,
    ngx_udp_unix_sendmsg_chain,
    ngx_writev_chain,
    0
};

ngx_unix_recv

5.3 調用監(jiān)聽的連接類的接口handler

這個干了什么?有點懵。往前看 3.1節(jié),在解析配置文件時,設置了該接口,其實就是ngx_http_init_connection函數,在其中設置了 客戶端連接類的讀寫事件接口分別是ngx_http_wait_request_handler 以及ngx_http_empty_handler,就不展開了

void
ngx_http_init_connection(ngx_connection_t *c)
{
	rev = c->read;
    rev->handler = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;
}

總結

一:解析配置文件,確認需要監(jiān)聽的端口(配置信息),也就是需要listen幾個fd,同時設置該監(jiān)聽端口的handler接口,將需要listen的端口信息放入cycle->listen,并設置了監(jiān)聽接口體的接口為ngx_http_init_connection

二:調用epoll模塊的接口,初始化及創(chuàng)建epoll

三:將需要監(jiān)聽的端口(一種描述的)納入epoll管理,同時設置其回調接口ngx_event_accept

四:當檢測到客戶端的連接調用ngx_event_accept,在該函數中將客戶端的連接又納入epoll管理(其實這是epoll模型的一種常用的寫法),同時調用了ngx_http_init_connection函數,該函數設置了客戶端連接的讀寫事件處理接口handler

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • nginx去掉前端配置的路徑前綴兩種情況

    nginx去掉前端配置的路徑前綴兩種情況

    今天接到一個配置nginx的需求是,需要訪問某個域名時,nginx可以去掉前綴去代理訪問到后端,這篇文章主要給大家介紹了關于nginx去掉前端配置的路徑前綴的兩種情況,需要的朋友可以參考下
    2024-05-05
  • PHP的Symfony和CodeIgniter框架的Nginx重寫規(guī)則配置

    PHP的Symfony和CodeIgniter框架的Nginx重寫規(guī)則配置

    這篇文章主要介紹了PHP的Symfony和CodeIgniter框架的Nginx重寫規(guī)則配置,文中截取配置中關鍵的一些rewrite寫法進行講解,需要的朋友可以參考下
    2016-01-01
  • nginx將https協議反向代理到http協議請求上的實現

    nginx將https協議反向代理到http協議請求上的實現

    本文主要介紹了nginx將https協議反向代理到http協議請求上的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-10-10
  • Nginx中部署Angular項目遇到的坑巨坑

    Nginx中部署Angular項目遇到的坑巨坑

    這篇文章主要介紹了Nginx中部署Angular項目遇到的坑巨坑,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-10-10
  • 詳解nginx 配置多個tomcat共用80端口

    詳解nginx 配置多個tomcat共用80端口

    本篇文章主要介紹了nginx 配置多個tomcat共用80端口,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • nginx攔截非法host或直接使用ip訪問的請求的實現

    nginx攔截非法host或直接使用ip訪問的請求的實現

    非法攔截可以有效防止直接通過IP或本地綁定域名的方式進行非法訪問,本文主要介紹了nginx攔截非法host或直接使用ip訪問的請求的實現,具有一定的參考價值,感興趣的可以了解一下
    2024-12-12
  • Nginx更新SSL證書的實現步驟

    Nginx更新SSL證書的實現步驟

    本文主要介紹了Nginx更新SSL證書的實現步驟,包括下載新證書、備份舊證書、配置新證書、驗證配置及遇到問題時的解決方法,感興趣的了解一下
    2025-12-12
  • nginx禁止某個IP訪問站點的設置方法

    nginx禁止某個IP訪問站點的設置方法

    近期發(fā)現博客遭到某些人的惡意灌水,頻繁地利用發(fā)帖機器人發(fā)表評論,給博客的管理帶來諸多不便,搜索了一下資料,可以利用nginx的ngx_http_access_module 模塊設置允許/禁止哪些ip或ip段訪問站點。
    2010-12-12
  • Nginx中if語句的判斷條件與多條件判斷詳解

    Nginx中if語句的判斷條件與多條件判斷詳解

    這篇文章主要介紹了關于Nginx中if語句的判斷條件與多條件判斷的相關資料,文中給出了詳細的示例代碼,對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。
    2017-03-03
  • win2003下nginx 0.8.38 安裝配置備忘

    win2003下nginx 0.8.38 安裝配置備忘

    經不住蠱惑,決定在 Windows Server 2003 下安裝試用一下,并與 PHP 進行集成。
    2010-12-12

最新評論

晴隆县| 平安县| 乐东| 德州市| 虎林市| 皋兰县| 宁陕县| 罗定市| 太原市| 都匀市| 潞城市| 南皮县| 禄劝| 安徽省| 崇仁县| 榆林市| 卢湾区| 佳木斯市| 泰安市| 鹿邑县| 天门市| 雅江县| 山东省| 栖霞市| 突泉县| 小金县| 五常市| 景洪市| 宜川县| 勐海县| 金湖县| 特克斯县| 通辽市| 治县。| 呼图壁县| 济宁市| 三河市| 政和县| 三穗县| 杭锦后旗| 德安县|