?追根究底?Libevent內(nèi)部實(shí)現(xiàn)原理初探

?追根究底?Libevent內(nèi)部實(shí)現(xiàn)原理初探

Libevent確實(shí)方便了開發(fā)人員食呻,對(duì)于定時(shí)器、信號(hào)處理、關(guān)心的文件或者套接字搁进,只需要掛載到event_base上面浪感,設(shè)置好對(duì)應(yīng)的回調(diào)函數(shù)和參數(shù)就可以了,當(dāng)對(duì)應(yīng)的事件發(fā)生時(shí)饼问,Libevent會(huì)自動(dòng)調(diào)度相應(yīng)的回調(diào)函數(shù)進(jìn)行處理。

本文就按照之前在sshinner(https://github.com/taozhijiang/sshinner)中使用Libevent的過程揭斧,以這些接口函數(shù)作為突破點(diǎn)莱革,沿著代碼走了一朝,嘗試探究一下Libevent的內(nèi)部工作流程是怎樣的讹开。由于本人能力有限盅视,有些東西可能不夠詳盡或者準(zhǔn)確,還望不吝指出旦万。

一闹击、創(chuàng)建event_base

struct event_base * main_base = event_base_new(void);

主要是根據(jù)參數(shù),創(chuàng)建event_base結(jié)構(gòu)體成艘,然后初始化一些數(shù)據(jù)赏半,如果對(duì)默認(rèn)的參數(shù)不滿意需要個(gè)性化配置的話,可以先創(chuàng)建event_config淆两,然后調(diào)用event_base_new_with_config來創(chuàng)建断箫。其中在eventops這個(gè)變量中,按照優(yōu)先級(jí)順序排序羅列了常見的IO復(fù)用模型秋冰,比如kqueue仲义、epoll、poll剑勾、select等埃撵,由于Libevent是跨平臺(tái)的,這些IO復(fù)用在有些平臺(tái)可能是不可用的虽另,同時(shí)你還可以在event_config中選擇過濾某些不想要的模型暂刘。

當(dāng)選定了某個(gè)IO復(fù)用模型之后,其操作結(jié)構(gòu)eventop就被添加到base->evsel中洲赵,然后調(diào)用其特定的init初始化函數(shù)鸳惯。這些操作跟文件系統(tǒng)file_operations結(jié)構(gòu)極為的類似。

那我們接著跟下去叠萍,看看大名鼎鼎的epoll類提供了哪些操作吧:

const struct eventop epollops = {

"epoll",

epoll_init,

epoll_nochangelist_add,

epoll_nochangelist_del,

epoll_dispatch,

epoll_dealloc,

1, /* need reinit */

EV_FEATURE_ET|EV_FEATURE_O1|EV_FEATURE_EARLY_CLOSE,

0

};

在初始化函數(shù)epoll_init當(dāng)中芝发,基本就類似epoll使用時(shí)候標(biāo)準(zhǔn)化的準(zhǔn)備工作:首先調(diào)用epoll_create創(chuàng)建epfd,然后預(yù)先創(chuàng)建INITIAL_NEVENT(32)個(gè)空間用于存放epoll_event苛谷,如果使用了timerfd辅鲸,則再調(diào)用timerfd_create創(chuàng)建對(duì)應(yīng)的timerfd。最后這些fd以及epoll_event都存放在struct epollop當(dāng)中腹殿,然后作為epoll_init函數(shù)的返回保存在base->evbase上独悴。

struct epollop {

struct epoll_event *events; ? ?//數(shù)組

int nevents;

int epfd;

#ifdef USING_TIMERFD

int timerfd;

#endif

};

在創(chuàng)建event_base的最后例书,還調(diào)用了event_base_priority_init進(jìn)行了一個(gè)初始化操作,如果有多個(gè)優(yōu)先級(jí)刻炒,就有對(duì)應(yīng)的多個(gè)等待隊(duì)列掛靠在base->activequeues上面决采,而base->nactivequeues記錄了優(yōu)先級(jí)的數(shù)目。

二坟奥、創(chuàng)建listen套接字树瞭,并建立connect事件偵聽

2.1 基本過程

listener = evconnlistener_new_bind(srvopt.main_base, accept_conn_cb, NULL,

LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1/*backlog*/,

(struct sockaddr*)&sin, sizeof(sin));

這個(gè)算是個(gè)簡(jiǎn)化版的函數(shù),你可以自己先手動(dòng)建立和綁定socket爱谁,然后再調(diào)用evconnlistener_new建立connect事件偵聽晒喷。這個(gè)函數(shù)給socket設(shè)置了一個(gè)高大上的符號(hào)SO_KEEPALIVE(SO_KEEPALIVE 保持連接檢測(cè)對(duì)方主機(jī)是否崩潰,避免服務(wù)器永遠(yuǎn)阻塞于TCP連接的輸入)访敌,SOCKET開發(fā)還是有很多參數(shù)的凉敲,比如之前sshinner網(wǎng)絡(luò)一直出問題,消息不能及時(shí)的被發(fā)送接收寺旺,最后跟蹤shadowsockets-libev發(fā)現(xiàn)爷抓,是要給socket添加TCP_NODELAY參數(shù),問題才得以解決迅涮。

在evconnlistener_new函數(shù)中废赞,首先調(diào)用listen,然后分配evconnlistener_event這個(gè)數(shù)據(jù)結(jié)構(gòu)叮姑,base作為struct evconnlistener類型傳遞給用戶空間唉地,而listener主要作為內(nèi)部隱藏的數(shù)據(jù)結(jié)構(gòu),為通用的struct event數(shù)據(jù)類型传透。

struct evconnlistener_event {

struct evconnlistener base;

struct event listener;

};

struct evconnlistener {

const struct evconnlistener_ops *ops;

void *lock;

evconnlistener_cb cb;

evconnlistener_errorcb errorcb;

void *user_data;

unsigned flags;

short refcnt;

int accept4_flags;

unsigned enabled : 1;

};

struct event {

struct event_callback ev_evcallback;

/* for managing timeouts */

union {

TAILQ_ENTRY(event) ev_next_with_common_timeout;

int min_heap_idx;

} ev_timeout_pos;

evutil_socket_t ev_fd;

struct event_base *ev_base;

union {

/* used for io events */

struct {

LIST_ENTRY (event) ev_io_next;

struct timeval ev_timeout;

} ev_io;

/* used by signal events */

struct {

LIST_ENTRY (event) ev_signal_next;

short ev_ncalls;

/* Allows deletes in callback */

short *ev_pncalls;

} ev_signal;

} ev_;

short ev_events;

short ev_res; ? ? ? ?/* result passed to event callback */

struct timeval ev_timeout;

};

通過上面的數(shù)據(jù)結(jié)構(gòu)可以清晰的發(fā)現(xiàn)耘沼,調(diào)用evconnlistener_new_bind函數(shù)作為參數(shù)提供的回調(diào)函數(shù)和參數(shù),都被賦值給了evconnlistener_event->base.cb和base.user_data上面朱盐。接下來調(diào)用了兩個(gè)比較重要的函數(shù):

event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,

listener_read_cb, lev);

