libevent源碼學習(19):緩衝區抽象bufferevent

目錄

引言

bufferevent結構

bufferevent_private結構體

bufferevent結構體

創建一個基於socket的bufferevent

啓動bufferevent

EVUTIL_UPCAST宏函數

bufferevent水位設置

bufferevent從fd中讀取數據

bufferevent向fd中寫入數據

從bufferevent中讀出數據

向bufferevent中寫入數據

bufferevent的回調機制

設置bufferevent的回調函數

爲bufferevent的socket建立連接


以下源碼均基於libevent-2.0.21-stable。

引言

       在前文中說到,evbuffer結構是用來描述一個緩衝區的,而對於每一個fd來說,都對應於兩個緩衝區——一個用於讀,一個則用於寫。而對於每一個fd來說,要想對其緩衝區描述清楚,僅僅通過一個evbuffer結構是完全不夠的,因此,bufferevent就從fd的層面對緩衝區進行了抽象並封裝,用戶只需操作fd相應的bufferevent即可完成對fd的讀寫了。

       從另一個方面來說,如果直接對fd進行讀寫,那麼這種方式屬於“不帶緩衝”的讀寫fd;而如果對fd相應的bufferevent進行讀寫,那麼這種方式就屬於“帶讀寫緩衝”的讀寫fd。

bufferevent結構

       bufferevent的結構比較特殊,它和event_base不一樣:event_base的定義放在event_internal.h中,並不對用戶開放,對用戶開放的event.h中只有event_base的聲明,因此,用戶是無法去訪問event_base的任何成員的,也不能用event_base myeb;這樣的方式去創建一個event_base棧實例或者堆實例,要想創建就只能通過調用event_base_new這樣函數去創建;而在bufferevent中則不一樣了,可以看到在對用戶開放的bufferevent_struct.h文件中就定義了bufferevent的結構,這樣就使得用戶可以直接去訪問bufferevent中的這些變量,也可以直接創建一個bufferevent的棧實例或者堆實例,如下所示:

       那麼爲什麼bufferevent沒有像event_base那樣禁止用戶去創建實例或訪問成員呢?在bufferevent結構體前面有這樣一段話:

       也就是說,bufferevent從原則上來說是不應該把這些信息暴露給用戶的,但是爲了兼容之前版本的libevent,纔將bufferevent的信息暴露出來。不過bufferevent也做了一些工作來避免用戶訪問一部分信息,這就是bufferevent_private結構體:

bufferevent_private結構體

/** Parts of the bufferevent structure that are shared among all bufferevent
 * types, but not exposed in bufferevent_struct.h. */
struct bufferevent_private {
	/** The underlying bufferevent structure. */
	struct bufferevent bev;  //bufferevent_private對應的bufferevent

	/** Evbuffer callback to enforce watermarks on input. */
	struct evbuffer_cb_entry *read_watermarks_cb;   //輸入緩衝區達到高水位回調函數

	/** If set, we should free the lock when we free the bufferevent. */
	unsigned own_lock : 1;   

	/** Flag: set if we have deferred callbacks and a read callback is
	 * pending. */
	unsigned readcb_pending : 1;   //爲1表示bufferevent的readcb被延遲迴調
	/** Flag: set if we have deferred callbacks and a write callback is
	 * pending. */
	unsigned writecb_pending : 1;  //爲1表示bufferevent的writecb被延遲迴調
	/** Flag: set if we are currently busy connecting. */
	unsigned connecting : 1; //置爲1說明connect成功連接或者需要重試
	/** Flag: set if a connect failed prematurely; this is a hack for
	 * getting around the bufferevent abstraction. */
	unsigned connection_refused : 1;  //置爲1說明發生了connect發生了ECONNREFUSED錯誤
	/** Set to the events pending if we have deferred callbacks and
	 * an events callback is pending. */
	short eventcb_pending; //如果被設置,表示bufferevent的errorcb被延遲迴調,eventcb_pending可能是多種事件的合成
	//可爲BEV_EVENT_READING、BEV_EVENT_WRITING、BEV_EVENT_EOF、
	//BEV_EVENT_ERROR、BEV_EVENT_TIMEOUT、BEV_EVENT_CONNECTED
	
	/** If set, read is suspended until one or more conditions are over.
	 * The actual value here is a bitfield of those conditions; see the
	 * BEV_SUSPEND_* flags above. */
	bufferevent_suspend_flags read_suspended; //如果被設置,表明bufferevent的讀事件監聽被掛起,設置的值表示讀事件監聽被掛起的原因

	/** If set, writing is suspended until one or more conditions are over.
	 * The actual value here is a bitfield of those conditions; see the
	 * BEV_SUSPEND_* flags above. */
	bufferevent_suspend_flags write_suspended;//如果被設置,表明bufferevent的寫事件監聽被掛起,設置的值表示寫事件監聽被掛起的原因

	/** Set to the current socket errno if we have deferred callbacks and
	 * an events callback is pending. */
	int errno_pending;// socket出錯時的錯誤信息

	/** The DNS error code for bufferevent_socket_connect_hostname */
	int dns_error;

	/** Used to implement deferred callbacks */
	struct deferred_cb deferred; //用於插入到base的延遲迴調隊列,用於延遲迴調readcb、writecb和errorcb

	/** The options this bufferevent was constructed with */
	enum bufferevent_options options; //bufferevent的屬性

	/** Current reference count for this bufferevent. */
	int refcnt;//多線程情況下可能多個線程中同時使用bufferevent,爲了防止在某一個線程釋放bufferevent導致其它線程不能正常工作,就設置一個引用計數,當引用計數爲0表明沒有線程使用這個bufferevent了就可以釋放了

	/** Lock for this bufferevent.  Shared by the inbuf and the outbuf.
	 * If NULL, locking is disabled. */
	void *lock;//鎖變量

	/** Rate-limiting information for this bufferevent */
	struct bufferevent_rate_limit *rate_limiting;
};

      在該結構體開頭的英文說明中也提到了,bufferevent_private實際上就是bufferevent的一部分,但是並不對用戶開放,可以看到bufferevent_private中有一個成員就是bufferevent類型的。因此,就可以把bufferevent_private理解爲bufferevent的私有成員集合,而相應的“公開”的成員則都在bufferevent_private中bufferevent類型的變量中。

      接着再來看看bufferevent結構體。

bufferevent結構體

struct bufferevent {
	/** Event base for which this bufferevent was created. */
	struct event_base *ev_base;
	/** Pointer to a table of function pointers to set up how this
	    bufferevent behaves. */
	const struct bufferevent_ops *be_ops; //bufferevent的操作函數

	/** A read event that triggers when a timeout has happened or a socket
	    is ready to read data.  Only used by some subtypes of
	    bufferevent. */
	struct event ev_read;
	/** A write event that triggers when a timeout has happened or a socket
	    is ready to write data.  Only used by some subtypes of
	    bufferevent. */
	struct event ev_write;

	/** An input buffer. Only the bufferevent is allowed to add data to
	    this buffer, though the user is allowed to drain it. */
	struct evbuffer *input;   //輸入緩衝區

