您好,登錄后才能下訂單哦!
這篇文章主要介紹“libevent業務數據處理的方法”,在日常操作中,相信很多人在libevent業務數據處理的方法問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”libevent業務數據處理的方法”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
一個 loop 的主要結構一般如下所示:
while (!m_bQuitFlag)
{
epoll_or_select_func();
handle_io_events();
handle_other_things();
}
對于一些業務邏輯處理比較簡單、不會太耗時的應用來說,handle_io_events() 方法除了收發數據也可以直接用來直接做業務的處理,即其結構如下:
void handle_io_events()
{
//收發數據
recv_or_send_data();
//解包并處理數據
decode_packages_and_process();
}
其中 recv_or_send_data() 方法中調用 send/recv API 進行實際的網絡數據收發。以收數據為例,收完數據存入接收緩沖區后,接下來進行解包處理,然后進行業務處理,例如一個登陸數據包,其業務就是驗證登陸的賬戶密碼是否正確、記錄其登陸行為等等。從程序函數調用堆棧來看,這些業務處理邏輯其實是直接在網絡收發數據線程中處理的。我的意思是:網絡線程調用 handle_io_events() 方法,handle_io_events() 方法調用 decode_packages_and_process() 方法,decode_packages_and_process() 方法做具體的業務邏輯處理。
需要注意的是,為了讓網絡層與業務層脫耦,網絡層中通常會提供一些回調函數的接口,這些回調函數我們將其指向具體的業務處理函數。以 libevent 網絡庫的用法為例:
int main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct event *signal_event;
struct sockaddr_in sin;
base = event_base_new();
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
//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;
}
//signal_cb是我們自定義回調函數
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;
}
//啟動loop
event_base_dispatch(base);
evconnlistener_free(listener);
event_free(signal_event);
event_base_free(base);
printf("done\n");
return 0;
}
上述代碼根據 libevent 自帶的 helloworld 示例修改而來,其中 listener_cb 和 signal_cb 是自定義的回調函數,有相應的事件觸發后,libevent 的事件循環會調用我們設置的回調,在這些回調函數中,我們可以編寫自己的業務邏輯代碼。
這種基本的服務器結構,我們可以繪制成如下流程圖:
這是這個結構的最基本邏輯,在這基礎上可以延伸出很多變體。不知道讀者有沒有發現,上述流程圖中第三步解包和業務邏輯處理這一步中(位于 handle_io_events() 中的 decode_packages_and_process() 方法中),如果業務邏輯處理過程比較耗時(例如,從數據庫取大量數據、寫文件),那么會導致 網絡線程在這個步驟停留時間很長,導致很久以后才能執行下一次循環,影響網絡數據的檢測和收發,最終導致整個程序的效率低下。
因此,對于這種情形,我們需要將業務處理邏輯單獨拆出來交給另外的業務工作線程處理,業務工作線程可以是一個線程池,這個過程業務數據從網絡線程組流向業務線程組。
這樣的程序結構圖如下圖所示:
上圖中,對于網絡線程將業務數據包交給業務線程,可以使用一個共享的業務數據隊列來實現,此時網絡線程是生產者,業務線程從業務數據隊列中取出任務去處理,業務線程是消費者。業務線程處理完成后如果需要將結果數據發出去,則再將數據交給網絡線程。這里處理后的數據從業務線程再次流向網絡線程,那么如何將數據從業務線程交給網絡線程呢?這里以發數據為例,一般有三種方法:
方法一
直接調用相應的的發數據的方法,如果你的網絡線程本身也會調用這些發數據的方法,那么此時就可能會出現網絡線程和業務線程同時對發方法進行調用,相當于多個線程同時調用 socket send 函數,這樣可能會導致同一個連接上的數據順序有問題,此時的做法時,利用鎖機制,同一時刻只有一個線程可以調用 socket send 方法。這里給出一段偽代碼,假設 TcpConnection 對象表示某路連接,無論網絡線程還是業務線程處理完數據后需要發送數據,則使用:
void TcpConnection::sendData(const std::string& data)
{
//加上鎖
std::lock_guard<std::mutex> scoped_lock(m_mutexForConnection);
//在這里調用 send
}
方法一的做法在設計上來說,存在讓人不滿意的地方,即數據發送應該屬于網絡層自己的事情,而不是其他模塊(這里指的是業務線程)強行搶奪過來越俎代庖。
方法二
前面章節介紹了存在定時器結構的情況,網絡線程結構變成如下流程:
while (!m_bQuitFlag)
{
check_and_handle_timers();
epoll_or_select_func();
handle_io_events();
}
業務線程可以將需要發送的數據放入另外一個共享區域中(例如相應的 TcpConnection 對象的一個成員變量中),定時器定時從這個共享區域取出來,再發送出去,這種方案的優點是網絡線程做了它該做的事情,缺點是需要添加定時器,讓程序邏輯變得復雜,且定時器是每隔一段時間才會觸發,發送的數據可能會有一定的延遲。
方法三
利用線程執行流中的 handle_other_things() 方法,再來看下前面章節中介紹的基本結構:
while (!m_bQuitFlag)
{
epoll_or_select_func();
handle_io_events();
handle_other_things();
}
到此,關于“libevent業務數據處理的方法”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。