evconnlistener_enable(&lev->base);

evconnlistener_enable(&lev->base)通過追根述源是調(diào)用了event_listener_enable群嗤,最后調(diào)用了event_add(&lev_e->listener, NULL)。而event_assign和event_add都是比較重要的函數(shù)兵琳,event_assign類似于event_new的作用狂秘,只不過參數(shù)是一個(gè)已經(jīng)初始化了的struct event,而event_add則是把event由initialized狀態(tài)變成pending狀態(tài)躯肌,以便開始接收事件者春,其實(shí)后面可以發(fā)現(xiàn),bufferevent_enable等接口清女,底層也是調(diào)用的event_add實(shí)現(xiàn)的钱烟。

接下來把上面這兩個(gè)函數(shù)慢慢品讀。

2.2 event_assign調(diào)用

event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST,

listener_read_cb, lev);

初看上面比較奇怪,在evconnlistener_new這個(gè)函數(shù)的上半部分已經(jīng)設(shè)置了一個(gè)base.cb和base.user_data了拴袭,怎么下面又調(diào)用一個(gè)event_assign來設(shè)置一個(gè)listener_read_cb回調(diào)呢读第?其實(shí)上面是用戶提供的callback和args,但是這并沒有直接跟某個(gè)事件相關(guān)聯(lián)拥刻,而下面的event_assign卻是設(shè)置了&lev->listener(標(biāo)準(zhǔn)的struct event類型)為固定的listener_read_cb回調(diào)函數(shù)怜瞒,當(dāng)發(fā)生了EV_READ就會(huì)被自動(dòng)調(diào)用。然后在listener_read_cb中泰佳,我們發(fā)現(xiàn):