	/** An input buffer. Only the bufferevent is allowed to drain data
	    from this buffer, though the user is allowed to add it. */
	struct evbuffer *output;   //輸出緩衝區

	struct event_watermark wm_read;   //輸入緩衝區的高低水位
	struct event_watermark wm_write;  //輸出緩衝區的高低水位

	bufferevent_data_cb readcb;  //每次讀取結束後,如果輸入緩衝區的長度高於低水位,則調用readcb
	bufferevent_data_cb writecb; //每次寫出結束後,如果輸出緩衝區的長度低於低水位,則調用writecb
	/* This should be called 'eventcb', but renaming it would break
	 * backward compatibility */
	bufferevent_event_cb errorcb; //如果讀取或者寫入發生錯誤,就調用errorcb 
	void *cbarg; //readcb、writecb和errorcb的共用參數

	struct timeval timeout_read;  //ev_read的超時值
	struct timeval timeout_write;  //ev_write的超時值

	/** Events that are currently enabled: currently EV_READ and EV_WRITE
	    are supported. */
	short enabled;    //反映當前的bufferevent是可讀還是可寫
};

      其中主要有以下幾個非常重要的成員變量:

1.ev_read和ev_write:用來分別監聽讀事件和寫事件的event變量。bufferevent並沒有用一個成員變量去存儲它對應監聽的fd,這是沒有必要的,因爲event類型的ev_read和ev_write變量內部本身就需要指定監聽的fd;

2.input和output:evbuffer類型的兩個成員變量,分別對應於讀緩衝區和寫緩衝區;

3.wm_read和wm_write:event_watermark結構體類型變量,用來描述緩衝區的高低水位值,因此這兩個變量分別對應於讀緩衝區的高低水位以及寫緩衝區的高低水位;

4.readcb、writecb和errorcb:三者各自對應了一個回調函數,分別對應於讀緩衝區發生一次讀操作寫緩衝區發生一次寫操作以及發生特殊事件這三種情況。

     

     以上大概知道了bufferevent及bufferevent_private的構成,接下來就通過bufferevent的應用來分析一下各個成員的作用。

創建一個基於socket的bufferevent

      創建一個基於socket的bufferevent,是通過bufferevent_socket_new函數實現的,該函數定義如下:

struct bufferevent *
bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
    int options)
{
	struct bufferevent_private *bufev_p;
	struct bufferevent *bufev;

#ifdef WIN32
	if (base && event_base_get_iocp(base))
		return bufferevent_async_new(base, fd, options);
#endif

	if ((bufev_p = mm_calloc(1, sizeof(struct bufferevent_private)))== NULL)//分配一個新的bufferevent_private
		return NULL;
	//爲buffevent_pri中的bufferevent創建輸入輸出緩衝區,綁定event_base,設置bufferevent爲可寫狀態,保存bufferevent_ops_socket到bufferevent的options
	if (bufferevent_init_common(bufev_p, base, &bufferevent_ops_socket,
				    options) < 0) {//初始化綁定base和ops等信息到bufferevent_pri上
		mm_free(bufev_p);
		return NULL;
	}
	bufev = &bufev_p->bev;
	evbuffer_set_flags(bufev->output, EVBUFFER_FLAG_DRAINS_TO_FD);//添加flag
	//讀寫事件均關聯同一文件描述符
	event_assign(&bufev->ev_read, bufev->ev_base, fd,
	    EV_READ|EV_PERSIST, bufferevent_readcb, bufev);//設置讀事件回調函數等信息
	event_assign(&bufev->ev_write, bufev->ev_base, fd,
	    EV_WRITE|EV_PERSIST, bufferevent_writecb, bufev);//設置寫事件回調函數等信息

	evbuffer_add_cb(bufev->output, bufferevent_socket_outbuf_cb, bufev);//向輸出緩衝區的回調函數隊列中插入bufferevent_socket_outbuf_cb

	evbuffer_freeze(bufev->input, 0);//禁止從輸入緩衝區尾部添加數據,即不能從fd中讀取數據
	evbuffer_freeze(bufev->output, 1);//禁止從輸出緩衝區的頭部刪除或添加數據,即不能向fd中寫入數據

	return bufev;
}

int
bufferevent_init_common(struct bufferevent_private *bufev_private,
    struct event_base *base,
    const struct bufferevent_ops *ops,
    enum bufferevent_options options)
{
	struct bufferevent *bufev = &bufev_private->bev;//bufferevent包含在bufferevent_pri中

	if (!bufev->input) {//如果bufev_private中的bufferevent的輸入緩衝區沒有分配,那麼就重新分配
		if ((bufev->input = evbuffer_new()) == NULL)
			return -1;
	}

	if (!bufev->output) {//如果bufev_private中的bufferevent的輸出緩衝區沒有分配,那麼就重新分配
		if ((bufev->output = evbuffer_new()) == NULL) {
			evbuffer_free(bufev->input);//如果輸出緩衝區分配失敗,那麼就把銷燬剛纔分配成功的輸入緩衝區
			return -1;
		}
	}

	bufev_private->refcnt = 1;//bufferevent_pri的引用計數初始化爲1
	bufev->ev_base = base;//將base綁定到bufferevent上

	/* Disable timeouts. *///清空讀寫事件的定時器
	evutil_timerclear(&bufev->timeout_read);
	evutil_timerclear(&bufev->timeout_write);

	bufev->be_ops = ops;//綁定調用函數結構體

	/*
	 * Set to EV_WRITE so that using bufferevent_write is going to
	 * trigger a callback.  Reading needs to be explicitly enabled
	 * because otherwise no data will be available.
	 */
	bufev->enabled = EV_WRITE;//初始化爲可寫狀態

#ifndef _EVENT_DISABLE_THREAD_SUPPORT
	if (options & BEV_OPT_THREADSAFE) {//如果支持多線程並且設置了options爲線程安全
		if (bufferevent_enable_locking(bufev, NULL) < 0) {//爲bufferevent分配鎖
			/* cleanup */
			evbuffer_free(bufev->input);
			evbuffer_free(bufev->output);
			bufev->input = NULL;
			bufev->output = NULL;
			return -1;
		}
	}
#endif
	if ((options & (BEV_OPT_DEFER_CALLBACKS|BEV_OPT_UNLOCK_CALLBACKS))
	    == BEV_OPT_UNLOCK_CALLBACKS) {//BEV_OPT_DEFER_CALLBACKS和BEV_OPT_UNLOCK_CALLBACKS必須都設置
		event_warnx("UNLOCK_CALLBACKS requires DEFER_CALLBACKS");
		return -1;
	}
	if (options & BEV_OPT_DEFER_CALLBACKS) {//如果開啓延遲調用readcb、writecb和errorcb
		if (options & BEV_OPT_UNLOCK_CALLBACKS)
			event_deferred_cb_init(&bufev_private->deferred,
			    bufferevent_run_deferred_callbacks_unlocked,
			    bufev_private);//設置bufev_private->deferred的回調函數fn和參數arg
		else
			event_deferred_cb_init(&bufev_private->deferred,
			    bufferevent_run_deferred_callbacks_locked,
			    bufev_private);
	}

	bufev_private->options = options;
	//綁定buffevent到輸入輸出緩衝區的parent成員上
	evbuffer_set_parent(bufev->input, bufev);
	evbuffer_set_parent(bufev->output, bufev);

