轉:發佈一個高性能的Reactor模式的C++網絡庫:evpp

簡介
https://github.com/Qihoo360/evpp是一個基於libevent開發的現代化的支持C++11特性的高性能網絡庫,自帶TCP/UDP/HTTP等協議的異步非阻塞式的服務器和客戶端庫。

特性
現代版的C++11接口
非阻塞異步接口都是C++11的functional/bind形式的回調仿函數(不是libevent中的C風格的函數指針)
非阻塞純異步多線程TCP服務器/客戶端
非阻塞純異步多線程HTTP服務器/客戶端
非阻塞純異步多線程UDP服務器
支持多進程模式
優秀的跨平臺特性和高性能(繼承自libevent的優點)
除此之外,基於該庫之上,還提供兩個附帶的應用層協議庫:

evmc :一個純異步非阻塞式的memcached的C++客戶端庫,支持membase集羣模式。該庫已經用於生產環境,每天發起1000多億次memcache查詢請求。詳情請見:evmc readme
evnsq : 一個純異步非阻塞式的NSQ的C++客戶端庫,支持消費者、生產者、服務發現等特性。該庫已經用於生產環境,每天生產200多億條日誌消息。詳情請見:evnsq readme
將來還會推出redis的客戶端庫。

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

接口簡單易用,最好是C++接口
多線程,也能支持多進程
最好是基於libevent實現(因爲現有的歷史遺留框架、基礎庫等是依賴libevent),這樣能很方便嵌入libevent的事件循環,否則改動較大或者集成起來的程序可能會有很多跨線程的調用(這些會帶來編程的複雜性以及跨線程鎖帶來的性能下降)
基於這些需求,可供選擇的不多,所以我們只能自己開發一個。開發過程中,接口設計方面基本上大部分是參考muduo項目來設計和實現的,當然也做了一些取捨和增改;同時也大量借鑑了Golang的一些設計哲學和思想。下面舉幾個小例子來說明一下:

Duration : 這是一個時間區間相關的類,自帶時間單位信息,參考了Golang項目中的Duration實現。我們在其他項目中見到太多的時間是不帶單位的,例如timeout,到底是秒、毫秒還是微秒?需要看文檔說明或具體實現,好一點的設計會將單位帶在變量名中,例如timeout_ms,但還是沒有Duration這種獨立的類好。目前C++11中也有類似的實現std::chrono::duration,但稍顯複雜,沒有咱們這個借鑑Golang實現的版本來的簡單明瞭
Buffer : 這是一個緩衝區類,融合了muduo和Golang兩個項目中相關類的設計和實現
http::Server : 這是一個http服務器類,自帶線程池,它的事件循環和工作線程調度,完全是線程安全的,業務層不用太多關心跨線程調用問題。同時,還將http服務器的核心功能單獨抽取出來形成http::Service類,是一個可嵌入型的服務器類,可以嵌入到已有的libevent事件循環中
網絡地址的表達就僅僅使用"ip:port"這種形式字符串表示,就是參考Golang的設計
httpc::ConnPool是一個http的客戶端連接池庫,設計上儘量考慮高性能和複用。以後基於此還可以增加負載均衡和故障轉移等特性
另外,我們實現過程中極其重視線程安全問題,一個事件相關的資源必須在其所屬的EventLoop(每個EventLoop綁定一個線程)中初始化和析構釋放,這樣我們能最大限度的減少出錯的可能。爲了達到這個目標我們重載event_add和event_del等函數,每一次調用event_add,就在對應的線程私有數據中記錄該對象,在調用event_del時,檢查之前該線程私有數據中是否擁有該對象,然後在整個程序退出前,再完整的檢查所有線程的私有數據,看看是否仍然有對象沒有析構釋放。具體實現稍有區別,詳細代碼實現可以參考 https://github.com/Qihoo360/evpp/blob/master/evpp/inner_pre.cc#L46~L87。我們如此苛刻的追求線程安全,只是爲了讓一個程序能安靜的平穩的退出或Reload,因爲我們深刻的理解“編寫永遠運行的系統,和編寫運行一段時間後平靜關閉的系統是兩碼事”,後者要困難的多得多。

吞吐量Benchmark測試報告
本文用 ping pong 測試來對比evpp與libevent、boost.asio、muduo] 等網絡的吞吐量,測試結果表明evpp吞吐量與boost.asio、muduo等相當,比libevent高17%~130%左右。

evpp本身是基於libevent實現的,不過evpp只是用了libevent的事件循環,並沒有用libevent的evbuffer,而是自己參考muduo和Golang實現了自己的網絡IO讀寫類Buffer。

性能測試相關的代碼都在這裏:https://github.com/Qihoo360/evpp/tree/master/benchmark/.

測試對象
evpp-0.2.0 based on libevent-2.0.21
boost.asio-1.53
libevent-2.0.21
系統環境
操作系統:Linux CentOS 6.2, 2.6.32-220.7.1.el6.x86_64
硬件CPU:Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz幾個簡單的示例代碼
TCP Echo服務器

#include <evpp/tcp_server.h>
#include <evpp/buffer.h>
#include <evpp/tcp_conn.h>

int main(int argc, char* argv[]) {
    std::string addr = "0.0.0.0:9099";
    int thread_num = 4;
    evpp::EventLoop loop;
    evpp::TCPServer server(&loop, addr, "TCPEchoServer", thread_num);
    server.SetMessageCallback([](const evpp::TCPConnPtr& conn,
                                 evpp::Buffer* msg,
                                 evpp::Timestamp ts) {
        conn->Send(msg);
    });
    server.SetConnectionCallback([](const evpp::TCPConnPtr& conn) {
        if (conn->IsConnected()) {
            LOG_INFO << "A new connection from " << conn->remote_addr();
        } else {
            LOG_INFO << "Lost the connection from " << conn->remote_addr();
        }
    });
    server.Init();
    server.Start();
    loop.Run();
    return 0;
}



HTTP Echo服務器

#include <evpp/exp.h>
#include <evpp/http/http_server.h>

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",
                           [](evpp::EventLoop* loop,
                              const evpp::http::ContextPtr& ctx,
                              const evpp::http::HTTPSendResponseCallback& cb) {
        cb(ctx->body().ToString()); }
    );
    server.Init(ports);
    server.Start();
    while (!server.IsStopped()) {
        usleep(1);
    }
    return 0;
}



UDP Echo服務器

#include <evpp/exp.h>
#include <evpp/udp/udp_server.h>
#include <evpp/udp/udp_message.h>

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

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



致謝
感謝奇虎360公司對該項目的支持
感謝libevent, glog, gtest, Golang等項目
evpp深度參考了muduo項目的實現和設計,非常感謝Chen Shuo
--------------------- 
作者:zieckey 
來源:CSDN 
原文:https://blog.csdn.net/zieckey/article/details/63760757 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

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