cb = lev->cb; ? ?//

user_data = lev->user_data; ? ?//

cb(lev, new_fd, (struct sockaddr*)&ss, (int)socklen, ?user_data);

errorcb(lev, user_data);

所以說盼砍,其實(shí)Libevent內(nèi)部根本沒有什么諸如LISTEN的事件,還是用的標(biāo)準(zhǔn)EV_READ(因?yàn)樽钭畹讓拥膃poll異步只能監(jiān)聽read/write/except事件)逝她,只是做了個(gè)封裝,當(dāng)連接之后激活EV_READ(為什么呢睬捶?為什么呢黔宛?)的回調(diào)函數(shù),而出錯(cuò)了就調(diào)用err_callback函數(shù)擒贸,所以accept_conn_cb實(shí)際是被手動(dòng)調(diào)用的臀晃。

2.3 event_add調(diào)用

evconnlistener_enable的調(diào)用被翻譯到event_add函數(shù),其實(shí)不光光是這里介劫,后面最常用的bufferevent_enable這類函數(shù)徽惋,其實(shí)也是翻譯到底層的event_add函數(shù)(event_add_nolock_)上面。

struct evconnlistener_event *lev_e =

EVUTIL_UPCAST(lev, struct evconnlistener_event, base); ? ?//這個(gè)宏比較好看

return event_add(&lev_e->listener, NULL);

#define EVUTIL_UPCAST(ptr, type, field) ? ? ? ? ? ? ? ?\

((type *)(((char*)(ptr)) - evutil_offsetof(type, field)))

event_add有兩個(gè)參數(shù)座韵,后面一個(gè)參數(shù)是struct timeval的超時(shí)參數(shù)险绘,如果是NULL就表示無限期等待,這里先就不考慮這種情況誉碴。其中最主要做的事情就是調(diào)用evmap_io_add_/evmap_signal_add_函數(shù)將事件加入到base當(dāng)中:

// 對(duì)照上面宦棺,返回-1 error, 0 沒有實(shí)際操作, 1 真實(shí)添加了

res = evmap_io_add_(base, ev->ev_fd, ev);

res = evmap_signal_add_(base, (int)ev->ev_fd, ev);

// 上面函數(shù)的核心操作

GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,

evsel->fdinfo_len);

evsel->add(base, ev->ev_fd,

old, (ev->ev_events & EV_ET) | res, extra);

首先,由于Libevent的設(shè)計(jì)是跨平臺(tái)的黔帕,而Windows和Linux對(duì)socket和fd的表達(dá)和處理方式不同代咸,GET_IO_SLOT_AND_CTOR的行為也有差異:Windows使用的是hashtable維護(hù)著struct event_map_entry結(jié)構(gòu),而Linux平臺(tái)就直接是用的指針數(shù)組(數(shù)組成黄,元素類型是指針)呐芥,用fd作為偏移來索引,指針指向的結(jié)構(gòu)按需分配奋岁,十分的簡(jiǎn)潔高效思瘟。

/* Used to map signal numbers to a list of events. ?If EVMAP_USE_HT is not

defined, this structure is also used as event_io_map, which maps fds to a

list of events.

*/

struct event_signal_map {

/* An array of evmap_io * or of evmap_signal *; empty entries are

* set to NULL. */

void **entries;

/* The number of entries available in entries */

int nentries;

};

/** Mapping from file descriptors to enabled (added) events */

struct event_io_map io;

/** Mapping from signal numbers to enabled (added) events. */

struct event_signal_map sigmap;