	return 0;
}

        可以看到,在bufferevent_socket_new中,主要對bufferevent中的成員進行了一系列初始化,並且該函數需要傳入一個fd,而這個fd,就應當是socket的fd了。前面說過,bufferevent監聽的fd實際上是體現在ev_read和ev_write中的,也就是說會把傳入的socket的fd用來設置到ev_read和ev_write中。整個函數主要做了以下幾件事:

這裏需要注意以下地方:

1.設置bufferevent_private的成員deferred的回調函數爲bufferevent_run_deferred_callbacks_locked/unlocked。這裏的deferred變量其實和evbuffer中的deferred變量的作用是相同的,都是用於開啓延遲迴調時,deferred被添加到event_base的defer_queue中,當主循環處理defer_queue中的deferred時就會調用這裏的bufferevent_run_deferred_callbacks_locked/unlocked函數。

2.設置ev_read和ev_write的回調函數分別爲bufferevent_readcb和bufferevent_writecb,也就是說,當bufferevent監聽的fd發送可讀事件或可寫事件時,就會去調用bufferevent_readcb或bufferevent_writecb函數;

3.爲輸出緩衝區outbuf的回調函數隊列中添加了bufferevent_socket_outbuf_cb函數,也就是說,當輸出緩衝區outbuf發生改變時,就總是會去處理回調隊列中的函數,bufferevent_socket_outbuf_cb也得以調用,該函數定義如下:

static void
bufferevent_socket_outbuf_cb(struct evbuffer *buf,
    const struct evbuffer_cb_info *cbinfo,
    void *arg)//輸出緩衝區的回調函數
{
	struct bufferevent *bufev = arg;
	struct bufferevent_private *bufev_p =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);

	if (cbinfo->n_added &&
	    (bufev->enabled & EV_WRITE) &&
	    !event_pending(&bufev->ev_write, EV_WRITE, NULL) &&
	    !bufev_p->write_suspended) {//如果輸出緩衝區增加了字符,並且輸出緩衝區可寫,並且沒有監聽ev_write的可寫事件,bufferevent的可寫事件監聽也沒有被掛起
		/* Somebody added data to the buffer, and we would like to
		 * write, and we were not writing.  So, start writing. */
		//如果向輸出緩衝區中寫入了數據,但是此時並沒有監聽fd的可寫事件,那麼現在就開始監聽可寫事件
		if (be_socket_add(&bufev->ev_write, &bufev->timeout_write) == -1) {
		    /* Should we log this? */
		}
	}
}

       該函數的主要作用是:由於在寫緩衝區寫出所有數據後會取消對可寫事件的監聽,因此該函數就可以在用戶向寫緩衝區中寫入數據後重新開始監聽可寫事件。

4.bufferevent的使能/禁用/銷燬等功能都集合在一個函數集合結構體bufferevent_ops_socket中,該結構體定義如上圖中所示,它包含了7個函數,當然這些函數都是內部使用的,並不對外開放,分別爲:

①evutil_offsetof(struct bufferevent_private, bev):獲取bufferevent_private結構體中bufferevent類型成員的偏移量;

②be_socket_enable:啓動bufferevent;

③be_socket_disable:禁用bufferevent;

④be_socket_destruct:銷燬bufferevent;

⑤be_socket_adj_timeouts:設置ev_read和ev_write的監聽超時;

⑥be_socket_flush:無任何作用;

⑦be_socket_ctrl:傳入BEV_CTRL_SET_FD或BEV_CTRL_GET_FD參數可設置或獲取bufferevent監聽的fd。  

5.bufferevent_socket_new還需要指定options,這是一個枚舉類型bufferevent_options,options有以下幾種選項:

①BEV_OPT_CLOSE_ON_FREE = (1<<0),   //是否在關閉bufferevent的時候也關閉bufferevent對應的fd

②BEV_OPT_THREADSAFE = (1<<1),  //是否使用鎖

③BEV_OPT_DEFER_CALLBACKS = (1<<2),  //是否延遲調用readcb、writecb和errorcb

④BEV_OPT_UNLOCK_CALLBACKS = (1<<3)    //延遲迴調時,是否取消鎖,該項必須與③共同使用

       一旦爲bufferevent_socket_new設置了BEV_OPT_DEFER_CALLBACKS,那麼bufferevent的readcb、writecb和errorcb都會被延遲調用,這與bufferevent的deferred成員息息相關。

啓動bufferevent

       通過bufferevent_socket_new,只是創建並初始化了一個bufferevent。通過前面已經知道,bufferevent實際上也是基於ev_read和ev_write這兩個事件通知來進行的。因此要想啓動bufferevent,那麼就必須要讓這兩個event處於監聽狀態,這就需要用到bufferevent_enable函數了:

int
bufferevent_enable(struct bufferevent *bufev, short event)//event參數可以爲EV_READ和EV_WRITE的組合,根據event來啓動相應的bufferevent監聽
{	//調用該函數後,bufferevent才正式開始工作
	struct bufferevent_private *bufev_private =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);//獲得bufferevent對應的bufferevent_pri
	short impl_events = event;
	int r = 0;

	_bufferevent_incref_and_lock(bufev);//上鎖並增加引用計數
	if (bufev_private->read_suspended)//如果是讀掛起,就取消讀事件監聽
		impl_events &= ~EV_READ;
	if (bufev_private->write_suspended)//如果是寫掛起,就取消寫事件監聽
		impl_events &= ~EV_WRITE;

	bufev->enabled |= event;

	if (impl_events && bufev->be_ops->enable(bufev, impl_events) < 0)//根據impl_events的結果來啓動bufferevent的監聽
		r = -1;

	_bufferevent_decref_and_unlock(bufev);//使用結束,解鎖並減少引用計數
	return r;
}


static int
be_socket_enable(struct bufferevent *bufev, short event)//添加讀監聽或寫監聽
{
	if (event & EV_READ) {//
		if (be_socket_add(&bufev->ev_read,&bufev->timeout_read) == -1)//添加讀事件監聽
			return -1;
	}
	if (event & EV_WRITE) {
		if (be_socket_add(&bufev->ev_write,&bufev->timeout_write) == -1)//添加寫事件監聽
			return -1;
	}
	return 0;
}

#define be_socket_add(ev, t)			\
	_bufferevent_add_event((ev), (t))

int
_bufferevent_add_event(struct event *ev, const struct timeval *tv)
{
	if (tv->tv_sec == 0 && tv->tv_usec == 0)
		return event_add(ev, NULL);
	else
		return event_add(ev, tv);
}

       在bufferevent_enable函數中會調用bufev->be_ops->enable,這實際上就是前面爲bufferevent綁定的操作函數集合中的enable函數,它被設置爲be_socket_enable,在該函數中又會分別以ev_read和ev_write作爲參數來調用be_socket_add函數,可以看到,這個函數最終還是調用的event_add函數來把ev_read和ev_write添加到event_base的事件監聽中。

       在此之後,bufferevent纔開始監聽fd的可讀和可寫事件,緩衝區的作用才能真正發揮出來。

