開源:evpp-基于 libevent 開發高性能網絡服務器

leonora806 7年前發布 | 40K 次閱讀 libevent 高性能 網絡技術 C/C++

evpp

簡介

evpp 是一個基于 libevent 開發的現代化C++11高性能網絡服務器,自帶TCP/UDP/HTTP等協議的異步非阻塞式的服務器和客戶端庫。

特性

  1. 現代版的C++11接口
  2. 非阻塞異步接口都是C++11的functional/bind形式的回調仿函數(不是 libevent 中的C風格的函數指針)
  3. 非阻塞純異步多線程TCP服務器/客戶端
  4. 非阻塞純異步多線程HTTP服務器/客戶端
  5. 非阻塞純異步多線程UDP服務器
  6. 支持多進程模式
  7. 優秀的跨平臺特性和高性能(繼承自 libevent 的優點)

除此之外,基于該庫之上,還提供兩個附帶的應用層協議庫:

  1. evmc :一個純異步非阻塞式的 memcached 的C++客戶端庫,支持 membase 集群模式。詳情請見: evmc readme
  2. evnsq : 一個純異步非阻塞式的 NSQ 的C++客戶端庫,支持消費者、生產者、服務發現等特性。詳情請見: evnsq readme

將來還會推出 redis 的客戶端庫。

項目由來

我們開發小組負責的業務需要用到TCP協議來建設長連接網關服務和一些其他的一些基于TCP的短連接服務,在調研開源項目的過程中,沒有發現一個合適的庫來滿足我們要求。結合我們自身的業務情況,理想中的C++網絡庫應具備一下幾個特性:

  1. 接口簡單易用,最好是C++接口
  2. 多線程,也能支持多進程
  3. 最好是基于 libevent 實現(因為現有的歷史遺留框架、基礎庫等是依賴 libevent ),這樣能很方便嵌入 libevent 的事件循環,否則改動較大或者集成起來的程序可能會有很多跨線程的調用

基于這些需要,可供選擇的不多,所以我們只能自己開發一個。開發過程中,接口設計方面基本上大部分是參考 muduo 項目來設計和實現的,當然也做了一些取舍和增改;同時也大量借鑒了 Golang 的一些設計哲學和思想,舉幾個小例子來說明一下:

  1. Duration : 這是一個時間區間相關的類,自帶時間單位信息,參考了 Golang 項目中的 Duration 實現。我們在其他項目中見到太多的時間是不帶單位的,例如 timeout ,到底是秒、毫秒還是微秒?需要看文檔說明或具體實現,好一點的設計會將單位帶在變量名中,例如 timeout_ms ,但還是沒有 Duration 這種獨立的類好。目前C++11中也有類似的實現 std::chrono::duration ,但稍顯復雜,沒有咱們這個借鑒 Golang 實現的版本來的簡單明了。
  2. Buffer : 這是一個緩沖區類,融合了 muduo 和 Golang 兩個項目中相關類的設計和實現
  3. http::Server : 這是一個http服務器類,自帶線程池,它的事件循環和工作線程調度,完全是線程安全的,業務層不用太多關心跨線程調用問題。同時,還將http服務器的核心功能單獨抽取出來形成 http::Service 類,是一個可嵌入型的服務器類,可以嵌入到已有的 libevent 事件循環中。
  4. 網絡地址的表達就僅僅使用 "ip:port" 這種形式字符串表示,就是參考 Golang 的設計
  5. httpc::ConnPool 是一個http的客戶端連接池庫,設計上盡量考慮高性能和復用。以后基于此還可以增加負載均衡和故障轉移等特性。

另外,我們實現過程中極其重視線程安全問題,一個事件相關的資源必須在其所屬的 EventLoop 中初始化和析構釋放,這樣我們能最大限度的減少出錯的可能。為了達到這個目標我們重載 event_add 和 event_del 等函數,每一次調用 event_add ,就在對應的線程私有數據中記錄該對象,在調用 event_del 時,檢查之前該線程私有數據中是否擁有該對象,然后在整個程序退出前,再完整的檢查所有線程的私有數據,看看是否仍然有對象沒有析構釋放,詳細代碼實現可以參考 https://github.com/Qihoo360/evpp/blob/master/evpp/inner_pre.cc#L46~L87 。我們如此苛刻的追求線程安全,只是為了讓一個程序能安靜的平穩的退出或Reload,因為我們深刻的理解“編寫永遠運行的系統,和編寫運行一段時間后平靜關閉的系統是兩碼事”,后者要困難的多。

快速開始

請見 Quick Start

Examples

An echo TCP server

include <evpp/exp.h>

include <evpp/tcp_server.h>

include <evpp/tcp_conn.h>

include <evpp/buffer.h>

void OnMessage(const evpp::TCPConnPtr& conn, evpp::Buffer* msg, evpp::Timestamp ts) { std::string s = msg->NextAllString(); LOG_INFO << "Received a message [" << s << "]"; conn->Send(s); if (s == "quit" || s == "exit") { conn->Close(); } }

int main(int argc, char* argv[]) { std::string addr = std::string("0.0.0.0:9999"); evpp::EventLoop loop; evpp::TCPServer server(&loop, addr, "TCPEcho", 0); server.SetMessageCallback(&OnMessage); server.Init(); server.Start(); loop.Run(); return 0; }</pre>

An echo HTTP server

include <evpp/exp.h>

include <evpp/http/http_server.h>

void RequestHandler(evpp::EventLoop* loop, const evpp::http::ContextPtr& ctx, const evpp::http::HTTPSendResponseCallback& cb) { cb(ctx->body.ToString()); }

int main(int argc, char* argv[]) { std::vector<int> ports = {9009, 23456, 23457}; int thread_num = 2; evpp::http::Server server(thread_num); server.RegisterHandler("/echo", &RequestHandler); server.Init(ports); server.Start(); while (!server.IsStopped()) { usleep(1); } return 0; }</pre>

An echo UDP server

include <evpp/exp.h>

include <evpp/udp/udp_server.h>

include <evpp/udp/udp_message.h>

void DefaultHandler(evpp::EventLoop* loop, evpp::udp::MessagePtr& msg) { evpp::udp::SendMessage(msg); }

int main(int argc, char* argv[]) { std::vector<int> ports = {1053, 5353}; evpp::udp::Server server; server.SetMessageHandler(&DefaultHandler); server.Init(ports); server.Start();

while (!server.IsStopped()) {
    usleep(1);
}
return 0;

}</pre>

用戶列表

 

來自:https://github.com/Qihoo360/evpp/blob/master/readme_cn.md

 

 本文由用戶 leonora806 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!