在每一個(gè)event_base結(jié)構(gòu)體中,都定義了struct event_signal_map類型的兩個(gè)成員io和sigmap(Linux平臺(tái))厦取,用于信號(hào)量和FD與events之間的事件映射潮太。然后看GET_IO_SLOT_AND_CTOR(GET_SIGNAL_SLOT_AND_CTOR)這個(gè)宏,查詢或者創(chuàng)建fd對(duì)應(yīng)的struct evmap_io對(duì)象ctx,將當(dāng)前的事件和之前的事件進(jìn)行合并铡买,并調(diào)用evsel->add進(jìn)行更新(最終反應(yīng)到底層epoll上面就是epoll_ctl命令進(jìn)行操作了)更鲁,并把當(dāng)前的event結(jié)構(gòu)體插入到前面找到的ctx->events鏈表當(dāng)中。

三奇钞、建立主事件循環(huán)

無論是主線程澡为,還是對(duì)于每個(gè)線程池用自己的event_base,最終都會(huì)調(diào)用這個(gè)函數(shù)作為主循環(huán)進(jìn)行事件處理景埃。

event_base_loop(main_base, 0);

先不考慮那些FLAG(控制何時(shí)推出啊啥的)媒至,在event_base_loop主要做的事情是:

/* F1 */ event_queue_make_later_events_active(base);

/* F2 */ res = evsel->dispatch(base, tv_p);

/* F3 */ timeout_process(base);

F1的函數(shù)主要是在Libevent中引用了Deferred Callback機(jī)制,操作上就是從event_base的active_later_queue隊(duì)列中將事件取出來谷徙,然后添加到activequeues[evcb->evcb_pri]對(duì)應(yīng)優(yōu)先級(jí)隊(duì)列上面拒啰。

F2對(duì)于epoll類型,就是調(diào)用epoll_dispatch函數(shù):首先調(diào)用epoll_apply_changes(base);對(duì)event_base->changelist上面掛靠的所有對(duì)fd的事件修改都執(zhí)行底層修改使之生效完慧;然后用res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);獲取被激活的事件谋旦;對(duì)獲取到的每個(gè)fd的事件,提取被激活的事件類型屈尼,然后調(diào)用evmap_io_active_(base, events[i].data.fd, ev | EV_ET);函數(shù)處理册着。

evmap_io_active_(struct event_base *base, evutil_socket_t fd, short events)

{

struct event_io_map *io = &base->io;

struct evmap_io *ctx;

struct event *ev;

GET_IO_SLOT(ctx, io, fd, evmap_io);

LIST_FOREACH(ev, &ctx->events, ev_io_next) {

if (ev->ev_events & events)

event_active_nolock_(ev, ev->ev_events & events, 1);

}

}

執(zhí)行的結(jié)果就是,這個(gè)fd對(duì)應(yīng)的evmap_io上的所有事件脾歧,以及Deferred Callback事件甲捏,都被收集添加到event_base->activequeues[evcb->evcb_pri]隊(duì)列中去。

于是乎鞭执,最后的好戲就是:

if (N_ACTIVE_CALLBACKS(base)) {

int n = event_process_active(base);

在event_process_active中調(diào)用了event_process_active_single_queue司顿。當(dāng)然作者考慮的細(xì)節(jié)還是比較細(xì)膩的:如果當(dāng)前被調(diào)度的活動(dòng)事件過多,就考慮在timer和maxcb兩個(gè)維度上限制本輪的事件處理量蚕冬,而在event_process_active_single_queue中免猾,會(huì)不斷從事件鏈表中取出事件處理(包括執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù))。從實(shí)現(xiàn)方式上看來囤热,按照優(yōu)先級(jí)從高到低的順序猎提,每一輪只處理一個(gè)最高優(yōu)先級(jí)非空事件隊(duì)列中的事件,然后就返回了旁蔼。這樣看來锨苏,如果高優(yōu)先級(jí)的事件太多太活躍,那么低優(yōu)先級(jí)的事件還是會(huì)有被餓死的風(fēng)險(xiǎn)棺聊。

四伞租、基于bufferevent的普通socket讀寫事件

bufferevent使得網(wǎng)絡(luò)的開發(fā)變的很方便,無論是從事件還是底層的evbuffer都提供了一套豐富靈活的接口限佩。但是需要注意的是bufferevent目前只能用于TCP連接的類型葵诈,對(duì)于UDP只能手動(dòng)建立struct event事件裸弦,然后設(shè)置事件和回調(diào)函數(shù)了,而且在回調(diào)函數(shù)中作喘,一般也只能調(diào)用sendto/recvfrom等操作接口理疙。