EVUTIL_UPCAST宏函數

       在Libevent中,這個宏函數是經常用到的,它的調用形式基本如下所示:

struct bufferevent_private *bufev_p = EVUTIL_UPCAST(bev, struct bufferevent_private, bev);

      這裏傳入的三個參數分別爲bev,、struct bufferevent_private和bev,需要注意的是,第一個和第三個參數同名不同意,前者是傳入的bufferevent類型的參數bev,而後者是指的bufferevent_private中bufferevent類型的成員bev

      該宏函數的作用是:將bufferevent類型的變量向上轉換,轉換爲bufferevent_private類型。其宏定義如下:

#define EVUTIL_UPCAST(ptr, type, field)				\
	((type *)(((char*)(ptr)) - evutil_offsetof(type, field)))

#ifdef offsetof
#define evutil_offsetof(type, field) offsetof(type, field)
#else
#define evutil_offsetof(type, field) ((off_t)(&((type *)0)->field))
#endif

        先來看evutil_offsetof函數。傳入evutil_offsetof函數的兩個參數爲struct bufferevent_private和它的成員bev。這裏主要考慮#define evutil_offsetof(type, field) ((off_t)(&((type *)0)->field))的情況。宏定義替換後,evutil_offsetof宏函數內部就變成了((off_t)(&((struct bufferevent_private *)0)->bev)),這有什麼作用呢?

       首先,(struct bufferevent_private *)0就把0這個地址強制轉換爲了bufferevent_private 類型的指針,這那麼就可以通過這個指針去訪問bufferevent_private類型下的各個成員。接着再對成員bev取地址,那麼得到的就是這個bufferevent類型的成員bev的地址。而由於整個結構體的首地址是0,那麼bev的地址實際上也就是bev相對於整個bufferevent_private結構體的偏移量。因此,evutil_offsetof宏函數實際上就是得到bufferevent_private結構體中,bufferevent類型的成員bev的偏移量。

       接着再回到EVUTIL_UPCAST宏中,前面已經得到了evutil_offsetof的結果,接下來就需要用(char *)(ptr)減去這個結果,而ptr是傳入參數bufferevent *類型的bev,換句話說,這個ptr就是bev所指向的bufferevent類型變量的地址,從邏輯上來說,假設這個bufferevent變量是從屬於某一個bufferevent_private變量(Libevent中就是這樣),那麼用這個bufferevent類型變量的地址減去前面得到的bufferevent_private結構體中bufferevent類型成員的偏移量,得到的就是這個bufferevent類型變量對應的bufferevent_private變量的地址了。如圖所示:

       需要注意到的是,由於ptr本身是bufferevent *類型的,而前面evutil_offsetof的結果是一個off_t類型,在linux中,它實際上就是long類型。如果用ptr直接去減去這個off_t類型的值,那麼得到的結果實際上是ptr - sizeof(bufferevent) * evutil_offsetof,這個結果顯然不是我們想要的,因此就在做減運算之前將ptr臨時轉換爲char *類型,這樣計算結果就是ptr - sizeof(char) * evutil_offsetof。這樣就得到了bufferevent_private類型變量的地址,但是這只是值相同而已,要想對相應的變量進行操作,那麼就還需要對前面的結果執行(bufferevent_private *)強制轉換,這就是爲什麼EVUTIL_UPCAST宏前面會加上(type *)。

       綜上,EVUTIL_UPCAST宏的作用實際上就是根據一個bufferevent類型的變量,獲取指向其所對應的bufferevent_private變量的指針,實現了向上轉換。不過需要注意的是,由於在Libevent中,在創建每個bufferevent之前都是先創建bufferevent_private的,因此這樣的向上轉換是安全的;而在很多實際情況下,子結構體都是單獨定義,而不是從父結構體定義中得到的,這時如果執行向上轉換是非常不安全的。

bufferevent水位設置

       雖說是設置bufferevent的水位,實際上是設置bufferevent中讀寫緩衝區的水位,這一點可以參考水位機制。bufferevent的水位設置,是通過bufferevent_setwatermark函數實現的,該函數定義如下:

void
bufferevent_setwatermark(struct bufferevent *bufev, short events,
    size_t lowmark, size_t highmark)//events可以是讀或寫,設置相應讀/寫的高低水位,可以同時設置讀和寫的水位
{
	struct bufferevent_private *bufev_private =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);//獲得bufferevent對應的bufferevent_pri

	BEV_LOCK(bufev);
	if (events & EV_WRITE) {//設置寫高低水位
		bufev->wm_write.low = lowmark;
		bufev->wm_write.high = highmark;
	}

	if (events & EV_READ) {//設置讀高低水位
		bufev->wm_read.low = lowmark;
		bufev->wm_read.high = highmark;

		if (highmark) {//如果設置了讀高水位
			/* There is now a new high-water mark for read.
			   enable the callback if needed, and see if we should
			   suspend/bufferevent_wm_unsuspend. */

			if (bufev_private->read_watermarks_cb == NULL) {
				bufev_private->read_watermarks_cb =
				    evbuffer_add_cb(bufev->input,
						    bufferevent_inbuf_wm_cb,
						    bufev);//添加bufferevent_inbuf_wm_cb到輸入緩衝區的回調函數隊列中
			}
			evbuffer_cb_set_flags(bufev->input,
				      bufev_private->read_watermarks_cb,
				      EVBUFFER_CB_ENABLED|EVBUFFER_CB_NODEFER);

			if (evbuffer_get_length(bufev->input) > highmark)
				bufferevent_wm_suspend_read(bufev);//如果輸入緩衝區的長度以及超過了高水位就掛起buffrevent的讀事件監聽
			else if (evbuffer_get_length(bufev->input) < highmark)
				bufferevent_wm_unsuspend_read(bufev);//如果輸入緩衝區的長度還未達到高水位就恢復bufferevent的讀事件監聽
		} else {//如果設置高水位爲0
			/* There is now no high-water mark for read. */
			if (bufev_private->read_watermarks_cb)
				evbuffer_cb_clear_flags(bufev->input,
				    bufev_private->read_watermarks_cb,
				    EVBUFFER_CB_ENABLED);//取消EVBUFFER_CB_ENABLED,回調時就不會調用了
			bufferevent_wm_unsuspend_read(bufev);//恢復讀事件監聽
		}
	}
	BEV_UNLOCK(bufev);
}

      從函數中可以看出,該函數只是對讀緩衝區的高水位進行了特殊處理,它主要做了以下幾件事:

1.向讀緩衝區的回調隊列中添加bufferevent_inbuf_wm_cb函數,並將其綁定到bufferevent_private中的read_watermarks_cb成員,並設置read_watermarks_cb的flag爲NODEFER,表明該回調函數不會被延遲調用;

2.如果讀緩衝區的數據量高於高水位,就調用bufferevent_wm_suspend_read函數;

3.如果讀緩衝區的數據量低於高水位,就調用bufferevent_wm_unsuspend_read函數;

4.如果設置高水位爲0,就會直接調用bufferevent_wm_unsuspend_read函數。

       現在來看看bufferevent_wm_suspend_read和bufferevent_wm_unsuspend_read函數:

