diff --git a/.gitignore b/.gitignore index 259148f..09ca400 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,10 @@ *.exe *.out *.app + +# blade build +blade-bin/ +build64_release/ + +#vs code +.vscode/ diff --git a/chive/base/AsynLogging.cc b/chive/base/AsynLogging.cc new file mode 100755 index 0000000..e69de29 diff --git a/chive/base/AsynLogging.h b/chive/base/AsynLogging.h new file mode 100755 index 0000000..e69de29 diff --git a/chive/base/Atomic.h b/chive/base/Atomic.h new file mode 100644 index 0000000..3993e2c --- /dev/null +++ b/chive/base/Atomic.h @@ -0,0 +1,65 @@ +#ifndef CHIVE_BASE_ATOMIC_H +#define CHIVE_BASE_ATOMIC_H + +#include "chive/base/noncopyable.h" +#include + +namespace chive +{ +namespace base +{ +template +class AtomicIntegerT : noncopyable +{ +public: + AtomicIntegerT():value_(0){} + + T get() { + return __atomic_load_n(&value_, __ATOMIC_SEQ_CST); + } + + T getAndAdd(T x) { + return __atomic_fetch_add(&value_, x, __ATOMIC_SEQ_CST); + } + + T addAndGet(T x) { + return getAndAdd(x) + x; + } + + T incrementAndGet() { + return addAndGet(1); + } + + T decrementAndGet() { + return addAndGet(-1); + } + + void add(T x) { + getAndAdd(x); + } + + void increment() { + incrementAndGet(); + } + + void decrement() { + decrementAndGet(); + } + + T getAndSet(T newVal) { + return __atomic_exchange_n(&value_, newVal, __ATOMIC_SEQ_CST); + } + +private: + volatile T value_; +}; +} // namespace base + +/** + * 提供给chive::net namespace下直接使用 + */ +using AtomicInt32 = base::AtomicIntegerT; +using AtomicInt64 = base::AtomicIntegerT; +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/base/BUILD b/chive/base/BUILD new file mode 100755 index 0000000..51e6281 --- /dev/null +++ b/chive/base/BUILD @@ -0,0 +1,32 @@ +cc_library( + name = 'chive_base', + srcs = [ + 'CurrentThread.cc', + 'CountDownLatch.cc', + 'Thread.cc', + 'FileUtil.cc', + ], + deps = [ + '#pthread', + '//chive/base/clog/:chive_log' + ], + extra_cppflags = [ + '-std=c++17', + # '-Wall', # show warning as error + '-w', # no warning + '-g', + ] +) + +cc_library( + name = 'chive_fileutil', + srcs = [ + 'FileUtil.cc' + ], + extra_cppflags = [ + '-std=c++17', + # '-Wall', # show warning as error + '-w', # no warning + '-g', + ] +) diff --git a/chive/base/Condition.h b/chive/base/Condition.h new file mode 100755 index 0000000..9996421 --- /dev/null +++ b/chive/base/Condition.h @@ -0,0 +1,57 @@ +#ifndef CHIVE_BASE_CONDITION_H +#define CHIVE_BASE_CONDITION_H + +#include "chive/base/MutexLock.h" + +#include +#include +#include /*provide ETIMEDOUT */ + +namespace chive +{ +class Condition : noncopyable { +public: + explicit Condition(MutexLock& mutex) : mutex_(mutex) { + int flag = pthread_cond_init(&cond_, nullptr); + assert(flag == 0); (void)flag; + } + + ~Condition() { + int flag = pthread_cond_destroy(&cond_); + assert(flag == 0); (void)flag; + } + + void wait() { + /// FIXME: need lock guard or not? + int flag = pthread_cond_wait(&cond_, mutex_.getPthreadMutexPtr()); + assert(flag == 0); (void)flag; + } + + bool waitForSecond(int second) { + struct timespec timeout{}; + // CLOCK_REALTIME 和 CLOCK_MONOTONIC 的区别 + // clock_getres 和 clock_gettime的区别 + /*clock_getres(CLOCK_REALTIME, &timeout);*/ + clock_getres(CLOCK_MONOTONIC, &timeout); + timeout.tv_sec += second; + return pthread_cond_timedwait( + &cond_, mutex_.getPthreadMutexPtr(), &timeout) == ETIMEDOUT; + } + + void notify() { + int flag = pthread_cond_signal(&cond_); + assert(flag == 0); (void)flag; + } + + void notifyall() { + int flag = pthread_cond_broadcast(&cond_); + assert(flag == 0); (void)flag; + } + +private: + MutexLock& mutex_; + pthread_cond_t cond_; +}; +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/base/ContentTypes.h b/chive/base/ContentTypes.h new file mode 100755 index 0000000..46b785b --- /dev/null +++ b/chive/base/ContentTypes.h @@ -0,0 +1,39 @@ +#ifndef CHIVE_NET_CONTENT_TYPES_H +#define CHIVE_NET_CONTENT_TYPES_H + +namespace chive +{ + +/** + * 主流的body content type + * + * application/x-www-form-urlencoded: 不属于http content-type规范,通常用于浏览器表单提交, + * 格式:name1=value1&name2=value2, POST会放入http body,GET则显示在在URL + * urlencoded格式如 URL中出现 %E4%BD%A0,与unicode(\uxxxx) 区分 + * + * + */ +enum class ContentType +{ + ApplicationJson, + ApplicationXml, + ApplicationBase64, + ApplicationXW3FormUrlEncoded, /**/ + ApplicationOctetStream, /*二进制流或字节数组*/ + MultipartFormdata, /*表单*/ + TextPlain, + TextCss, + TextHtml, + ApplicationJavascript, + OtherType +}; + +enum class MimeType +{ + TextXml, + TextHtml, +}; + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/base/CountDownLatch.cc b/chive/base/CountDownLatch.cc new file mode 100755 index 0000000..547b50b --- /dev/null +++ b/chive/base/CountDownLatch.cc @@ -0,0 +1,31 @@ +#include "chive/base/CountDownLatch.h" + +using namespace chive; + +CountDownLatch::CountDownLatch(int count) + : mutex_{}, + condition_(mutex_), + count_(count) +{ +} + +void CountDownLatch::wait() { + MutexLockGuard lock(mutex_); + while(count_ > 0) { + condition_.wait(); + } +} + +void CountDownLatch::countDown() { + MutexLockGuard lock(mutex_); + --count_; + if(count_ == 0) { + condition_.notifyall(); + } +} + +int CountDownLatch::getCount() const { + MutexLockGuard lock(mutex_); + return count_; +} + diff --git a/chive/base/CountDownLatch.h b/chive/base/CountDownLatch.h new file mode 100755 index 0000000..61fbc71 --- /dev/null +++ b/chive/base/CountDownLatch.h @@ -0,0 +1,30 @@ +#ifndef CHIVE_BASE_COUNTDOWNLATCH_H +#define CHIVE_BASE_COUNTDOWNLATCH_H + +#include "chive/base/noncopyable.h" +#include "chive/base/Condition.h" +#include "chive/base/MutexLock.h" + +namespace chive +{ +class CountDownLatch : noncopyable { +public: + + explicit CountDownLatch(int count); + + void wait(); + + void countDown(); + + int getCount() const; +private: + // + mutable MutexLock mutex_; + // 相比muduo源码,这里省去了clang线程安全检查注解 guard_by + /// FIXME: + Condition condition_; + int count_; +}; +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/base/CurrentThread.cc b/chive/base/CurrentThread.cc new file mode 100755 index 0000000..df1024c --- /dev/null +++ b/chive/base/CurrentThread.cc @@ -0,0 +1,30 @@ +#include "chive/base/CurrentThread.h" + +// using namespace chive; + +#include +#include /* For SYS_xxx definitions */ + +namespace chive +{ +namespace CurrentThread +{ +// 必须在与 .h 相同的namespace 下定义,否则导致二义性 +// 即,编译器认不出是 .cc 的 还是 .h 的 +// 定义 extern __thread +__thread int t_cachedTid = 0; +__thread char t_tidString[32]; +__thread int t_tidStringLength = 6; +__thread const char* t_threadName = "unknown"; +static_assert(std::is_same::value, "pid_t should be int"); + +void cachedTid() { + if(t_cachedTid == 0) { + t_cachedTid = static_cast(::syscall(SYS_gettid)); + t_tidStringLength = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid); + } +} +} // namespace CurrentThread + +} // namespace chive + diff --git a/chive/base/CurrentThread.h b/chive/base/CurrentThread.h new file mode 100755 index 0000000..53fd268 --- /dev/null +++ b/chive/base/CurrentThread.h @@ -0,0 +1,41 @@ +#ifndef V_CURRENTTHREAD_H +#define V_CURRENTTHREAD_H + +#include +#include +namespace chive +{ +namespace CurrentThread { + // 使用extern告诉编译器这些__thread修饰的变量定义在 + // 另一个文件,只能初始化为编译器常量 + extern __thread int t_cachedTid; + extern __thread char t_tidString[32]; + extern __thread int t_tidStringLength; + extern __thread const char* t_threadName; + + void cachedTid(); + + inline int tid() { + // __builtin_expect是GCC的內建函数 + //作用:编译器分支预测,减少跳转指令,从而优化性能 + if(__builtin_expect(t_cachedTid == 0, 0)) { + cachedTid(); + } + return t_cachedTid; + } + + inline int tidStringLength() { + return t_tidStringLength; + } + // 未实现设置线程名 + inline const char* name() { + return t_threadName; + } + //根据主线程的pid == tid + inline bool isMainThread() { + return tid() == ::getpid(); + } +}; +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/base/FileUtil.cc b/chive/base/FileUtil.cc new file mode 100755 index 0000000..7797037 --- /dev/null +++ b/chive/base/FileUtil.cc @@ -0,0 +1,36 @@ +#include "chive/base/FileUtil.h" +#include "chive/base/clog/chiveLog.h" +#include + +using namespace chive; +using namespace chive::FileUtil; + +const char* FILE_PATH = "/data/chive/upload/"; + +File::File(std::string filename, ContentType type) + : filename_(filename), + contentType_(type), + writtenBytes_(0) +{ + +} + +File::~File() +{ + +} + +void File::output(const char* begin, const char* end) +{ + fout_.open(FILE_PATH + filename_, std::ios::out|std::ios::binary); + if (!fout_.is_open()) + { + // CHIVE_LOG_ERROR("Cannot open file %s", FILE_PATH + filename_); + std::cout << "err" << std::endl; + } + else + { + fout_ << std::string(begin, end); + fout_.close(); + } +} diff --git a/chive/base/FileUtil.h b/chive/base/FileUtil.h new file mode 100755 index 0000000..edd74b0 --- /dev/null +++ b/chive/base/FileUtil.h @@ -0,0 +1,59 @@ +/** + * File.h + * 文件抽象对象,包括文件操作的方法 + */ + +#ifndef CHIVE_BASE_FILE_H +#define CHIVE_BASE_FILE_H + +#include "chive/base/copyable.h" +#include "chive/base/ContentTypes.h" +#include // for off_t +#include + +namespace chive +{ +namespace FileUtil +{ + +class File : copyable +{ +public: + explicit File(std::string filename, ContentType type = ContentType::TextPlain); + ~File(); + // 输出到指定路径 + // void output(std::string outputPath); + // 将char[begin, end]输出到FILE_PATH/filename + void output(const char* begin, const char* end); + + //void flush(); + + //void append(const char* text, size_t len); + + off_t writtenBytes() const + { + return writtenBytes_; + } + +private: + // size_t write(const char* text, size_t len); + + std::string filename_; + ContentType contentType_; + MimeType mimeType_; + // suffixType_; 扩展名 + // + std::fstream fout_; + // char buffer_[64 * 1024]; // 64KB + off_t writtenBytes_; // +}; + + +} // namespace FileUtil + + + +} // namespace chive + + +#endif diff --git a/chive/base/Logger.cc b/chive/base/Logger.cc new file mode 100755 index 0000000..57c2c7a --- /dev/null +++ b/chive/base/Logger.cc @@ -0,0 +1,106 @@ +#include "chive/base/Logger.h" +#include "chive/base/CurrentThread.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace chive; + +ConsoleLogger chive::debug; + +// 日志级别string信息 +static const std::map LogLevelInfo = +{ + {LogLevel::TRACE, "TRACE"}, + {LogLevel::DEBUG, "DEBUG"}, + {LogLevel::INFO, "INFO"}, + {LogLevel::WARN, "WARN"}, + {LogLevel::ERROR, "ERROR"}, + {LogLevel::FATAL, "FATAL"} +}; + +std::ostream& operator<<(std::ostream& stream, const tm* tmStr) { + return stream << 1900 + tmStr->tm_year << '-' + << std::setfill('0') << std::setw(2) << tmStr->tm_mon + 1 << '-' + << std::setfill('0') << std::setw(2) << tmStr->tm_mday << ' ' + << std::setfill('0') << std::setw(2) << tmStr->tm_hour << ':' + << std::setfill('0') << std::setw(2) << tmStr->tm_min << ':' + << std::setfill('0') << std::setw(2) << tmStr->tm_sec; +} + +BaseLogger::LogStream BaseLogger::operator()(LogLevel level) { + return LogStream(*this, level); +} + +const tm* BaseLogger::getLocalTime() { + auto now = std::chrono::system_clock::now(); + auto inTime = std::chrono::system_clock::to_time_t(now); + localtime_r(reinterpret_cast(&inTime), &localtime_); + + return &localtime_; +} + +void BaseLogger::endline(LogLevel level, const std::string& message) { + MutexLockGuard lock(mutex_); + // 调用纯虚函数,多态,调用派生类具体实现 + output(getLocalTime(), LogLevelInfo.find(level)->second, message); +} + +BaseLogger::LogStream::LogStream(BaseLogger& logger, LogLevel level) + : logger_(logger), + level_(level) { + +} + +BaseLogger::LogStream::LogStream(const BaseLogger::LogStream& other) + //: std::ostringstream(other), + :logger_(other.logger_), + level_(other.level_) { + +} + +BaseLogger::LogStream::~LogStream() { + logger_.endline(level_, static_cast(std::move(str()))); +} + +// 私有方法,由endline调用,endline加锁保证线程安全 +void ConsoleLogger::output(const tm* tmPtr, const std::string& level, const std::string& message) { + std::cout << '[' << tmPtr << ']' + << '[' << level << ']' + << "[pid " << getpid() << " tid " << CurrentThread::tid() << "]" + << '\t' << message + << std::endl; +} + +// 初始化列表基类构造函数 +FileLogger::FileLogger(std::string filename) noexcept : BaseLogger() { + std::string validFilename(filename.size(), '\0'); + std::regex express(R"(/|:| |>|<|\"|\\*|\\?|\\|)"); + std::regex_replace(validFilename.begin(), filename.begin(), filename.end(), express,"-"); + file_.open(validFilename, std::fstream::out | std::fstream::app | std::fstream::ate); + + assert(!file_.fail()); +} + +FileLogger::~FileLogger() { + file_.flush(); + file_.close(); +} + + +void FileLogger::output(const tm *tmPtr, const std::string &levelStr, const std::string &messageStr) { + file_ << '[' << tmPtr << ']' + << '[' << levelStr << ']' + << '\t' << messageStr << std::endl; + file_.flush(); +} + + + + + diff --git a/chive/base/Logger.h b/chive/base/Logger.h new file mode 100755 index 0000000..727619d --- /dev/null +++ b/chive/base/Logger.h @@ -0,0 +1,126 @@ +/// +/// reference to https://github.com/FutaAlice/cpp11logger +/// + +#ifndef CHIVE_BASE_LOGGER_H +#define CHIVE_BASE_LOGGER_H + +#include +#include +#include +#include + +#include "chive/base/MutexLock.h" +#include "chive/base/noncopyable.h" + +/* +#include or #include + +#ifndef _TM_DEFINED +struct tm { + int tm_sec; // 秒 – 取值区间为[0,59] + int tm_min; // 分 - 取值区间为[0,59] + int tm_hour; // 时 - 取值区间为[0,23] + int tm_mday; // 一个月中的日期 - 取值区间为[1,31] + int tm_mon; // 月份(从一月开始,0代表一月) - 取值区间为[0,11] + int tm_year; // 年份,其值等于实际年份减去1900 + int tm_wday; // 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 + int tm_yday; // 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 + int tm_isdst; // 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。 + }; +#define _TM_DEFINED +#endif +*/ + +/* +struct tm * gmtime(const time_t *timer); // UTC,世界标准时间,即格林尼治时间 +struct tm * localtime(const time_t * timer); // 本地时间 +*/ + +namespace chive +{ +// 日志等级,使用c++11强类型枚举 +enum class LogLevel +{ + TRACE, + DEBUG, + INFO, + WARN, + ERROR, + FATAL +}; + +// 抽象基类 +class BaseLogger { +// 内部类 +class LogStream; + +public: + BaseLogger() = default; + virtual ~BaseLogger() = default; + + virtual LogStream operator()(LogLevel loglevel = LogLevel::DEBUG); + +private: + MutexLock mutex_; + tm localtime_{}; + + /** + * 获取本地时间 + */ + const tm* getLocalTime(); + + /** + * @param level + * @param message + */ + void endline(LogLevel level, const std::string& message); + + /** + * 纯虚函数,打印日志消息 + * @param tmPtr 本地时间 + * @param level 日志等级 + * @param message 消息内容 + */ + virtual void output(const tm* tmPtr, + const std::string& level, + const std::string& message) = 0; +}; + +// BaseLogger::LogStream 内部类的定义 +class BaseLogger::LogStream : public std::ostringstream { +public: + LogStream(BaseLogger& logger, LogLevel level); + LogStream(const LogStream& other); + ~LogStream() override; +private: + BaseLogger& logger_; + LogLevel level_; +}; + +// 控制台日志类 +class ConsoleLogger : public BaseLogger { + // c++11特性:继承构造函数 + using BaseLogger::BaseLogger; + void output(const tm* tmPtr, const std::string& level, const std::string& message) override; +}; + +// 文档日志类 +class FileLogger + : public BaseLogger, + private noncopyable { +public: + explicit FileLogger(std::string filename) noexcept; + ~FileLogger() override; +private: + std::ofstream file_; + void output(const tm* tmPtr, const std::string& level, const std::string& message) override; +}; + +// 使用extern关键字声明外部变量 +// 实现全局实例(只定义一次) +extern ConsoleLogger debug; +//extern FileLogger CHIVE_LOG; +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/base/MutexLock.h b/chive/base/MutexLock.h new file mode 100644 index 0000000..be08d65 --- /dev/null +++ b/chive/base/MutexLock.h @@ -0,0 +1,86 @@ +#ifndef CHIVE_BASE_MUTEXLOCK_H +#define CHIVE_BASE_MUTEXLOCK_H + +#include "chive/base/noncopyable.h" +#include "chive/base/CurrentThread.h" + +#include +#include +#include + + +namespace chive +{ +/** + * MutexLock封装pthread mutex + */ +class MutexLock : noncopyable { +public: + MutexLock() : mutex_{}, holder_(0) { + int flag = pthread_mutex_init(&mutex_, nullptr); + assert(flag == 0); (void)flag; + } + + ~MutexLock() { + assert(holder_ == 0); + int flag = pthread_mutex_destroy(&mutex_); + assert(flag == 0); (void)flag; + } + + /** + * 当前线程是否为加锁线程 + * @return + */ + bool isLockedByThisThread() { + return holder_ == CurrentThread::tid(); + } + + void assertLocked() { + bool flag = isLockedByThisThread(); + assert(flag); (void)flag; + } + + void lock() { + int flag = pthread_mutex_lock(&mutex_); + assert(flag == 0); (void)flag; + holder_ = CurrentThread::tid(); + } + + void unlock() { + holder_ = 0; + int flag = pthread_mutex_unlock(&mutex_); + assert(flag == 0); (void)flag; + } + + pthread_mutex_t* getPthreadMutexPtr() { + return &mutex_; + } + +private: + pthread_mutex_t mutex_; + pid_t holder_; //持有该锁的线程Id +}; + +/** + * MutexLockGuard:RAII管理加锁和解锁 + * 创建时构造-加锁,离开作用域时,析构-解锁 + */ +class MutexLockGuard: noncopyable { +public: + explicit MutexLockGuard(MutexLock &mutex):mutex_(mutex) { + mutex_.lock(); + } + ~MutexLockGuard() { + mutex_.unlock(); + } +private: + MutexLock& mutex_; //声明为引用类型 +}; + +// 防止类似误用 MutexLockGuard(mutex_) +// 临时对象不能长时间持有锁,产生后又马上销毁 +// 正确写法 MutexLockGuard lock(mutex_) +#define MutexLockGuard(x) error "Missing guard object name" +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/base/Thread.cc b/chive/base/Thread.cc new file mode 100755 index 0000000..9414f33 --- /dev/null +++ b/chive/base/Thread.cc @@ -0,0 +1,106 @@ +#include "chive/base/Thread.h" +#include "chive/base/clog/chiveLog.h" + +#include +#include +#include +#include +#include + +using namespace chive; +// using namespace chive::base; + +AtomicInt32 Thread::numCreated_; + +Thread::Thread(const ThreadFunc& func, const std::string& name) + : started_ (false), + joined_ (false), + pthreadId_ (0), + tid_ (0), + func_ (std::move(func)), + name_ (name), + latch_(1) +{ + setDefaultName(); +} + +Thread::~Thread() +{ + if (started_ && !joined_) + { /// detach 线程,由系统接管 + pthread_detach(pthreadId_); + } +} + +void Thread::setDefaultName() +{ + int num = numCreated_.incrementAndGet(); + if (name_.empty()) + { + char buf[32]; + snprintf(buf, sizeof(buf), "Thread#%d", num); + name_ = buf; + } +} + +void Thread::start() +{ + assert(!started_); + started_ = true; + + if (0 != pthread_create(&pthreadId_, NULL, &startThread, this)) + { + started_ = false; + CHIVE_LOG_ERROR("pthread_create failed!"); + } + else + { + CHIVE_LOG_DEBUG("create thread %p with %ld", this, pthreadId_); + latch_.wait(); + assert(tid_ > 0); + } +} + +int Thread::join() +{ + assert(started_); + assert(!joined_); + joined_ = true; + return pthread_join(pthreadId_, NULL); +} + +pid_t Thread::gettid() +{ + return static_cast(syscall(__NR_gettid)); +} + + +void* Thread::startThread(void* obj) +{ + auto* thread = static_cast(obj); + thread->runInThread(); + return nullptr; +} + +void Thread::runInThread() +{ + tid_ = Thread::gettid(); + latch_.countDown(); + try + { + func_(); // 执行注册给线程的函数 + } + catch (const std::exception& ex) + { + fprintf(stderr, "exception caught in Thread %s\n", name_.c_str()); + fprintf(stderr, "reason: %s\n", ex.what()); + /// FIXME: 增加crash堆栈 + // fprintf(stderr, "stack trace: %s\n", ex.stackTrace()); + abort(); + } + catch (...) + { + fprintf(stderr, "unknown exception caught in Thread %s\n", name_.c_str()); + throw; //rethrow + } +} \ No newline at end of file diff --git a/chive/base/Thread.h b/chive/base/Thread.h new file mode 100755 index 0000000..46cb230 --- /dev/null +++ b/chive/base/Thread.h @@ -0,0 +1,71 @@ +#ifndef CHIVE_BASE_THREAD_H +#define CHIVE_BAST_THREAD_H + +#include "chive/base/noncopyable.h" +#include "chive/base/Atomic.h" +#include "chive/base/CountDownLatch.h" + +#include +#include +#include +#include +#include + +namespace chive +{ +// namespace base +// { +class Thread : noncopyable +{ +public: + using ThreadFunc = std::function; + + explicit Thread(const ThreadFunc&, const std::string& name = std::string()); + + ~Thread(); + + void start(); + + int join(); + + bool started() const { return started_; } + + pid_t tid() const { return tid_; } + + const std::string& name() const { return name_; } + + static int numCreated() { return numCreated_.get(); } + + static pid_t gettid(); + +private: + void setDefaultName(); + + bool started_; /// + bool joined_; /// + pthread_t pthreadId_; /// POSIX tid + pid_t tid_; /// Kernel tid + ThreadFunc func_; /// 线程函数 + std::string name_; /// 线程名 + CountDownLatch latch_; + + static AtomicInt32 numCreated_; + + + /** + * 线程创建时传入的函数指针 + * @param obj 传入线程对象 + */ + static void* startThread(void* obj); + + /** + * 执行注册给线程的函数 + */ + void runInThread(); +}; +// } // namespace base + + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/base/clog/BUILD b/chive/base/clog/BUILD new file mode 100755 index 0000000..80dc49a --- /dev/null +++ b/chive/base/clog/BUILD @@ -0,0 +1,12 @@ +cc_library( + name = 'chive_log', + srcs = [ + 'chiveLog.cc' + ], + extra_cppflags = [ + '-std=c++17', + # '-Wall', # show warning as error + '-w', # no warning + '-g', + ] +) \ No newline at end of file diff --git a/chive/base/clog/chiveLog.cc b/chive/base/clog/chiveLog.cc new file mode 100755 index 0000000..3ed0154 --- /dev/null +++ b/chive/base/clog/chiveLog.cc @@ -0,0 +1,267 @@ +#include "chiveLog.h" +#include + +//外部变量定义 +LevelInfoSet g_logLvInfo[CDebugLevel::MAXLV] = { + {1, "[DEBUG]"}, + {1, " [INFO]"}, + {1, " [WARN]"}, + {1, "[ERROR]"}, + {1, " [NONE]"}, + {1, " [VERB]"}, +}; + +// 静态全局日志上下文 +/** + * pthread.h 静态初始化 + * #define PTHREAD_MUTEX_INITIALIZER (pthread_mutex_t)GENERIC_INITIALIZER + * #define GENERIC_INITIALIZER ((void *) (size_t) -1) + */ +static CLogContext logContext = { + false, + NULL, + "/data/chive/clog/chive_log_0.txt", + 0, + 0, + 0, + 0, + {"",""}, + PTHREAD_MUTEX_INITIALIZER, + PTHREAD_MUTEX_INITIALIZER +}; + +static pthread_t fileThread; +static int flagThread = -1; +static pthread_cond_t newfileCondition = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t newfileMutex = PTHREAD_MUTEX_INITIALIZER; + + +void writeToConsole(const char* logLine) { + printf("%s\n", logLine); +} + +const char* getFileName(const char* filepath) { + // C 库函数 char *strrchr(const char *str, int c) + // 在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置 + const char* filename = strrchr(filepath, static_cast('/')); + if( NULL != filename) { + filename += 1; // 后移一个位置才是文件名开头 + } else { + filename = filepath; + } + return filename; +} + +void logDebugPrint(char const * module, CDebugLevel level, char const * pFormat, ...) { + // 没有开启log,直接返回 + if(logContext.logEnabled == 0) { + return; + } + + char strBuffer[MAX_BUFFER_SIZE]; + + // 输出日志字符串 + va_list args; //获取 ... 参数列表 + va_start(args, pFormat); + vsnprintf(strBuffer, sizeof(strBuffer), pFormat, args); + va_end(args); + + // 写出到控制台 + // if ((level == CDebugLevel::ERROR) + // || (level == CDebugLevel::DEBUG) + // || (strcmp(module, "CHIVE") == 0 && level == CDebugLevel::INFO) ) { + // /// FIXME: + // writeToConsole(strBuffer); + // } + + // file未创建 + if (logContext.pLogFile == NULL) { + return; + } + // 加上时间和线程等信息 + int len = strToLog(strBuffer); + // strBuffer ends with '\0', so console log based on 'printf' + // can perform normally + writeToConsole(strBuffer); + // since '\0' means the end-of-line, so replace '\0' with '\n' + // to make console log compatible with file log + if (strBuffer[len-1] == '\0' ) { + strBuffer[len-1] = '\n'; + } else if(strBuffer[len-1] != '\n') { + strBuffer[len++] = '\n'; + } + + pthread_mutex_lock(&(logContext.bufMutex)); + if (logContext.bufLen + len < MAX_BUFFER_SIZE) { + // 当前buf bucket 偏移 bufLen开始写入长度为len的strBuffer + memcpy(logContext.buf[logContext.bufId] + logContext.bufLen, strBuffer, len); + logContext.bufLen += len; // 更新当前buf bucket的长度 + pthread_mutex_unlock(&(logContext.bufMutex)); + } + else { // 当前buf bucket不够写,需要换到下一个buf bucket + int curBufId = logContext.bufId; + int curBufLen = logContext.bufLen; + + // 准备一个新的buf bucket + logContext.bufId = (logContext.bufId + 1) % BUFFER_COUNT; + memset(logContext.buf[logContext.bufId], 0, MAX_BUFFER_SIZE); //清空buf + memcpy(logContext.buf[logContext.bufId], strBuffer, len); + logContext.bufLen = len; //更新new buf bucket 的长度 + + // 如果当前缓冲区的日志已经超过一个文件的大小,需要换到新文件 + // 换新文件的逻辑是对旧文件重命名,然后打开新文件 + if ((logContext.writeCnt + 1) * MAX_BUFFER_SIZE > MAX_FILE_SIZE) { + getDebugLogFile(&logContext); // 通知线程重命名文件 + } + pthread_mutex_unlock(&logContext.bufMutex); + + // 写出一个已写满的buf到文件 + pthread_mutex_lock(&logContext.fileMutex); + if (NULL != logContext.pLogFile) { + ++logContext.writeCnt; + ///FIXME: 这里为什么需要一个>=的判断 + if (curBufLen >= MAX_BUFFER_SIZE) { + logContext.buf[curBufId][MAX_BUFFER_SIZE-1] = '\0'; + } + else { + logContext.buf[curBufId][curBufLen] = '\0'; + } + fprintf(logContext.pLogFile, "%s", logContext.buf[curBufId]); + } + pthread_mutex_unlock(&(logContext.fileMutex)); + } +} + +int strToLog(char* strBuffer) { + char newStrBuf[MAX_BUFFER_SIZE]; + struct timeval tv; + struct timezone tz; + gettimeofday(&tv, &tz); + struct tm* curTime = localtime((time_t*)&tv.tv_sec); + + if (curTime != NULL) { + // 格式 年-月-日 时:分:秒:微秒 进程号 线程号 格式化字符串 + snprintf(newStrBuf, MAX_BUFFER_SIZE - 1, "%04d-%02d-%02d %02d:%02d:%02d.%03ld %4d %4ld %s", + curTime->tm_year + 1900, curTime->tm_mon+1, curTime->tm_mday, + curTime->tm_hour, curTime->tm_min, curTime->tm_sec, tv.tv_usec / 1000, + getpid(), gettid_clog(), + strBuffer); + } + int len = strlen(newStrBuf); + /// fix bug#memcpy vs strcpy + /// memcpy 拷贝前num个,忽略\0; strcpy 遇到 \0 结束拷贝 + /// 所以这里必须是len+1才能把 \0 也拷贝过去 + memcpy(strBuffer, newStrBuf, len+1); + // strBuffer[len] = '\0'; + return len+1; +} + +bool getDebugLogFile(CLogContext* pLogContext) { + pLogContext->writeCnt = 0; + if (pLogContext->pLogFile != NULL) { + fclose(pLogContext->pLogFile); + } + + pthread_mutex_lock(&newfileMutex); + if (access(CLOG_FILE, F_OK) == 0) { // 检查CLOG_FILE是否存在 + // 将 chive_log_0.txt 重命名为 chive_log_tmp.txt + // 然后通知后台线程对CLOG_DIR目录下的log文件按序重命名 + if (rename(CLOG_FILE, CLOG_FILE_TMP) == -1) { // 对CLOG_FILE重命名 + pthread_mutex_unlock(&newfileMutex); // 重名名失败,释放锁,返回false + return false; + } + } + // 唤醒重命名线程 + pthread_cond_signal(&newfileCondition); + pthread_mutex_unlock(&newfileMutex); + + // 完成重命名之后,打开新文件 chive_log_0.txt,新的log继续写入到chive_log_0.txt + if((pLogContext->pLogFile = fopen(pLogContext->filePath, "a")) == NULL) { + return false; + } + setbuf(pLogContext->pLogFile, NULL); // 设置文件流的缓冲区为Null即不用缓冲区 + return true; +} + +void* fileThreadProcess(void* arg) { + printf("rename thread on...\n"); + while(true) { + pthread_mutex_lock(&newfileMutex); + pthread_cond_wait(&newfileCondition, &newfileMutex); + + char fileSrc[MAX_PATH_LENGTH], fileDst[MAX_PATH_LENGTH]; + // VLOG_DIR目录下的log文件重新按序命名 + // e.g. chive_log_11.txt ==> chive_log_12.txt + // ... + // chive_log_1.txt ==> chive_log_2.txt + for (int i = logContext.maxFileCnt - 2; i > 0; --i) { + sprintf(fileSrc, "%s%s%d%s", CLOG_DIR, "chive_log_", i, ".txt"); + if (access(fileSrc, F_OK) == 0) { + sprintf(fileDst, "%s%s%d%s", CLOG_DIR, "chive_log_", i+1, ".txt"); + if(rename(fileSrc, fileDst) == -1) { + break; + } + } + } + + // chive_log_tmp.txt ==> chive_log_1.txt + // 如此就能保证每次把最新log写入到文件 chive_log_0.txt + sprintf(fileSrc, "%s%s%s", CLOG_DIR, "chive_log_tmp", ".txt"); + sprintf(fileDst, "%s%s%s", CLOG_DIR, "chive_log_1", ".txt"); + if(access(fileSrc, F_OK) == 0) { + rename(fileSrc, fileDst); + } + pthread_mutex_unlock(&newfileMutex); + } +} + +void startLogPrint(unsigned *groupEnabled) { + if(-1 == flagThread) { + flagThread = pthread_create(&fileThread, NULL, fileThreadProcess, NULL); + if(flagThread) { + printf("create thread failed\n"); + } + } + // enable log + logContext.logEnabled = true; + + if (access(CLOG_DIR, 0) != 0 && !createLogDir(CLOG_DIR)) { + printf("create dir failed\n"); + return; + } + logContext.maxFileCnt = 128; + // 创建log文件填充pLogFile + if (NULL == logContext.pLogFile && !getDebugLogFile(&logContext)) { + printf("get debug file failed\n"); + return; + } + // 初始化开启的log level + ///FIXME: + + if (NULL != logContext.pLogFile && access(logContext.filePath, F_OK) != 0) { + getDebugLogFile(&logContext); + } +} + +bool createLogDir(const char *path) { + printf("create log dir\n"); + std::string builder; + std::string sub; + std::string folder(path); + + for(auto it = folder.begin(); it != folder.end(); ++it) { + const char c = *it; + sub.push_back(c); + if( c == '/' || it == folder.end() - 1) { + builder.append(sub); + printf("%s\n", builder.c_str()); + if (0 != access(builder.c_str(), 0)) { // 检查子路径是否存在,不存在就创建 + if (-1 == mkdir(builder.c_str(), 0777)) { + return false; + } + } + sub.clear(); + } + } + return true; +} diff --git a/chive/base/clog/chiveLog.h b/chive/base/clog/chiveLog.h new file mode 100755 index 0000000..4075055 --- /dev/null +++ b/chive/base/clog/chiveLog.h @@ -0,0 +1,127 @@ +#ifndef CHIVE_BASE_CLOG_CHIVELOG_H +#define CHIVE_BASE_CLOG_CHIVELOG_H + +#include +#include // rename()/access() +#include +#include // vsnprintf +#include // 字符串函数集 +#include // access() +#include +#include +#include + +#define CLOG_DIR "/data/chive/clog/" +#define CLOG_FILE "/data/chive/clog/chive_log_0.txt" +#define CLOG_FILE_TMP "/data/chive/clog/chive_log_tmp.txt" + +#define MAX_BUFFER_SIZE 1024 +#define BUFFER_COUNT 1024 +#define MAX_PATH_LENGTH 128 +#define MAX_FILE_SIZE (64 * (1 << 20)) // 64M + +#define gettid_clog() syscall(__NR_gettid) + +// debug级别 +enum CDebugLevel { + DEBUG, + INFO, + WARN, + ERROR, + NONE, + VERB, + MAXLV, /*max level*/ +}; + +// debug模块 +enum class DebugGroup { + NONE_GROUP, + CHIVE_GROUP +}; + +//日志上下文 +struct CLogContext { + bool logEnabled; // + FILE* pLogFile; // + char filePath[MAX_PATH_LENGTH]; // + int maxFileCnt; // 已写的最大文件个数 + int bufLen; // 当前全部buf的长度 + int writeCnt; // 已用的buf个数 + int bufId; // 当前写buf数组的下标, < BUFFER_COUNT + char buf[BUFFER_COUNT][MAX_BUFFER_SIZE]; // 预分配缓冲区 + pthread_mutex_t bufMutex; // buf 互斥访问 + pthread_mutex_t fileMutex; // +}; + +// 日志级别信息集合 +// 当前主要是保存level string name +struct LevelInfoSet{ + unsigned group_enable; // + const char *name; // level name +}; + + +// 声明外部变量,作为全局实例 +extern LevelInfoSet g_logLvInfo[CDebugLevel::MAXLV]; + +#define CHIVE_LOG(module, level, levelString, fmt, args...) \ + if(g_logLvInfo[level].group_enable ) { \ + char format[MAX_BUFFER_SIZE]; \ + sprintf(format, "%s %s:%d %s() %s", levelString, getFileName(__FILE__), __LINE__, __FUNCTION__, fmt); \ + logDebugPrint( \ + (module), \ + level, \ + format, \ + ##args); \ + } + +// 目前支持5种级别打印日志 +#define CHIVE_LOG_ERROR(fmt, args...) \ + CHIVE_LOG("CHIVE", CDebugLevel::ERROR, g_logLvInfo[CDebugLevel::ERROR].name, fmt, ##args) +#define CHIVE_LOG_WARN(fmt, args...) \ + CHIVE_LOG("CHIVE", CDebugLevel::WARN, g_logLvInfo[CDebugLevel::WARN].name, fmt, ##args) +#define CHIVE_LOG_INFO(fmt, args...) \ + CHIVE_LOG("CHIVE", CDebugLevel::INFO, g_logLvInfo[CDebugLevel::INFO].name, fmt, ##args) +#define CHIVE_LOG_DEBUG(fmt, args...) \ + CHIVE_LOG("CHIVE", CDebugLevel::DEBUG, g_logLvInfo[CDebugLevel::DEBUG].name, fmt, ##args) +#define CHIVE_LOG_VERB(fmt, args...) \ + CHIVE_LOG("CHIVE", CDebugLevel::VERB, g_logLvInfo[CDebugLevel::VERB].name, fmt, ##args) + + +bool createLogDir(const char *path); + +void writeToConsole(const char* logLine); + +const char* getFileName(const char* filepath); +/** + * 日志打印核心函数 + */ +// void logDebugPrint(const char* module, VDebugLevel level, const char* pFormat, ...); +void logDebugPrint(char const * module, CDebugLevel level, char const * pFormat, ...); + +/** + * 将枚举变量名转换为字符串 + * @param group + * @return + */ +const char* groupToStr(DebugGroup group); + +/** + * 将字符串转为带时间和线程信息的日志字符串 + * @param strBuffer 未添加时间和线程信息的字符串 + * @return 添加信息后的字符串长度 + */ +int strToLog(char* strBuffer); + +/** + * 唤醒线程执行重命名,打开新的log文件 + * @param pLogContext 日志上下文 + */ +bool getDebugLogFile(CLogContext* pLogContext); + +/** + * 开始log线程 + */ +void startLogPrint(unsigned *groupEnabled); + +#endif \ No newline at end of file diff --git a/chive/base/clog/makefile b/chive/base/clog/makefile new file mode 100755 index 0000000..6404894 --- /dev/null +++ b/chive/base/clog/makefile @@ -0,0 +1,9 @@ +clog:test.o chiveLog.o + g++ test.o -g chiveLog.o -o clog -pthread -std=c++17 + rm *.o -f +test.o:test.cc + g++ -c -g test.cc -o test.o -std=c++17 +chiveLog.o:chiveLog.cc + g++ -c -g chiveLog.cc -o chiveLog.o -std=c++17 +clean: + rm clog -f diff --git a/chive/base/clog/test.cc b/chive/base/clog/test.cc new file mode 100755 index 0000000..ab2f97b --- /dev/null +++ b/chive/base/clog/test.cc @@ -0,0 +1,13 @@ +#include "chiveLog.h" + +int main() { + startLogPrint(NULL); + + while(1) { + CHIVE_LOG_ERROR("%s", "helloworld"); + CHIVE_LOG_INFO("%s", "helloworld"); + CHIVE_LOG_VERB("%s", "helloworld"); + CHIVE_LOG_WARN("%s", "helloworld"); + CHIVE_LOG_DEBUG("%s", "helloworld"); + } +} diff --git a/chive/base/copyable.h b/chive/base/copyable.h new file mode 100755 index 0000000..c20b769 --- /dev/null +++ b/chive/base/copyable.h @@ -0,0 +1,12 @@ +#ifndef CHIVE_BASE_COPYABLE_H +#define CHIVE_BASE_COPYABLE_H +namespace chive +{ +class copyable { +protected: + copyable() = default; + ~copyable() = default; +}; +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/Acceptor.cc b/chive/net/Acceptor.cc new file mode 100755 index 0000000..31ceb03 --- /dev/null +++ b/chive/net/Acceptor.cc @@ -0,0 +1,83 @@ +#include "chive/net/Acceptor.h" +#include "chive/net/EventLoop.h" +#include "chive/net/InetAddress.h" +#include "chive/base/clog/chiveLog.h" + +#include +#include +#include +#include + +using namespace chive; +using namespace chive::net; + + + +int createNonblocking() +{ + int sockfd = ::socket(AF_INET, + SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); + if (sockfd < 0) + { + CHIVE_LOG_ERROR("::socket create failed! sockfd %d", sockfd); + } + return sockfd; +} + + +Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport) + : loop_ (loop), + acceptSocket_ (createNonblocking()), + acceptChannel_ (loop, acceptSocket_.fd()), + listening_ (false) +{ + CHIVE_LOG_DEBUG("created acceptor %p with socket %d channel %p in eventloop %p", + this, acceptSocket_.fd(), &acceptChannel_, loop); + acceptSocket_.setReuseAddr(true); + acceptSocket_.bindAddress(listenAddr); + acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this)); +} + +Acceptor::~Acceptor() +{ + acceptChannel_.disableAll(); + acceptChannel_.remove(); +} + + +void Acceptor::listen() +{ + loop_->assertInLoopThread(); + listening_ = true; + acceptSocket_.listen(); + acceptChannel_.enableReading(); +} + +void Acceptor::handleRead() +{ + loop_->assertInLoopThread(); + + // 接收客户端连接,获取客户端信息 + InetAddress peerAddr; + int connfd = acceptSocket_.accept(&peerAddr); + if (connfd >= 0) + { + std::string hostport = peerAddr.toIpPort(); + CHIVE_LOG_DEBUG("listening socket %d accepts client addr %s in connfd %d", + acceptSocket_.fd(), hostport.c_str(), connfd); + + if (newConnCallback_) + { + newConnCallback_(connfd, peerAddr); /// 回调 + } + else + { + ::close(connfd); + } + } + else + { + CHIVE_LOG_ERROR("accept failed, connfd %d", connfd); + ///FIXME: need extra operations?? + } +} \ No newline at end of file diff --git a/chive/net/Acceptor.h b/chive/net/Acceptor.h new file mode 100755 index 0000000..01ec91c --- /dev/null +++ b/chive/net/Acceptor.h @@ -0,0 +1,63 @@ +#ifndef CHIVE_NET_ACCEPTOR_H +#define CHIVE_NET_ACCEPTOR_H + +#include +#include "chive/base/noncopyable.h" +#include "chive/net/Channel.h" +#include "chive/net/Socket.h" + +namespace chive +{ +namespace net +{ + +class EventLoop; +class InetAddress; + +// Acceptor is only seen internally, the users cannot use it directly. +// +class Acceptor : noncopyable +{ +public: + /// FIXME: + /// replace sockfd(int) with Socket(obj) + /// base on movable constructor + /// using NewConnectionCallback = std::function; + using NewConnectionCallback = std::function; + + Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport = false); + ~Acceptor(); + + /** + * 获取监听状态 + */ + bool listening() const { return listening_; } + + /** + * 开始监听 + */ + void listen(); + + /** + * 设置新连接的回调函数 + */ + void setNewConnectionCallback(const NewConnectionCallback& cb) + { newConnCallback_ = cb; } + +private: + + void handleRead(); + + EventLoop* loop_; /// + Socket acceptSocket_; /// + Channel acceptChannel_; // + NewConnectionCallback newConnCallback_; /// 回调函数 + bool listening_; /// 监听状态 + + +}; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/BUILD b/chive/net/BUILD index ed317cc..6cfb522 100755 --- a/chive/net/BUILD +++ b/chive/net/BUILD @@ -3,6 +3,29 @@ cc_library( srcs = [ 'EventLoop.cc', 'Channel.cc', - 'poller.cc' + 'Poller.cc', + 'poller/EPollPoller.cc', + 'TimerQueue.cc', + 'Timer.cc', + 'Acceptor.cc', + 'Socket.cc', + 'InetAddress.cc', + 'TcpConnection.cc', + 'TcpServer.cc', + 'Buffer.cc', + 'EventLoopThread.cc', + 'EventLoopThreadPool.cc', + 'SocketOps.cc', + 'Connector.cc', + 'TcpClient.cc', + ], + deps = [ + # '//chive/net/poller/:chive_net_poller' + ], + extra_cppflags = [ + '-std=c++17', + # '-Wall', # show warning as error + '-w', # no warning + '-g', ] ) diff --git a/chive/net/Buffer.cc b/chive/net/Buffer.cc new file mode 100755 index 0000000..70fb1ed --- /dev/null +++ b/chive/net/Buffer.cc @@ -0,0 +1,144 @@ +#include "chive/net/Buffer.h" +#include "chive/base/clog/chiveLog.h" +#include +#include +#include +#include + +using namespace chive; +using namespace chive::net; + +const char Buffer::kCRLF[] = "\r\n"; + +Buffer::Buffer(size_t initialSize) + : buffer_ (kCheapPrepend + initialSize), + readerIndex_(kCheapPrepend), + writerIndex_(kCheapPrepend) +{ + assert(readableBytes() == 0); + assert(writableBytes() == initialSize); + assert(prependableBytes() == kCheapPrepend); +} + + +ssize_t Buffer::readFd(int fd, int* savedErrno) +{ + /// stack上创建临时缓冲区 extrabuf 使得总的缓冲区足够大 + /// 足以一次性把数据读到buf上, chive 采用level trigger + /// 然后再判断需不需要对buffer_ 扩容 + /// 好处是减少buffer_的预分配内存 + char extrabuf[65536]; + /// ref: https://linux.die.net/man/2/readv + struct iovec vec[2]; + const size_t writable = writableBytes(); + vec[0].iov_base = begin() + writerIndex_; + vec[0].iov_len = writable; + vec[1].iov_base = extrabuf; + vec[1].iov_len = sizeof(extrabuf); + + ssize_t n = readv(fd, vec, 2); + if (n < 0) { + *savedErrno = errno; + } else if (static_cast(n) <= writable ) { + /// 可用空间足够,移动 writerIndex_ 游标 + writerIndex_ += n; + } else { + /// 可用空间不足,append操作会扩容 + /// FIXME: (1)和(2) 应该是原子的,否则,如果(1)执行完毕而(2)扩容失败 + /// 导致结果不正确 + writerIndex_ = buffer_.size(); // (1) + append(extrabuf, n - writable); // (2) + + /// 如下if成立,则可能还有数据没读出来 (数据量 > 64KiB) + if (n == static_cast(writable + sizeof(extrabuf))) { + n += readFd(fd, savedErrno); + } + } + return n; +} + +void Buffer::append(const char* data, size_t len) +{ + ensureWritableBytes(len); + std::copy(data, data + len, beginWrite()); + encWritten(len); //增加已写入的计数 +} + +// for httpResponse +void Buffer::append(const std::string& msg) +{ + append(msg.data(), msg.size()); +} +// 保证有足够的可写空间 +void Buffer::ensureWritableBytes(size_t len) +{ + if (writableBytes() < len) + { + makeSpace(len); //可用空间不足,扩容 + } + assert(writableBytes() >= len); +} + + +void Buffer::encWritten(size_t len) +{ + assert(len <= writableBytes()); + writerIndex_ += len; +} + +std::string Buffer::retrieveAllAsString() +{ + return retrieveAsString(readableBytes()); +} + +std::string Buffer::retrieveAsString(size_t len) +{ + assert(len <= readableBytes()); + std::string result(peek(), len); + retrieve(len); + return result; +} + +void Buffer::retrieve(size_t len) +{ + assert(len <= readableBytes()); + if (len <= readableBytes()) + { + readerIndex_ += len; + } + else + { + retrieveAll(); + } +} + +void Buffer::retrieveAll() +{ + readerIndex_ = kCheapPrepend; + writerIndex_ = kCheapPrepend; +} + +void Buffer::makeSpace(size_t len) +{ + /*前置空闲空间 + 后部可写空间的大小不足以写入len字节的数据*/ + if (writableBytes() + prependableBytes() < len + kCheapPrepend) + { + buffer_.resize(writerIndex_ + len); + } + /*本地移动可读的数据到begin() + kCheapPrepend开端*/ + else + { + assert(kCheapPrepend < readerIndex_); + size_t readable = readableBytes(); + std::copy(begin() + readerIndex_, begin() + writerIndex_, begin() + kCheapPrepend); + readerIndex_ = kCheapPrepend; + writerIndex_ = readerIndex_ + readable; + assert(readable == readableBytes()); + } +} + +const char* Buffer::findCRLF() const +{ + const char* crlf = std::search(peek(), beginWrite(), kCRLF, kCRLF+2); + return crlf == beginWrite() ? NULL : crlf; +} \ No newline at end of file diff --git a/chive/net/Buffer.h b/chive/net/Buffer.h new file mode 100755 index 0000000..d16aa9d --- /dev/null +++ b/chive/net/Buffer.h @@ -0,0 +1,119 @@ +#ifndef CHIVE_NET_BUFFER_H +#define CHIVE_NET_BUFFER_H + +#include "chive/base/copyable.h" +#include +#include +#include + +namespace chive +{ +namespace net +{ + +/// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer +/// +/// @code +/// +-------------------+------------------+------------------+ +/// | prependable bytes | readable bytes | writable bytes | +/// | | (CONTENT) | | +/// +-------------------+------------------+------------------+ +/// | | | | +/// 0 <= readerIndex <= writerIndex <= size +/// +/// 使用可移动的readerIndex,充分利用prependable bytes的动态空间 +/// 可以兼顾内存使用量和使用效率 +/// @endcode + +class Buffer : copyable +{ +public: + /** + * 预留8字节的空间 + * 已读的bytes也会算入prepend space的一部分 + * prepend space 是一个动态的区域 + */ + static const size_t kCheapPrepend = 8; + static const size_t kInitialSize = 1024; + explicit Buffer(size_t initialSize = kInitialSize); + ssize_t readFd(int fd, int* savedErrno); + + // 当前剩余容量还可写的字节数 + size_t writableBytes() const + { return buffer_.size() - writerIndex_; } + + // 可读的字节数 + size_t readableBytes() const + { return writerIndex_ - readerIndex_; } + + // 当前的prependable space大小 + size_t prependableBytes() const + { return readerIndex_; } + + const char* peek() const + { return begin() + readerIndex_; } + + // for findCRLF() + const char* beginWrite() const + { return begin() + writerIndex_; } + + char* beginWrite() + { return begin() + writerIndex_; } + + // 保证足够的可写空间 >= len + void ensureWritableBytes(size_t len); + + // 将data append 到writerIndex_ + void append(const char* data, size_t len); + + void append(const std::string& msg); + + // 增加已写的计数 + void encWritten(size_t len); + + // 可写空间的大小 + int writable(); + + // 读取全部可读的字节作为string返回 + std::string retrieveAllAsString(); + + // 读取可读的len字节作为string返回 + std::string retrieveAsString(size_t len); + + // 移动readIndex_ 和 writeIndex_ 游标 + void retrieve(size_t len); + void retrieveAll(); + + const char* findCRLF() const; + void retrieveUntil(const char* end) + { + assert(peek() <= end); + assert(end <= beginWrite()); + retrieve(end - peek()); + } + + std::string toString() const + { + return std::string(peek(), static_cast(readableBytes())); + } + +private: + char* begin() + { return &*buffer_.begin(); } + + const char* begin() const + { return &*buffer_.begin(); } + + void makeSpace(size_t len); + + std::vector buffer_; // 可扩容的buf + size_t readerIndex_; // 读取的游标 + size_t writerIndex_; // 写入的游标 + + static const char kCRLF[]; +}; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/Callbacks.h b/chive/net/Callbacks.h new file mode 100755 index 0000000..e7a07cb --- /dev/null +++ b/chive/net/Callbacks.h @@ -0,0 +1,34 @@ +#ifndef CHIVE_NET_CALLBACKS_H +#define CHIVE_NET_CALLBACKS_H + +#include +#include + +namespace chive +{ +namespace net +{ + +class TcpConnection; +class Buffer; + +using TcpConnectionPtr = std::shared_ptr; +using ConnectionCallback = std::function; +using Timestamp = uint64_t; + +using MessageCallback = std::function; +using CloseCallback = std::function; +// 低水位回调 +using WriteCompleteCallback = std::function; +// 高水位回调 +using HighWaterMarkCallback = std::function; + +void defaultConnectionCallback(const TcpConnectionPtr& conn); +void defaultMessageCallback(const TcpConnectionPtr& conn, + Buffer* buffer, + Timestamp receiveTime); +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/Channel.cc b/chive/net/Channel.cc index 66ac6b6..5294ad0 100755 --- a/chive/net/Channel.cc +++ b/chive/net/Channel.cc @@ -1,41 +1,119 @@ #include "chive/net/Channel.h" #include "chive/net/EventLoop.h" +#include "chive/base/clog/chiveLog.h" #include using namespace chive; using namespace chive::net; -// === static data member declaration === +// === static data member declaration === const int Channel::kNoneEvent = 0; const int Channel::kReadEvent = POLLIN | POLLPRI; const int Channel::kWriteEvent = POLLOUT; -Channel::Channel(EventLoop* evloop, int fd): - loop_(evloop), - fd_(fd), - events_(0), - revents_(0), - index_(-1) +Channel::Channel(EventLoop *evloop, int fd) : loop_(evloop), + fd_(fd), + events_(0), + revents_(0), + index_(-1), + eventHanding_(false), + addedToLoop_(false), + tied_(false) + { + CHIVE_LOG_DEBUG("created channel %p with fd %d in eventloop %p", this, fd, evloop); +} + +Channel::~Channel() +{ + assert(!eventHanding_); // 正在处理事件 不能析构 + assert(!addedToLoop_); // 没被添加到loop 不能析构 + CHIVE_LOG_DEBUG("channel %p with socket fd %d", this, fd_); + if (loop_->isInLoopThread()) + { + assert(!loop_->hasChannel(this)); + } + CHIVE_LOG_DEBUG("destroyed channel %p with socket fd %d", this, fd_); +} + +void Channel::tie(const std::shared_ptr& obj) +{ + tie_ = obj; + tied_ = true; } -// Channel::~Channel() void Channel::update() { loop_->updateChannel(this); } -void Channel::handleEvent() +void Channel::handleEvent(Timestamp receiveTime) +{ + CHIVE_LOG_DEBUG("channel %p handle events", this); + eventHanding_ = true; //标志置位 正在处理事件 + std::shared_ptr guard; + if (tied_) + { + guard = tie_.lock(); + if (guard) + { + handleEventWithGuard(receiveTime); + } + } + else + { + handleEventWithGuard(receiveTime); + } + eventHanding_ = false; //标志复位 退出处理事件 +} + +void Channel::handleEventWithGuard(Timestamp receiveTime) { - if(revents_ & (POLLERR | POLLNVAL)) { - if(errorCallback_) errorCallback_(); + if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) + { + CHIVE_LOG_WARN("POLLHUP event"); + if (closeCallback_) + closeCallback_(); } - if(revents_ & (POLLIN | POLLPRI | POLLRDHUP)) { - if(readCallback_) readCallback_(); + if (revents_ & (POLLERR | POLLNVAL)) + { + if (errorCallback_) + errorCallback_(); } - if(revents_ & POLLOUT) { - if(writeCallback_) writeCallback_(); + if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) + { + if (readCallback_) + readCallback_(receiveTime); } + if (revents_ & POLLOUT) + { + if (writeCallback_) + writeCallback_(); + } +} + + +void Channel::disableWriting() +{ + events_ &= ~kWriteEvent; + update(); +} + +void Channel::disableAll() +{ + events_ = kNoneEvent; + update(); +} + +bool Channel::isWriting() +{ + return static_cast(events_ & kWriteEvent); } +void Channel::remove() +{ + assert(isNoneEvent()); + addedToLoop_ = false; + loop_->removeChannel(this); +} \ No newline at end of file diff --git a/chive/net/Channel.h b/chive/net/Channel.h index 8d219d2..6443566 100755 --- a/chive/net/Channel.h +++ b/chive/net/Channel.h @@ -3,24 +3,31 @@ #include "chive/base/noncopyable.h" #include - +#include namespace chive { namespace net { class EventLoop; - -class Channel: chive::noncopyable +class Channel: noncopyable { public: + using Timestamp = uint64_t; using EventCallback = std::function; + using ReadEventCallback = std::function; + Channel(EventLoop* evloop, int fd); - ~Channel() = default; + ~Channel(); - void handleEvent(); + /** + * channel核心,根据不同的revents值调用对应的回调函数 + */ + void handleEvent(Timestamp receiveTime); - // ==== set event callbasks - void setReadCallback(const EventCallback& cb) { + /** + * 设置回调函数 + */ + void setReadCallback(const ReadEventCallback& cb) { readCallback_ = cb; } void setWriteCallback(const EventCallback& cb) { @@ -29,32 +36,83 @@ class Channel: chive::noncopyable void setErrorCallback(const EventCallback& cb) { errorCallback_ = cb; } + void setCloseCallback(const EventCallback& cb) { + closeCallback_ = cb; + } + + /** + * 绑定对象 + */ + void tie(const std::shared_ptr&); int getFd() { return fd_;} int getEvents() { return events_;} void setRevents(int revts) { revents_ = revts; } + /** + * 是否未设置事件 + */ bool isNoneEvent() const { return events_ == kNoneEvent; } - // === enable events === + /** + * 开启可读,将fd更新到loop + */ void enableReading() { events_ |= kReadEvent; update(); } + /** + * 开启可写,将fd事件更新到loop + */ void enableWriting() { events_ |= kWriteEvent; update(); } - // === FIXME: disable events? === + + /** + * 设置不可写,原因 + * Epoll 采用 LT 模式,只需要在需要时关注写事件 + * 否则socket fd一直可写会频繁唤醒IO线程造成busy loop + */ + void disableWriting(); + + /** + * 设置全部事件不可用 + */ + void disableAll(); + + /** + * 是否正在写数据 + */ + bool isWriting(); + + /** + * 从EventLoop中移除该Channel + */ + void remove(); // === for poller === int getIndex() {return index_; } void setIndex(int idx) { index_ = idx; } + /** + * 获取channel所属的event loop + */ EventLoop* getOwnerLoop() { return loop_; } private: + /** + * 通过loop将fd及其事件更新到poller + */ void update(); - // === event number ==== + + /** + * handleEvent的核心 + */ + void handleEventWithGuard(Timestamp receiveTime); + + /** + * 事件编号,需要include POSIX头文件 + */ static const int kNoneEvent; static const int kReadEvent; static const int kWriteEvent; @@ -68,9 +126,15 @@ class Channel: chive::noncopyable // if added, index_ >= 0 // === callbacks === - EventCallback readCallback_; + ReadEventCallback readCallback_; EventCallback writeCallback_; EventCallback errorCallback_; + EventCallback closeCallback_; + + bool eventHanding_; /// 是否在处理事件 + bool addedToLoop_; /// 是否添加到EventLoop中 + std::weak_ptr tie_; /// 绑定对象 + bool tied_; /// 是否绑定了对象 }; } // namespace net diff --git a/chive/net/Connector.cc b/chive/net/Connector.cc new file mode 100755 index 0000000..93a6dec --- /dev/null +++ b/chive/net/Connector.cc @@ -0,0 +1,232 @@ +#include "chive/net/Connector.h" +#include "chive/base/clog/chiveLog.h" +#include "chive/net/Channel.h" +#include "chive/net/EventLoop.h" +#include "chive/net/SocketOps.h" + +#include + +using namespace chive; +using namespace chive::net; + +// static member +const int Connector::kMaxRetryDelayMs; + +Connector::Connector(EventLoop* loop, const InetAddress& serverAddr) + : loop_(loop), + serverAddr_(serverAddr), + connect_(false), + state_(State::kDisconnected), + retryDelayMs_(kInitRetryDelayMs) +{ + CHIVE_LOG_DEBUG("ctor %p", this); +} + +Connector::~Connector() +{ + CHIVE_LOG_DEBUG("dtor %p", this); + assert(!channel_); //保证channel已先销毁 +} + +void Connector::start() +{ + connect_ = true; + ///FIXME: unsafe + loop_->runInLoop( + std::bind(&Connector::startInLoop, this)); +} + +void Connector::startInLoop() +{ + loop_->assertInLoopThread(); + assert(state_ == State::kDisconnected); + if (connect_) { + connect(); + } else { + CHIVE_LOG_DEBUG("do not connect"); + } +} + +void Connector::stop() +{ + connect_ = false; + ///FIXME: unsafe + loop_->queueInLoop( + std::bind(&Connector::stopInLoop, this)); +} + +void Connector::stopInLoop() +{ + loop_->assertInLoopThread(); + if (state_ == State::kConnecting) + { + setState(State::kDisconnected); + int sockfd = removeAndResetChannel(); + retry(sockfd); + } +} + +void Connector::connect() +{ + int sockfd = socketops::createNonblockingOrDie(serverAddr_.family()); + // 发起连接 + int ret = socketops::connect(sockfd, socketops::sockaddr_cast(&serverAddr_.getSockAddr())); + int savedErrno = (ret == 0) ? 0 : errno; + ///FIXME: handle errno + switch (savedErrno) + { + case 0: + case EINPROGRESS: /*Linux 非阻塞connect,EINPROGRESS,正在连接*/ + case EINTR: + case EISCONN: + /*stevens书中说明要在connect后,继续判断该socket是否可写,可写则证明连接成功*/ + connecting(sockfd); + break; + case EAGAIN: + case EADDRINUSE: + case EADDRNOTAVAIL: + case ECONNREFUSED: + case ENETUNREACH: + retry(sockfd); + break; + + case EACCES: + case EPERM: + case EAFNOSUPPORT: + case EALREADY: + case EBADF: + case EFAULT: + case ENOTSOCK: + CHIVE_LOG_ERROR("connector error, errno %d", savedErrno); + socketops::close(sockfd); + break; + default: + CHIVE_LOG_ERROR("Unexpected error, errno %d", savedErrno); + socketops::close(sockfd); + break; + } +} + +void Connector::restart() +{ + loop_->assertInLoopThread(); + setState(State::kDisconnected); + retryDelayMs_ = kInitRetryDelayMs; + connect_ = true; + startInLoop(); +} + +/** + * Linux 非阻塞 socket + * 处于正在连接的状态 + * 设置回调,开启可写以检查是否连接成功 (可写事件来到) + */ +void Connector::connecting(int sockfd) +{ + setState(State::kConnecting); + assert(!channel_); + channel_.reset(new Channel(loop_, sockfd)); + ///FIXME: unsafe + channel_->setWriteCallback(std::bind(&Connector::handleWrite, this)); + ///FIXME: unsafe + channel_->setErrorCallback(std::bind(&Connector::handleError, this)); + // trigger epoller 监听可写事件,若可写说明连接成功,触发回调 `handleWrite()` + // handleWrite 检查连接是否真的成功,避免'自连接'或其他错误 遇到Error可尝试retry连接 + channel_->enableWriting(); +} + +/** + * 停止channel上事件,移除channel并置空channel + * NOTED: 为何要放在IO loop线程? + */ +int Connector::removeAndResetChannel() +{ + channel_->disableAll(); + channel_->remove(); + int sockfd = channel_->getFd(); + + // must transfer `resetChannel()` to I/O loop + /// FIXME: unsafe + loop_->queueInLoop(std::bind(&Connector::resetChannel, this)); + return sockfd; +} + +// 销毁channel,置空shared_ptr channel_ +void Connector::resetChannel() +{ + channel_.reset(); +} + +/** + * Tcp自连接的问题 + * ref: + * 1. https://www.jianshu.com/p/fe7383e3f14c + * 2. https://blog.csdn.net/l101606022/article/details/80100640 + * 3. socket连接和http连接:https://blog.csdn.net/wwd0501/article/details/52412396 + */ +void Connector::handleWrite() +{ + CHIVE_LOG_DEBUG("connector now state %d", static_cast(state_)); + if (state_ == State::kConnecting) + { + // 移除channel + int sockfd = removeAndResetChannel(); + int err = socketops::getSocketError(sockfd); + if (err) { + ///FIXME: + /// #define strerror_r(...) (pthread_testcancel(), strerror_r(__VA_ARGS__)) + CHIVE_LOG_WARN("SO_ERROR = %d", err); + retry(sockfd); + } else if (socketops::isSelfConnect(sockfd)) { + CHIVE_LOG_WARN("self connect, retry connecting at socket %d", sockfd); + retry(sockfd); + } else { + setState(State::kConnected); + if (connect_) { + newConnectionCallback_(sockfd); + } else { + socketops::close(sockfd); + } + } + } + else + { + ///FIXME: what happened? + assert(state_ == State::kDisconnected); + } +} + +void Connector::handleError() +{ + CHIVE_LOG_ERROR("state = %d", static_cast(state_)); + if (state_ == State::kConnecting) + { + int sockfd = removeAndResetChannel(); + int err = socketops::getSocketError(sockfd); + CHIVE_LOG_ERROR("SO_ERROR = %s", err); + retry(sockfd); + } +} + +/** + * 尝试重连server端 + */ +void Connector::retry(int sockfd) +{ + // 1. 关闭旧的socket,重新连接 + socketops::close(sockfd); + setState(State::kDisconnected); + if (connect_) + { + CHIVE_LOG_INFO("retry connecting to %s in %d milliseconds", + serverAddr_.toIpPort(), retryDelayMs_); + //2. 转移到IO线程进行重连 + loop_->runAfter(retryDelayMs_/1000.0, + std::bind(&Connector::startInLoop, shared_from_this())); + retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs); + } + else + { + CHIVE_LOG_DEBUG("do not connect"); + } +} \ No newline at end of file diff --git a/chive/net/Connector.h b/chive/net/Connector.h new file mode 100755 index 0000000..7468ebd --- /dev/null +++ b/chive/net/Connector.h @@ -0,0 +1,72 @@ +#ifndef CHIVE_NET_CONNECTOR_H +#define CHIVE_NET_CONNECTOR_H + +#include "chive/base/noncopyable.h" +#include "chive/net/InetAddress.h" + +#include +#include + +namespace chive +{ +namespace net +{ +class EventLoop; +class Channel; +class Connector : noncopyable, + public std::enable_shared_from_this +{ +public: + using NewConnectionCallback = std::function; + + Connector(EventLoop* loop, const InetAddress& serverAddr); + ~Connector(); + + void setNewConnectionCallback(const NewConnectionCallback& cb) + { newConnectionCallback_ = cb; } + + void start(); + void restart(); + void stop(); + + const InetAddress& serverAddress() const + { return serverAddr_; } + +private: + enum class State + { + kDisconnected, + kConnecting, + kConnected, + }; + static const int kMaxRetryDelayMs = 30 * 1000; + static const int kInitRetryDelayMs = 500; + + void setState(State s) + { state_ = s; } + + void startInLoop(); + void stopInLoop(); + void connect(); + void connecting(int sockfd); + void handleWrite(); + void handleError(); + void retry(int sockfd); + void resetChannel(); + int removeAndResetChannel(); + + EventLoop* loop_; + InetAddress serverAddr_; + bool connect_; /// 是否允许重连 + ///FIXME: use atomic variable? + State state_; + std::unique_ptr channel_; + NewConnectionCallback newConnectionCallback_; + int retryDelayMs_; + +}; +} // namespace net + +} // namespace chie + +#endif \ No newline at end of file diff --git a/chive/net/EventLoop.cc b/chive/net/EventLoop.cc index 9426b6b..ad0beaa 100755 --- a/chive/net/EventLoop.cc +++ b/chive/net/EventLoop.cc @@ -1,56 +1,264 @@ #include "chive/net/EventLoop.h" -#include "chive/net/Channel.h" -#include "chive/net/poller.h" - +// #include "chive/net/Channel.h" +#include "chive/net/poller/EPollPoller.h" +#include "chive/base/clog/chiveLog.h" #include #include #include +#include +#include +#include +#include using namespace chive; using namespace chive::net; +// 局部线程存储 +__thread EventLoop* t_loopInThisThread = nullptr; +// const int kPollTimeMs = 10000; +/** + * 创建一个eventfd并返回 + */ +int createEventfd() +{ + int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (evtfd < 0) + { + CHIVE_LOG_ERROR("Abort since creating eventfd failed! evtfd %d", evtfd); + abort(); //中断程序 + } + CHIVE_LOG_DEBUG("created event fd %d", evtfd); + return evtfd; +} + + +//静态方法 +EventLoop* EventLoop::getEventLoopOfCurrentThread() +{ + return t_loopInThisThread; +} + +// 注意初始化的顺序与声明的顺序要一致 EventLoop::EventLoop(): looping_(false), quit_(false), - poller_(new Poller(this)) + threadId_(CurrentThread::tid()), + poller_(new EPollPoller(this)), + callingPendingFunctors_(false), + wakeupFd_(createEventfd()), + wakeupChannel_(new Channel(this, wakeupFd_)), + timerQueue_(new TimerQueue(this)) { + CHIVE_LOG_DEBUG("created EventLoop %p in thread %d", this, threadId_); + if(t_loopInThisThread) + { + CHIVE_LOG_ERROR("Another EventLoop %p exits in this thread %d", + t_loopInThisThread, threadId_); + } + else + { + t_loopInThisThread = this; + } + //设置wakeupfd的回调函数,wakeupfd上有可读事件时调用 + wakeupChannel_->setReadCallback( + std::bind(&EventLoop::handleRead, this)); + // 将可读event注册到poller + wakeupChannel_->enableReading(); } EventLoop::~EventLoop() { - //assert(!mLooping); + CHIVE_LOG_DEBUG("EventLoop %p of thread %d destructs in thread %d", + this, threadId_, CurrentThread::tid()); + + assert(!looping_); + + // 移除 eventf并关闭 + wakeupChannel_->disableAll(); + wakeupChannel_->remove(); + ::close(wakeupFd_); + t_loopInThisThread = nullptr; } void EventLoop::loop() { assert(!looping_); + assertInLoopThread(); looping_ = true; quit_ = false; + + CHIVE_LOG_DEBUG("EventLoop %p start looping", this); + while(!quit_) { activeChannels_.clear(); - poller_->poll(kPollTimeMs, &activeChannels_); + auto now = poller_->poll(kPollTimeMs, &activeChannels_); + CHIVE_LOG_DEBUG("eventloop %p polls from poller %p now %ld", + this, poller_.get(), now); for(ChannelList::iterator it = activeChannels_.begin(); it != activeChannels_.end(); it++) { - (*it)->handleEvent(); + (*it)->handleEvent(now); } + // 每次从poll中监听到事件时,检查是否有pendingFunctors待处理 + doPendingFunctors(); } - std::cout << "loop end" << std::endl; + CHIVE_LOG_DEBUG("EventLoop %p stop looping", this); looping_ = false; } void EventLoop::updateChannel(Channel* channel) { assert(channel->getOwnerLoop() == this); + assertInLoopThread(); poller_->updateChannel(channel); } +void EventLoop::removeChannel(Channel* channel) +{ + CHIVE_LOG_DEBUG("inform poller %p to remove channel %p", + poller_.get(), channel); + assert(channel->getOwnerLoop() == this); + assertInLoopThread(); + poller_->removeChannel(channel); +} + +bool EventLoop::hasChannel(Channel* channel) +{ + assert(channel->getOwnerLoop() == this); + assertInLoopThread(); + return poller_->hasChannel(channel); +} + +//FIXME: 是否中断程序执行? +void EventLoop::abortNotInLoopThread() +{ + CHIVE_LOG_ERROR("Error:EventLoop %p was created in threadId_ %d current thread %d", + this, threadId_, CurrentThread::tid()); +} + void EventLoop::quit() { quit_ = true; -} \ No newline at end of file + // + // if(!isInLoopThread()) + // { + // wakeup(); + // } +} + +TimerId EventLoop::runAt(Timer::Timestamp timeout, const Timer::TimerCallback& cb) +{ + return timerQueue_->addTimer(std::move(cb), timeout, 0); +} + +TimerId EventLoop::runAfter(Timer::TimeType delay, const Timer::TimerCallback& cb) +{ + ///FIXME: static_cast是否多余 + ///FIXME: 传入的delay应该是微秒,还是秒 + return runAt(static_cast(Timer::now() + delay), std::move(cb)); +} + +TimerId EventLoop::runEvery(Timer::TimeType interval, const Timer::TimerCallback& cb) +{ + return timerQueue_->addTimer(std::move(cb), Timer::now() + interval, interval); +} + +void EventLoop::cancel(TimerId timerId) +{ + return timerQueue_->cancel(timerId); +} + + +void EventLoop::runInLoop(const Functor& cb) +{ + //CHIVE_LOG_DEBUG("is loop thread %d",isInLoopThread()); + // 如果是在 IO 线程,那么直接执行 cb + if(isInLoopThread()) + { + CHIVE_LOG_DEBUG("in loop thread, callback now"); + cb(); + } + else // 否则放到 pendingFunctors_,唤醒 IO 线程去处理cb + { + queueInLoop(cb); + } +} + +void EventLoop::queueInLoop(const Functor& cb) +{ + // 需要加锁保护临界区,因为queueInLoop在非 IO 线程上被调用 + // pendingFunctors_ 同时可被 IO 线程访问 + CHIVE_LOG_DEBUG("trace in EventLoop::queueInLoop()"); + { + MutexLockGuard lock(mutex_); + pendingFunctors_.push_back(cb); + } + + // 如果不是在 IO 线程 或者 正在处理pending functors + // 需要执行唤醒操作wakeup,唤醒 IO 线程处理wakeup fd + + // 为什么callingPendingFunctos_ = true也要执行wakeup()操作? + // 见EventLoop::doPendingFunctors() + // pendung functor 可能再调用 queuInLoop(cb2),为了让cb2能被及时执行 + // 所以callingPendingFunctors为true的时候也尝试唤醒 + if(!isInLoopThread() || callingPendingFunctors_) + { + wakeup(); + } +} + +void EventLoop::doPendingFunctors() +{ + CHIVE_LOG_DEBUG("trace in EventLoop::doPendingFunctors"); + std::vector functors; + callingPendingFunctors_ = true; //此时在IO线程,标志位不需要加锁保护 + + // 同步,pendingFunctors_ 是共享对象 + { + MutexLockGuard lock(mutex_); + // 用局部对象换取共享对象,防止其他线程阻塞在等待 pendingFunctors_ 上 + // 减小临界区的长度,同时避免了死锁 (functor可能再调用queueInLoop() ) + functors.swap(pendingFunctors_); + } + + CHIVE_LOG_DEBUG("functors size %d", functors.size()); + for (size_t i = 0; i < functors.size(); ++i) + { + functors[i](); + } + callingPendingFunctors_ = false; // 处理完pending task,重新置位 +} + +void EventLoop::wakeup() +{ + //向wakeupFd_写入8个字节,让poller收到wakeupFd_上有可读事件 + uint64_t one = 1; + ssize_t n = ::write(wakeupFd_, &one, sizeof(one)); + if (n != sizeof(one)) + { + CHIVE_LOG_ERROR("writes %d bytes instead of 8", n); + } + CHIVE_LOG_INFO("write %d bytes", sizeof(one)); +} + +/** + * wakeupFd_可读事件到来时的回调函数 + */ +void EventLoop::handleRead() +{ + // 写入时写入8字节, 读出时一次读出8字节,表示一次通信 + uint64_t one = 1; + ssize_t n = ::read(wakeupFd_, &one, sizeof(one)); + if (n != sizeof(one)) + { + CHIVE_LOG_ERROR("reads %d bytes instead of 8", n); + + } +} + + diff --git a/chive/net/EventLoop.h b/chive/net/EventLoop.h index 52e8a55..3560542 100755 --- a/chive/net/EventLoop.h +++ b/chive/net/EventLoop.h @@ -1,46 +1,122 @@ #ifndef CHIVE_NET_EVENTLOOP_H #define CHIVE_NET_EVENTLOOP_H #include "chive/base/noncopyable.h" +#include "chive/net/TimerId.h" +#include "chive/net/Timer.h" +#include "chive/net/Channel.h" +#include "chive/net/TimerQueue.h" +// #include "chive/net/Poller.h" +#include "chive/base/CurrentThread.h" + +#include "chive/base/MutexLock.h" + #include #include #include #include +// #include +// #include + -//using pid_t = long int; namespace chive { namespace net { -class Channel; + +//前置声明 +// class Channel; class Poller; +// class TimerQueue; +// class TimerId; class EventLoop : chive::noncopyable { public: + + using Functor = std::function; //pending task 函数对象类型 + EventLoop(); ~EventLoop(); void loop(); - /* + inline void assertInLoopThread() { if(!isInLoopThread()) { abortNotInLoopThread(); } } + inline bool isInLoopThread() { - return true; + return threadId_ == CurrentThread::tid(); } - */ - void updateChannel(Channel* channel); - void quit(); + + void updateChannel(Channel* channel); + void removeChannel(Channel* channel); + + bool hasChannel(Channel* channel); + + void quit(); + + /** + * 在IO线程里执行用户任务回调,用于线程间调配任务 + * @param cb 待执行的回调函数 + */ + void runInLoop(const Functor& cb); + + /** + * 将任务回调放入到pending队列 + */ + void queueInLoop(const Functor& cb); + + // methods for add Timer to TimerQueue --- begin + TimerId runAt(Timer::Timestamp time, const Timer::TimerCallback& cb); + + TimerId runAfter(Timer::TimeType delay, const Timer::TimerCallback& cb); + + TimerId runEvery(Timer::TimeType interval, const Timer::TimerCallback& cb); + // methods for add Timer to TimerQueue --- end + + /** + * 取消一个定时器 + * cancel操作是线程安全的 + */ + void cancel(TimerId timerId); + + /** + * 唤醒poller + */ + void wakeup(); + + + + static EventLoop* getEventLoopOfCurrentThread(); + private: - //void abortNotInLoopThread(); - bool looping_; - bool quit_; using ChannelList = std::vector; - ChannelList activeChannels_; + + + void abortNotInLoopThread(); + + bool looping_; //FIXME: need atomic + bool quit_; //FIXME: need atomic + const pid_t threadId_; // eventloop所在线程ID std::unique_ptr poller_; + + // add for transferring operations of TimerQueue to IO thread ---- begin + void handleRead(); //唤醒IO线程loop,处理pending任务 + void doPendingFunctors(); //处理pending task + bool callingPendingFunctors_; //标识是否正在处理pending task + int wakeupFd_; // wakeup fd 唤醒线程处理pending task + std::unique_ptr wakeupChannel_; //专用于wakeup fd 上的readable事件,分发给handleRead() + std::vector pendingFunctors_; //等待处理的回调任务,需要枷锁保护,因为TimerQueue可以在另一个线程访问之 + MutexLock mutex_; //互斥量 + std::unique_ptr timerQueue_; //定时器队列 + // add for transferring operations of TimerQueue to IO thread ---- end + + + Timer::Timestamp pollReturnTime_; + ChannelList activeChannels_; }; } // namespace net diff --git a/chive/net/EventLoopThread.cc b/chive/net/EventLoopThread.cc new file mode 100755 index 0000000..102f524 --- /dev/null +++ b/chive/net/EventLoopThread.cc @@ -0,0 +1,65 @@ +#include "chive/net/EventLoopThread.h" +#include "chive/net/EventLoop.h" +#include "chive/base/clog/chiveLog.h" + +using namespace chive; +using namespace chive::net; + +EventLoopThread::EventLoopThread(const ThreadInitCallback& cb, + const std::string& name) + : loop_ (NULL), + exiting_ (false), + thread_ (std::bind(&EventLoopThread::threadFunc, this), name), + mutex_ (), + cond_ (mutex_), + callback_ (cb) +{ +} + +EventLoopThread::~EventLoopThread() +{ + exiting_ = true; + if (loop_ != NULL) + { + loop_->quit(); + thread_.join(); + } +} + +EventLoop* EventLoopThread::startLoop() +{ + assert(!thread_.started()); + thread_.start(); // 创建线程 + EventLoop* loop = NULL; + { + MutexLockGuard lock(mutex_); + while (loop_ == NULL) + { + cond_.wait(); + } + loop = loop_; + } + return loop; +} + +void EventLoopThread::threadFunc() +{ + EventLoop loop; // stack obj + if (callback_) + { + callback_(&loop); + } + + { + MutexLockGuard lock(mutex_); + loop_ = &loop; + CHIVE_LOG_DEBUG("loop2 %p", loop_); + cond_.notify(); + } + + loop_->loop(); + /// FIXME:是否必要? + /// loop结束对loop_置空 + // MutexLockGuard lock(mutex_); + // loop_ = nullptr; +} \ No newline at end of file diff --git a/chive/net/EventLoopThread.h b/chive/net/EventLoopThread.h new file mode 100755 index 0000000..f6cedfd --- /dev/null +++ b/chive/net/EventLoopThread.h @@ -0,0 +1,43 @@ +#ifndef CHIVE_EVENTLOOP_THREAD_H +#define CHIVE_EVENTLOOP_THREAD_H + +#include "chive/base/noncopyable.h" +#include "chive/base/Thread.h" // include +#include "chive/base/Atomic.h" +// #include + +namespace chive +{ +namespace net +{ + +class EventLoop; + +class EventLoopThread : noncopyable +{ +public: + using ThreadInitCallback = std::function; + + EventLoopThread(const ThreadInitCallback& cb = ThreadInitCallback(), + const std::string& name = std::string()); + ~EventLoopThread(); + + EventLoop* startLoop(); + +private: + void threadFunc(); + + EventLoop* loop_; + bool exiting_; + Thread thread_; + bool canGo = false; + MutexLock mutex_; + Condition cond_; + ThreadInitCallback callback_; +}; +} // namespace net + +} // namespace chive + + +#endif \ No newline at end of file diff --git a/chive/net/EventLoopThreadPool.cc b/chive/net/EventLoopThreadPool.cc new file mode 100755 index 0000000..dc936cc --- /dev/null +++ b/chive/net/EventLoopThreadPool.cc @@ -0,0 +1,95 @@ +#include "chive/net/EventLoopThreadPool.h" +#include "chive/net/EventLoop.h" +#include "chive/net/EventLoopThread.h" +#include "chive/base/clog/chiveLog.h" + +#include +#include + +using namespace chive; +using namespace chive::net; + +EventLoopThreadPool::EventLoopThreadPool(EventLoop* baseLoop, const std::string& name) + : baseLoop_ (baseLoop), + name_ (name), + started_ (false), + numThreads_ (0), + next_ (0) +{ + CHIVE_LOG_DEBUG("created eventloopthreadpool %p name %s", this, name_.c_str()); +} + +EventLoopThreadPool::~EventLoopThreadPool() +{ + // all loops are stack obj + CHIVE_LOG_DEBUG("eventloopthreadpool %p name %s destructed.", this, name_.c_str()); +} + +void EventLoopThreadPool::start(const ThreadInitCallback& cb) +{ + assert(!started_); + baseLoop_->assertInLoopThread(); + started_ = true; + + char buf[name_.size() + 32]; + for (int i = 0; i < numThreads_; ++i) + { + memset(buf, '\0', sizeof(buf)); + snprintf(buf, sizeof(buf), "%s%d", name_.c_str(), i); + EventLoopThread* t = new EventLoopThread(cb, buf); + threads_.push_back(std::unique_ptr(t)); + // 得到loop* + loops_.push_back(t->startLoop()); + } + // 如果是单线程服务就返回baseLoop_ + if (numThreads_ == 0 && cb) + { + cb(baseLoop_); + } +} + +// round-robin +EventLoop* EventLoopThreadPool::getNextLoop() +{ + baseLoop_->assertInLoopThread(); + assert(started_); + EventLoop* loop = baseLoop_; + + if (!loops_.empty()) + { + loop = loops_[next_]; + ++next_; + if (static_cast(next_) >= loops_.size()) + { + next_ = 0; + } + } + return loop; +} + +// hash code +EventLoop* EventLoopThreadPool::getLoopForHash(size_t hashCode) +{ + baseLoop_->assertInLoopThread(); + EventLoop* loop = baseLoop_; + + if (!loops_.empty()) + { + loop = loops_[hashCode % loops_.size()]; + } + return loop; +} + +std::vector EventLoopThreadPool::getAllLoops() +{ + baseLoop_->assertInLoopThread(); + assert(started_); + if (loops_.empty()) + { + return std::vector(1, baseLoop_); + } + else + { + return loops_; + } +} \ No newline at end of file diff --git a/chive/net/EventLoopThreadPool.h b/chive/net/EventLoopThreadPool.h new file mode 100755 index 0000000..0accbe8 --- /dev/null +++ b/chive/net/EventLoopThreadPool.h @@ -0,0 +1,57 @@ +#ifndef CHIVE_NET_EVENTLOOPTHREADPOOL_H +#define CHIVE_NET_EVENTLOOPTHREADPOLL_H + +#include "chive/base/noncopyable.h" +#include +#include +#include +#include + +namespace chive +{ +namespace net +{ +class EventLoop; +class EventLoopThread; + +class EventLoopThreadPool : noncopyable +{ +public: + using ThreadInitCallback = std::function; + + EventLoopThreadPool(EventLoop* baseLoop, const std::string& name); + ~EventLoopThreadPool(); + void setThreadNum(int numThreads) { numThreads_ = numThreads; } + void start(const ThreadInitCallback& cb = ThreadInitCallback()); + + /** + * valid after calling start() + * round-robin selection + */ + EventLoop* getNextLoop(); + + EventLoop* getLoopForHash(size_t hashCode); + + std::vector getAllLoops(); + + bool started() const + { return started_; } + + const std::string& name() const + { return name_; } + +private: + EventLoop* baseLoop_; + std::string name_; + bool started_; + int numThreads_; + int next_; + std::vector> threads_; + std::vector loops_; + +}; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/IgnoreSigPipe.h b/chive/net/IgnoreSigPipe.h new file mode 100755 index 0000000..62aae9e --- /dev/null +++ b/chive/net/IgnoreSigPipe.h @@ -0,0 +1,27 @@ +#ifndef CHIVE_NET_IGNORESIGPIPE_H +#define CHIVE_NET_IGNORESIGPIPE_H + +#include +#include "chive/base/clog/chiveLog.h" + +namespace chive +{ +namespace net +{ +class IgnoreSigPipe +{ +public: + IgnoreSigPipe() + { + CHIVE_LOG_DEBUG("IgnoreSigPipe inited"); + ::signal(SIGPIPE, SIG_IGN); + } +}; + +// 全局对象 +extern IgnoreSigPipe initObj; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/InetAddress.cc b/chive/net/InetAddress.cc new file mode 100755 index 0000000..f243f9e --- /dev/null +++ b/chive/net/InetAddress.cc @@ -0,0 +1,106 @@ +#include "chive/net/InetAddress.h" +#include "chive/base/clog/chiveLog.h" +// #include "chive/base/Logger.h" + +#include +#include + +#include /// htobe16()... + +using namespace chive::net; + +// /* Structure describing an Internet socket address. */ +// struct sockaddr_in { +// sa_family_t sin_family; /* address family: AF_INET */ +// uint16_t sin_port; /* port in network byte order */ +// struct in_addr sin_addr; /* internet address */ +// }; + +// /* Internet address. */ +// typedef uint32_t in_addr_t; +// struct in_addr { +// in_addr_t s_addr; /* address in network byte order */ +// }; + + +// static const in_addr_t kInaddrAny = INADDR_ANY; +InetAddress::InetAddress(uint16_t port) + : addr_ {} /*memset(&addr_, sizeof arrd_)*/ +{ + addr_.sin_family = AF_INET; + addr_.sin_addr.s_addr = INADDR_ANY; + addr_.sin_port = htobe16(port); // 主机字节序转网络字节序 +} + +InetAddress::InetAddress(const std::string& ip, uint16_t port) + : addr_{} +{ + addr_.sin_family = AF_INET; + addr_.sin_port = htobe16(port); + if (::inet_pton(AF_INET, ip.c_str(), &addr_.sin_addr) <= 0) + { + CHIVE_LOG_ERROR("inet_pton failed!"); + } +} + +std::string InetAddress::toIp() const +{ + const int size = 32; + char buf[size]; + ::inet_ntop(AF_INET, &addr_.sin_addr, buf, static_cast(size)); + return buf; // 隐式转换 +} + +std::string InetAddress::toIpPort() const +{ + const int size = 32; + char buf[size]; + // + // #define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */ + char host[INET_ADDRSTRLEN] = "INVALID"; + ::inet_ntop(AF_INET, &addr_.sin_addr, host, static_cast(sizeof(host))); + + uint16_t port = be16toh(addr_.sin_port); + snprintf(buf, size, "%s:%d", host, port); + return buf; +} + + +uint64_t InetAddress::toPort() const +{ + return be16toh(portNetEndian()); // 网络字节序到主机序端口号 +} + +const sockaddr_in& InetAddress::getSockAddr() const +{ + return addr_; +} + +void InetAddress::setSockAddr(const sockaddr_in& addr) +{ + addr_ = addr; +} + +sockaddr_in InetAddress::getLocalAddress(int sockfd) +{ + sockaddr_in localAddr {}; + socklen_t addrLen = sizeof(localAddr); + if (::getsockname(sockfd, reinterpret_cast(&localAddr), + static_cast(&addrLen) ) < 0) + { + CHIVE_LOG_ERROR("getsockname failed!"); + } + return localAddr; +} + +sockaddr_in InetAddress::getPeerAddress(int sockfd) +{ + sockaddr_in peerAddr {}; + socklen_t addrLen = sizeof(peerAddr); + if (::getpeername(sockfd, reinterpret_cast(&peerAddr), + static_cast(&addrLen)) < 0) + { + CHIVE_LOG_ERROR("getpeername failed!"); + } + return peerAddr; +} \ No newline at end of file diff --git a/chive/net/InetAddress.h b/chive/net/InetAddress.h new file mode 100755 index 0000000..2fe2581 --- /dev/null +++ b/chive/net/InetAddress.h @@ -0,0 +1,99 @@ +#ifndef CHIVE_NET_INETADDRESS_H +#define CHIVE_NET_INETADDRESS_H + + +#include + +#include "chive/base/copyable.h" +#include +#include + + + +namespace chive +{ +namespace net +{ + +namespace sockets +{ + const struct sockaddr* sockaddr_cast(const struct sockaddr_in6* addr); +} // namespace sockets + +class InetAddress : chive::copyable +{ +public: + explicit InetAddress(uint16_t port = 0); + + InetAddress(const std::string& ip, uint16_t port); + + explicit InetAddress(const struct sockaddr_in& addr) + : addr_ (addr) + { } + + /** + * get sa_family + */ + sa_family_t family() const { return addr_.sin_family; } + + /** + * get "Ip" string + */ + std::string toIp() const; + + /** + * get "Ip:port" string + */ + std::string toIpPort() const; + + /** + * get port number + */ + uint64_t toPort() const; + + /** + * 获取sockaddr_in地址信息 + */ + const sockaddr_in& getSockAddr() const; + + /** + * 设置地址信息 + */ + void setSockAddr(const sockaddr_in& addr); + + /** + * 获取网络字节序的IP地址 + */ + uint32_t ipNetEndian() const { return addr_.sin_addr.s_addr; } + + /** + * 获取网络字节序的端口号 + */ + uint16_t portNetEndian() const { return addr_.sin_port; } + + /** + * 获取本地地址信息 + */ + static sockaddr_in getLocalAddress(int sockfd); + + /** + * 获取客户端地址信息 + */ + static sockaddr_in getPeerAddress(int sockfd); + + /** + * 解析主机名到IP地址 + * -- 线程安全的 -- + */ + static bool resolve(std::string& hostname, InetAddress* result); + + +private: + /// only support IPv4 + struct sockaddr_in addr_; +}; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/Poller.cc b/chive/net/Poller.cc new file mode 100755 index 0000000..6a903db --- /dev/null +++ b/chive/net/Poller.cc @@ -0,0 +1,20 @@ +#include "chive/net/Poller.h" +#include "chive/base/Logger.h" + +using namespace chive; +using namespace chive::net; + + +Poller::Poller(EventLoop* loop) + : ownerLoop_(loop) +{ +} + +Poller::~Poller() = default; + +bool Poller::hasChannel(Channel* channel) const +{ + assertInLoopThread(); + auto it = channels_.find(channel->getFd()); + return it != channels_.end() && it->second == channel; +} diff --git a/chive/net/Poller.h b/chive/net/Poller.h new file mode 100755 index 0000000..ee41862 --- /dev/null +++ b/chive/net/Poller.h @@ -0,0 +1,51 @@ +#ifndef CHIVE_NET_POLLER_H +#define CHIVE_NET_POLLER_H + +#include "chive/base/noncopyable.h" +#include "chive/net/Channel.h" +#include "chive/net/EventLoop.h" + +#include +#include +#include +#include +#include + +namespace chive +{ +namespace net +{ +using Timestamp = int64_t; +class Poller : chive::noncopyable +{ +public: + using ChannelList = std::vector ; + Poller(EventLoop* evloop); + virtual ~Poller(); + + virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0; + virtual void updateChannel(Channel* channel) = 0; + virtual void removeChannel(Channel* channel) = 0; + virtual bool hasChannel(Channel* channel) const; + + static Poller* newDefaultPoller(EventLoop* loop); + + void assertInLoopThread() const + { + ownerLoop_->assertInLoopThread(); + } + +protected: + using ChannelMap = std::map; + // remove PollFdList/fillActiveChannels + // using PollFdList = std::vector; + // void fillActiveChannels(int numEvents, ChannelList* activeChannels) const; + // PollFdList pollfds_; + ChannelMap channels_; + EventLoop* ownerLoop_; +}; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/Socket.cc b/chive/net/Socket.cc new file mode 100755 index 0000000..1706baf --- /dev/null +++ b/chive/net/Socket.cc @@ -0,0 +1,237 @@ +#include "chive/net/Socket.h" +#include "chive/base/clog/chiveLog.h" +#include "chive/net/InetAddress.h" +#include "chive/base/Logger.h" + +#include +#include +#include /// srtuct tcp_info +#include +#include +#include /// snprintf +#include +#include + + +using namespace chive; +using namespace chive::net; + +Socket::~Socket() +{ + if(isValid()) /// sockfd_ >= 0才能close + { + CHIVE_LOG_DEBUG("close socket %p with fd %d", this, sockfd_); + ::close(sockfd_); + } +} + +Socket::Socket(Socket &&socket) noexcept + : sockfd_ (socket.sockfd_) +{ + socket.setNoneFd(); +} + +Socket& Socket::operator=(Socket&& rhs) noexcept +{ + if (this != &rhs) + { + sockfd_ = rhs.fd(); + rhs.setNoneFd(); + } + return *this; +} + +void Socket::setNoneFd() +{ + sockfd_ = -1; +} + +/* +#include +#include +// 获取/设置socket状态 +int getsockopt(int s, int level, int optname, void* optval, + socklen_t* optlen); +int setsockopt( int socket, int level, int option_name, + const void *option_value, size_t option_len); +*/ +bool Socket::getTcpInfo(struct tcp_info* tcpInfo) const +{ + socklen_t len = sizeof(*tcpInfo); + memset(tcpInfo, 0, len); + return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpInfo, &len) == 0; +} + +bool Socket::getTcpInfoString(char* buf, int len) const +{ + struct tcp_info tcpi; + bool ok = getTcpInfo(&tcpi); + if (ok) + { + snprintf(buf, len, "unrecovered=%u " + "rto=%u ato=%u snd_mss=%u rcv_mss=%u " + "lost=%u retrans=%u rtt=%u rttvar=%u " + "sshthresh=%u cwnd=%u total_retrans=%u", + tcpi.tcpi_retransmits, // Number of unrecovered [RTO] timeouts + tcpi.tcpi_rto, // Retransmit timeout in usec + tcpi.tcpi_ato, // Predicted tick of soft clock in usec + tcpi.tcpi_snd_mss, + tcpi.tcpi_rcv_mss, + tcpi.tcpi_lost, // Lost packets + tcpi.tcpi_retrans, // Retransmitted packets out + tcpi.tcpi_rtt, // Smoothed round trip time in usec + tcpi.tcpi_rttvar, // Medium deviation + tcpi.tcpi_snd_ssthresh, + tcpi.tcpi_snd_cwnd, + tcpi.tcpi_total_retrans); // Total retransmits for entire connection + } + return ok; +} + +void Socket::bindAddress(const InetAddress& localAddr) +{ + sockaddr_in address = localAddr.getSockAddr(); + int ret = ::bind(sockfd_, reinterpret_cast(&address), sizeof(address)); + if (ret < 0) + { + CHIVE_LOG_ERROR("bind address failed!"); + } +} + +void Socket::listen() +{ + CHIVE_LOG_DEBUG("socket %d begin listening...", sockfd_); + // SOMAXCONN 内核参数, 默认128,可调优 + if(::listen(sockfd_, SOMAXCONN) < 0) + { + CHIVE_LOG_ERROR("listen at %d failed!", sockfd_); + } +} + +int Socket::accept(InetAddress* peerAddr) +{ + sockaddr_in address {}; + auto addressLen = static_cast(sizeof(address)); + /// ::accept4 非标准扩展,#include + /// 4th param 设置操作类型 + int connfd = ::accept4(sockfd_, reinterpret_cast(&address), + &addressLen, SOCK_NONBLOCK|SOCK_CLOEXEC); + if(connfd >= 0) + { + peerAddr->setSockAddr(address); + CHIVE_LOG_DEBUG("socket %d accept new connection fd %d", sockfd_, connfd); + return connfd; + } + else + { + int savedErrno = errno; + CHIVE_LOG_ERROR("::accept4 failed, errno %d", savedErrno); + switch (savedErrno) + { + case EAGAIN: + CHIVE_LOG_DEBUG("EAGAIN"); + break; + case ECONNABORTED: + case EINTR: + case EPROTO: // ??? + case EPERM: + case EMFILE: // per-process lmit of open file desctiptor ??? + // expected errors + errno = savedErrno; + break; + case EBADF: + case EFAULT: + case EINVAL: + case ENFILE: + case ENOBUFS: + case ENOMEM: + case ENOTSOCK: + case EOPNOTSUPP: + // unexpected errors + CHIVE_LOG_ERROR("unexpected error of ::accept4, errno %d", savedErrno); + break; + default: + CHIVE_LOG_ERROR("unknown error of ::accept4, errno %d",savedErrno); + break; + } + return -1; + } // end else +} + +/* +noted ::close 和 ::shutdown 的区别 +#include +int shutdown(int sockfd,int howto); //返回成功为0,出错为-1 +SHUT_RD, 值为 0,关闭连接的读端 +SHUR_WR, 值为 1, 关闭连接的写端 +SHUT_RDWR, 值为2, 关闭连接的读写两端 +*/ +void Socket::shutdownWrite() +{ // 关闭连接的写端 + if(shutdown(sockfd_, SHUT_WR) < 0) + { + CHIVE_LOG_ERROR("shutdown fd %d failed!", sockfd_); + } +} + +void Socket::setTcpNoDelay(bool on) +{ + int opt = on? 1 : 0; + if (::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, + &opt, static_cast( sizeof(opt) ) ) < 0 ) + { + CHIVE_LOG_ERROR("::setsockopt on sockfd %d failed!", sockfd_); + } +} + +void Socket::setReuseAddr(bool on) +{ + int opt = on? 1 : 0; + if (::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, + &opt, static_cast( sizeof(opt) ) ) < 0 ) + { + CHIVE_LOG_ERROR("::setsockopt on sockfd %d failed!", sockfd_); + } +} + +void Socket::setReusePort(bool on) +{ +#ifdef SO_REUSEPORT + int opt = on ? 1 : 0; + int ret = ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, + &opt, static_cast( sizeof(opt) ) ); + if (ret < 0 && on) + { + CHIVE_LOG_ERROR("::setsockopt set SO_REUSEPORT failed."); + } +#else + if (on) + { + CHIVE_LOG_ERROR("SO_REUSEPORT is not supported."); + } +#endif +} + +void Socket::setKeepAlive(bool on) +{ + int opt = on ? 1 : 0; + if (::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, + &opt, static_cast( sizeof(opt) ) ) < 0) + { + CHIVE_LOG_ERROR("::setsockopt on sockfd %d failed!", sockfd_); + } +} + +int Socket::getSocketError() { + int optval; + socklen_t optlen = static_cast(sizeof(optval)); + if (::getsockopt(sockfd_, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) + { + CHIVE_LOG_ERROR("::getsockopt on sockfd %d failed!", sockfd_); + return errno; + } + else + { + return optval; + } +} \ No newline at end of file diff --git a/chive/net/Socket.h b/chive/net/Socket.h new file mode 100755 index 0000000..4af4060 --- /dev/null +++ b/chive/net/Socket.h @@ -0,0 +1,125 @@ +#ifndef CHIVE_NET_SOCKET_H +#define CHIVE_NET_SOCKET_H + +#include "chive/base/noncopyable.h" + +struct tcp_info; /// #include + +namespace chive +{ +namespace net +{ + +class InetAddress; + +/** + * socket fd 的封装 + * --- 线程安全,所有操作都委托给操作系统 + */ + +class Socket : noncopyable +{ +public: + explicit Socket(int sockfd) + : sockfd_ (sockfd) + { } + + /** + * 移动构造 + */ + Socket(Socket&& sock) noexcept; + + Socket& operator=(Socket&& rhs) noexcept; + + ~Socket(); + + /** + * 设置sockfd_为-1从而不可用 + */ + void setNoneFd(); + + bool isValid() { return sockfd_ >= 0; } + + /** + * 获取socket fd + */ + int fd() const { return sockfd_; } + + /** + * 获取tcp info + * @param tcpInfo 指针获取tcp_info + * @return + */ + bool getTcpInfo(struct tcp_info* tcpInfo) const; + + /** + * 获取tcp info 字符串形式 + * @param buf + * @param len + * @return + */ + bool getTcpInfoString(char* buf, int len) const; + + /** + * 绑定地址 + * @param localAddr 待绑定的地址对象 + */ + void bindAddress(const InetAddress& localAddr); + + /** + * 监听sockfd_ + * 地址已被使用时abort + */ + void listen(); + + /** + * 接收新连接并返回 连接的socket + * @param peerAddr 获取peer address信息 + * @return success返回sockfd / error返回-1 + */ + int accept(InetAddress* peerAddr); + + /** + * shutdown 写端 + */ + void shutdownWrite(); + + /** + * enable/disable TCP_NODELAY + * 禁用 Nagle 算法 + */ + void setTcpNoDelay(bool on); + + /** + * enable/disable SO_RESUEADDR + * 设置地址复用 + */ + void setReuseAddr(bool on); + + /** + * enble/disable SO_REUSEPORT + * 设置端口复用 + */ + void setReusePort(bool on); + + /** + * enable/disable SO_KEEPALIVE + * 设置keep alive 长连接 + */ + void setKeepAlive(bool on); + + /** + * 获取socket fd 上的errno 错误码 + * @return errno + */ + int getSocketError(); + +private: + int sockfd_; +}; + +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/SocketOps.cc b/chive/net/SocketOps.cc new file mode 100755 index 0000000..b15c5f6 --- /dev/null +++ b/chive/net/SocketOps.cc @@ -0,0 +1,156 @@ +#include "chive/net/SocketOps.h" + + +using namespace chive; +using namespace chive::net; + + +int socketops::createNonblockingOrDie(sa_family_t family) +{ + int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); + if (sockfd < 0) + { + CHIVE_LOG_ERROR("create socket failed! socket %d", sockfd); + } + return sockfd; +} + +int socketops::connect(int sockfd, const struct sockaddr* addr) +{ + return ::connect(sockfd, addr, static_cast(sizeof(sockaddr))); +} + +int socketops::bindOrDie(int sockfd, const struct sockaddr* addr) +{ + int ret = ::bind(sockfd, addr, static_cast(sizeof(sockaddr))); + if (ret < 0) { + CHIVE_LOG_ERROR("bind sockfd %d failed, ret %d", sockfd, ret); + } +} + +void socketops::listenOrDie(int sockfd) +{ + int ret = ::listen(sockfd, SOMAXCONN); + if (ret < 0) { + CHIVE_LOG_ERROR("listen %d failed, ret %d", sockfd, ret); + } +} +// int accept(int sockfd, struct sockaddr_in6* addr); + +void socketops::close(int sockfd) +{ + if (::close(sockfd) < 0) + { + CHIVE_LOG_ERROR("error occurs when closing sockfd %d", sockfd); + } +} + +/** + * 检查sockfd上是否有错误发生 + * @return 返回错误码 + */ +int socketops::getSocketError(int sockfd) +{ + int optval; + socklen_t optlen = static_cast(sizeof(optval)); + + if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) + { + return errno; + } + else + { + return optval; + } +} + +struct sockaddr* socketops::sockaddr_cast(struct sockaddr_in6* addr) +{ + return static_cast(static_cast(addr)); +} + +struct sockaddr* socketops::sockaddr_cast(struct sockaddr_in* addr) +{ + return static_cast(static_cast(addr)); +} +const struct sockaddr* socketops::sockaddr_cast(const struct sockaddr_in* addr) +{ + return static_cast(static_cast(addr)); +} + +struct sockaddr_in6 socketops::getLocalAddr6(int sockfd) +{ + struct sockaddr_in6 localaddr; + memset(&localaddr, 0, sizeof(localaddr)); + socklen_t addrlen = static_cast(sizeof(localaddr)); + if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0) + { + CHIVE_LOG_ERROR("getsockname at socket %d failed!", sockfd); + } + return localaddr; +} + +struct sockaddr_in socketops::getLocalAddr4(int sockfd) +{ + struct sockaddr_in localaddr; + memset(&localaddr, 0, sizeof(localaddr)); + socklen_t addrlen = static_cast(sizeof(localaddr)); + if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0) + { + CHIVE_LOG_ERROR("getsockname at socket %d failed!", sockfd); + } + return localaddr; +} + +struct sockaddr_in6 socketops::getPeerAddr6(int sockfd) +{ + struct sockaddr_in6 peeraddr; + memset(&peeraddr, 0, sizeof(peeraddr)); + socklen_t addrlen = static_cast(sizeof(peeraddr)); + if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0) + { + CHIVE_LOG_ERROR("getpeername at socket %d failed!", sockfd); + } + return peeraddr; +} + +struct sockaddr_in socketops::getPeerAddr4(int sockfd) +{ + struct sockaddr_in peeraddr; + memset(&peeraddr, 0, sizeof(peeraddr)); + socklen_t addrlen = static_cast(sizeof(peeraddr)); + if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0) + { + CHIVE_LOG_ERROR("getpeername at socket %d failed!", sockfd); + } + return peeraddr; +} + +/** + * 检查是否socket自连接 + * 原理: 检查local和peer端的ip和port是否相同,相同则说明发生了自连接 + */ +bool socketops::isSelfConnect(int sockfd) +{ + struct sockaddr_in6 localaddr = getLocalAddr6(sockfd); + struct sockaddr_in6 peeraddr = getPeerAddr6(sockfd); + if (localaddr.sin6_family == AF_INET) // IPv4 + { + const struct sockaddr_in* laddr4 = reinterpret_cast(&localaddr); + const struct sockaddr_in* paddr4 = reinterpret_cast(&peeraddr); + return laddr4->sin_port == paddr4->sin_port + && laddr4->sin_addr.s_addr == paddr4->sin_addr.s_addr; + } + else if (localaddr.sin6_family == AF_INET6) // IPv6 + { + // int memcmp (const void *s1, const void *s2, size_t n); -- #include + // 比较s1和s2的前n个字符 + return localaddr.sin6_port == peeraddr.sin6_port + && memcmp(&localaddr.sin6_addr, &peeraddr.sin6_addr, sizeof(localaddr.sin6_addr)) == 0; + } + else + { + CHIVE_LOG_ERROR("Unknown protocol, not support!"); + return false; + } +} \ No newline at end of file diff --git a/chive/net/SocketOps.h b/chive/net/SocketOps.h new file mode 100755 index 0000000..7b87e5b --- /dev/null +++ b/chive/net/SocketOps.h @@ -0,0 +1,63 @@ +#pragma once +#ifndef CHIVE_NET_SOCKETOPS_H +#define CHIVE_NET_SOCKETOPS_H + +#include "chive/base/clog/chiveLog.h" + +#include +#include +#include +#include +#include // snprintf +#include // readv +#include +#include + +namespace chive +{ +namespace net +{ +namespace socketops +{ +int createNonblockingOrDie(sa_family_t family); + +int connect(int sockfd, const struct sockaddr* addr); + +int bindOrDie(int sockfd, const struct sockaddr* addr); + +void listenOrDie(int sockfd); +// int accept(int sockfd, struct sockaddr_in6* addr); + +void close(int sockfd); + +/** + * 检查sockfd上是否有错误发生 + * @return 返回错误码 + */ +int getSocketError(int sockfd); + +struct sockaddr* sockaddr_cast(struct sockaddr_in6* addr); +struct sockaddr* sockaddr_cast(struct sockaddr_in* addr); +const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr); +struct sockaddr_in6 getLocalAddr6(int sockfd); + +struct sockaddr_in getLocalAddr4(int sockfd); + +struct sockaddr_in6 getPeerAddr6(int sockfd); + +struct sockaddr_in getPeerAddr4(int sockfd); + +/** + * 检查是否socket自连接 + * 原理: 检查local和peer端的ip和port是否相同,相同则说明发生了自连接 + */ +bool isSelfConnect(int sockfd); + +} // namespace socketops + +} // namespace net + +} // namespace chive + + +#endif \ No newline at end of file diff --git a/chive/net/TcpClient.cc b/chive/net/TcpClient.cc new file mode 100755 index 0000000..8da75ae --- /dev/null +++ b/chive/net/TcpClient.cc @@ -0,0 +1,159 @@ +#include "chive/net/TcpClient.h" +#include "chive/net/Connector.h" +#include "chive/net/EventLoop.h" +#include "chive/net/SocketOps.h" + +#include "chive/base/clog/chiveLog.h" +#include // snprintf +#include + + +using namespace chive; +using namespace chive::net; +using namespace std::placeholders; + +namespace chive +{ +namespace net +{ +namespace cb +{ +void removeConnection(EventLoop* loop, const TcpConnectionPtr& conn) +{ + loop->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn)); +} + +void removeConnector(const ConnectorPtr& connector) +{ + CHIVE_LOG_DEBUG("it's time to remove connector %p", connector.get()); +} +} // namespace cb + +} // namespace net + +} // namespace chive + +TcpClient::TcpClient(EventLoop* loop, + const InetAddress& serverAddr, + const std::string& name) + : loop_(loop), + connector_(new Connector(loop, serverAddr)), + name_(name), + connectionCallback_(defaultConnectionCallback), + messageCallback_(defaultMessageCallback), + retry_(false), + connect_(false), + nextConnId_(1) +{ + connector_->setNewConnectionCallback( + std::bind(&TcpClient::newConnection, this, _1)); + CHIVE_LOG_INFO("TcpClient %s - connector %p", name_.c_str(), connector_.get()); +} + +/** + * TcpClient销毁,保证TcpConnection安全关闭 + */ +TcpClient::~TcpClient() +{ + CHIVE_LOG_INFO("TcpClient %s - connector %p", name_.c_str(), connector_.get()); + TcpConnectionPtr conn; + bool unique = false; + { + MutexLockGuard lock(mutex_); + unique = connection_.unique(); // connection_.use_count() == 1 + conn = connection_; + } + if(conn) + { + assert(loop_ == conn->getLoop()); + CloseCallback callback = std::bind(&cb::removeConnection, loop_, _1); + loop_->runInLoop(std::bind(&TcpConnection::setCloseCallback, conn, callback)); + if (unique) /*只有一个引用,可强制关闭连接*/ + { + conn->forceClose(); + } + } + else + { + // connection_ 已关闭,connector_可以安全地移除 + connector_->stop(); + loop_->runAfter(1, std::bind(&cb::removeConnector, connector_)); + } +} + +void TcpClient::connect() +{ + CHIVE_LOG_INFO("TcpClient %s - connecting to %s", + name_.c_str(), connector_->serverAddress().toIpPort().c_str()); + connect_ = true; + connector_->start(); +} + +void TcpClient::disconnect() +{ + connect_ = false; + + { + MutexLockGuard lock(mutex_); + // 单边断开,关闭写端 + if (connection_) + { + connection_->shutdown(); + } + } +} + +void TcpClient::stop() +{ + connect_ = false; + connector_->stop(); +} + +void TcpClient::newConnection(int sockfd) +{ + loop_->assertInLoopThread(); + InetAddress peerAddr(socketops::getPeerAddr4(sockfd)); + char buf[32]; + snprintf(buf, sizeof(buf), ":%s#%d", + peerAddr.toIpPort().c_str(), nextConnId_); + ++nextConnId_; + std::string connName = name_ + buf; + + InetAddress localAddr(socketops::getLocalAddr4(sockfd)); + TcpConnectionPtr conn(new TcpConnection(loop_, + connName, + sockfd, + localAddr, + peerAddr)); + conn->setConnectionCallback(connectionCallback_); + conn->setMessageCallback(messageCallback_); + conn->setWriteCompleteCallback(writeCompleteCallback_); + conn->setCloseCallback(std::bind(&TcpClient::removeConnection, this, _1)); + { + MutexLockGuard lock(mutex_); + connection_ = conn; + } + conn->connectEstablished(); +} + +// 与 newConnection配对操作 +// 保证在removeConnection之后不再有访问connection_的方法被调用 +void TcpClient::removeConnection(const TcpConnectionPtr& conn) +{ + loop_->assertInLoopThread(); + assert(loop_ == conn->getLoop()); + + { + MutexLockGuard lock(mutex_); + assert(connection_ == conn); + connection_.reset(); + } + + loop_->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn)); + if (retry_ && connect_) + { + CHIVE_LOG_INFO("TcpClient [ %s ] reconnects to %s", + name_.c_str(), connector_->serverAddress().toIpPort().c_str()); + connector_->restart(); + } +} \ No newline at end of file diff --git a/chive/net/TcpClient.h b/chive/net/TcpClient.h new file mode 100755 index 0000000..caa0e05 --- /dev/null +++ b/chive/net/TcpClient.h @@ -0,0 +1,83 @@ +#ifndef CHIVE_NET_TCPCLIENT_H +#define CHIVE_NET_TCPCLIENT_H + +#include "chive/base/MutexLock.h" +#include "chive/net/TcpConnection.h" // + +// #include +namespace chive +{ +namespace net +{ +class Connector; +using ConnectorPtr = std::shared_ptr; + +class TcpClient : noncopyable +{ +public: + TcpClient(EventLoop* loop, const InetAddress& serverAddr, const std::string& name); + ~TcpClient(); + + void connect(); + void disconnect(); + void stop(); + + TcpConnectionPtr connection() const + { + MutexLockGuard lock(mutex_); + return connection_; + } + + EventLoop* getLoop() const { return loop_; } + + bool retry() const { return retry_; } + + void enableRetry() { retry_ = true; } + + const std::string& name() const { return name_; } + + // Not thead safe + void setConnectionCallback(ConnectionCallback cb) + { + connectionCallback_ = std::move(cb); + } + + ///FIXME: why do the follow setters should be thread-safe? + // Not thread safe + void setMessageCallback(MessageCallback cb) + { + messageCallback_ = std::move(cb); + } + + // Not thread safe + void setWriteCompleteCallback(WriteCompleteCallback cb) + { + writeCompleteCallback_ = std::move(cb); + } + +private: + // Not thread safe but in IO loop + void newConnection(int sockfd); + // Not thread safe but in IO loop + void removeConnection(const TcpConnectionPtr& conn); + + EventLoop* loop_; + ConnectorPtr connector_; //avoid revealing Connector directly + const std::string name_; // tcp client name + ConnectionCallback connectionCallback_; + MessageCallback messageCallback_; + WriteCompleteCallback writeCompleteCallback_; + + bool retry_; // can retry or not + bool connect_; // begin connecting + int nextConnId_; + + mutable MutexLock mutex_; + TcpConnectionPtr connection_; /// guard by mutex_ +}; +} // namespace net + +} // namespace chive + + +#endif \ No newline at end of file diff --git a/chive/net/TcpConnection.cc b/chive/net/TcpConnection.cc new file mode 100755 index 0000000..d165b76 --- /dev/null +++ b/chive/net/TcpConnection.cc @@ -0,0 +1,330 @@ +#include "chive/net/TcpConnection.h" +#include "chive/net/EventLoop.h" +#include "chive/net/Socket.h" +#include "chive/net/Channel.h" +#include "chive/base/clog/chiveLog.h" +#include + +using namespace chive; +using namespace chive::net; +using namespace std::placeholders; + +// declaration in Callbacks.h +void chive::net::defaultConnectionCallback(const TcpConnectionPtr& conn) +{ + CHIVE_LOG_DEBUG("connection localAddr %s peerAddr %s connected state %s", + conn->localAddress().toIpPort().c_str(), + conn->peerAddress().toIpPort().c_str(), + (conn->isConnected()? "Up" : "Down")); +} + +void chive::net::defaultMessageCallback(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) +{ + CHIVE_LOG_DEBUG("connection localAddr %s peerAddr %s connected state %s received data %s", + conn->localAddress().toIpPort().c_str(), + conn->peerAddress().toIpPort().c_str(), + (conn->isConnected()? "Up" : "Down"), + buf->retrieveAllAsString().c_str()); +} + +TcpConnection::TcpConnection(EventLoop* loop, + const std::string& name, + int sockfd, + const InetAddress& localAddr, + const InetAddress& peerAddr) + : loop_ (loop), + name_ (name), + state_ (kConnecting), + socket_ (new Socket(sockfd)), + channel_ (new Channel(loop, sockfd)), + localAddr_ (localAddr), + peerAddr_ (peerAddr), + highWaterMark_ (HIGH_WATER_MARK_SIZE) + +{ + CHIVE_LOG_DEBUG("new connection %p created: { name: %d, sockfd: %d }", this, name_, sockfd); + channel_->setReadCallback( + std::bind(&TcpConnection::handleRead, this, _1)); + channel_->setWriteCallback( + std::bind(&TcpConnection::handleWrite, this)); + channel_->setCloseCallback( + std::bind(&TcpConnection::handleClose, this)); + channel_->setErrorCallback( + std::bind(&TcpConnection::handleError, this)); + socket_->setKeepAlive(true); +} + +TcpConnection::~TcpConnection() +{ + CHIVE_LOG_DEBUG("disconnected tcp connection %p with channel %p socket %p fd %d", + this, channel_.get(), socket_.get(), socket_->fd()); +} + +/// 由channel的handleEvent回调 +void TcpConnection::handleRead(Timestamp receiveTime) +{ + CHIVE_LOG_DEBUG("tcp connection %p handle read from connfd %d", + this, channel_->getFd()); + int savedErrno = 0; + ssize_t n = inputBuffer_.readFd(channel_->getFd(), &savedErrno); + if ( n > 0) + { + messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); + } + else if (n == 0) + { + handleClose(); + } + else + { + errno = savedErrno; + handleError(); + } +} + +void TcpConnection::handleWrite() +{ + loop_->assertInLoopThread(); + if (channel_->isWriting()) + { + ssize_t n = ::write(socket_->fd(), + outputBuffer_.peek(), + outputBuffer_.readableBytes()); + if (n > 0) { + outputBuffer_.retrieve(static_cast(n)); + if (outputBuffer_.readableBytes() == 0) { + // 发送完毕,移除可写事件,防止busy-loop + channel_->disableWriting(); + // 执行写完的回调 + if (writeCompleteCallback_) { + loop_->queueInLoop( + std::bind(writeCompleteCallback_, shared_from_this())); + } + // 正在执行关闭,则调用shutdownInLoop()继续执行关闭过程 + if (state_ == kDisconnecting) { + shutdownInLoop(); + } + } else { + CHIVE_LOG_INFO("tcp connection %p socket %d need write more data", + this, socket_->fd()); + } + } + else { + // int savedErrno = errno; + /// NOTE: 不需要处理错误 + /// 如果发生n==0,那么handleRead会读到0字节,继而关闭连接 --- from muduo + /// FIXME: handleRead会读到0字节? + CHIVE_LOG_ERROR("tcp connection %p write to socket %d error", + this, socket_->fd()); + } + } + else + { + CHIVE_LOG_INFO("tcp connection %p is down, no more writing", this); + } +} + +void TcpConnection::handleClose() +{ + loop_->assertInLoopThread(); + CHIVE_LOG_DEBUG("client disconnected now state %d in connfd %d", + state_, channel_->getFd()); + assert(state_ == kConnected || state_ == kDisconnecting); + setState(kDisconnected); + // 只是移除fd上全部事件,没有close掉fd, 让Socket对象自己析构 + // 目的:便于定位内存泄露 -- from muduo + channel_->disableAll(); + if (closeCallback_) { + // 回调 TcpServer::removeConnection + closeCallback_(shared_from_this()); + } +} + +void TcpConnection::handleError() +{ + int err = socket_->getSocketError(); + CHIVE_LOG_ERROR("tcp connection %p [ name: ] - SO_ERROR %d", + this, name_.c_str(), err); +} + +void TcpConnection::connectEstablished() +{ + CHIVE_LOG_DEBUG("tcp connection %p established connection", this); + loop_->assertInLoopThread(); + assert(state_ == kConnecting); + setState(kConnected); + //将this指针作为shared_ptr指针 + channel_->tie(shared_from_this()); + //向内核事件表注册connection关联的socket + channel_->enableReading(); + //连接建立完成,回调 + connectionCallback_(shared_from_this()); +} + +void TcpConnection::connectDestroyed() +{ + CHIVE_LOG_DEBUG("tcp connection %p desctroyed", this); + loop_->assertInLoopThread(); + assert(state_ == kConnected || state_ == kDisconnected); + setState(kDisconnected); + channel_->disableAll(); + + /// FIXME: + /// 使用disconnectedCallback 而不是 connectionCallback + if(connectionCallback_) + connectionCallback_(shared_from_this()); + + loop_->removeChannel(channel_.get()); +} + + +void TcpConnection::send(Buffer* buf) +{ + if (state_ == kConnected) + { + if (loop_->isInLoopThread()) { + sendInLoop(buf->peek(), buf->readableBytes()); + buf->retrieveAll(); + } else { + /// NOTE: 因为sendInLoop有重载,所以需要指出函数指针类型 + /// ref:https://www.cnblogs.com/and_swordday/p/4643975.html + void (TcpConnection::*fp)(const std::string& message) = &TcpConnection::sendInLoop; + loop_->runInLoop( + std::bind(fp, this, buf->retrieveAllAsString())); + } + } +} + +///FIXME: need or not? +void TcpConnection::send(const void* message, size_t len) +{ + send(std::string(static_cast(message), len)); +} + +void TcpConnection::send(const std::string& message) +{ + if (state_ == kConnected) + { + if (loop_->isInLoopThread()) { + sendInLoop(message); + } else { + /// NOTE: 因为sendInLoop有重载,所以需要指出函数指针类型 + /// ref:https://www.cnblogs.com/and_swordday/p/4643975.html + void (TcpConnection::*fp)(const std::string& message) = &TcpConnection::sendInLoop; + loop_->runInLoop( + std::bind(fp, this, message)); + } + } +} + + +void TcpConnection::shutdown() +{ + if (state_ == kConnected) + { + setState(kDisconnecting); // 关闭写入端 + loop_->runInLoop( + ///NOTE: 使用shared_from_this() 而不是 this + std::bind(&TcpConnection::shutdownInLoop, shared_from_this())); + } +} + +void TcpConnection::forceClose() +{ + if (state_ == kConnected || state_ == kDisconnecting) + { + setState(kDisconnecting); + loop_->queueInLoop( + std::bind(&TcpConnection::forceCloseInLoop, shared_from_this())); + } +} + +void TcpConnection::forceCloseInLoop() +{ + loop_->assertInLoopThread(); + if (state_ == kConnected || state_ == kDisconnecting) + { + handleClose(); + } +} + +void TcpConnection::sendInLoop(const std::string& message) +{ + sendInLoop(message.data(), message.size()); +} + +void TcpConnection::sendInLoop(const void* data, size_t len) +{ + loop_->assertInLoopThread(); + ssize_t nwrote = 0; + size_t remaining = len; + bool faultError = true; + // add for give up writing when disconnected. + if (state_ == kDisconnected) { + CHIVE_LOG_WARN("disconnected, give up writing"); + return; + } + // 1. 先检查outputBuffer_ 有没有未发送的数据, 如无则直接写 + if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) + { + nwrote = ::write(socket_->fd(), data, len); + if (nwrote >= 0) { + remaining = len - nwrote; + if (remaining == 0 && writeCompleteCallback_) { + ///NOTE: 不是runInLoop, 因为发送完毕的回调不需要实时 + loop_->queueInLoop( + std::bind(writeCompleteCallback_, shared_from_this())); + } + } else { + nwrote = 0; + if (errno != EWOULDBLOCK) { + CHIVE_LOG_ERROR("tcpconn %p write to socket %d failed, errno %d", + this, channel_->getFd(), errno); + /// FIXME: any other error?? + if (errno == EPIPE || errno == ECONNRESET) { + faultError = true; + } + } + } + } + + // 如果channel处于写事件状态或者outputBuffer_上有未发送的数据 + // 那么当前数据发送的任务需要排队, 以防数据乱序 + // 解决方案: 开启channel上的写事件 + assert(remaining <= len); + if (remaining > 0 && !faultError) + { + // 处理高水位回调,防止数据本地堆积 + size_t oldLen = outputBuffer_.readableBytes(); + if (oldLen + remaining >= highWaterMark_ + && oldLen < highWaterMark_ + && highWaterMarkCallback_) + { + loop_->queueInLoop( + std::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining)); + } + outputBuffer_.append(static_cast(data) + nwrote, remaining); + if (!channel_->isWriting()) { + channel_->enableWriting(); + } + } +} + + +void TcpConnection::shutdownInLoop() +{ + loop_->assertInLoopThread(); + // 如果channel上没有写事件发生才能shutdown + // 否则会丢失写事件 + if (!channel_->isWriting()) + { + socket_->shutdownWrite(); + } +} + +void TcpConnection::setKeepAlive(bool on) +{ socket_->setKeepAlive(on); } + +void TcpConnection::setTcpNoDelay(bool on) +{ socket_->setTcpNoDelay(on); } + diff --git a/chive/net/TcpConnection.h b/chive/net/TcpConnection.h new file mode 100755 index 0000000..eee247e --- /dev/null +++ b/chive/net/TcpConnection.h @@ -0,0 +1,150 @@ +#ifndef CHIVE_NET_TCPCONNECTION_H +#define CHIVE_NET_TCPCONNECTION_H + +#include "chive/base/noncopyable.h" +#include "chive/net/Callbacks.h" +#include "chive/net/InetAddress.h" // 用到了InetAddress实例 +#include "chive/net/Buffer.h" + +#include +#include +#include //C++17 any + +namespace chive +{ + +namespace net +{ + +class Socket; +class Channel; +class EventLoop; +class InetAddress; + +static const int HIGH_WATER_MARK_SIZE = 64 * 1024 * 1024; + +class TcpConnection : noncopyable, public std::enable_shared_from_this +{ +public: + TcpConnection(EventLoop* loop, + const std::string& name, + int sockfd, + const InetAddress& localAddr, + const InetAddress& peerAddr); + ~TcpConnection(); + + /** + * 建立tcp connection的时候调用 + * -- 只允许调用一次 + */ + void connectEstablished(); + + /** + * 移除tcp connection的时候调用 + * -- 只允许调用一次 + */ + void connectDestroyed(); + + EventLoop* getLoop() const { return loop_; } + const std::string& name() const { return name_; } + const InetAddress& localAddress() const { return localAddr_; } + const InetAddress& peerAddress() const { return peerAddr_; } + bool isConnected() const { return state_ == kConnected; } + bool isDisConnected() const { return state_ == kDisconnected; } + + // -- thread safe + void send(Buffer* buf); + void send(const std::string& message); + void send(const void* message, size_t len); + // -- thread safe + void shutdown(); + + // force closing tcp connection begin on 2020/10/10 + void forceClose(); + void forceCloseInLoop(); + // force closing tcp connection end on 2020/10/10 + + void setContext(const std::any& context) + { context_ = context; } + + const std::any& getContext() const + {return context_; } + + std::any* getMutableContext() + { return &context_; } + + /** + * -- TCP keepalive + * 定期检查TCP连接是否还存在,如果应用层有做‘心跳’的话则不是必须的 + * 但一个通用的网络库应该暴露其接口 + */ + void setKeepAlive(bool on); + /** + * -- TCP No Delay 对低延迟网络服务关键 + * 禁用Nagle算法,避免连续发包出现延迟, + */ + void setTcpNoDelay(bool on); + void setConnectionCallback(const ConnectionCallback& cb) { connectionCallback_ = cb; } + void setMessageCallback(const MessageCallback& cb) { messageCallback_ = cb; } + void setCloseCallback(const CloseCallback& cb) { closeCallback_ = cb; } + // ‘低水位回调’ + void setWriteCompleteCallback(const WriteCompleteCallback& cb) { writeCompleteCallback_ = cb; } + // ‘高水位回调' + void setWaterMarkCallback(const HighWaterMarkCallback& cb) + {} +private: + // TcpConnection 状态 (tcp connection的状态转移) + // 初始化状态即为 connecting + enum StateE + { + kConnecting, // init state + kConnected, // connecting -> connected, after connectEstablished() + kDisconnecting, // kConnected -> Disconnecting, after shutdown() + kDisconnected, // connected/Disconnecting -> disconnected, after handleClose() + }; + + /// FIXME: 需要线程安全吗?? + void setState(StateE s) { state_ = s; } + + // 事件处理 + void handleRead(Timestamp receiveTime); + void handleWrite(); + void handleClose(); + void handleError(); + + /** + * 在IO线程发送数据给客户端 + * @param message 待发送的消息 + */ + void sendInLoop(const std::string& message); + void sendInLoop(const void* message, size_t len); + void shutdownInLoop(); + + EventLoop* loop_; + std::string name_; + ///FIXME: 状态标志位 用 atomic ?? + StateE state_; + std::unique_ptr socket_; + std::unique_ptr channel_; + InetAddress localAddr_; + InetAddress peerAddr_; + ConnectionCallback connectionCallback_; + MessageCallback messageCallback_; + CloseCallback closeCallback_; + /// 数据发送完毕时回调 + WriteCompleteCallback writeCompleteCallback_; + /// 高水位回调: 发送速率 > 对方接收速率, + /// 防止数据堆积在本地 + HighWaterMarkCallback highWaterMarkCallback_; + size_t highWaterMark_; /// 触发高水位回调的阈值 + Buffer inputBuffer_; /// 接收数据的缓冲区 + Buffer outputBuffer_; /// 发送数据的缓冲区 + + std::any context_; +}; +} // namespace net + +} // namespace chive + + +#endif \ No newline at end of file diff --git a/chive/net/TcpServer.cc b/chive/net/TcpServer.cc new file mode 100755 index 0000000..d25d6dc --- /dev/null +++ b/chive/net/TcpServer.cc @@ -0,0 +1,119 @@ +#include "chive/net/TcpServer.h" +#include "chive/net/InetAddress.h" +#include "chive/base/clog/chiveLog.h" +#include "chive/net/Acceptor.h" +#include "chive/net/EventLoop.h" + + +#include +#include + +using namespace chive; +using namespace chive::net; +using namespace std::placeholders; // function对象占位符 + +#ifdef CHIVE_IGNORE_SIGPIPE +// 全局对象定义, 忽略SIGPIPE +IgnoreSigPipe initObj; +#endif + + +TcpServer::TcpServer(EventLoop* loop, const InetAddress& listenAddr, const std::string& name, bool reuseport) + : loop_ (loop), + name_(name), + ipPort_(listenAddr.toIpPort()), + acceptor_ (new Acceptor(loop, listenAddr, reuseport)), + connectionCallback_(defaultConnectionCallback), + messageCallback_(defaultMessageCallback), + started_ (false), + nextConnId_(1), + threadPool_(new EventLoopThreadPool(loop, "chive_evtloopthreadpool#"+name)) +{ + CHIVE_LOG_DEBUG("created tcpserver %p", this); + acceptor_->setNewConnectionCallback( + std::bind(&TcpServer::newConnection, this, _1, _2)); +} + +TcpServer::~TcpServer() +{ + CHIVE_LOG_DEBUG("tcpserver quit!"); +} + +void TcpServer::start() +{ + if (!started_) + { + CHIVE_LOG_DEBUG("start tcpserver %p acceptor %p listenfd run in eventloop %p", + this, acceptor_.get(), loop_); + assert(!acceptor_->listening()); + // 在IO线程进行监听 + loop_->runInLoop( + std::bind(&Acceptor::listen, acceptor_.get())); + } +} + +void TcpServer::setThreadNum(int numThreads) +{ + assert(0 <= numThreads); + threadPool_->setThreadNum(numThreads); +} + +void TcpServer::setConnectionCallback(const ConnectionCallback& cb) +{ + CHIVE_LOG_DEBUG("set connection callback for tcpserver %p", this); + connectionCallback_ = cb; +} + +void TcpServer::setMessageCallback(const MessageCallback& cb) +{ + CHIVE_LOG_DEBUG("set message callback for tcpserver %p", this); + messageCallback_ = cb; +} + +void TcpServer::setWriteCompleteCallback(const WriteCompleteCallback& cb) +{ + CHIVE_LOG_DEBUG("set write complete callback for tcpserver %p", this); + writeCompleteCallback_ = cb; +} + +void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr) +{ + loop_->assertInLoopThread(); + char buf[32]; + snprintf(buf, sizeof(buf), "#%d", nextConnId_); + ++nextConnId_; + std::string connName = name_ + buf; + + CHIVE_LOG_INFO("new connection is coming: { connName: %s from peerAddr %s }", + connName.c_str(), peerAddr.toIpPort().c_str()); + + InetAddress localAddr(InetAddress::getLocalAddress(sockfd)); + + TcpConnectionPtr conn( + new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr)); + connections_[connName] = conn; + conn->setConnectionCallback(connectionCallback_); + conn->setMessageCallback(messageCallback_); + conn->setCloseCallback( + std::bind(&TcpServer::removeConnection, this, _1)); + conn->setWriteCompleteCallback(writeCompleteCallback_); + conn->connectEstablished(); +} + +void TcpServer::removeConnection(const TcpConnectionPtr& conn) +{ + loop_->runInLoop( + std::bind(&TcpServer::removeConnectInLoop, this, conn)); +} +// 保证回调在conn的ioloop进行 +void TcpServer::removeConnectInLoop(const TcpConnectionPtr& conn) +{ + loop_->assertInLoopThread(); + CHIVE_LOG_INFO("remove connection %p named %d", + conn.get(), conn->name().c_str()); + size_t n = connections_.erase(conn->name()); + assert(n == 1); (void)n; + EventLoop* ioLoop = conn->getLoop(); + ioLoop->queueInLoop( + std::bind(&TcpConnection::connectDestroyed, conn)); +} \ No newline at end of file diff --git a/chive/net/TcpServer.h b/chive/net/TcpServer.h new file mode 100755 index 0000000..9d990ed --- /dev/null +++ b/chive/net/TcpServer.h @@ -0,0 +1,89 @@ +#ifndef CHIVE_NET_TCPSERVER_H +#define CHIVE_NET_TCPSERVER_H + +#define CHIVE_IGNORE_SIGPIPE +#include "chive/base/noncopyable.h" +#include "chive/net/TcpConnection.h" +#include "chive/net/EventLoopThreadPool.h" + +#include +#include +#include + +#ifdef CHIVE_IGNORE_SIGPIPE +#include "chive/net/IgnoreSigPipe.h" // init global IgnoreSigPipe +#endif + +namespace chive +{ +namespace net +{ +class Acceptor; + + +class TcpServer : noncopyable +{ +public: + using ThreadInitCallback = std::function; + + TcpServer(EventLoop* loop, const InetAddress& listenAddr, const std::string& name, bool reuseport = false); + ~TcpServer(); + + /** + * 启动server + * -- 线程安全 + */ + void start(); + + const std::string ipPort() const { return ipPort_; } + const std::string name() const { return name_; } + EventLoop* getLoop() const { return loop_; } + + /** + * 设置threadpool的线程个数 + * 必须在start()前调用 + * @param numThreads + */ + void setThreadNum(int numThreads); + void setThreadInitCallback(const ThreadInitCallback& cb) + { threadInitCallback_ = cb; } + + // -- 非线程安全 -- + void setConnectionCallback(const ConnectionCallback& cb); + + // -- 非线程安全 -- + void setMessageCallback(const MessageCallback& cb); + + void setWriteCompleteCallback(const WriteCompleteCallback& cb); + +private: + using ConnectionMap = std::map; + + /** + * 新连接到达被accpeted后,创建Tcp Connection,放入ConnectionMap + * @param sockfd accepted返回的connfd + * @param peerAddr 客户端地址信息 + */ + void newConnection(int sockfd, const InetAddress& peerAddr); + + void removeConnection(const TcpConnectionPtr& conn); + void removeConnectInLoop(const TcpConnectionPtr& conn); + + EventLoop* loop_; // the acceptor loop + const std::string ipPort_; + const std::string name_; /// the key of ConnectionMap + std::unique_ptr acceptor_; /// avoid revealing/exposing acceptor + ConnectionCallback connectionCallback_; /// + MessageCallback messageCallback_; /// + WriteCompleteCallback writeCompleteCallback_; + ThreadInitCallback threadInitCallback_; + bool started_; /// + int nextConnId_; /// + ConnectionMap connections_; /// + std::unique_ptr threadPool_; +}; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/Timer.cc b/chive/net/Timer.cc new file mode 100644 index 0000000..2231a34 --- /dev/null +++ b/chive/net/Timer.cc @@ -0,0 +1,59 @@ +#include "chive/net/Timer.h" + +#include +#include + +using namespace chive; +using namespace chive::net; + +//静态变量初始化 +AtomicInt64 Timer::s_numCreated_; + +Timer::Timer(const TimerCallback& cb, Timestamp timeout, TimeType interval) + : callback_(cb), + expiredTime_(timeout), + interval_(interval), + repeat_(interval_ > 0), + sequence_(s_numCreated_.incrementAndGet()) { + +} + +void Timer::run() const { + callback_(); +} + +bool Timer::isValid() { + return expiredTime_ >= Timer::now(); +} + +void Timer::restart(Timestamp now) { + if(repeat_) { + //周期执行,给当前到期时间续命一个周期 + expiredTime_ = now + interval_; + } else { + expiredTime_ = invalid(); + } +} + +/** + * 获取当前系统时间戳(微秒单位) + */ +Timer::Timestamp Timer::now() { + /** + * timeval { + * time_t tv_sec; + * suseconds_t tv_usec; + * } + */ + std::shared_ptr tv(std::make_shared()); + // 第二个参数用于指定timezone + gettimeofday(tv.get(), nullptr); //该函数不是系统调用,deprecated + + //获取秒 * 1000*1000 + 微秒 得到基于微秒的时间戳 + return static_cast(tv->tv_sec * Timer::kMicroSecondsPerSecond + + tv->tv_usec); +} + +int64_t Timer::createNum() { + return s_numCreated_.get(); +} \ No newline at end of file diff --git a/chive/net/Timer.h b/chive/net/Timer.h new file mode 100644 index 0000000..96bef14 --- /dev/null +++ b/chive/net/Timer.h @@ -0,0 +1,106 @@ +#ifndef CHIVE_NET_TIMER_H +#define CHIVE_NET_TIMER_H + +#include +#include + +#include "chive/base/noncopyable.h" +#include "chive/base/Atomic.h" + +namespace chive +{ +namespace net +{ + +class Timer : noncopyable +{ +public: + using TimerCallback = std::function; // 定时器回调函数类型 + using Timestamp = int64_t; // 时间戳数据类型 + using TimeType = int64_t; // 其他时间类型 + + const static TimeType kMicroSecondsPerSecond = 1000 * 1000; //每秒的微秒数 1000 * 1000 + + /** + * 构造函数 + * @param cb + * @param timeout + * @param interval + */ + Timer(const TimerCallback& cb, Timestamp timeout, TimeType interval); + + /** + * 执行回调 + */ + void run() const; + + /** + * 获取定时器的到期时间 + * @return 到期时间戳 + */ + Timestamp getExpiredTime() const { + return expiredTime_; + } + + /** + * 更新到期时间 + * @param newTimeout + */ + void updateExpiradTime(Timestamp newTimeout) { + expiredTime_ = newTimeout; + } + + /** + * 是否周期性执行 + */ + bool repeat() const { + return repeat_; + } + + /** + * 获取定时器序列号 + * @return 序列号 + */ + int64_t getSequence() const { + return sequence_; + } + + /** + * 判断是否有效时间戳,大于当前时间戳才被认为是有效可用的 + * @return true/false + */ + bool isValid(); + + /** + * 返回无效时间戳 + */ + Timestamp invalid() const { + return 0; + } + + void restart(Timestamp now); + + /** + * 获取当前时间戳,即自 1970-01-01 00:00:00 的微秒 + * @return 微秒 + */ + static Timestamp now(); + + //获取一个序列号 + static int64_t createNum(); + +private: + const TimerCallback callback_; // 定时回调函数 + Timestamp expiredTime_; // 到期时间戳 + const double interval_; // 执行周期 + const bool repeat_; // 是否周期性执行 + const int64_t sequence_; // 定时器序列号 + + static AtomicInt64 s_numCreated_; //序列号生成器 +}; +} // namespace net + +} // namespace chive + + +#endif \ No newline at end of file diff --git a/chive/net/TimerId.h b/chive/net/TimerId.h new file mode 100644 index 0000000..208e478 --- /dev/null +++ b/chive/net/TimerId.h @@ -0,0 +1,39 @@ +#ifndef CHIVE_NET_TIMERID_H +#define CHIVE_NET_TIMERID_H + +#include "chive/base/noncopyable.h" +#include "chive/net/Timer.h" + +#include + +namespace chive +{ +namespace net +{ + +// class Timer; + +// 提供给用户用于标识定时器 +// 用于注销定时器队列中的定时器 +// Timer是非线程安全的所以不能直接给用户 +class TimerId +{ +public: + TimerId() : sequence_(0) {} + explicit TimerId(const std::weak_ptr &timer) + : timer_(timer), + sequence_(timer.lock()? timer.lock()->getSequence() : 0) + {} + + friend class TimerQueue; + +private: + std::weak_ptr timer_; //使用弱指针 + int64_t sequence_; + +}; + +} // namespace net +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/TimerQueue.cc b/chive/net/TimerQueue.cc new file mode 100644 index 0000000..907430e --- /dev/null +++ b/chive/net/TimerQueue.cc @@ -0,0 +1,275 @@ +#include "chive/net/TimerQueue.h" +// #include "chive/base/Logger.h" +#include "chive/base/clog/chiveLog.h" +#include "chive/net/EventLoop.h" + +#include "chive/net/Timer.h" + +#include +#include +#include +#include +#include +#include +#include // memset + +using namespace chive; +using namespace chive::net; + +// timerfd 操作集合 +namespace timerfdOps +{ +timespec howMuchTimeFromNow(Timer::Timestamp when); +/** + * 创建一个tiemrfd + */ +int createTimerfd() +{ + int timerfd = ::timerfd_create(CLOCK_MONOTONIC, + TFD_NONBLOCK | TFD_CLOEXEC); + if (timerfd < 0) + { + CHIVE_LOG_ERROR("timerfd_create failed! timerfd %d", timerfd); + } + CHIVE_LOG_ERROR("created timerfd %d", timerfd); + return timerfd; +} + +/* +与timerfd_settime()有关的两个结构体 +struct timespec { + time_t tv_sec; // Seconds + long tv_nsec; // Nanoseconds +}; +struct itimerspec { + struct timespec it_interval; // Interval for periodic timer + struct timespec it_value; // Initial expiration +}; +*/ +/** + * 重置timerfd超时时间,仅当插入的定时器是最早到期的定时器 + * 如果不重置将错过最早的定时器 + */ +void resetTimerfd(int timerfd, Timer::Timestamp expiration) +{ + CHIVE_LOG_DEBUG("trace in reset timerfd"); + struct itimerspec newValue; + struct itimerspec oldValue; + memset(&oldValue, 0, sizeof(oldValue)); + memset(&newValue, 0, sizeof(newValue)); + + newValue.it_value = howMuchTimeFromNow(expiration); + // expiration可能已经过期,需要重新设置新的超时时间 + int ret = timerfd_settime(timerfd, 0, &newValue, &oldValue); + if(ret) { + CHIVE_LOG_DEBUG("timerfd_setttime, ret %d", ret); + } +} + +timespec howMuchTimeFromNow(Timer::Timestamp when) +{ + Timer::Timestamp microseconds = when - Timer::now(); + if(microseconds < 100) { + microseconds = 100; + } + timespec ts{}; + ts.tv_sec = static_cast(microseconds / Timer::kMicroSecondsPerSecond); + ts.tv_nsec = static_cast((microseconds % Timer::kMicroSecondsPerSecond) * 1000); + + return ts; +} + +void readTimerfd(int timerfd, Timer::Timestamp now) +{ + uint64_t howmany = 1; + + ssize_t n = read(timerfd, &howmany, sizeof(howmany)); + CHIVE_LOG_DEBUG("TimerQueue::readTimerfd() read %d bytes from timerfd %d", n, timerfd); + if(n != sizeof(howmany)) { + CHIVE_LOG_ERROR("reads %d bytes instead of 8", n); + } +} +}; + +TimerQueue::TimerQueue(EventLoop* loop) + :loop_(loop), + timerfd_(timerfdOps::createTimerfd()), + timerfdChannel_(loop, timerfd_), + timers_(), + callingExpiredTimers_(false) +{ + // 设置"读"回调函数并开启可读 (更新到epoll) + timerfdChannel_.setReadCallback(std::bind(&TimerQueue::handleRead, this)); + timerfdChannel_.enableReading(); +} + +TimerQueue::~TimerQueue() +{ + CHIVE_LOG_DEBUG("timerqueue %p destructed", this); + timerfdChannel_.disableAll(); + timerfdChannel_.remove(); + ::close(timerfd_); // 关闭fd + for (const auto& timer : timers_) + { + CHIVE_LOG_DEBUG("remove timer %ld", timer.second->getSequence()); + // delete timer.second; // RAII + } +} + +TimerId TimerQueue::addTimer(const Timer::TimerCallback& cb, Timer::Timestamp when, Timer::TimeType interval) { + std::shared_ptr timer(new Timer(cb, when, interval)); + // 将添加定时器的操作转移到 IO 线程,让 IO 线程执行添加操作 + // 如此不加锁也能保证线程安全 + CHIVE_LOG_DEBUG("trace in TimerQueue::addTimer()"); + loop_->runInLoop(std::bind(&TimerQueue::addTimerInLoop, this, timer)); + return *(new TimerId(std::weak_ptr(timer))); +} + +void TimerQueue::cancel(const TimerId& timerId) +{ + // 注意到timer_成员是weak_ptr所以需要先lock检查 + // 引用对象是否还存在 + auto timer = timerId.timer_.lock(); + if(timer) { + loop_->runInLoop( + std::bind(&TimerQueue::cancelInLoop, this, timerId)); + } +} + +// FIXME: timer传参会不会导致引用计数+1 +void TimerQueue::addTimerInLoop(const std::shared_ptr& timer) +{ + CHIVE_LOG_DEBUG("trace in TimerQueue::addTimerInLoop()"); + loop_->assertInLoopThread(); + bool isEarliest = insert(timer); + + if(isEarliest) { + CHIVE_LOG_INFO("trace in TimerQueue::addTimerInLoop() timestamp %d", + timer->getExpiredTime()); + timerfdOps::resetTimerfd(timerfd_, timer->getExpiredTime()); + } +} + +void TimerQueue::cancelInLoop(const TimerId& timerId) +{ + loop_->assertInLoopThread(); + assert(timers_.size() == activeTimers_.size()); + // TimeQueue是TimerId的友元类,可以直接访问private成员 + auto cancelTimer = timerId.timer_.lock(); + assert(cancelTimer); // 检查弱指针的对象引用是都还存在 + ActiveTimer timer(cancelTimer, cancelTimer->getSequence()); + ActiveTimerSet::iterator it = activeTimers_.find(timer); + if (it != activeTimers_.end()) + { + // 同时删除两个容器中的定时器 + auto n = timers_.erase(Entry(it->first->getExpiredTime(), it->first)); + assert(n == 1); (void)n; + activeTimers_.erase(it); + } + else if (callingExpiredTimers_) + { + // 防止定时器自注销 + cancelingTimers_.insert(timer); + } + // 检查删除的完整性 + assert(timers_.size() == activeTimers_.size()); +} + + +bool TimerQueue::insert(const std::shared_ptr& timer) +{ + loop_->assertInLoopThread(); + assert(timers_.size() == activeTimers_.size()); + bool earliestChanged = false; + Timer::Timestamp when = timer->getExpiredTime(); + // 如果插入的timer是第一个,或者是目前timestamp最早的一个 + // 那么earliestChanged为true + auto it = timers_.begin(); + if(it == timers_.end() || when < it->first) { + earliestChanged = true; + } + { + auto result = timers_.insert(Entry(when, timer)); + assert(result.second); (void)result; + } + { + auto result = activeTimers_.insert(ActiveTimer(timer, timer->getSequence())); + assert(result.second); (void)result; + } + CHIVE_LOG_DEBUG("timestamp %ld timer sequence %ld", when,timer->getSequence()); + assert(timers_.size() == activeTimers_.size()); + return earliestChanged; +} + +std::vector TimerQueue::getExpired(Timer::Timestamp now) { + + CHIVE_LOG_DEBUG("get expired timer by now %ld", now); + std::vector expired; + Entry sentry = std::make_pair(now, std::shared_ptr()); + auto it = timers_.lower_bound(sentry); + + assert(it == timers_.end() || now < it->first); + + // 移除过期的非周期定时器 + std::copy(timers_.begin(), it, std::back_inserter(expired)); + timers_.erase(timers_.begin(), it); + + // + return expired; +} + +void TimerQueue::reset(const std::vector& expired, Timer::Timestamp now) +{ + for (const auto& it : expired) + { + // ActiveTimer: std::pair, int64_t> + ActiveTimer timer(it.second, it.second->getSequence()); + // 如果timer是周期性触发的 (且不在cancel),那么需要重新启动 + if (it.second->repeat() + && cancelingTimers_.find(timer) == cancelingTimers_.end()) + { + it.second->restart(now); + insert(it.second); + } + // else Timer is wrapper by std::shared_ptr, it will destructes itself + } + + // 选择下一个timer的到期时间作为下一个时间戳 + // 检查该timer是否合法 + if(!timers_.empty()) + { + auto nextTimer = timers_.begin()->second; // timer is type std::shared_ptr + if(nextTimer->isValid()) + { + timerfdOps::resetTimerfd(timerfd_, nextTimer->getExpiredTime()); + } + } +} + +void TimerQueue::handleRead() +{ + loop_->assertInLoopThread(); + Timer::Timestamp now = Timer::now(); + timerfdOps::readTimerfd(timerfd_, now); + std::vector expiredTimers = getExpired(now); + + /// 应对timer在回调函数中被cancel -- begin + // 标志位,正在处理定时器任务 + callingExpiredTimers_ = true; + cancelingTimers_.clear(); + /// 应对timer在回调函数中被cancel -- end + + CHIVE_LOG_DEBUG("run %d tasks", expiredTimers.size()); + // 逐个调用定时器上绑定的任务 + for(const auto& timer : expiredTimers) + { + timer.second->run(); + } + /// 应对timer在回调函数中被cancel -- begin + callingExpiredTimers_ = false; + /// 应对timer在回调函数中被cancel -- end + + //处理完定时器任务需要重置 + ///TODO: canceled的请求不再放到timers_和activeTimers_ + reset(expiredTimers, now); +} diff --git a/chive/net/TimerQueue.h b/chive/net/TimerQueue.h new file mode 100644 index 0000000..39e56cb --- /dev/null +++ b/chive/net/TimerQueue.h @@ -0,0 +1,107 @@ +#ifndef CHIVE_NET_TIMERQUEUE_H +#define CHIVE_NET_TIMERQUEUE_H + +#include +#include +#include + +#include "chive/base/noncopyable.h" +#include "chive/net/Channel.h" +#include "chive/net/Timer.h" +#include "chive/net/TimerId.h" + + +namespace chive +{ +namespace net +{ + +class EventLoop; + +class TimerQueue : noncopyable +{ +public: + explicit TimerQueue(EventLoop* loop); + ~TimerQueue(); + + /** + * 添加一个定时器 + * @param cb 定时器到期的回调函数 + * @param timeout 定时器的到期时间戳 + * @param intervel 定时器的周期时间间隔 (无周期触发则为0) + * @return 返回唯一标识定时器的 + */ + TimerId addTimer(const Timer::TimerCallback &cb, + Timer::Timestamp timeout, + Timer::TimeType interval); + /** + * 取消一个已添加的定时器 + * @param 唯一标识定时器的 + */ + void cancel(const TimerId& timerId); + +private: + using Entry = std::pair>; + using TimerList = std::set; + + // add for cancel() --begin + using ActiveTimer = std::pair, int64_t>; + using ActiveTimerSet = std::set; + // add for cancel() --end + + EventLoop* loop_; // + const int timerfd_; // 定时器 fd + Channel timerfdChannel_; //定时器timerfd专用channel + TimerList timers_; //定时器列表 + + // add for cancel() timer --begin + ActiveTimerSet activeTimers_; + ActiveTimerSet cancelingTimers_; /// 防止timer"自注销",即在回调中被注销 + /// FIXME: 使用atomic?? + bool callingExpiredTimers_; /// 防止timer"自注销" + // add for cancel() timer --end + + /** + * 定时器到期,处理timerfd可读事件 + */ + void handleRead(); + + /** + * 获取到期的定时器列表 + * @param 当前时间戳 + * @return 返回到期的定时器列表 + */ + std::vector getExpired(Timer::Timestamp now); + + /** + * 重置周期定时器的到期时间(续命),选择下一个到期时间更新timerfd + * 移除到期的一次性定时器 + * @param expired 到期的定时器列表(包括周期定时器) + * @param now 当前时间戳 + */ + void reset(const std::vector& expired, Timer::Timestamp now); + + /** + * 插入一个定时器 + * @param timer 定时器(智能指针) + * @return + */ + bool insert(const std::shared_ptr& timer); + + /** + * 作为回调函数, 用于IO线程异步添加定时器 + * 由EventLoop调用doPendingFunctors()分发执行 + */ + void addTimerInLoop(const std::shared_ptr& timer); + + /** + * 在EventLoop中撤销一个定时器,由cancel()调用 + * @param tiemrId 标识定时器的 + */ + void cancelInLoop(const TimerId& timerId); +}; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/http/BUILD b/chive/net/http/BUILD new file mode 100644 index 0000000..769fcc0 --- /dev/null +++ b/chive/net/http/BUILD @@ -0,0 +1,16 @@ +cc_library( + name = 'chive_http', + srcs = [ + 'HttpBody.cc', + 'HttpRequest.cc', + 'HttpResponse.cc', + 'HttpContext.cc', + 'HttpServer.cc', + ], + extra_cppflags = [ + '-std=c++17', + # '-Wall', # show warning as error + '-w', # no warning + '-g', + ] +) diff --git a/chive/net/http/HttpBody.cc b/chive/net/http/HttpBody.cc new file mode 100755 index 0000000..34e4ec0 --- /dev/null +++ b/chive/net/http/HttpBody.cc @@ -0,0 +1,55 @@ +#include "chive/net/http/HttpBody.h" +#include "chive/base/clog/chiveLog.h" +#include + +using namespace chive; +using namespace chive::net; + +HttpBody::HttpBody(const std::string& body, const std::string& boundary) + : body_(body), + boundary_(boundary) +{ +} + +void HttpBody::parse() +{ + CHIVE_LOG_DEBUG("body all %s", body_.c_str()); + std::string spliter = "--"+boundary_+"\r\n"; + auto pos = body_.find_first_of(spliter); + pos += spliter.size(); + + CHIVE_LOG_DEBUG("begin parse -- %s",body_.substr(pos).c_str()); + // content-disposition + + //filename + auto filenamePos = body_.find("filename=", pos); + filenamePos += std::string("filename=").size(); + // 去掉双引号 + std::string fileSegment = body_.substr(filenamePos); // 注意有 \r\n + CHIVE_LOG_DEBUG("body substr %d : %s", filenamePos, fileSegment.c_str()); + std::string exchanger = "\r\n"; + auto lineChangerPos = fileSegment.find_first_of(exchanger); + std::string finalFilename = fileSegment.substr(0, lineChangerPos); + std::string finalFilename2(&*(finalFilename.begin()+1), &*(finalFilename.end()-1)); + CHIVE_LOG_DEBUG("body substr2 %d : %s, boundary %s", + lineChangerPos, finalFilename2.c_str(), boundary_.c_str()); + + auto fileBegin = fileSegment.find("\r\n\r\n"); + auto fileEnd = fileSegment.find_last_of("\r\n\r\n") - spliter.size() - 2; + std::string fileContent = fileSegment.substr(fileBegin+4, fileEnd - fileBegin-4); + CHIVE_LOG_DEBUG("filebegin %d fileend %d file content %s", fileBegin, fileEnd, fileContent.c_str()); + + // 将filecontent写入/data/chive/upload/finalFilename2 + std::fstream fout; + fout.open("./"+finalFilename2, std::ios::out|std::ios::binary); + if (!fout.is_open()) + { + CHIVE_LOG_DEBUG("open file err"); + } + else + { + fout << fileContent; + fout.flush(); + fout.close(); + } +} diff --git a/chive/net/http/HttpBody.h b/chive/net/http/HttpBody.h new file mode 100755 index 0000000..6f7344f --- /dev/null +++ b/chive/net/http/HttpBody.h @@ -0,0 +1,92 @@ +#ifndef CHIVE_NET_HTTPBODY_H +#define CHIVE_NET_HTTPBODY_H + +#include "chive/base/noncopyable.h" +#include "chive/base/ContentTypes.h" +#include "chive/base/FileUtil.h" + +///FIXME: how to parse multipart/form-data +/// Method POST, PUT, DELETE support body filed (which body content-type should be supported in priority) +/// Method GET doesn't support body field + +/* + +content-type: multipart/form-data; boundary=----WebKitFormBoundaryzAq7xCCRBiuAI8E8 + +Body content +------WebKitFormBoundary9wcq6lyUzDdPCBZc +Content-Disposition: form-data; name="file"; filename="file_test.txt" +Content-Type: text/plain + +this a test file to chive server. +------WebKitFormBoundary9wcq6lyUzDdPCBZc +Content-Disposition: form-data; name="name" + +stsfang +------WebKitFormBoundary9wcq6lyUzDdPCBZc +Content-Disposition: form-data; name="name2"; filename="blob" +Content-Type: application/json + +hello +------WebKitFormBoundary9wcq6lyUzDdPCBZc-- + +*/ + +#include +#include +#include //std::pair +namespace chive +{ +namespace net +{ + +class HttpBody : noncopyable +{ +public: + + HttpBody(const std::string& body, const std::string& boundary); + + /** + * 解析Http body 的内容, 必须在调用其他成员方法前调用,且应该仅调用一次 + */ + void parse(); + //ContentType type(); + //isMultipart(); + + +private: + enum class ContentDisposition + { + FormData + }; + // 表示表单数据的Item + struct Segment + { + ContentDisposition disp_; + std::string name_; // name属性 + std::string filename_; // filename, 非文件的value,默认设置为blob? + ContentType type_; // 单个key-value的contentType,一般不用,暂把key-value当做字符串 + std::string key_; + std::string value_; + }; + using DataPair = std::pair; + using FormData = std::vector; + + + ContentType contentType_; /// body content type + uint64_t contentLength_; /// body content length + //== form data + //std::vector fileList_; /// file list from multipart/formdata + FormData formData_; /// form data + //== form data + + const std::string& body_; + const std::string boundary_; /// body 参数分隔边界 + +}; +} // namespace net + +} // namespace chive + + +#endif \ No newline at end of file diff --git a/chive/net/http/HttpContext.cc b/chive/net/http/HttpContext.cc new file mode 100755 index 0000000..b6c08aa --- /dev/null +++ b/chive/net/http/HttpContext.cc @@ -0,0 +1,157 @@ +#include "chive/net/http/HttpContext.h" +#include "chive/base/clog/chiveLog.h" +#include "chive/net/Buffer.h" +#include +#include "chive/net/http/HttpBody.h" + +using namespace chive; +using namespace chive::net; + +HttpContext::HttpContext() + : state_(HttpParseState::kExpectRequestLine) +{ +} + +bool HttpContext::parseRequest(Buffer *buf, Timestamp receiveTime) +{ + bool ok = true; + bool hasMore = true; + while (hasMore) + { + if (state_ == HttpParseState::kExpectRequestLine) + { + const char *crlf = buf->findCRLF(); // crlf的地址 + if (crlf) + { + CHIVE_LOG_DEBUG("buf peek %s", buf->peek()); + ok = processRequestLine(buf->peek(), crlf); // 提取请求行 + if (ok) + { + request_.setReceiveTime(receiveTime); + buf->retrieveUntil(crlf + 2); // 移动buffer的readerIndex到请求行后 + state_ = HttpParseState::kExpectHeader; // 转移到提取首部的状态 + } + else + { + hasMore = false; // 请求行提取失败,终止处理 + } + } + else + { + hasMore = false; // 请求行提取失败,终止处理 + } + } + /// + /// header field : value \r\n + /// + else if (state_ == HttpParseState::kExpectHeader) + { + const char *crlf = buf->findCRLF(); // 查找首部行的crlf结束符 + if (crlf) + { + const char *colon = std::find(buf->peek(), crlf, ':'); // 在 [begin(), crlf]之间找':' + if (colon != crlf) + { + request_.addHeader(buf->peek(), colon, crlf); // 找到一个 ':' 则说明一个首部字段 + } + else + { + ///FIXME: + /// no find colon, just empty line, end of header + state_ = HttpParseState::kExpectBody; + // hasMore = false; + } + buf->retrieveUntil(crlf + 2); + } + else + { + hasMore = false; + } + } + else if (state_ == HttpParseState::kExpectBody) + { + bool ret = parseRequestBody(buf); + if (!ret) + { + ///FIXME: handle the error + } + state_ = HttpParseState::kGotAll; + hasMore = false; + } + } // end while + return ok; +} + +bool HttpContext::parseRequestBody(Buffer *buf) +{ + std::string b = buf->retrieveAllAsString(); + std::string contentType = request_.getHeader("content-type"); + CHIVE_LOG_DEBUG("Header content type %s", contentType.c_str()); + std::string target("boundary="); + auto pos = contentType.find(target); + pos += target.size(); + CHIVE_LOG_DEBUG("body content type %s", contentType.substr(pos).c_str()); + HttpBody body(b, contentType.substr(pos)); + body.parse(); + if (!b.empty()) + { + request_.setBody(b); + } + else + { + CHIVE_LOG_WARN("no http body"); + } +} + +void HttpContext::reset() +{ + state_ = HttpParseState::kExpectRequestLine; + HttpRequest dummy; + request_.swap(dummy); +} + +/// +/// Http Method | space | URL | space | proto version | \r\n +/// +bool HttpContext::processRequestLine(const char *begin, const char *end) +{ + bool succeed = false; + const char *start = begin; + const char *space = std::find(start, end, ' '); // method + if (space != end && request_.setMethod(start, space)) + { + start = space + 1; + space = std::find(start, end, ' '); // ur + if (space != end) + { + const char *questionMark = std::find(start, space, '?'); + if (questionMark != space) + { + request_.setPath(start, questionMark); // path + request_.setQuery(questionMark, space); // query string + } + else + { + request_.setPath(start, space); // only path + } + + start = space + 1; + + ///FIXME: + ///增加对http2.0的支持 + succeed = end - start == 8 && std::equal(start, end - 1, "HTTP/1."); + if (succeed) + { + if (*(end - 1) == '1') + { + request_.setVersion(HttpRequest::HttpVersion::kHttp20); + } + else + { + succeed = false; + } + } + } + return succeed; + } +} \ No newline at end of file diff --git a/chive/net/http/HttpContext.h b/chive/net/http/HttpContext.h new file mode 100755 index 0000000..dd143bb --- /dev/null +++ b/chive/net/http/HttpContext.h @@ -0,0 +1,53 @@ +#ifndef CHIVE_NET_HTTP_CONTEXT_H +#define CHIVE_NET_HTTP_CONTEXT_H + +#include "chive/base/copyable.h" +#include "chive/net/http/HttpRequest.h" + + + +namespace chive +{ +namespace net +{ +class Buffer; + +class HttpContext : copyable +{ +public: + using Timestamp = uint64_t; + + enum class HttpParseState + { + kExpectRequestLine, /// 解析请求行 + kExpectHeader, /// 解析请求头部 + kExpectBody, /// 解析请求体 + kGotAll /// 完成解析 + }; + + HttpContext(); + bool parseRequest(Buffer* buf, Timestamp receiveTime); + bool gotAll() const + { return state_ == HttpParseState::kGotAll; } + + void reset(); + + const HttpRequest& request() const + { return request_; } + + HttpRequest& request() + { return request_; } + +private: + bool processRequestLine(const char* begin, const char* end); + bool parseRequestBody(Buffer* buf); + HttpParseState state_; + HttpRequest request_; +}; + +} // namespace net + +} // namespace chive + + +#endif \ No newline at end of file diff --git a/chive/net/http/HttpRequest.cc b/chive/net/http/HttpRequest.cc new file mode 100755 index 0000000..21068d3 --- /dev/null +++ b/chive/net/http/HttpRequest.cc @@ -0,0 +1,110 @@ +#include "chive/net/http/HttpRequest.h" +#include "chive/base/clog/chiveLog.h" + +#include +#include +#include // isspace() +#include +#include + +using namespace chive::net; + +HttpRequest::HttpRequest() + : method_ (HttpMethod::INVALID), + version_ (HttpVersion::kUnknown) +{ +} + +bool HttpRequest::setMethod(const char* start, const char* end) +{ + assert(method_ == HttpMethod::INVALID); + + std::string _m(start, end); + /// FIXME: + /// 检查_m是否完整的http type + if ("GET" == _m) { + method_ = HttpMethod::GET; + } else if ("POST" == _m) { + method_ = HttpMethod::POST; + } else if ("PUT" == _m) { + method_ = HttpMethod::PUT; + } else if ("HEAD" == _m) { + method_ = HttpMethod::HEAD; + } else if ("DELETE" == _m) { + method_ = HttpMethod::DELETE; + } else { + method_ = HttpMethod::INVALID; + CHIVE_LOG_ERROR("No such method type!"); + } +} + +const char* HttpRequest::methodToString() const +{ + switch (method_) + { + case HttpMethod::GET: + return "GET"; + case HttpMethod::POST: + return "POST"; + case HttpMethod::PUT: + return "PUT"; + case HttpMethod::HEAD: + return "HEAD"; + case HttpMethod::DELETE: + return "DELETE"; + default: + CHIVE_LOG_ERROR("No such method type!"); + break; + } +} + +/** + * #include +C 库函数 int isspace(int c) 检查所传的字符是否是空白字符。 +' ' (0x20) space (SPC) 空格符 +'\t' (0x09) horizontal tab (TAB) 水平制表符 +'\n' (0x0a) newline (LF) 换行符 +'\v' (0x0b) vertical tab (VT) 垂直制表符 +'\f' (0x0c) feed (FF) 换页符 +'\r' (0x0d) carriage return (CR) 回车符 +*/ +void HttpRequest::addHeader(const char* start, const char* colon, const char* end) +{ + std::string field(start, colon); + ++colon; + while(colon < end && isspace(*colon)) + { + ++colon; + } + std::string value(colon, end); + // 去掉value尾部的空格 + while(!value.empty() && isspace(value[value.size()-1])) + { + value.resize(value.size()-1); + } + headers_[field] = value; +} + +std::string HttpRequest::getHeader(const std::string field) const +{ + std::string value; + HttpHeader::const_iterator it = headers_.find(field); + if (it != headers_.end()) + { + value = it->second; + } + return value; +} + +void HttpRequest::swap(HttpRequest& that) +{ + std::swap(method_, that.method_); + std::swap(version_, that.version_); + path_.swap(that.path_); + query_.swap(that.query_); + std::swap(receiveTime_, that.receiveTime_); + headers_.swap(that.headers_); +} + + + diff --git a/chive/net/http/HttpRequest.h b/chive/net/http/HttpRequest.h new file mode 100755 index 0000000..07032cb --- /dev/null +++ b/chive/net/http/HttpRequest.h @@ -0,0 +1,101 @@ +#ifndef CHIVE_NET_HTTP_REQUEST_H +#define CHIVE_NET_HTTP_REQUEST_H + +#include "chive/base/copyable.h" +#include +#include + +/// ref: https://www.cnblogs.com/MandyCheng/p/11151803.html +/// http报文格式:https://www.cnblogs.com/kageome/p/10859996.html + + +namespace chive +{ +namespace net +{ +class HttpRequest : copyable +{ +public: + using Timestamp = uint64_t; + using HttpHeader = std::map; + using HttpBody = std::string; + + enum class HttpMethod + { + INVALID, GET, POST, HEAD, PUT, DELETE + }; + + enum class HttpVersion + { + kUnknown, kHttp10, kHttp11, kHttp20 + }; + + HttpRequest(); + void setVersion(HttpVersion v) + { version_ = v; } + + HttpVersion version() const + { return version_; } + + /** + * 设置HTTP Method + * @param start 字符串开始的地址 + * @param end 字符串结束的地址 + * @return true / false + */ + bool setMethod(const char* start, const char* end); + + HttpMethod method() const + { return method_; } + + const char* methodToString() const; + + void setPath(const char* start, const char* end) + { path_.assign(start, end); } + + const std::string path() const + { return path_; } + + void setQuery(const char* start, const char* end) + { query_.assign(start, end); } + + const std::string query() const + { return query_; } + + void setReceiveTime(Timestamp t) + { receiveTime_ = t; } + + Timestamp receiveTime() const + { return receiveTime_; } + + void addHeader(const char* start, const char* colon, const char* end); + + std::string getHeader(const std::string field) const; + + const HttpHeader& getHeaders() const + { return headers_; } + + void setBody(const std::string& body) + { body_ = std::move(body); } + + const std::string getBody() const + { return body_; } + + void swap(HttpRequest& that); + + +private: + HttpVersion version_; + HttpMethod method_; + std::string path_; + std::string query_; + Timestamp receiveTime_; + HttpHeader headers_; + HttpBody body_; + +}; + +} // namespace net +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/http/HttpResponse.cc b/chive/net/http/HttpResponse.cc new file mode 100644 index 0000000..cbf61da --- /dev/null +++ b/chive/net/http/HttpResponse.cc @@ -0,0 +1,41 @@ +#include "chive/net/http/HttpResponse.h" +#include "chive/net/Buffer.h" + +#include + +using namespace chive; +using namespace chive::net; + +void HttpResponse::appendToBuffer(Buffer* output) const +{ + char buf[32]; + snprintf(buf, sizeof(buf), "HTTP/1.1 %d", statusCode_); + output->append(buf); + output->append(statusMessage_); + output->append("\r\n"); + + /// long-connection or shor-connection + if (closeConnection_) + { + output->append("Connection: close\r\n"); + } + else + { + snprintf(buf, sizeof(buf), "Content-Length: %zd\r\n", body_.size()); + output->append(buf); + output->append("Connection: Keep-Alive\r\n"); + } + + /// add request header to response + for (const auto& header : headers_) + { + output->append(header.first); + output->append(": "); + output->append(header.second); + output->append("\r\n"); + } + + output->append("\r\n"); + output->append(body_); + +} \ No newline at end of file diff --git a/chive/net/http/HttpResponse.h b/chive/net/http/HttpResponse.h new file mode 100644 index 0000000..67c25ee --- /dev/null +++ b/chive/net/http/HttpResponse.h @@ -0,0 +1,70 @@ +#ifndef CHIVE_NET_HTTP_RESPONSE_H +#define CHIVE_NET_HTTP_RESPONSE_H + +#include "chive/base/copyable.h" +#include +#include + + +namespace chive +{ +namespace net +{ + +class Buffer; +class HttpResponse : public copyable +{ +public: + using HttpHeader = std::map; + + enum class HttpStatusCode + { + kUnknown, + k200OK = 200, + k301MovedPermanently = 301, + k400BadRequest = 400, + k404NotFound = 404, + }; + + explicit HttpResponse(bool close) + : statusCode_(HttpStatusCode::kUnknown), + closeConnection_(close) + {} + + void setStatusCode(HttpStatusCode code) + { statusCode_ = code; } + + void setStatusMessage(const std::string& message) + { statusMessage_ = message; } + + void setCloseConnection(bool on) + { closeConnection_ = on; } + + bool closeConnection() const + { return closeConnection_; } + + void setContentType(const std::string& contentType) + { addHeader("Content-Type", contentType); } + + void addHeader(const std::string& key, const std::string& value) + { headers_[key] = value; } + + void setBody(const std::string& body) + { body_ = body;} + + void appendToBuffer(Buffer* output) const; + +private: + HttpHeader headers_; + HttpStatusCode statusCode_; + /// FIXME: add http version + std::string statusMessage_; + bool closeConnection_; + std::string body_; +}; + +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/http/HttpServer.cc b/chive/net/http/HttpServer.cc new file mode 100644 index 0000000..21f3b11 --- /dev/null +++ b/chive/net/http/HttpServer.cc @@ -0,0 +1,93 @@ +#include "chive/net/http/HttpServer.h" +#include "chive/net/http/HttpRequest.h" +#include "chive/net/http/HttpResponse.h" +#include "chive/net/http/HttpContext.h" +#include "chive/base/clog/chiveLog.h" + +#include + +using namespace chive; +using namespace chive::net; +using namespace std::placeholders; + +namespace chive +{ +namespace net +{ +namespace cb +{ +void dfaultHttpCallback(const HttpRequest&, HttpResponse* resp) +{ + resp->setStatusCode(HttpResponse::HttpStatusCode::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); +} + +} // namespace cb +} // namespace net +} // namespace chive + +HttpServer::HttpServer(EventLoop* loop, + const InetAddress& listenAddr, + const std::string& name, + bool reuseport) + : server_(loop, listenAddr, name, reuseport), + httpCallback_(cb::dfaultHttpCallback) +{ + server_.setConnectionCallback( + std::bind(&HttpServer::onConnection, this, _1)); + server_.setMessageCallback( + std::bind(&HttpServer::onMessage, this, _1, _2, _3)); +} + +void HttpServer::start() +{ + CHIVE_LOG_WARN("HttpServer [ %s ] starts listening on %s", server_.name().c_str(), server_.ipPort().c_str()); + server_.start(); +} + +void HttpServer::onConnection(const TcpConnectionPtr& conn) +{ + if (conn->isConnected()) + { + conn->setContext(HttpContext()); + } +} + +void HttpServer::onMessage(const TcpConnectionPtr& conn, + Buffer* buf, + Timestamp receiveTime) +{ + CHIVE_LOG_DEBUG("got buffer %s", buf->toString().c_str()); + HttpContext* context = std::any_cast(conn->getMutableContext()); + + if (!context->parseRequest(buf, receiveTime)) + { + conn->send("HTTP/1.1 400 Bad Request\r\n\r\n"); + conn->shutdown(); + } + + if (context->gotAll()) + { + onRequest(conn, context->request()); + context->reset(); + } +} + +void HttpServer::onRequest(const TcpConnectionPtr& conn, const HttpRequest& req) +{ + const std::string& connection = req.getHeader("Connection"); + bool close = connection == "close" || + (req.version() == HttpRequest::HttpVersion::kHttp10 && connection != "Keep-Alive"); + HttpResponse resp(close); + httpCallback_(req, &resp); // 处理request,填充response + Buffer buf; + resp.appendToBuffer(&buf); // 将response内容填充到buffer,发送给client + conn->send(&buf); + + if (resp.closeConnection()) // 短链接,写完则关闭连接 + { + conn->shutdown(); + } + +} \ No newline at end of file diff --git a/chive/net/http/HttpServer.h b/chive/net/http/HttpServer.h new file mode 100644 index 0000000..af23f55 --- /dev/null +++ b/chive/net/http/HttpServer.h @@ -0,0 +1,51 @@ +#ifndef CHIVE_NET_HTTP_HTTPSERVER_H +#define CHIVE_NET_HTTP_HTTPSERVER_H + +#include "chive/net/TcpServer.h" +#include "chive/base/noncopyable.h" +#include +#include + +namespace chive +{ +namespace net +{ + +class HttpRequest; +class HttpResponse; + +class HttpServer : noncopyable +{ +public: + using HttpCallback = std::function; + + HttpServer(EventLoop* loop, + const InetAddress& listenAddr, + const std::string& name, + bool reuseport = false); + + EventLoop* getLoop() const + { return server_.getLoop(); } + + void setHttpCallback(const HttpCallback& cb) + { httpCallback_ = cb; } + + void setThreadNum(int numThreads) + { server_.setThreadNum(numThreads); } + + void start(); + +private: + void onConnection(const TcpConnectionPtr& conn); + void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime); + void onRequest(const TcpConnectionPtr&, const HttpRequest&); + + TcpServer server_; + HttpCallback httpCallback_; +}; + +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/chive/net/http/codec/HttpPostRequestDecoder.h b/chive/net/http/codec/HttpPostRequestDecoder.h new file mode 100755 index 0000000..ff0488d --- /dev/null +++ b/chive/net/http/codec/HttpPostRequestDecoder.h @@ -0,0 +1,122 @@ +#ifndef CHIVE_NET_CODEC_HTTP_POST_DECODER_H +#define CHIVE_NET_CODEC_HTTP_POST_DECODER_H + +#include "chive/net/http/HttpRequest.h" + +#include +#include + + + /** + * states follow NOTSTARTED PREAMBLE ( (HEADERDELIMITER DISPOSITION (FIELD | + * FILEUPLOAD))* (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE (MIXEDDELIMITER + * MIXEDDISPOSITION MIXEDFILEUPLOAD)+ MIXEDCLOSEDELIMITER)* CLOSEDELIMITER)+ + * EPILOGUE + * + * First getStatus is: NOSTARTED + * + * Content-type: multipart/form-data, boundary=AaB03x => PREAMBLE in Header + * --AaB03x => HEADERDELIMITER + * content-disposition: form-data; name="field1" => DISPOSITION + * Joe Blow => FIELD + * --AaB03x => HEADERDELIMITER + * content-disposition:form-data; name="pics" => DISPOSITION + * Content-type: multipart/mixed,boundary=BbC04y + * --BbC04y => MIXEDDELIMITER + * Content-disposition: attachment; filename="file1.txt" => MIXEDDISPOSITION + * Content-Type: text/plain + * ... contents of file1.txt ... => MIXEDFILEUPLOAD --BbC04y => + * MIXEDDELIMITER Content-disposition: file; filename="file2.gif" => + * MIXEDDISPOSITION Content-type: image/gif Content-Transfer-Encoding: + * binary + * + * ...contents of file2.gif... => MIXEDFILEUPLOAD --BbC04y-- => + * MIXEDCLOSEDELIMITER --AaB03x-- => CLOSEDELIMITER + * + * Once CLOSEDELIMITER is found, last getStatus is EPILOGUE + */ + +class HttpPostRequestDecoder { +public: + static bool isMultipart(const HttpRequest& request) { + std::string mimeType = request.getHeader("content-type"); + if (mimeType != nullptr && mimeType.startsWith("multipart/form-data")) { + return getMultipartDataBoundary(mimeType) != nullptr; + } + return false; + } + + static std::vector + getMultipartDataBoundary(const std::string& contentType) { + // Check if Post using "multipart/form-data; boundary=--89421926422648 [; charset=xxx]" + std::vector headerContentType = splitHeaderContentType(contentType); + std::string multipartHeader = "multipart/form-data"; + if(headerContentType[0].compare(multipartHeader) == 0) { + ///FIXME: + ///find out boundary value + } + return headerContentType; + } + + static std::vector + splitHeaderContentType(const std::string& contentType) { + //"multipart/form-data; boundary=--89421926422648 [; charset=xxx]" + // like: "multipart/form-data; boundary=--89421926422648; charset=utf8" + int aStart, aEnd; + int bStart, bEnd; + int cStart, cEnd; + + aStart = findNonWhiteSpace(contentType, 0); + aEnd = contentType.find_first_of(';'); + /// static const size_type npos = -1 + if(aEnd == -1) { + return { contentType, "", ""}; + } + bStart = findNonWhiteSpace(contentType, aEnd + 1); + if (contentType[aEnd - 1] == ' ') { + aEnd--; + } + bEnd = contentType.find_first_of(';', bStart); + if (bEnd == -1) { + bEnd = findEndOfString(contentType); + return { + contentType.substr(aStart, aEnd-aStart), + contentType.substr(bStart, bEnd-bStart), + "" + }; + } + cStart = findNonWhiteSpace(contentType, bEnd+1); + if (contentType[bEnd-1] ==' ') { + bEnd--; + } + cEnd = findEndOfString(contentType); + return { + contentType.substr(aStart, aEnd-aStart), + contentType.substr(bStart, bEnd-bStart), + contentType.substr(cStart, cEnd-cStart) + }; + } + + static int findNonWhiteSpace(const std::string& s, int start) { + int result = -1; + for (result = start; result < s.length(); ++result) { + if (s[result] != ' ') { + break; + } + } + return result; + } + + static int findEndOfString(const std::string& s) { + int result; + for (result = s.length(); result > 0; --result) { + if (s[result - 1] != ' ') { + break; + } + } + return result; + } + +}; + +#endif \ No newline at end of file diff --git a/chive/net/poller.cc b/chive/net/poller.cc deleted file mode 100755 index 3b30c46..0000000 --- a/chive/net/poller.cc +++ /dev/null @@ -1,73 +0,0 @@ -#include "chive/net/poller.h" - -using namespace chive; -using namespace chive::net; - - -Poller::Poller(EventLoop* loop):ownerLoop_(loop) {} - -Poller::~Poller(){} - -int Poller::poll(int timeoutMs, ChannelList* activeChannels) -{ - int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs); - //FIXME: record timestamp - if(numEvents > 0) { - std::cout << "fill events" << std::endl; - fillActiveChannels(numEvents, activeChannels); - } else if(numEvents == 0) { - - } else { - - } - //FIXME: should return timestamp - return 0; -} - -void Poller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const -{ - using poller_iter = PollFdList::const_iterator; - for(auto pfd = pollfds_.begin(); pfd != pollfds_.end() && numEvents > 0; ++pfd) - { - // 将监听到的fd及其上的events封装到Channel - // 填充到activeChannels返回给调用者 - if(pfd->revents > 0) - { - --numEvents; - ChannelMap::const_iterator ch = channels_.find(pfd->fd); - Channel* channel = ch->second; - channel->setRevents(pfd->revents); - activeChannels->push_back(channel); - } - } -} - -void Poller::updateChannel(Channel* channel) -{ - std::cout << "fd = " << channel->getFd() << " events = " << channel->getEvents() << std::endl; - if(channel->getIndex() < 0) // a new one, add to pollfds_ - { - struct pollfd pfd; - pfd.fd = channel->getFd(); - pfd.events = static_cast(channel->getEvents()); - pfd.revents = 0; - std::cout <<"poll fds size "; - std::cout << pollfds_.size() << std::endl; - pollfds_.push_back(pfd); - int idx = static_cast(pollfds_.size()) - 1; - channel->setIndex(idx); - channels_[pfd.fd] = channel; - } - else // update a existing one - { - assert(channels_.find(channel->getFd()) != channels_.end()); - assert(channels_[channel->getFd()] == channel); - int idx = channel->getIndex(); - assert(0 <= idx && idx < static_cast(pollfds_.size())); - struct pollfd& pfd = pollfds_[idx]; - assert(pfd.fd == channel->getFd() || pfd.fd == -1); - pfd.events = static_cast(channel->getEvents()); - pfd.revents = 0; - if(channel->isNoneEvent()) { pfd.fd = -1; } - } -} \ No newline at end of file diff --git a/chive/net/poller.h b/chive/net/poller.h deleted file mode 100755 index 97bd820..0000000 --- a/chive/net/poller.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef CHIVE_NET_POLLER_H -#define CHIVE_NET_POLLER_H - -#include "chive/base/noncopyable.h" -#include "chive/net/Channel.h" -#include "chive/net/EventLoop.h" - -#include -#include -#include -#include -#include - -namespace chive -{ -namespace net -{ -class Poller : chive::noncopyable -{ -public: - using ChannelList = std::vector ; - Poller(EventLoop* evloop); - ~Poller(); - - int poll(int timeoutMs, ChannelList* activeChannels); - void updateChannel(Channel* channel) __attribute__ ((optimize(0))); - -private: - using ChannelMap = std::map; - using PollFdList = std::vector; - - void fillActiveChannels(int numEvents, ChannelList* activeChannels) const; - PollFdList pollfds_; - ChannelMap channels_; - EventLoop* ownerLoop_; -}; -} // namespace net - -} // namespace chive - -#endif \ No newline at end of file diff --git a/chive/net/poller/EPollPoller.cc b/chive/net/poller/EPollPoller.cc new file mode 100755 index 0000000..16c6590 --- /dev/null +++ b/chive/net/poller/EPollPoller.cc @@ -0,0 +1,210 @@ +#include "chive/net/poller/EPollPoller.h" +#include "chive/base/clog/chiveLog.h" + +#include +#include +#include // errno +#include //strerror() +/* +epoll_create(int size) // linux 独有 +epoll_create1(int flags) // >= kernel version >= 2.6.27, glibc version >= 2.9 +如不指定flags, epoll_create1 和 epoll_create 没区别 +*/ + + +using namespace chive::net; + +namespace +{ +const int kNew = -1; +const int kAdded = 1; +const int kDeleted = 2; +} + +EPollPoller::EPollPoller(EventLoop* loop) + : Poller(loop), /*基类poller构造函数*/ + epollfd_(::epoll_create1(EPOLL_CLOEXEC)), + events_(kInitEventListSize) +{ + if(epollfd_ < 0) + { + CHIVE_LOG_ERROR("create epollfd failed!, epollfd_ %d", epollfd_); + } + CHIVE_LOG_DEBUG("created epoller %p with epollfd %d", this, epollfd_); +} + +EPollPoller::~EPollPoller() +{ + CHIVE_LOG_DEBUG("epoller closed.") + ::close(epollfd_); //关闭内核事件表 +} + +Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels) +{ + CHIVE_LOG_DEBUG("epoller %p begins polling...", this); + CHIVE_LOG_DEBUG("epoller %p now registerd channels total number %d", this, channels_.size()); + // 开始监听epollfd + int numEvents = ::epoll_wait(epollfd_, + /*&*events_.begin(),*/ + events_.data(), + static_cast(events_.size()), + timeoutMs); + int savedErrno = errno; //errno是全局共享的,所以需要本地保存 + Timer::Timestamp now = Timer::now(); //当前时间戳 + if (numEvents > 0) + { + // 填充活跃的channel + CHIVE_LOG_INFO("epoller %p get active event number %d", this, numEvents); + fillActiveChannels(numEvents, activeChannels); + + // 如果注册的event达到了events_的容量,需要给events_扩容 + // 扩容的策略是每次double + + // events_.size() 返回类型是 size_type + if (static_cast(numEvents) == events_.size()) + { + events_.resize(events_.size() * 2); + } + } + else if (numEvents == 0) + { + CHIVE_LOG_DEBUG("epoller %p didn't get any active channel", this); + } + else + { + CHIVE_LOG_ERROR("epoller %p get some err happened, erron %s", this, strerror(errno)); + errno = savedErrno; /// 将本地的errno写回全局errno + /// FIXME: 是否增加errno的handler + } + return now; +} + +void EPollPoller::updateChannel(Channel* channel) +{ + Poller::assertInLoopThread(); // 调用基类方法 + const int index = channel->getIndex(); // channel.index_ 初始 -1 + CHIVE_LOG_DEBUG("fd = %d events = %d status = %d", + channel->getFd(), channel->getEvents(), index); + + if(index == kNew || index == kDeleted) + { + int fd = channel->getFd(); + if (index == kNew) + { + assert(channels_.find(fd) == channels_.end()); + channels_[fd] = channel; + } + else + { + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); /// ? channel移除了但没有从map移除 + } + channel->setIndex(kAdded); + update(EPOLL_CTL_ADD, channel); + } + else + { + int fd = channel->getFd(); (void) fd; + ///FIXME: only enable in DEBUG mode + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); + assert(index == kAdded); + if (channel->isNoneEvent()) // + { + update(EPOLL_CTL_DEL, channel); + channel->setIndex(kDeleted); + } + else + { + update(EPOLL_CTL_MOD, channel); + } + } +} + +void EPollPoller::removeChannel(Channel* channel) +{ + Poller::assertInLoopThread(); + int fd = channel->getFd(); + CHIVE_LOG_DEBUG("poller %p remove channel %p with socket fd = %d", + this, channel, fd); + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); + assert(channel->isNoneEvent()); + + int index = channel->getIndex(); + assert(index == kAdded || index == kDeleted); + size_t n = channels_.erase(fd); + assert(n == 1); (void)n; + + if (index == kAdded) + { + update(EPOLL_CTL_DEL, channel); + } + channel->setIndex(kNew); +} + +/// private + +void EPollPoller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const +{ + CHIVE_LOG_DEBUG("numEvents %d events_size %d", numEvents, events_.size()); + assert(static_cast(numEvents) <= events_.size()); + for (int i = 0; i < numEvents; ++i) + { + // 在EPollPoller::update() 中, 使用 epoll_event.data.ptr保存channel指针 + // 所以这里可以从中取出channel + auto *channel = static_cast(events_[i].data.ptr); +#ifndef NDEBUG + int fd = channel->getFd(); + auto it = channels_.find(fd); + assert(it != channels_.end()); + assert(it->second == channel); +#endif + channel->setRevents(events_[i].events); /// 设置channel上到来的事件 + activeChannels->push_back(channel); /// 填充到activeChannels + } +} + +void EPollPoller::update(int operation, Channel* channel) +{ + epoll_event event{}; + // struct epoll_event { + // unint32_t events; + // poll_data_t data; + // }; + + // struct union epoll_data { + // void* ptr; + // int fd; + // uint32_t u32; + // unit64_t u64; + // } epoll_data_t; + + event.events = static_cast(channel->getEvents()); + event.data.ptr = channel; + int fd = channel->getFd(); + // 向内核事件表epollfd_ 上的fd执行operation操作 + // EPOLL_CTL_ADD /EPOLL_CTL_MOD/ EPOLL_CTL_DEL + if (epoll_ctl(epollfd_, operation, fd, &event) < 0) + { + CHIVE_LOG_ERROR("epoll_ctl op = %s fd = %d", + operationToString(operation), fd) + } +} + +/// static members +std::string EPollPoller::operationToString(int op) +{ + switch (op) + { + case EPOLL_CTL_ADD: + return "epoll add"; + case EPOLL_CTL_MOD: + return "epoll modify"; + case EPOLL_CTL_DEL: + return "epoll delete"; + default: + assert(false); // 非法操作,便于调试阶段暴露 + return "Unknown Operation"; + } +} \ No newline at end of file diff --git a/chive/net/poller/EPollPoller.h b/chive/net/poller/EPollPoller.h new file mode 100755 index 0000000..e3bf945 --- /dev/null +++ b/chive/net/poller/EPollPoller.h @@ -0,0 +1,74 @@ +#ifndef CHIVE_NET_POLLER_EPOLLPOLLER_H +#define CHICE_NET_POLLER_EPOLLPOLLER_H + +#include "chive/net/Poller.h" // include "EventLoop.h" +#include +#include + +// 前置声明,在源文件引入 +struct epoll_event; + +namespace chive +{ +namespace net +{ + +/// IO多路复用 (epoll) +class EPollPoller : public Poller +{ +public: + EPollPoller(EventLoop* loop); + ~EPollPoller() override; //重载 + + /** + * epoll开始监听 + * @param timeoutMs 监听的超时时间 + * @param activeChannels 获取激活的channels + * @return poll监听返回的时间戳 + */ + Timestamp poll(int timeoutMs, ChannelList* activeChannels) override; + + /** + * 更新channel上的信息到epoll + * @param channel 待更新的channel + */ + void updateChannel(Channel* channel) override; + + /** + * 从epoll上移除channel + * @param channel 待移除的channel + */ + void removeChannel(Channel* channel) override; + +private: + using EventList = std::vector; + + /** + * 填充活跃的channel + * @param numEvents active状态的事件数 + * @param activeChannels 获取active的channel + */ + void fillActiveChannels(int numEvents, ChannelList* activeChannels) const; + + /** + * 在Channel的fd上执行operation操作 (移除|更新|添加 fd事件等) + * @param operation 操作类型 + * @param channel 被操作的对象 + */ + void update(int operation, Channel* channel); + + int epollfd_; // 内核事件表 fd + EventList events_; // 内核事件表上的事件列表 + + static const int kInitEventListSize = 16; // + + /** + * 将操作op操作名转换为字符串 + */ + static std::string operationToString(int op); +}; +} // namespace net + +} // namespace chive + +#endif \ No newline at end of file diff --git a/test/acceptor/BUILD b/test/acceptor/BUILD new file mode 100755 index 0000000..4343733 --- /dev/null +++ b/test/acceptor/BUILD @@ -0,0 +1,16 @@ +cc_binary( + name = 'test01', + srcs = [ + 'test01.cc' + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '#pthread' + ], + extra_cppflags = [ + '-std=c++14', +# '-Wall', + '-g', + ] +) diff --git a/test/acceptor/test01.cc b/test/acceptor/test01.cc new file mode 100755 index 0000000..35487a4 --- /dev/null +++ b/test/acceptor/test01.cc @@ -0,0 +1,53 @@ +#include "chive/net/InetAddress.h" +#include "chive/net/Acceptor.h" +#include "chive/net/EventLoop.h" + +#include "chive/base/clog/chiveLog.h" + +#include + +#include + +using namespace chive; +using namespace chive::net; + + +void newConnection1(int sockfd, const InetAddress& peerAddr) +{ + CHIVE_LOG_INFO("newConnection accepted a new connection from %s", + peerAddr.toIpPort().c_str()); + const char* msg = "How are you?\n"; + ::write(sockfd, msg, sizeof(msg)); + ::close(sockfd); +} + +void newConnection2(int sockfd, const InetAddress& peerAddr) +{ + CHIVE_LOG_INFO("newConnection accepted a new connection from %s", + peerAddr.toIpPort().c_str()); + const char* msg = "Are you ok?\n"; + ::write(sockfd, msg, sizeof(msg)); + ::close(sockfd); +} + +int main() +{ + CHIVE_LOG_INFO("main() pid = %d", getpid()); + startLogPrint(NULL); // + CHIVE_LOG_INFO("BEGIN"); + + // port 1 + InetAddress listenAddr1(9909); + // port 2 + InetAddress listenAddr2(9908); + + EventLoop loop; + + Acceptor acceptor1(&loop, listenAddr1); + Acceptor acceptor2(&loop, listenAddr2); + acceptor1.setNewConnectionCallback(newConnection1); + acceptor2.setNewConnectionCallback(newConnection2); + acceptor1.listen(); + acceptor2.listen(); + loop.loop(); +} \ No newline at end of file diff --git a/test/epoll/test01.cc b/test/epoll/test01.cc new file mode 100755 index 0000000..e69de29 diff --git a/test/eventloop/BUILD b/test/eventloop/BUILD index bfc64ed..9125312 100755 --- a/test/eventloop/BUILD +++ b/test/eventloop/BUILD @@ -4,7 +4,8 @@ cc_binary( 'looper.cc', ], deps = [ - '//chive/net/:eventloop', + '//chive/net/:chive_net', + '//chive/base/:chive_base', '#pthread' ], ) \ No newline at end of file diff --git a/test/evtloopthreadpool/BUILD b/test/evtloopthreadpool/BUILD new file mode 100755 index 0000000..3422775 --- /dev/null +++ b/test/evtloopthreadpool/BUILD @@ -0,0 +1,17 @@ +cc_binary( + name = 'test01', + srcs = [ + 'test01.cc', + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '#pthread', + ], + extra_cppflags = [ + '-std=c++14', + #'-Wall', # show warning as error + '-w', # no warning + '-g', + ] +) \ No newline at end of file diff --git a/test/evtloopthreadpool/test01.cc b/test/evtloopthreadpool/test01.cc new file mode 100755 index 0000000..a04363d --- /dev/null +++ b/test/evtloopthreadpool/test01.cc @@ -0,0 +1,33 @@ +#include "chive/net/EventLoop.h" +#include "chive/net/EventLoopThread.h" +#include "chive/base/clog/chiveLog.h" + + +#include +// #define NDEBUG +#include + +void runInThread() +{ + CHIVE_LOG_DEBUG("run in thread...") +} + +int main() +{ + startLogPrint(nullptr); + CHIVE_LOG_DEBUG("main() begin..."); + + chive::net::EventLoopThread loopThread; + chive::net::EventLoop* loop = loopThread.startLoop(); + CHIVE_LOG_DEBUG("main() run after..."); + + loop->runInLoop(runInThread); + loop->runAfter(2000, runInThread); + // sleep(1); + + sleep(6); + loop->quit(); + + CHIVE_LOG_DEBUG("main() exit main..."); + +} \ No newline at end of file diff --git a/test/httpserver/BUILD b/test/httpserver/BUILD new file mode 100644 index 0000000..0ac932c --- /dev/null +++ b/test/httpserver/BUILD @@ -0,0 +1,19 @@ +cc_binary( + name = 'httpserver', + srcs = [ + 'testhttpserver.cc' + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '//chive/net/http/:chive_http', + '#pthread' + ], + extra_cppflags = [ + '-std=c++17', +# '-Wall', + '-w', + '-g', + '-O0' + ] +) diff --git a/test/httpserver/testhttpserver.cc b/test/httpserver/testhttpserver.cc new file mode 100644 index 0000000..2714b0f --- /dev/null +++ b/test/httpserver/testhttpserver.cc @@ -0,0 +1,67 @@ +#include "chive/net/http/HttpServer.h" +#include "chive/net/http/HttpRequest.h" +#include "chive/net/http/HttpResponse.h" +#include "chive/net/EventLoop.h" +#include "chive/base/clog/chiveLog.h" + +#include +#include + +using namespace chive; +using namespace chive::net; + +// extern +bool benchmark = false; + +void onRequest(const HttpRequest& req, HttpResponse* resp) +{ + CHIVE_LOG_DEBUG("method %s path %s query %s", req.methodToString(), req.path().c_str(), req.query().c_str()); + CHIVE_LOG_DEBUG("Headers %s %s", req.methodToString(), req.path().c_str()); + if (!benchmark) + { + const std::map& headers = req.getHeaders(); + for (const auto& header : headers) + { + CHIVE_LOG_DEBUG("%s: %s", header.first.c_str(), header.second.c_str()); + } + + CHIVE_LOG_DEBUG("Body content %s", req.getBody().c_str()); + } + + if (req.path() == "/") + { + resp->setStatusCode(HttpResponse::HttpStatusCode::k200OK); + resp->setStatusMessage("OK"); + resp->addHeader("Server", "Chive"); + std::string now = "2020.10.01 16:18:00"; + resp->setBody("This is title" + "

