从Qihoo360/evppfork 出来的项目,感谢Qihoo360/evpp团队,evpp是一个优秀的项目。
1.日志回调函数:evpp本身的日志,可以在CMake文件中配置绑定到glog,但是我的程序里并不适用glog,所以我修改了这部分,可以让用户指定自己的日志输出函数。见evpp/logger.h,调用方可以调用set_log_handler()
或者set_log_stdout()
把日志输出到控制台(调试)或者输出到自己定义的回调函数.
2.HTTP的初始化:Server::Init()
函数限定一定要监听"0.0.0.0",这在很多情况下都不能这样做,只能绑定某个IP,于是我改了一下接口:
bool Init(const std::string& ip_port);//新增
bool Init(const std::string& ip, int listen_port); //新增需要用户指定IP
bool Init(const std::string& ip, const std::vector<int>& listen_ports); //新增需要用户指定IP
bool Init(const std::string& ip, const std::string& listen_ports/*like "80,8080,443"*/); //新增需要用户指定IP
3.上面修改的Server::Init()
函数与测试用例可能不兼容,请在CMakeList中注释掉测试用例和example
#if (NOT EVPP_VCPKG_BUILD)
# add_subdirectory (test)
# add_subdirectory (examples)
# add_subdirectory (benchmark)
#endif ()
4.修正了几个地方缺少vector的include语句
5.Context::FindQueryFromURI()
函数,返回pair,first表示是否能找到,second表示GET参数值。主要用于区别是否有该值,该值是否为空的情况。另外增加了模板化,兼容string_view与string。
6.支持C++14,使用std::string_view提升性能,不失兼容性。注意与G++版本兼容。CMakeList.txt需要修改一个地方:
set (CXX_FLAGS
-g
-fPIC
-Wall
-Wno-unused-parameter
-Wno-unused-function
-Wunused-variable
-Wunused-value
-Wshadow
-Wcast-qual
-Wcast-align
-Wwrite-strings
-Wsign-compare
-Winvalid-pch
-fms-extensions
-Wfloat-equal
-Wextra
-std=c++14 #这里改成c++14
)
evpp是一个基于libevent开发的现代化C++11高性能网络服务器,自带TCP/UDP/HTTP等协议的异步非阻塞式的服务器和客户端库。
- 现代版的C++11接口
- 非阻塞异步接口都是C++11的functional/bind形式的回调仿函数(不是libevent中的C风格的函数指针)
- CPU多核友好和线程安全
- 非阻塞纯异步多线程TCP服务器/客户端
- 非阻塞纯异步多线程HTTP服务器/客户端
- 非阻塞纯异步多线程UDP服务器
- 支持多进程模式
- 优秀的跨平台特性和高性能(继承自libevent的优点)
- 已经经过详尽的测试,并已经用于生产环境,每天承载数万亿次的网络通信
- 非常容易安装和集成,evpp 能够很容易就打包出 deb/rpm/tar.gz 等格式的安装包
除此之外,基于该库之上,还提供两个附带的应用层协议库:
- evmc :一个纯异步非阻塞式的
memcached
的C++客户端库,支持membase
集群模式。该库已经用于生产环境,每天发起3000多亿次memcache查询请求。详情请见:evmc readme - evnsq : 一个纯异步非阻塞式的
NSQ
的C++客户端库,支持消费者、生产者、服务发现等特性。该库已经用于生产环境,每天生产1300多亿条日志消息。详情请见: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#L36~L87。我们如此苛刻的追求线程安全,只是为了让一个程序能安静的平稳的退出或Reload,因为我们深刻的理解“编写永远运行的系统,和编写运行一段时间后平静关闭的系统是两码事”,后者要困难的多得多。
请见 Quick Start
The IO Event performance benchmark against Boost.Asio, 该测试表明在该场景下evpp比asio高 20%~50% 上下
The ping-pong benchmark against Boost.Asio, 该测试表明在该场景下evpp比asio高 5%~20%% 上下
The throughput benchmark against libevent2, 该测试表明在该场景下evpp比libevent高 17%~130% 上下
The throughput benchmark against Boost.Asio, 该测试表明在该场景下evpp与asio的性能基本相当,互相没有明显优势
The throughput benchmark against Boost.Asio(中文), 该测试表明在该场景下evpp与asio的性能基本相当,互相没有明显优势
The throughput benchmark against muduo(中文), 该测试表明在该场景下evpp与muduo的性能基本相当,互相没有明显优势
本文用 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
#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;
}
#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;
}
#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;
}
欢迎申请加入该名单 ;-)