#define bufferevent_wm_suspend_read(b) \
	bufferevent_suspend_read((b), BEV_SUSPEND_WM)
#define bufferevent_wm_unsuspend_read(b) \
	bufferevent_unsuspend_read((b), BEV_SUSPEND_WM)


void
bufferevent_suspend_read(struct bufferevent *bufev, bufferevent_suspend_flags what)
{
	struct bufferevent_private *bufev_private =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);//獲取bufferevent對應的bufferevent_pri
	BEV_LOCK(bufev);
	if (!bufev_private->read_suspended)//如果沒有掛起讀事件監聽,就調用disable函數掛起,停止從fd中read
		bufev->be_ops->disable(bufev, EV_READ);
	bufev_private->read_suspended |= what;//記錄掛起的原因
	BEV_UNLOCK(bufev);
}

void
bufferevent_unsuspend_read(struct bufferevent *bufev, bufferevent_suspend_flags what)
{
	struct bufferevent_private *bufev_private =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
	BEV_LOCK(bufev);
	bufev_private->read_suspended &= ~what;//刪除導致掛起的該原因
	//如果此時read_suspended爲0,說明當前bufferevent讀事件監聽掛起,並且掛起原因是what,沒有其他原因導致掛起,此時就可以重新恢復讀事件監聽了
	//如果bufferevent是可監聽讀的狀態,就恢復讀事件監聽
	if (!bufev_private->read_suspended && (bufev->enabled & EV_READ))
		bufev->be_ops->enable(bufev, EV_READ);
	BEV_UNLOCK(bufev);
}

static int
be_socket_disable(struct bufferevent *bufev, short event)//刪除讀監聽或寫監聽
{
	struct bufferevent_private *bufev_p =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
	if (event & EV_READ) {//如果是由讀事件引起的
		if (event_del(&bufev->ev_read) == -1)//刪除讀事件的監聽
			return -1;
	}
	/* Don't actually disable the write if we are trying to connect. */
	if ((event & EV_WRITE) && ! bufev_p->connecting) {//如果是由寫事件引起的並且沒有連接
		if (event_del(&bufev->ev_write) == -1)//刪除寫事件的監聽
			return -1;
	}
	return 0;
}

       簡單來說,bufferevent_wm_suspend_read和bufferevent_wm_unsuspend_read函數會分別調用bufferevent操作函數集合中的disable和enable函數,enable函數前面說過,就是通過event_add來添加對相應事件的監聽,而disable函數則是相反,通過event_del來取消對相應事件的監聽。

       當調用bufferevent_wm_suspend_read函數時,不僅會需要對ev_read事件的監聽,還會設置read_suspended, 如果是由水位導致的掛起,那麼就會設置read_suspended爲BEV_SUSPEND_WM。換句話說,通過bufferevent_private的read_suspended成員可以知道是否掛起了讀事件的監聽,如果掛起了則read_suspended則記錄了掛起的原因。

bufferevent從fd中讀取數據

      從前面的分析可知,bufferevent通過ev_read來監聽fd的可讀事件,當可讀事件發生後,就會調用bufferevent_readcb函數,這個函數就會將數據從fd中讀取到緩衝區中,該函數定義如下:

static void
bufferevent_readcb(evutil_socket_t fd, short event, void *arg)//fd可讀就觸發該函數
{
	struct bufferevent *bufev = arg;
	struct bufferevent_private *bufev_p =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
	//向上轉換,bufferevent類型轉換爲bufferevent_pri類型,
	
	struct evbuffer *input;
	int res = 0;
	short what = BEV_EVENT_READING;
	ev_ssize_t howmuch = -1, readmax=-1;

	_bufferevent_incref_and_lock(bufev);//bufferevent引用計數加1並且加鎖

	if (event == EV_TIMEOUT) {//如果是超時激活就進行錯誤處理
		/* Note that we only check for event==EV_TIMEOUT. If
		 * event==EV_TIMEOUT|EV_READ, we can safely ignore the
		 * timeout, since a read has occurred */
		what |= BEV_EVENT_TIMEOUT;
		goto error;
	}

	input = bufev->input;

	/*
	 * If we have a high watermark configured then we don't want to
	 * read more data than would make us reach the watermark.
	 */
	if (bufev->wm_read.high != 0) {//如果設置了讀高水位
		howmuch = bufev->wm_read.high - evbuffer_get_length(input);//計算高水位與當前讀緩衝區的大小
		/* we somehow lowered the watermark, stop reading */
		if (howmuch <= 0) {//如果讀緩衝區的buffer大小已經達到了高水位
			bufferevent_wm_suspend_read(bufev);//掛起讀監聽事件後結束
			goto done;
		}
	}
	readmax = _bufferevent_get_read_max(bufev_p);//獲取最多能讀取的字節數,如果用戶設置了限速則以限速大小爲準,如果沒有設置,則默認爲16384
	if (howmuch < 0 || howmuch > readmax) /* The use of -1 for "unlimited"
					       * uglifies this code. XXXX */
		howmuch = readmax;//如果沒有設置讀高水位,或者是設置了高水位但是讀緩衝區的空間比readmax還大,那就以readmax爲準
	if (bufev_p->read_suspended)//如果讀事件被掛起,那就直接結束
		goto done;
	//接下來就開始從fd中讀取數據到輸入緩衝區中
	evbuffer_unfreeze(input, 0);//打開輸入緩衝區的尾部,即可以從fd中讀取數據到輸入緩衝區
	res = evbuffer_read(input, fd, (int)howmuch); /* XXXX evbuffer_read would do better to take and return ev_ssize_t */
	evbuffer_freeze(input, 0);//關閉輸入緩衝區的尾部,不可以從fd中讀取數據到輸入緩衝區了

	......
}

        該函數會通過_bufferevent_get_read_max來獲取最多可讀入的字節數,如果沒有設置限速的話,那麼默認爲16K。根據這個值去設置howmuch,不過最終並不一定就從fd中讀取howmuch個字節,具體讀取了多少,會由evbuffer_read函數返回。

bufferevent向fd中寫入數據

        bufferevent通過ev_write來監聽fd的可寫事件,當可寫事件發生,那麼就會調用bufferevent_write_cb函數,該函數定義如下:

static void
bufferevent_writecb(evutil_socket_t fd, short event, void *arg)
{
	struct bufferevent *bufev = arg;
	struct bufferevent_private *bufev_p =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
	int res = 0;
	short what = BEV_EVENT_WRITING;
	int connected = 0;
	ev_ssize_t atmost = -1;

	_bufferevent_incref_and_lock(bufev);

	......
	atmost = _bufferevent_get_write_max(bufev_p);//獲取由讀寫速率限制的最多一次能寫入的字節數,最多爲16K

	if (bufev_p->write_suspended)
		goto done;

	if (evbuffer_get_length(bufev->output)) {//如果輸出緩衝區中有數據
		evbuffer_unfreeze(bufev->output, 1);//打開輸出緩衝區的頭部
		res = evbuffer_write_atmost(bufev->output, fd, atmost);//按照最多能寫的字節數將輸出緩衝區的數據寫出到fd中
		evbuffer_freeze(bufev->output, 1);//關閉輸出緩衝區的頭部
		......
	}

	......
}

      在bufferevent_write_cb函數中,調用的_bufferevent_get_write_max與讀回調中相同,都是獲取最多一次能寫入的字節數,如果沒有設置限速,那麼就爲16K,然後再調用evbuffer_write_atmost把數據從緩衝區寫到fd中,可參考緩衝區寫出