struct event_base *base = evconnlistener_get_base(listener);

struct bufferevent *bev =

bufferevent_socket_new(base, fd, 0 /*BEV_OPT_CLOSE_ON_FREE*/);

bufferevent_setcb(bev, bufferread_cb, NULL, bufferevent_cb, NULL);

bufferevent_enable(bev, EV_READ|EV_WRITE);

上面算是在網(wǎng)絡(luò)開發(fā)中用的最頻繁的了,比如在listener的connection回調(diào)函數(shù)中泞坦,接收到一個(gè)新的套接字fd窖贤,那么就對(duì)這個(gè)套接字設(shè)置bufferevent事件,設(shè)置對(duì)應(yīng)的回調(diào)函數(shù)贰锁。

在bufferevent_socket_new函數(shù)中:

struct bufferevent_private *bufev_p;

struct bufferevent *bufev;

bufferevent_init_common_(bufev_p, base, &bufferevent_ops_socket, options);

bufev_p->bev;

event_assign(&bufev->ev_read, bufev->ev_base, fd,

EV_READ|EV_PERSIST|EV_FINALIZE, bufferevent_readcb, bufev);

event_assign(&bufev->ev_write, bufev->ev_base, fd,

EV_WRITE|EV_PERSIST|EV_FINALIZE, bufferevent_writecb, bufev);

evbuffer_add_cb(bufev->output, bufferevent_socket_outbuf_cb, bufev);

evbuffer_freeze(bufev->input, 0);

evbuffer_freeze(bufev->output, 1);

跟之前的struct evconnlistener_event一樣赃梧,這里返回給用戶空間可用的struct bufferevent也是struct bufferevent_private的一部分,不過相比listener的單個(gè)struct event豌熄,這里的bufferevent內(nèi)容成員要復(fù)雜的多授嘀,其中我們比較熟悉的有:input、output兩個(gè)evbuffer锣险,可以調(diào)用evbuffer的族函數(shù)進(jìn)行相關(guān)的高級(jí)處理粤攒;此外還設(shè)置了be_ops為bufferevent_ops_socket,而enabled使能的時(shí)間中默認(rèn)為EV_WRITE囱持,所以EV_READ需要手動(dòng)enable;接下來設(shè)置bufev_private->deferred的callback回調(diào)函數(shù)和調(diào)用參數(shù)為bufferevent_run_deferred_callbacks_locked和bufev_private焕济。

最后的兩個(gè)event_assign分別將ev_read和ev_write兩個(gè)event的回調(diào)函數(shù)設(shè)置為了bufferevent_readcb/bufferevent_writecb纷妆。由于EV_WRITE默認(rèn)是使能的,所以還調(diào)用了evbuffer_add_cb設(shè)置其默認(rèn)的回調(diào)函數(shù)為bufferevent_socket_outbuf_cb晴弃。為了安全掩幢,還將兩個(gè)evbuffer先凍結(jié)起來,準(zhǔn)備工作還未就緒上鞠,所以此時(shí)還不允許數(shù)據(jù)傳輸际邻。

其實(shí),正如上面的例子芍阎,對(duì)于bufferevent世曾,通常的寫操作就使用其默認(rèn)的callback就可以了,實(shí)際開發(fā)當(dāng)中我們最關(guān)心的是讀事件谴咸,因?yàn)槲覀円邮諗?shù)據(jù)處理數(shù)據(jù)(即便只是轉(zhuǎn)發(fā)操作)轮听,而寫數(shù)據(jù)只要準(zhǔn)備好要發(fā)送的數(shù)據(jù),底層的寫就讓其自動(dòng)處理就可以了岭佳。

