基于libevent开发的高性能网络服务器介绍以及源码解析(一)
源码地址:https://github.com/Qihoo360/evpp
特性
1.现代版的C++11接口
2.非阻塞异步接口都是C++11的functional/bind形式的回调仿函数(不是libevent中的C风格的函数指针)
3.CPU多核友好和线程安全
4.非阻塞纯异步多线程TCP服务器/客户端
5.非阻塞纯异步多线程HTTP服务器/客户端
6.非阻塞纯异步多线程UDP服务器
7.支持多进程模式
8.优秀的跨平台特性和高性能(继承自libevent的优点)
类介绍
Duration : 这是一个时间区间相关的类,自带时间单位信息,参考了Golang项目中的Duration实现。
// Modeled after the time.Duration of Golang project.
#pragma once
#include "evpp/inner_pre.h"
namespace evpp {
// Duration表示两个瞬间之间经过的时间
// int64纳秒计数。表示限制了
// 最大可代表期限约为290年。
class EVPP_EXPORT Duration {
public:
static const int64_t kNanosecond; // = 1LL
static const int64_t kMicrosecond;// = 1000
static const int64_t kMillisecond;// = 1000 * kMicrosecond
static const int64_t kSecond; // = 1000 * kMillisecond
static const int64_t kMinute; // = 60 * kSecond
static const int64_t kHour; // = 60 * kMinute
public:
Duration();
explicit Duration(const struct timeval& t);
explicit Duration(int64_t nanoseconds);
explicit Duration(int nanoseconds);
explicit Duration(double seconds);
// Nanoseconds returns the duration as an integer nanosecond count.
int64_t Nanoseconds() const;
// These methods return double because the dominant
// use case is for printing a floating point number like 1.5s, and
// a truncation to integer would make them not useful in those cases.
// Seconds returns the duration as a floating point number of seconds.
double Seconds() const;
double Milliseconds() const;
double Microseconds() const;
double Minutes() const;
double Hours() const;
struct timeval TimeVal() const;
void To(struct timeval* t) const;
bool IsZero() const;
bool operator< (const Duration& rhs) const;
bool operator<=(const Duration& rhs) const;
bool operator> (const Duration& rhs) const;
bool operator>=(const Duration& rhs) const;
bool operator==(const Duration& rhs) const;
//重载运算符,用于实现时间的计算
Duration operator+=(const Duration& rhs);
Duration operator-=(const Duration& rhs);
Duration operator*=(int ns);
Duration operator/=(int ns);
private:
int64_t ns_; // nanoseconds
};
} // namespace evpp
#include "duration.inl.h"
这里的设计思路仿照了[Golang]实现的版本来的简单明了,目前C++11中也有类似的实现std::chrono::duration
,但稍显复杂,从.h函数可以看出,这个类基本能满足我们的需求。
Buffer: 这是一个缓冲区类,融合了
muduo`和[Golang]两个项目中相关类的设计和实现
// Modified from muduo project http://github.com/chenshuo/muduo
// @see https://github.com/chenshuo/muduo/blob/master/muduo/net/Buffer.h and https://github.com/chenshuo/muduo/blob/master/muduo/net/Buffer.cc
#pragma once
#include "evpp/inner_pre.h"
#include "evpp/slice.h"
#include "evpp/sockets.h"
#include <algorithm>
namespace evpp {
class EVPP_EXPORT Buffer {
public:
static const size_t kCheapPrependSize;
static const size_t kInitialSize;
explicit Buffer(size_t initial_size = kInitialSize, size_t reserved_prepend_size = kCheapPrependSize)
: capacity_(reserved_prepend_size + initial_size)
, read_index_(reserved_prepend_size)
, write_index_(reserved_prepend_size)
, reserved_prepend_size_(reserved_prepend_size) {
buffer_ = new char[capacity_];
assert(length() == 0);
assert(WritableBytes() == initial_size);
assert(PrependableBytes() == reserved_prepend_size);
}
~Buffer() {
delete[] buffer_;
buffer_ = nullptr;
capacity_ = 0;
}
void Swap(Buffer& rhs) {
std::swap(buffer_, rhs.buffer_);
std::swap(capacity_, rhs.capacity_);
std::swap(read_index_, rhs.read_index_);
std::swap(write_index_, rhs.write_index_);
std::swap(reserved_prepend_size_, rhs.reserved_prepend_size_);
}
// 跳过指定长度的缓存
void Skip(size_t len) {
if (len < length()) {
read_index_ += len;
} else {
Reset();
}
}
// Retrieve advances the reading index of the buffer
// Retrieve it the same as Skip.
void Retrieve(size_t len) {
Skip(len);
}
// 清空n个字节的缓存
void Truncate(size_t n) {
if (n == 0) {
read_index_ = reserved_prepend_size_;
write_index_ = reserved_prepend_size_;
} else if (write_index_ > read_index_ + n) {
write_index_ = read_index_ + n;
}
}
// 清空所有缓存,效果等同truncate(0)
void Reset() {
Truncate(0);
}
//将容器的容量增加到一个更大的值
//或等于len。如果len大于current capacity(),
//分配新的存储空间,否则该方法不执行任何操作。
void Reserve(size_t len) {
if (capacity_ >= len + reserved_prepend_size_) {
return;
}
// TODO add the implementation logic here
grow(len + reserved_prepend_size_);
}
//Read
public:
// Peek int64_t/int32_t/int16_t/int8_t with network endian
int64_t ReadInt64() {
int64_t result = PeekInt64();
Skip(sizeof result);
return result;
}
int32_t ReadInt32() {
int32_t result = PeekInt32();
Skip(sizeof result);
return result;
}
int16_t ReadInt16() {
int16_t result = PeekInt16();
Skip(sizeof result);
return result;
}
int8_t ReadInt8() {
int8_t result = PeekInt8();
Skip(sizeof result);
return result;
}
Slice ToSlice() const {
return Slice(data(), length());
}
std::string ToString() const {
return std::string(data(), length());
}
void Shrink(size_t reserve) {
Buffer other(length() + reserve);
other.Append(ToSlice());
Swap(other);
}
// ReadFromFD reads data from a fd directly into buffer,
// and return result of readv, errno is saved into saved_errno
ssize_t ReadFromFD(evpp_socket_t fd, int* saved_errno);
// Next返回一个切片,包含从缓冲区中取出的下一个n个字节,
// 如果缓冲区小于n个字节,Next返回整个缓冲区。
// 切片只在下一次调用读或写方法之前有效。
Slice Next(size_t len) {
if (len < length()) {
Slice result(data(), len);
read_index_ += len;
return result;
}
return NextAll();
}
// NextAll returns a slice containing all the unread portion of the buffer,
// advancing the buffer as if the bytes had been returned by Read.
Slice NextAll() {
Slice result(data(), length());
Reset();
return result;
}
std::string NextString(size_t len) {
Slice s = Next(len);
return std::string(s.data(), s.size());
}
std::string NextAllString() {
Slice s = NextAll();
return std::string(s.data(), s.size());
}
// ReadByte reads and returns the next byte from the buffer.
// If no byte is available, it returns '\0'.
char ReadByte() {
assert(length() >= 1);
if (length() == 0) {
return '\0';
}
return buffer_[read_index_++];
}
// UnreadBytes unreads the last n bytes returned
// by the most recent read operation.
void UnreadBytes(size_t n) {
assert(n < read_index_);
read_index_ -= n;
}
// Peek
public:
// Peek int64_t/int32_t/int16_t/int8_t with network endian
int64_t PeekInt64() const {
assert(length() >= sizeof(int64_t));
int64_t be64 = 0;
::memcpy(&be64, data(), sizeof be64);
return evppbswap_64(be64);
}
int32_t PeekInt32() const {
assert(length() >= sizeof(int32_t));
int32_t be32 = 0;
::memcpy(&be32, data(), sizeof be32);
return ntohl(be32);
}
int16_t PeekInt16() const {
assert(length() >= sizeof(int16_t));
int16_t be16 = 0;
::memcpy(&be16, data(), sizeof be16);
return ntohs(be16);
}
int8_t PeekInt8() const {
assert(length() >= sizeof(int8_t));
int8_t x = *data();
return x;
}
public:
// data返回length buffer. length()的指针,该指针保存着buffer的未读部分。
// 该数据只在下一次缓冲区修改前有效(即,
// 直到下一次调用Read、Write、Reset或Truncate方法)。
// 至少在下一次缓冲区修改之前,数据会为缓冲区内容起别名,
// 因此,对slice的立即更改将影响以后读取的结果。
const char* data() const {
return buffer_ + read_index_;
}
char* WriteBegin() {
return begin() + write_index_;
}
const char* WriteBegin() const {
return begin() + write_index_;
}
// length returns the number of bytes of the unread portion of the buffer
size_t length() const {
assert(write_index_ >= read_index_);
return write_index_ - read_index_;
}
// size returns the number of bytes of the unread portion of the buffer.
// It is the same as length().
size_t size() const {
return length();
}
// capacity returns the capacity of the buffer's underlying byte slice, that is, the
// total space allocated for the buffer's data.
size_t capacity() const {
return capacity_;
}
size_t WritableBytes() const {
assert(capacity_ >= write_index_);
return capacity_ - write_index_;
}
size_t PrependableBytes() const {
return read_index_;
}
// Helpers
public:
const char* FindCRLF() const {
const char* crlf = std::search(data(), WriteBegin(), kCRLF, kCRLF + 2);
return crlf == WriteBegin() ? nullptr : crlf;
}
const char* FindCRLF(const char* start) const {
assert(data() <= start);
assert(start <= WriteBegin());
const char* crlf = std::search(start, WriteBegin(), kCRLF, kCRLF + 2);
return crlf == WriteBegin() ? nullptr : crlf;
}
const char* FindEOL() const {
const void* eol = memchr(data(), '\n', length());
return static_cast<const char*>(eol);
}
const char* FindEOL(const char* start) const {
assert(data() <= start);
assert(start <= WriteBegin());
const void* eol = memchr(start, '\n', WriteBegin() - start);
return static_cast<const char*>(eol);
}
private:
char* begin() {
return buffer_;
}
const char* begin() const {
return buffer_;
}
void grow(size_t len) {
if (WritableBytes() + PrependableBytes() < len + reserved_prepend_size_) {
//grow the capacity
size_t n = (capacity_ << 1) + len;
size_t m = length();
char* d = new char[n];
memcpy(d + reserved_prepend_size_, begin() + read_index_, m);
write_index_ = m + reserved_prepend_size_;
read_index_ = reserved_prepend_size_;
capacity_ = n;
delete[] buffer_;
buffer_ = d;
} else {
// move readable data to the front, make space inside buffer
assert(reserved_prepend_size_ < read_index_);
size_t readable = length();
memmove(begin() + reserved_prepend_size_, begin() + read_index_, length());
read_index_ = reserved_prepend_size_;
write_index_ = read_index_ + readable;
assert(readable == length());
assert(WritableBytes() >= len);
}
}
private:
char* buffer_;
size_t capacity_;
size_t read_index_;
size_t write_index_;
size_t reserved_prepend_size_;
static const char kCRLF[];
};
}
以上展示部分buffer的源码,部分常用的方法这里就不展示了
总结:
evpp的时间类和缓存类大程度上借鉴了go的切片的思想