從bufferevent中讀出數據

      bufferevent的存在,使得用戶讀入數據時不再直接從fd中讀入數據,而是直接從讀緩衝區中讀取數據,接口函數爲bufferevent_write,該函數定義如下:

size_t
bufferevent_read(struct bufferevent *bufev, void *data, size_t size)//從緩衝區中讀出數據到data
{
	return (evbuffer_remove(bufev->input, data, size));
}

int
evbuffer_remove(struct evbuffer *buf, void *data_out, size_t datlen)//從buf中讀取datlen個字節到data_out中
{
	ev_ssize_t n;
	EVBUFFER_LOCK(buf);
	n = evbuffer_copyout(buf, data_out, datlen);//從buf的頭部開始拷貝datlen個數據到data_out中
	if (n > 0) {
		if (evbuffer_drain(buf, n)<0)//從buf中抽取出剛剛拷貝了的數據,相當於刪除了這些數據
			n = -1;
	}
	EVBUFFER_UNLOCK(buf);
	return (int)n;
}

       可見,bufferevent_read只是簡單調用了evbuffer_remove函數,而在evbuffer_remove函數中,會先調用evbuffer_copyout函數來把需要讀取的數據讀出來,然後再調用evbuffer_drain函數來把緩衝區中被讀取的數據刪除掉。可參考從緩衝區讀出數據

向bufferevent中寫入數據

       類似的,用戶寫出數據時不再直接寫到fd中,而是把數據先寫到寫緩衝區中,然後再由寫緩衝區把數據寫出,接口函數爲bufferevent_write,該函數定義如下:

int
bufferevent_write(struct bufferevent *bufev, const void *data, size_t size)//向緩衝區中寫入data
{
	if (evbuffer_add(bufev->output, data, size) == -1)
		return (-1);

	return 0;
}

       bufferevent_write內部只是簡單的調用了evbuffer_add函數,可直接參考向緩衝區中添加數據

bufferevent的回調機制

       bufferevent的回調機制,實際上和evbuffer的回調機制是相同的,只不過bufferevent除了兩個緩衝區各自處理回調隊列之外,還需要處理三個回調函數:readcb、writecb和errorcb。

       回到bufferevent_readcb中,有以下部分代碼:

static void
bufferevent_readcb(evutil_socket_t fd, short event, void *arg)//fd可讀就觸發該函數
{
	......
	if (res == -1) { //如果從fd中讀取數據時出錯
		int err = evutil_socket_geterror(fd);
		if (EVUTIL_ERR_RW_RETRIABLE(err))//判斷出錯原因是否爲EINTR或者EAGAIN
			goto reschedule;
		/* error case */
		what |= BEV_EVENT_ERROR;  //如果不是以上兩種就需要進行錯誤處理
	} else if (res == 0) {//如果連接關閉
		/* eof case */
		what |= BEV_EVENT_EOF;
	}

	if (res <= 0)
		goto error;

	......
	if (evbuffer_get_length(input) >= bufev->wm_read.low)//如果輸入緩衝區的長度高於低水位,那麼就應該調用readcb了
		_bufferevent_run_readcb(bufev);

	goto done;

 reschedule:
	goto done;

 error:
	......
	_bufferevent_run_eventcb(bufev, what);//根據what來調用bufferevent的錯誤處理函數errorcb
        ......
}

       在上述代碼中,如果在一次數據讀取結束後,讀緩衝區的數據量低於低水位,說明此次讀取的數據太少,就忽略本次讀取;否則就會調用_bufferevent_run_readcb函數,該函數定義如下:

void
_bufferevent_run_readcb(struct bufferevent *bufev)
{
	/* Requires that we hold the lock and a reference */
	struct bufferevent_private *p =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
	if (bufev->readcb == NULL)
		return;
	if (p->options & BEV_OPT_DEFER_CALLBACKS) {//如果選擇延遲迴調
		p->readcb_pending = 1;//表示readcb延遲迴調
		if (!p->deferred.queued)//如果bufferevent_pri的deferred不在base的defer_cb中
			SCHEDULE_DEFERRED(p);//就把bufferevent_pri的deferred添加到defer_cb中
	} else {//如果不選擇延遲迴調
		bufev->readcb(bufev, bufev->cbarg);//立刻調用readcb
	}
}

#define SCHEDULE_DEFERRED(bevp)						\
	do {								\
		bufferevent_incref(&(bevp)->bev);			\
		event_deferred_cb_schedule(				\
			event_base_get_deferred_cb_queue((bevp)->bev.ev_base), \
			&(bevp)->deferred);				\
	} while (0)

        可以看到,如果bufferevent設置了BEV_OPT_DEFER_CALLBACKS,那麼就會啓動延遲迴調,由SCHEDULE_DEFERRED宏函數來把bufferevent_private的deferred成員放到event_base中,這一點和evbuffer的做法是幾乎一樣的。如果沒有開啓延遲迴調,那麼就會立刻調用readcb函數。

        在錯誤處理方面,如果讀取數據出錯,那麼what變量還會記錄出錯的類型。該變量在開頭設置爲BEV_EVENT_READING,如果讀取出錯時因爲對端連接關閉,那麼what就被設置爲BEV_EVENT_READING|BEV_EVENT_EOF,此時通過what變量就可以知道這是在讀取數據時發生了對端關閉的錯誤。最終會將what變量作爲參數調用_bufferevent_run_eventcb函數,該函數定義如下:

void
_bufferevent_run_eventcb(struct bufferevent *bufev, short what)
{
	/* Requires that we hold the lock and a reference */
	struct bufferevent_private *p =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
	if (bufev->errorcb == NULL)
		return;
	if (p->options & BEV_OPT_DEFER_CALLBACKS) {//如果選擇延遲調用errorcb
		p->eventcb_pending |= what;//事件類型
		p->errno_pending = EVUTIL_SOCKET_ERROR();//錯誤信息
		if (!p->deferred.queued)//如果bufferevent_pri的deferred不在base的defer_cb中就插入
			SCHEDULE_DEFERRED(p);
	} else {//如果不選擇延遲調用errorcb,就立刻調用
		bufev->errorcb(bufev, what, bufev->cbarg);
	}
}

        可以看到,它其實和readcb的調用是基本一致的。

        在bufferevent_writecb中,也是大致相同的:


static void
bufferevent_writecb(evutil_socket_t fd, short event, void *arg)
{
	......
	 //輸出緩衝區的數據低於低水位,就調用_bufferevent_run_writecb。
	 //如果是第一次發送,並且還沒有數據發出,即使低於低水位,也不會調用_bufferevent_run_writecb
	if ((res || !connected) &&
	    evbuffer_get_length(bufev->output) <= bufev->wm_write.low) {
		_bufferevent_run_writecb(bufev);
	}

	goto done;

 reschedule:
	if (evbuffer_get_length(bufev->output) == 0) { //如果發送緩衝區沒有數據了,就取消寫事件監聽
		event_del(&bufev->ev_write);
	}
	goto done;

 error:
	bufferevent_disable(bufev, EV_WRITE);
	_bufferevent_run_eventcb(bufev, what);

 done:
	_bufferevent_decref_and_unlock(bufev);
}