說到底血巍,這里的bufferevent和evconnlistener類似,也是采用了兩段式設(shè)計(jì):在bufferevent中的ev_read/write被激活調(diào)度的時(shí)候珊随,其自動(dòng)執(zhí)行的是bufferevent_readcb/writecb函數(shù)述寡,在這些標(biāo)準(zhǔn)函數(shù)中會(huì)做一些的預(yù)先處理操作柿隙,比如evbuffer_read/evbuffer_write_atmost的讀寫,到最后通過bufferevent_trigger_nolock_調(diào)用用戶設(shè)定的回調(diào)函數(shù)鲫凶。然后你可能感興趣EV_WRITE默認(rèn)的bufferevent_socket_outbuf_cb干了啥禀崖?查看其代碼,其實(shí)也就是:檢查確保當(dāng)前ev_write是否是pending的掀序,如果不是就bufferevent_add_event_變成pending狀態(tài)就好了帆焕。我們知道,epoll事件最底層是操作系統(tǒng)直接驅(qū)動(dòng)的不恭,所以如果底層驅(qū)動(dòng)發(fā)現(xiàn)socket是可寫的叶雹,就可以調(diào)度底層發(fā)送數(shù)據(jù)了,因此這個(gè)函數(shù)實(shí)際上其實(shí)啥都沒做换吧。

最后的bufferevent_enable(bev, EV_READ|EV_WRITE);跟之前的evconnlistener_enable也大差不差的折晦,最終都是通過be_socket_enable->bufferevent_add_event_->event_add方式,將event加入到對(duì)應(yīng)的event_base上面去沾瓦,使之變?yōu)閜ending狀態(tài)满着。

五、小結(jié)

Libevent的代碼實(shí)現(xiàn)的十分精妙贯莺,注釋也比較多风喇,當(dāng)然也有一些參數(shù)和符號(hào)尚無注釋,自己暫時(shí)也沒能意會(huì)缕探,本文尚有很多需要補(bǔ)充之處魂莫。

哎,想想自己epoll+線程池兩個(gè)c文件搞定爹耗,而Libevent把這個(gè)做的如此之精妙(且尚無線程池模型)耙考,不得不由衷的讓人敬佩:牛人怎么就那么牛呢?這也使得我堅(jiān)信潭兽,初學(xué)者和企業(yè)應(yīng)用之間總隔著一條溝溝需要跨越倦始,如果沒有項(xiàng)目帶著你走,那就可以靠讀這些開源項(xiàng)目的代碼來彌合山卦,希望大家都能愉快的玩耍和成長(zhǎng)鞋邑!

本文完!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怒坯,一起剝皮案震驚了整個(gè)濱河市炫狱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剔猿,老刑警劉巖视译,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異归敬,居然都是意外死亡酷含,警方通過查閱死者的電腦和手機(jī)鄙早,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椅亚,“玉大人限番,你說我怎么就攤上這事⊙教颍” “怎么了弥虐?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)媚赖。 經(jīng)常有香客問我霜瘪,道長(zhǎng),這世上最難降的妖魔是什么惧磺? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任颖对,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己蛹尝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布孽鸡。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坑鱼,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音絮缅,去河邊找鬼。 笑死呼股,一個(gè)胖子當(dāng)著我的面吹牛耕魄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彭谁,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼吸奴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了缠局?” 一聲冷哼從身側(cè)響起则奥,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狭园,沒想到半個(gè)月后读处,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唱矛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年罚舱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了井辜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡管闷,死狀恐怖粥脚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情包个,我是刑警寧澤刷允,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站碧囊,受9級(jí)特大地震影響树灶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呕臂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一破托、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧歧蒋,春花似錦土砂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至阐虚,卻和暖如春序臂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背实束。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工奥秆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咸灿。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓构订,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親避矢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子悼瘾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 名稱 libev - 一個(gè) C 編寫的功能全面的高性能事件循環(huán)。 概要 示例程序 關(guān)于 libev Libev 是...
    hanpfei閱讀 15,300評(píng)論 0 5
  • 概述 Libev 是使用 Reactor 模型的實(shí)現(xiàn)的一個(gè)高性能事件循環(huán)庫(kù)审胸。它的主要實(shí)現(xiàn)包括: 在結(jié)構(gòu)上分離了事件...
    OMSobliga閱讀 7,099評(píng)論 0 4
  • epoll概述 epoll是linux中IO多路復(fù)用的一種機(jī)制亥宿,I/O多路復(fù)用就是通過一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多...
    發(fā)仔很忙閱讀 10,899評(píng)論 4 35
  • 觀察者類型 This section describes each watcher in detail, but ...
    hanpfei閱讀 1,060評(píng)論 0 1
  • 配置文件:Application.beans.xml bean配置文件砂沛,默認(rèn)無需修改application....
    Together5234閱讀 265評(píng)論 0 0