Libevent源碼分析 (1) hello-world


⑨月份接觸了久聞大名的libevent,當時想讀讀源碼,可是由於事情比較多一直沒有時間,現在手頭的東西基本告一段落了,我準備讀讀libevent的源碼,凡是我覺得有必要的內容均一一記錄,與君共勉。

首先要說說什麼是libevent:

libevent是一個事件通知庫,libevent API提供一種機制使得我們可以在一個文件描述符(file descriptor)發生特定事件時或者timeout發生時執行指定的回調函數。libevent意圖代替事件驅動服務器上的事件循環。應用程序只需要調用event_dispatch(),然後動態添加或者刪除事件,而不需要修改事件循環

1.構建環境

  • libevent 2.1.x
  • windows10(linux的epoll以後再說,先看看libevent對於IOCP的包裝吧)

如何構建環境可以參見官方教程Build and Install,非常簡單,基本上可以直接cmake。 由於我構建的環境不使用openssl,所以option(EVENT__DISABLE_OPENSSL OFF)要修改爲 option(EVENT__DISABLE_OPENSSL ON),然後直接生成解決方案。

2. 相逢#pragram push_macro

如果你和我一樣編譯過程中遇到error: static declaration of 'strtok_r' follows non-static declaration可以在前面加上一行#define EVENT__HAVE_STRTOK_R。 重點是這裏我學到了一個沒用過的功能

#define A "Hello"
    std::cout << A << std::endl;        //Hello
#pragma push_macro("A")
#define A "World"
#pragma push_macro("A")
#define A ":)"
    std::cout << A << std::endl;        //:)
#pragma pop_macro("A")
    std::cout << A << std::endl;        //World
#pragma pop_macro("A")
    std::cout << A << std::endl;        //Hello

注意別漏了A的雙引號。可以看出push_macro/pop_macro的確是實現了棧的效果。爲了再確認一下我們看看_clang_的實現:

///   #pragma push_macro("macro")
void Preprocessor::HandlePragmaPushMacro(Token &PushMacroTok) {
  ...
  IdentifierInfo *IdentInfo = ParsePragmaPushOrPopMacro(PushMacroTok);
  // Get the MacroInfo associated with IdentInfo.
  MacroInfo *MI = getMacroInfo(IdentInfo);
  // Push the cloned MacroInfo so we can retrieve it later.
  PragmaPushMacroInfo[IdentInfo].push_back(MI);
}
///   #pragma pop_macro("macro")
void Preprocessor::HandlePragmaPopMacro(Token &PopMacroTok) {
  ...
  // Find the vector<MacroInfo*> associated with the macro.
  llvm::DenseMap<IdentifierInfo*, std::vector<MacroInfo*> >::iterator iter =
    PragmaPushMacroInfo.find(IdentInfo);
  if (iter != PragmaPushMacroInfo.end()) {

    // Pop PragmaPushMacroInfo stack.
    iter->second.pop_back();
    if (iter->second.empty())
	  PragmaPushMacroInfo.erase(iter);
 
  }
}

3. libevent的hello-world

回到主題,這系列文章從libevent/sample/hello-world.c開始。hello-world.c是一個日常socket IO程序,當客戶端通過9995端口與服務器連接後服務器持續發送Hello, World!,不過現在用的是libevent事件回調實現的。

int
main(int argc, char **argv)
{
	struct event_base *base;
	struct evconnlistener *listener;
	struct event *signal_event;

    struct sockaddr_in sin;
#ifdef _WIN32	//在win上需要用WAStartup初始化winsock dll才能使用socket
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

///////////////////////////////////////////////////////////////////////////
/// 1.event_base_new使用默認設置創建一個指向event_base的指針
///////////////////////////////////////////////////////////////////////////
	base = event_base_new();
	if (!base) {
		fprintf(stderr, "Could not initialize libevent!\n");
		return 1;
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);

///////////////////////////////////////////////////////////////////////////
/// 2.evconnlistener_new_bind分配一個connection監聽對象,當有新TCP連接時執行
/// listener_cb回調
///////////////////////////////////////////////////////////////////////////
	listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
	    LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
	    (struct sockaddr*)&sin,
	    sizeof(sin));

	if (!listener) {
		fprintf(stderr, "Could not create a listener!\n");
		return 1;
	}
///////////////////////////////////////////////////////////////////////////
/// 3. evsignal_new是一個#define evsignal_new(b, x, cb, arg) \
///                   event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
///////////////////////////////////////////////////////////////////////////
	signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);

	if (!signal_event || event_add(signal_event, NULL)<0) {
		fprintf(stderr, "Could not create/add a signal event!\n");
		return 1;
	}
///////////////////////////////////////////////////////////////////////////
/// 4. event_base_dispatch等價於event_base_loop(event_base, 0),當一切準備就緒
/// 後就可以執行它,event_base_dispatch會一直運行,直到沒有任何註冊事件或者用戶調用
/// event_base_loopexit
///////////////////////////////////////////////////////////////////////////
	event_base_dispatch(base);

///////////////////////////////////////////////////////////////////////////
/// 5. 堆釋放
///////////////////////////////////////////////////////////////////////////
	evconnlistener_free(listener);
	event_free(signal_event);
	event_base_free(base);

	printf("done\n");
	return 0;
}

上面就是一個libevent的通用模板:首先創建event_base,然後綁定服務器socket監聽端口並註冊事件回調,最後開啓事件循環,剩下的工作就是編寫各個事件的回調。 當然別忘了釋放內存。

4.提前結束

歸納一下hello-world用到的libevent APIs:


這系列文章要分析的就是上圖的四個函數,它們包含了libevent一個完整的生命週期。最開始我準備所有內容寫到一篇,後面發現實在太長了,讀起來累,邏輯也混亂(函數調用棧太長了),所以我打算逐章分析它們。 enjoy!

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