void
_bufferevent_run_writecb(struct bufferevent *bufev)
{
	/* Requires that we hold the lock and a reference */
	struct bufferevent_private *p =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
	if (bufev->writecb == NULL)
		return;
	if (p->options & BEV_OPT_DEFER_CALLBACKS) {//如果選擇延遲迴調writecb
		p->writecb_pending = 1;//表示writecb延遲調用
		if (!p->deferred.queued)//如果bufferevent_pri的deferred不在base的defer_cb中
			SCHEDULE_DEFERRED(p);//就把bufferevent_pri的deferred添加到defer_cb中
	} else {
		bufev->writecb(bufev, bufev->cbarg);//如果不選擇延遲迴調,就立刻調用writecb
	}
}

       對於bufferevent_writecb,則是當一次寫入操作結束後,如果寫緩衝區的數據量高於低水位,那麼說明此次寫出量太少,就忽略此次寫出,否則就會調用bufferevent_private的writecb函數,在該函數中,也會進行延遲迴調判斷然後再進行處理。

 

      當添加到event_base延遲迴調隊列中的deferred激活後,就會調用bufferevent_socket_new中爲其設置的回調函數bufferevent_run_deferred_callbacks_locked/unlocked,這裏以locked爲例,該函數定義如下:

static void
bufferevent_run_deferred_callbacks_locked(struct deferred_cb *_, void *arg)
{
	struct bufferevent_private *bufev_private = arg;
	struct bufferevent *bufev = &bufev_private->bev;

	BEV_LOCK(bufev);
	if ((bufev_private->eventcb_pending & BEV_EVENT_CONNECTED) &&
	    bufev->errorcb) {
		/* The "connected" happened before any reads or writes, so
		   send it first. */
		bufev_private->eventcb_pending &= ~BEV_EVENT_CONNECTED;
		bufev->errorcb(bufev, BEV_EVENT_CONNECTED, bufev->cbarg);
	}
	if (bufev_private->readcb_pending && bufev->readcb) {
		bufev_private->readcb_pending = 0;
		bufev->readcb(bufev, bufev->cbarg);
	}
	if (bufev_private->writecb_pending && bufev->writecb) {
		bufev_private->writecb_pending = 0;
		bufev->writecb(bufev, bufev->cbarg);
	}
	if (bufev_private->eventcb_pending && bufev->errorcb) {
		short what = bufev_private->eventcb_pending;
		int err = bufev_private->errno_pending;
		bufev_private->eventcb_pending = 0;
		bufev_private->errno_pending = 0;
		EVUTIL_SET_SOCKET_ERROR(err);
		bufev->errorcb(bufev, what, bufev->cbarg);
	}
	_bufferevent_decref_and_unlock(bufev);
}

       在該函數中,就對readcb、writecb和errorcb進行調用。由此也可以發現,如果爲bufferevent開啓延遲調用,那麼就只是延遲調用了readcb、writecb和errorcb三個函數,並不會影響讀寫緩衝區各自的回調函數隊列中的函數。如果要讓讀寫緩衝區中的回調函數也延遲迴調,那麼就還需要每個緩衝區都調用evbuffer_defer_callbacks纔行。

設置bufferevent的回調函數

       bufferevent特有的三個回調函數readcb、writecb和errorcb,這是留給用戶自定義的。當讀緩衝區從fd中讀取數據後,如果讀取是有效的,那麼就會自動回調用戶設置的readcb函數,從邏輯上來說在readcb函數中應該使用bufferevent_read來讀取緩衝區中的數據,從另一方面也可以看到,只有當fd可讀時,讀緩衝區纔會從fd中讀取數據,數據讀取結束後纔會觸發readcb函數,如果在readcb中沒有將讀緩衝區讀乾淨,那麼也不會再觸發readcb函數了,只有等到fd下一次可讀的時候,纔有可能再次觸發readcb函數。因此,bufferevent的讀是一個邊沿觸發行爲。相同的寫也是一樣,這裏就不多說了。

       由bufferevent_setcb函數設置,該函數定義如下:

void
bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg)
{
	BEV_LOCK(bufev);

	bufev->readcb = readcb;
	bufev->writecb = writecb;
	bufev->errorcb = eventcb;

	bufev->cbarg = cbarg;
	BEV_UNLOCK(bufev);
}

爲bufferevent的socket建立連接

       通過bufferevent_socket_connect可以將bufferevent監聽的socket與對端進行連接,該函數需要傳入對端的相關信息,調用該函數後,如果未出錯,那麼bufferevent所監聽的fd將自動與對端連接。該函數定義如下:

int
bufferevent_socket_connect(struct bufferevent *bev,
    struct sockaddr *sa, int socklen)  //對bufferevent監聽的socket發起連接,如果傳入的bufferevent的socket爲負數非法,就重新創建一個socket再進行連接
{//不僅會對socket發起連接,還會添加監聽,包含了bufferevent_enable的功能
	struct bufferevent_private *bufev_p =
	    EVUTIL_UPCAST(bev, struct bufferevent_private, bev);

	evutil_socket_t fd;
	int r = 0;
	int result=-1;
	int ownfd = 0;

	_bufferevent_incref_and_lock(bev);

	if (!bufev_p)
		goto done;

	fd = bufferevent_getfd(bev);  //獲取bufferevent監聽的文件描述符
	if (fd < 0) { //如果fd不合法,就重新創建一個socket
		if (!sa)
			goto done;
		fd = socket(sa->sa_family, SOCK_STREAM, 0);
		if (fd < 0)
			goto done;
		if (evutil_make_socket_nonblocking(fd)<0) //設置爲非阻塞
			goto done;
		ownfd = 1;
	}
	if (sa) {

		r = evutil_socket_connect(&fd, sa, socklen);  //將socket與對端進行連接
		if (r < 0)//r爲-1表示發生了其他錯誤
			goto freesock;
	}

	bufferevent_setfd(bev, fd);  //設置bufferevent的fd
	if (r == 0) {//表示連接的握手請求已經發起
		if (! be_socket_enable(bev, EV_WRITE)) {//添加ev_write進行可寫事件監聽,由於握手請求已經發起,如果fd可寫調用writecb,說明已經連接成功了
			bufev_p->connecting = 1;
			result = 0;
			goto done;
		}
	} else if (r == 1) {//表示連接成功
		/* The connect succeeded already. How very BSD of it. */
		result = 0;
		bufev_p->connecting = 1;
		event_active(&bev->ev_write, EV_WRITE, 1); //激活可寫事件
	} else {//表示連接被拒絕(發生了ECONNREFUSED錯誤)
		/* The connect failed already.  How very BSD of it. */
		bufev_p->connection_refused = 1;//表示發生了ECONNREFUSED錯誤
		bufev_p->connecting = 1;
		result = 0;
		event_active(&bev->ev_write, EV_WRITE, 1); //激活可寫事件
	}
	//都需要在writecb中檢查是否真的連接成功,再進行數據寫入操作