Hello

Now is " + now + + ""); + } + else if (req.path() == "/hello") + { + resp->setStatusCode(HttpResponse::HttpStatusCode::k200OK); + resp->setStatusMessage("OK"); + resp->setContentType("text/plain"); + resp->addHeader("Server", "Muduo"); + resp->setBody("hello, world!\n"); + } + else + { + resp->setStatusCode(HttpResponse::HttpStatusCode::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); + } +} + +int main() +{ + int numThreads = 0; + startLogPrint(nullptr); + EventLoop loop; + HttpServer server(&loop, InetAddress("127.0.0.1", 8000), "dummy"); + server.setHttpCallback(onRequest); + server.setThreadNum(numThreads); + server.start(); + loop.loop(); +} \ No newline at end of file diff --git a/test/tcpclient/BUILD b/test/tcpclient/BUILD new file mode 100755 index 0000000..053826c --- /dev/null +++ b/test/tcpclient/BUILD @@ -0,0 +1,16 @@ +cc_binary( + name = 'test01', + srcs = [ + 'test01.cc' + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '#pthread' + ], + extra_cppflags = [ + '-std=c++17', +# '-Wall', + '-g', + ] +) diff --git a/test/tcpclient/test01.cc b/test/tcpclient/test01.cc new file mode 100755 index 0000000..a6986ee --- /dev/null +++ b/test/tcpclient/test01.cc @@ -0,0 +1,24 @@ +#include "chive/base/clog/chiveLog.h" +#include "chive/net/EventLoopThread.h" +#include "chive/net/TcpClient.h" + +#include + +using namespace chive; +using namespace chive::net; + +int main(int argc, char* argv[]) +{ + startLogPrint(nullptr); + + EventLoopThread loopThread; + { + InetAddress serverAddress("127.0.0.1", 1234); + TcpClient client(loopThread.startLoop(), serverAddress, "TcpClient"); + client.connect(); + sleep(50); + client.disconnect(); + } + + sleep(100); +} \ No newline at end of file diff --git a/test/tcpconnection/BUILD b/test/tcpconnection/BUILD new file mode 100755 index 0000000..4343733 --- /dev/null +++ b/test/tcpconnection/BUILD @@ -0,0 +1,16 @@ +cc_binary( + name = 'test01', + srcs = [ + 'test01.cc' + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '#pthread' + ], + extra_cppflags = [ + '-std=c++14', +# '-Wall', + '-g', + ] +) diff --git a/test/tcpconnection/test01.cc b/test/tcpconnection/test01.cc new file mode 100755 index 0000000..e80ae7f --- /dev/null +++ b/test/tcpconnection/test01.cc @@ -0,0 +1,45 @@ +#include "chive/net/TcpConnection.h" +#include "chive/net/EventLoop.h" +#include "chive/net/InetAddress.h" +#include "chive/net/TcpServer.h" +#include "chive/base/clog/chiveLog.h" + + +using namespace chive; +using namespace chive::net; + + +void onConnection(const TcpConnectionPtr& conn) +{ + if (conn->isConnected()) + { + CHIVE_LOG_INFO("new connection %s from %s", + conn->name().c_str(), + conn->peerAddress().toIpPort().c_str()); + } + else + { + CHIVE_LOG_DEBUG("connection %s is down", conn->name().c_str()); + } +} + +void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) +{ + CHIVE_LOG_DEBUG("receive %d bytes from connection %s timestamp %ld", + buf->readableBytes(), conn->name().c_str(), receiveTime); + CHIVE_LOG_INFO("receive data %s", buf->retrieveAllAsString().c_str()); +} + +int main() +{ + startLogPrint(NULL); + InetAddress listenAddr(9909); + EventLoop loop; + + TcpServer server(&loop, listenAddr, "chive_tcpserver"); + server.setConnectionCallback(onConnection); + server.setMessageCallback(onMessage); + server.start(); + + loop.loop(); +} \ No newline at end of file diff --git a/test/tcpserver/BUILD b/test/tcpserver/BUILD new file mode 100755 index 0000000..30c6b46 --- /dev/null +++ b/test/tcpserver/BUILD @@ -0,0 +1,16 @@ +cc_binary( + name = 'test01', + srcs = [ + 'echoserver.cc' + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '#pthread' + ], + extra_cppflags = [ + '-std=c++14', +# '-Wall', + '-g', + ] +) diff --git a/test/tcpserver/echoserver.cc b/test/tcpserver/echoserver.cc new file mode 100755 index 0000000..1aad0b0 --- /dev/null +++ b/test/tcpserver/echoserver.cc @@ -0,0 +1,58 @@ +#include "chive/net/TcpServer.h" +#include "chive/net/EventLoop.h" +#include "chive/net/InetAddress.h" +#include "chive/base/clog/chiveLog.h" +#include + +using namespace chive; +using namespace chive::net; + +static std::string msg1 = std::string("huhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh") + + std::string("llllllllllllllllllllllllllllllllllllllllllllllllllll") + + std::string("huuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu"); + +static std::string msg2 = "hello world"; + +void onConnection(const TcpConnectionPtr& conn) { + if (conn->isConnected()) { + CHIVE_LOG_DEBUG("new connection [%s] from %s", + conn->name().c_str(), + conn->peerAddress().toIpPort().c_str()); + CHIVE_LOG_DEBUG("sleep...."); + ::sleep(5); + + // conn->send(msg1); + conn->send(msg2); + conn->shutdown(); + } else { + CHIVE_LOG_WARN("connecion [%s] is down", conn->name().c_str()); + } +} + +void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime) { + CHIVE_LOG_DEBUG("receive %d bytes from connection [%s] at %ld", + buf->readableBytes(), + conn->name().c_str(), + receiveTime); + conn->send(buf->retrieveAllAsString()); +} + +void onWriteComplege(const TcpConnectionPtr& conn) { + conn->send("all sent, bye!"); +} + +int main() { + startLogPrint(NULL); + + CHIVE_LOG_DEBUG("main(): pid = %d", getpid()); + InetAddress listenAddr(9909); + EventLoop loop; + + TcpServer server(&loop, listenAddr, "chive_echoserver"); + server.setConnectionCallback(onConnection); + server.setMessageCallback(onMessage); + server.setWriteCompleteCallback(onWriteComplege); + server.start(); + + loop.loop(); +} \ No newline at end of file diff --git a/test/reactor/BUILD b/test/timerfd/BUILD similarity index 52% rename from test/reactor/BUILD rename to test/timerfd/BUILD index da353e8..da661a6 100755 --- a/test/reactor/BUILD +++ b/test/timerfd/BUILD @@ -5,6 +5,11 @@ cc_binary( ], deps = [ '//chive/net/:chive_net', + '//chive/base/:chive_base', '#pthread' - ], + ], + extra_cppflags = [ + '-std=c++14', +# '-Wall', + ] ) \ No newline at end of file diff --git a/test/reactor/test.cc b/test/timerfd/test.cc similarity index 100% rename from test/reactor/test.cc rename to test/timerfd/test.cc diff --git a/test/timerqueue/BUILD b/test/timerqueue/BUILD new file mode 100755 index 0000000..a6a44f6 --- /dev/null +++ b/test/timerqueue/BUILD @@ -0,0 +1,47 @@ +cc_binary( + name = 'test01', + srcs = [ + 'test03.cc' + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '#pthread' + ], + extra_cppflags = [ + '-std=c++14', +# '-Wall', + ] +) + +cc_binary( + name = 'test02', + srcs = [ + 'test01.cc' + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '#pthread' + ], + extra_cppflags = [ + '-std=c++14', +# '-Wall', + ] +) + +cc_binary( + name = 'test03', + srcs = [ + 'test03.cc' + ], + deps = [ + '//chive/net/:chive_net', + '//chive/base/:chive_base', + '#pthread' + ], + extra_cppflags = [ + '-std=c++14', +# '-Wall', + ] +) diff --git a/test/timerqueue/test01.cc b/test/timerqueue/test01.cc new file mode 100755 index 0000000..140efb9 --- /dev/null +++ b/test/timerqueue/test01.cc @@ -0,0 +1,19 @@ +#include "chive/net/EventLoop.h" +#include + +using namespace chive::net; +using namespace std; + +EventLoop* g_loop; + +// test abortNotInLoopThread +void threadfunc() { + g_loop->loop(); +} + +int main() { + EventLoop loop; + g_loop = &loop; + thread t(threadfunc); + t.join(); +} \ No newline at end of file diff --git a/test/timerqueue/test02.cc b/test/timerqueue/test02.cc new file mode 100755 index 0000000..e69de29 diff --git a/test/timerqueue/test03.cc b/test/timerqueue/test03.cc new file mode 100755 index 0000000..1286e2e --- /dev/null +++ b/test/timerqueue/test03.cc @@ -0,0 +1,52 @@ +// copied from muduo/net/tests/TimerQueue_unittest.cc + +#include "chive/net/EventLoop.h" + +#include +#include +#include +#include + +using namespace chive; +using namespace chive::net; +using namespace chive::base; +int cnt = 0; +EventLoop* g_loop; + +void printTid() +{ + printf("CHIVE pid = %d, tid = %d\n", getpid(), CurrentThread::tid()); + printf("CHIVE now %ld\n", Timer::now()); +} + +void print(const char* msg) +{ + printf("CHIVE msg %ld %s\n", Timer::now(), msg); + std::cout << "CHIVE print callback is thread id " << CurrentThread::tid() << std::endl; + if (++cnt == 20) + { + g_loop->quit(); + } +} + +int main() +{ + printTid(); + EventLoop loop; + g_loop = &loop; + + print("main"); + //测试不同线程提交 + std::thread t1([&]{ + loop.runAfter(2 * 1000 * 1000, std::bind(print, "once1")); + }); t1.join(); + loop.runAfter(1.5 * 1000 * 1000, std::bind(print, "once1.5")); + loop.runAfter(2.5 * 1000 * 1000, std::bind(print, "once2.5")); + loop.runAfter(3.5 * 1000 * 1000, std::bind(print, "once3.5")); + loop.runEvery(2 * 1000 * 1000, std::bind(print, "every2")); + loop.runEvery(3 * 1000 * 1000, std::bind(print, "every3")); + + loop.loop(); + print("main loop exits"); + sleep(1); +}