	goto done;

freesock:
	_bufferevent_run_eventcb(bev, BEV_EVENT_ERROR);//執行事件處理函數errorcb
	if (ownfd)
		evutil_closesocket(fd);
	/* do something about the error? */
done:
	_bufferevent_decref_and_unlock(bev);
	return result;
}

int
evutil_socket_connect(evutil_socket_t *fd_ptr, struct sockaddr *sa, int socklen)
{
	int made_fd = 0;

	if (*fd_ptr < 0) { //如果傳入的文件描述符非法,就重新創建一個非阻塞的socket
		if ((*fd_ptr = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
			goto err;
		made_fd = 1;
		if (evutil_make_socket_nonblocking(*fd_ptr) < 0) {
			goto err;
		}
	}

	if (connect(*fd_ptr, sa, socklen) < 0) {  //進行連接
		int e = evutil_socket_geterror(*fd_ptr); //連接失敗獲取錯誤
		if (EVUTIL_ERR_CONNECT_RETRIABLE(e)) //表明只是發起了連接
			return 0;
		if (EVUTIL_ERR_CONNECT_REFUSED(e)) //發生ECONNREFUSED錯誤
			return 2;
		goto err;
	} else {
		return 1;
	}

err:
	if (made_fd) {
		evutil_closesocket(*fd_ptr); //關閉套接字
		*fd_ptr = -1;
	}
	return -1;
}

#define EVUTIL_ERR_CONNECT_RETRIABLE(e)			\ 
	((e) == EINTR || (e) == EINPROGRESS)

#define EVUTIL_ERR_CONNECT_REFUSED(e)					\
	((e) == ECONNREFUSED)

       在bufferevent_socket_connect函數內部,是通過evutil_socket_connect來進行socket連接的。在Libevent中,使用的文件描述符大多數都是非阻塞的,這裏的socket也不例外。對於非阻塞connect來說,如果返回0,說明連接已經建立;如果返回-1,那麼就需要注意以下幾點:如果socket error爲EINPROGRESS,說明連接已經發起,不過還沒有建立,在這種情況下應該重新嘗試連接;如果error爲EINTR,說明連接被其他信號中斷,也應該重新嘗試連接;如果error爲ECONNREFUSED,那麼說明連接被拒絕。前面兩個都對應於EVUTIL_ERR_CONNECT_RETRIABLE宏,最後一個則對應於ECONNREFUSED,因此,當evutil_socket_connect函數返回1時,說明連接已經成功,返回0時說明連接已經發起不過還沒有成功,返回2時,則說明連接失敗。在UNP v2 P351中提到以下一段話:

       因此,對於前面所說的連接已經發起,不過還未成功建立的情況(r=0),此時就會對fd的可寫事件進行監聽,如果可寫事件觸發,說明連接建立,此時就可以從緩衝區向對端寫入數據了;如果連接已經成功建立(r=1),那麼說明此時可以直接寫數據,因此直接激活寫事件即可;如果連接被拒絕(r=2),依然是手動激活寫事件(這裏只是爲了在bufferevent_writecb中對連接錯誤進行統一處理,即刪除可寫事件監聽)。

       當socket發生可寫事件時,由於連接遇到錯誤時socket也可寫,因此可寫不一定就說明此時的socket連接上了。還要對可寫狀態下的socket進行進一步的判斷,而這一步判斷則是在可寫事件回調函數中進行。在前面說過,bufferevent監聽可寫事件是通過ev_write來實現的,ev_write的回調函數是bufferevent_writecb,相關代碼如下所示:

static void
bufferevent_writecb(evutil_socket_t fd, short event, void *arg)
{
	struct bufferevent *bufev = arg;
	struct bufferevent_private *bufev_p =
	    EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
	int res = 0;
	short what = BEV_EVENT_WRITING;
	int connected = 0;
	ev_ssize_t atmost = -1;

	_bufferevent_incref_and_lock(bufev);

	......

	if (bufev_p->connecting) {//如果需要檢查是否建立連接了
		int c = evutil_socket_finished_connecting(fd);
		//返回1說明沒有錯誤,也就是連接成功了
		//返回0說明發生了
		/* we need to fake the error if the connection was refused
		 * immediately - usually connection to localhost on BSD */
		if (bufev_p->connection_refused) {//如果發生了ECONNREFUSED錯誤,就設置c爲-1,就可以直接結束了
		  bufev_p->connection_refused = 0;  //重置爲0
		  c = -1;
		}

		if (c == 0) //表示連接還沒完成
			goto done;  //不重置bufev_p->connecting直接結束
		//c=0說明連接可能還在進行但是並沒有完成,那麼bufev_p->connecting就不重置,在下次writecb中重新判斷是否連接成功
		//c=1說明連接被拒絕,就會把bufev_p->connecting重置爲0,後面writecb也不會再重新判斷了
		bufev_p->connecting = 0;
		if (c < 0) {  //如果還有其它錯誤說明連接失敗就刪除讀寫事件的監聽
			event_del(&bufev->ev_write);
			event_del(&bufev->ev_read);
			_bufferevent_run_eventcb(bufev, BEV_EVENT_ERROR);
			goto done;
		} else {  //連接成功
			connected = 1; //表示首次連接成功

			_bufferevent_run_eventcb(bufev,
					BEV_EVENT_CONNECTED);//調用bufferevent的事件處理函數,表示首次連接成功
			if (!(bufev->enabled & EV_WRITE) ||
			    bufev_p->write_suspended) {//如果bufferevent不是可寫的狀態或者寫事件監聽被掛起,就刪除寫監聽
				event_del(&bufev->ev_write);
				goto done;
			}
		}
	}
	......
}

int
evutil_socket_finished_connecting(evutil_socket_t fd)
{
	int e;
	ev_socklen_t elen = sizeof(e);

	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&e, &elen) < 0)//獲取套接字的錯誤狀態,放到e中
		return -1;

	if (e) {//如果檢測到錯誤
		if (EVUTIL_ERR_CONNECT_RETRIABLE(e))//如果錯誤爲EINTR或者是EINPROGRESS,也不能說明連接失敗,那麼連接應該重試
			return 0;
		EVUTIL_SET_SOCKET_ERROR(e);//非EINTR或EINPROGRESS的其他錯誤
		return -1;
	}

	return 1;//沒有檢測到錯誤,說明連接已經成功了
}

       調用bufferevent_writecb,說明此時fd可寫,應該向其寫入數據了。而在寫入數據之前,則還要對socket的連接狀態進行確認。這裏就會使用evutil_socket_finished_connecting再次來判斷連接狀態,返回0和返回-1都表示連接沒有成功,不過前者表明連接應該重試,並不會刪除可寫事件的監聽,而後者則表明連接出現錯誤(包括前面的ECONNREFUSED),會立刻刪除可寫事件的監聽;如果返回1,說明連接成功建立,此時就只需要查看bufferevent是否處於可寫狀態(enable變量)或者寫事件監聽是否被掛起(write_suspend),如果允許執行寫操作,那麼接下來纔會進行數據寫入操作。

總結

       對於用戶來說,bufferevent大致流程如下所示:

 

 

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