├── html
├── CGI
│ ├── base64script
│ └── base64
├── img.jpg
├── favicon.ico
└── index.html
├── Log.cpp
├── docs
├── img
│ ├── image-20211026112349.png
│ └── image-20210512133857068.png
└── WebServer-1.md
├── .gitignore
├── makefile
├── MutexLock.h
├── Condition.h
├── Timer.h
├── Timer.cpp
├── Epoll.cpp
├── Log.h
├── Epoll.h
├── ThreadPool.h
├── README.md
├── Utils.h
├── ThreadPool.cpp
├── Utils.cpp
├── main.cpp
├── HttpHandler.h
└── HttpHandler.cpp
/html/CGI/base64script:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | /usr/bin/base64
3 |
--------------------------------------------------------------------------------
/html/img.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kiprey/WebServer/HEAD/html/img.jpg
--------------------------------------------------------------------------------
/html/CGI/base64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kiprey/WebServer/HEAD/html/CGI/base64
--------------------------------------------------------------------------------
/html/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kiprey/WebServer/HEAD/html/favicon.ico
--------------------------------------------------------------------------------
/Log.cpp:
--------------------------------------------------------------------------------
1 | #include "Log.h"
2 |
3 | // 全局日志输出锁,确保单个线程可以完整的输出一条语句
4 | MutexLock global_log_lock;
--------------------------------------------------------------------------------
/docs/img/image-20211026112349.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kiprey/WebServer/HEAD/docs/img/image-20211026112349.png
--------------------------------------------------------------------------------
/docs/img/image-20210512133857068.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kiprey/WebServer/HEAD/docs/img/image-20210512133857068.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # 生成的可执行文件
2 | WebServer
3 | # 生成的中间文件
4 | *.o
5 | # vscode 配置文件
6 | .vscode/
7 | # gdb 调试相关 { 是的直接手搓gdb调试 :) }
8 | .gdb_history
9 | # 其他需要ignore的文件
10 | ignore_*
11 | .VSCodeCounter/
12 |
--------------------------------------------------------------------------------
/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World
6 |
7 |
8 |
9 | Hello, Kiprey's Concurrent WebServer 1.0 !
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | SOURCE := $(wildcard *.cpp)
2 | INCLUDE :=
3 | OBJS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE)))
4 |
5 | TARGET := WebServer
6 | CC := g++
7 | LIBS := -lpthread
8 | CFLAGS := -std=c++11 -g3 -ggdb3 -Wall -O0 -fsanitize=address $(INCLUDE)
9 | CXXFLAGS:= $(CFLAGS)
10 |
11 | .PHONY : objs clean veryclean rebuild all
12 | all : $(TARGET)
13 | objs : $(OBJS)
14 | rebuild: veryclean all
15 | clean :
16 | rm -rf *.o
17 | veryclean : clean
18 | rm -rf $(TARGET)
19 |
20 | $(TARGET) : $(OBJS)
21 | $(CC) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)
22 |
--------------------------------------------------------------------------------
/MutexLock.h:
--------------------------------------------------------------------------------
1 | #ifndef MUTEXLOCK_H
2 | #define MUTEXLOCK_H
3 |
4 | #include
5 |
6 | /**
7 | * @brief MutexLock 将 pthread_mutex 封装成一个类,
8 | * 这样做的好处是不用记住那些繁杂的 pthread 开头的函数使用方式
9 | */
10 | class MutexLock
11 | {
12 | private:
13 | pthread_mutex_t mutex_;
14 | public:
15 | MutexLock() { pthread_mutex_init(&mutex_, nullptr); }
16 | ~MutexLock() { pthread_mutex_destroy(&mutex_); }
17 | void lock() { pthread_mutex_lock(&mutex_); }
18 | void unlock() { pthread_mutex_unlock(&mutex_); }
19 | pthread_mutex_t* getMutex() { return &mutex_; };
20 | };
21 |
22 | /**
23 | * @brief MutexLockGuard 主要是为了自动获取锁/释放锁, 防止意外情况下忘记释放锁
24 | * 而且块状的锁定区域更容易让人理解代码
25 | */
26 | class MutexLockGuard
27 | {
28 | private:
29 | MutexLock& lock_;
30 | public:
31 | /**
32 | * @brief 声明 MutexLockGuard 时自动上锁
33 | * @param lock 待锁定的资源
34 | */
35 | MutexLockGuard(MutexLock& mutex) : lock_(mutex) { lock_.lock(); }
36 | /**
37 | * @brief 当前作用域结束时自动释放锁, 防止遗忘
38 | */
39 | ~MutexLockGuard() { lock_.unlock(); }
40 | };
41 |
42 | #endif
--------------------------------------------------------------------------------
/Condition.h:
--------------------------------------------------------------------------------
1 | #ifndef CONDITION_H
2 | #define CONDITION_H
3 |
4 | #include
5 | #include
6 |
7 | #include "MutexLock.h"
8 |
9 | /**
10 | * @brief 条件变量,主要用于多线程中的锁
11 | * 与 MutexLock 一致,无需记住繁杂的函数名称
12 | * 条件变量主要是与mutex进行搭配,常用于资源分配相关的场景,
13 | * 例如当某个线程获取到锁以后,发现没有资源,则此时可以释放资源并等待条件变量
14 | * @note 注意: 使用条件变量时,必须上锁,防止出现多个线程共同使用条件变量
15 | */
16 | class Condition
17 | {
18 | private:
19 | MutexLock& lock_; // 目标 Mutex 互斥锁
20 | pthread_cond_t cond_; // 条件变量
21 | public:
22 | Condition(MutexLock& mutex) : lock_(mutex) { pthread_cond_init(&cond_, nullptr); }
23 | ~Condition() { pthread_cond_destroy(&cond_); }
24 | void notify() { pthread_cond_signal(&cond_); }
25 | void notifyAll() { pthread_cond_broadcast(&cond_); }
26 | void wait() { pthread_cond_wait(&cond_, lock_.getMutex()); }
27 | /**
28 | * @brief 等待当前的条件变量一段时间
29 | * @param sec 等待的时间(单位:秒)
30 | * @return 成功在时间内等待到则返回 true, 超时则返回 false
31 | */
32 | bool waitForSeconds(size_t sec)
33 | {
34 | timespec abstime;
35 | // 获取当前系统真实时间
36 | clock_gettime(CLOCK_REALTIME, &abstime);
37 | abstime.tv_sec += (time_t)sec;
38 | return ETIMEDOUT != pthread_cond_timedwait(&cond_, lock_.getMutex(), &abstime);
39 | }
40 | };
41 |
42 | #endif
--------------------------------------------------------------------------------
/Timer.h:
--------------------------------------------------------------------------------
1 | #ifndef TIMER_H
2 | #define TIMER_H
3 |
4 | #include
5 |
6 | class Timer
7 | {
8 | private:
9 | int timer_fd_;
10 | public:
11 | /**
12 | * @brief 初始时自动创建 timer fd,释放时自动释放timer fd
13 | * @param flag 用以设置 timer fd 的属性, TFD_NONBLOCK / TFD_NONBLOCK
14 | * @param sec 超时时间,单位秒
15 | * @param nsec 超时时间,单位纳秒
16 | */
17 | Timer(int flag = 0, time_t sec = 0, long nsec = 0);
18 | ~Timer();
19 |
20 | /**
21 | * @brief 获取与设置当前Timer的文件描述符
22 | */
23 | int getFd();
24 | void setFd(int fd);
25 |
26 | /**
27 | * @brief 判断当前timer fd是否可用
28 | * @return true表示可用,false表示不可用
29 | */
30 | bool isValid();
31 |
32 | /**
33 | * @brief 主动创建一个 timer fd,如果原先fd有效则不会重复创建
34 | * @param flag 用以设置 timer fd 的属性, TFD_NONBLOCK / TFD_NONBLOCK
35 | * @note 该函数调用的内部函数在错误时会设置 errno
36 | * @note 必须在使用该类成员中的其他函数之前, 确保该函数成功调用
37 | */
38 | bool create(int flag = 0);
39 |
40 | /**
41 | * @brief 设置一次性定时器
42 | * @param sec 表示定时器的秒级时间
43 | * @param nsec 表示定时器的纳秒级时间
44 | * @return true 表示设置正常,false表示设置失败
45 | * @note 若sec和nsec同时为0 ,则表示关闭定时器. sec & nsec 共同表示定时器的超时时间
46 | * @note 该函数调用的内部函数在错误时会设置 errno
47 | */
48 | bool setTime(time_t sec, long nsec);
49 |
50 | /**
51 | * @brief 取消定时器, 即setTime(0 ,0)
52 | * @return true 表示设置正常,false表示设置失败
53 | * @note 该函数调用的内部函数在错误时会设置 errno
54 | */
55 | bool cancel();
56 |
57 | /**
58 | * @brief 销毁timer fd
59 | */
60 | void destroy();
61 |
62 | /**
63 | * @brief 获取当前定时器距离下一次超时的时间
64 | * @return 返回timespec结构的时间
65 | * @note 若该函数执行失败,则返回的timespec结构体中,两个字段均为负数
66 | * @note 该函数调用的内部函数在错误时会设置 errno
67 | */
68 | timespec getNextTimeout();
69 | };
70 |
71 | #endif
--------------------------------------------------------------------------------
/Timer.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "Log.h"
4 | #include "Timer.h"
5 | #include "Utils.h"
6 |
7 | Timer::Timer(int flag, time_t sec, long nsec) : timer_fd_(-1)
8 | {
9 | if(create(flag))
10 | setTime(sec, nsec);
11 | }
12 |
13 | Timer::~Timer()
14 | {
15 | cancel();
16 | destroy();
17 | }
18 |
19 | int Timer::getFd()
20 | {
21 | return timer_fd_;
22 | }
23 |
24 | void Timer::setFd(int fd)
25 | {
26 | timer_fd_ = fd;
27 | }
28 |
29 | bool Timer::isValid()
30 | {
31 | return timer_fd_ >= 0;
32 | }
33 |
34 | bool Timer::create(int flag)
35 | {
36 | // 这里使用 CLOCK_BOOTTIME **相对时间**, 排除了系统时间与系统休眠时间的干扰
37 | if(!isValid() && ((timer_fd_ = timerfd_create(CLOCK_BOOTTIME, flag)) == -1))
38 | return false;
39 | return true;
40 | }
41 |
42 | bool Timer::setTime(time_t sec, long nsec)
43 | {
44 | struct itimerspec timerspec;
45 | // 初始化为0
46 | memset(&timerspec, 0, sizeof(timerspec));
47 | // 设置超时事件,注意该定时器只是一次性的, 因为itimerspec.interval两个字段全为0
48 | timerspec.it_value.tv_nsec = nsec;
49 | timerspec.it_value.tv_sec = sec;
50 | if(!isValid() || (timerfd_settime(timer_fd_, 0, &timerspec, nullptr) == -1))
51 | return false;
52 | return true;
53 | }
54 |
55 | bool Timer::cancel()
56 | {
57 | // 设置itimerspec的value两个字段全为0时则表示取消
58 | return setTime(0, 0);
59 | }
60 |
61 | void Timer::destroy()
62 | {
63 | if(isValid())
64 | close(timer_fd_);
65 | timer_fd_ = -1;
66 | }
67 |
68 | timespec Timer::getNextTimeout()
69 | {
70 | itimerspec nextTime;
71 | if(!isValid() || (timerfd_gettime(timer_fd_, &nextTime) == -1))
72 | {
73 | ERROR("Timer getNextTimeout fail! (%s)", strerror(errno));
74 | timespec ret;
75 | ret.tv_nsec = ret.tv_sec = -1;
76 | return ret;
77 | }
78 | return nextTime.it_interval;
79 | }
--------------------------------------------------------------------------------
/Epoll.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "Epoll.h"
6 | #include "Log.h"
7 |
8 | Epoll::Epoll(int flag) : epoll_fd_(-1)
9 | {
10 | create(flag);
11 | }
12 |
13 | Epoll::~Epoll()
14 | {
15 | destroy();
16 | }
17 |
18 | bool Epoll::isEpollValid()
19 | {
20 | return epoll_fd_ >= 0;
21 | }
22 |
23 | bool Epoll::create(int flag)
24 | {
25 | // 这里添加 epoll_fd_ < 0 的判断条件,防止重复 create.
26 | if(!isEpollValid()
27 | && ((epoll_fd_ = epoll_create1(flag)) == -1))
28 | {
29 | ERROR("Create Epoll fail! (%s)", strerror(errno));
30 | return false;
31 | }
32 | return true;
33 | }
34 |
35 | bool Epoll::add(int fd, void* data, int event)
36 | {
37 | if(isEpollValid())
38 | {
39 | epoll_event ep_event;
40 | ep_event.events = event;
41 | ep_event.data.ptr = data;
42 |
43 | return (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ep_event) != -1);
44 | }
45 | return false;
46 | }
47 |
48 | bool Epoll::modify(int fd, void* data, int event)
49 | {
50 | if(isEpollValid())
51 | {
52 | epoll_event ep_event;
53 | ep_event.events = event;
54 | ep_event.data.ptr = data;
55 |
56 | return (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &ep_event) != -1);
57 | }
58 |
59 | return false;
60 | }
61 |
62 | bool Epoll::del(int fd)
63 | {
64 | if(isEpollValid())
65 | return (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr) != -1);
66 | return false;
67 | }
68 |
69 | int Epoll::wait(int timeout)
70 | {
71 | if(isEpollValid())
72 | return epoll_wait(epoll_fd_, events_, MAX_EVENTS, timeout);
73 | // -2 表示非 epoll 错误
74 | return -2;
75 | }
76 |
77 | void Epoll::destroy()
78 | {
79 | // 如果文件描述符正常,则进行销毁
80 | if(isEpollValid())
81 | close(epoll_fd_);
82 | // 重置 epoll_fd_ 为无效描述符
83 | epoll_fd_ = -1;
84 | }
85 |
86 | epoll_event Epoll::getEvent(size_t index)
87 | {
88 | assert(index < MAX_EVENTS);
89 | // 返回一个 const 指针
90 | return events_[index];
91 | }
92 |
--------------------------------------------------------------------------------
/Log.h:
--------------------------------------------------------------------------------
1 | #ifndef LOG_H
2 | #define LOG_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "MutexLock.h"
9 |
10 | #define cRST "\x1b[0m" // 终端红色字体代码
11 | #define cLRD "\x1b[1;91m" // 终端重置字体颜色代码
12 | #define cYEL "\x1b[1;93m" // 终端黄色字体代码
13 | #define cBRI "\x1b[1;97m" // 终端加粗白色字体代码
14 | #define cLBL "\x1b[1;94m" // 终端蓝色字体代码
15 |
16 | // 全局日志输出锁
17 | extern MutexLock global_log_lock;
18 |
19 | #if 1
20 | // 开启所有输出
21 | #define INFO(x...) do { \
22 | MutexLockGuard log_guard(global_log_lock); \
23 | fprintf(stdout, "(Thread %lx): ", syscall(SYS_gettid)); \
24 | fprintf(stdout, cLBL "[*] " cRST x); \
25 | fprintf(stdout, cRST "\n"); \
26 | fflush(stdout); \
27 | } while (0)
28 |
29 | #define WARN(x...) do { \
30 | MutexLockGuard log_guard(global_log_lock); \
31 | fprintf(stderr, "(Thread %lx): ", syscall(SYS_gettid)); \
32 | fprintf(stderr, cYEL "[!] " cBRI "WARNING: " cRST x); \
33 | fprintf(stderr, cRST "\n"); \
34 | fflush(stderr); \
35 | } while (0)
36 |
37 | #define ERROR(x...) do { \
38 | MutexLockGuard log_guard(global_log_lock); \
39 | fprintf(stderr, "(Thread %lx): ", syscall(SYS_gettid)); \
40 | fprintf(stderr, cLRD "[-] " cRST x); \
41 | fprintf(stderr, cRST "\n"); \
42 | fflush(stderr); \
43 | } while (0)
44 |
45 | #define FATAL(x...) do { \
46 | MutexLockGuard log_guard(global_log_lock); \
47 | fprintf(stderr, "(Thread %lx): ", syscall(SYS_gettid)); \
48 | fprintf(stderr, cRST cLRD "[-] PROGRAM ABORT : " cBRI x); \
49 | fprintf(stderr, cLRD "\n Location : " cRST "%s(), %s:%u\n\n", \
50 | __FUNCTION__, __FILE__, __LINE__); \
51 | fflush(stderr); \
52 | abort(); \
53 | } while (0)
54 |
55 | #else
56 |
57 | // 关闭所有输出
58 | #define INFO(x...)
59 | #define WARN(x...)
60 | #define ERROR(x...)
61 | #define FATAL(x...)
62 |
63 | #endif
64 |
65 | /**
66 | * @brief 调试用,表示某个代码区域不应该到达
67 | * @param x 可输出的信息
68 | */
69 | #define UNREACHABLE(x) FATAL("UNREACHABLE CODE");
70 |
71 | #endif
--------------------------------------------------------------------------------
/Epoll.h:
--------------------------------------------------------------------------------
1 | #ifndef EPOLL_H
2 | #define EPOLL_H
3 | #include
4 |
5 | #include "Utils.h"
6 |
7 | using namespace std;
8 |
9 | /**
10 | * @brief 供epoll使用的结构体
11 | */
12 | struct EpollEvent
13 | {
14 | int fd; // 被唤醒的 fd
15 | void* ptr; // 顺便携带的数据
16 | };
17 |
18 | class Epoll
19 | {
20 | public:
21 | /**
22 | * @brief 默认声明该 Epoll 类实例是自动分配 epoll实例
23 | */
24 | Epoll(int flag = 0);
25 | ~Epoll();
26 |
27 | /**
28 | * @brief 确定当前epoll文件描述符是否有效
29 | * @return 有效则返回 true, 无效则返回 false
30 | */
31 | bool isEpollValid();
32 |
33 | /**
34 | * @brief 创建一个 epoll 实例
35 | * @param flag 传递给 epoll_create1 的标志.可以设置 0 或 EPOLL_CLOEXEC.
36 | * @return 创建成功返回true,创建失败返回false
37 | * @note 该函数调用的内部函数在错误时会设置 errno
38 | * @note 必须在使用该类成员中的其他函数之前, 确保该函数成功调用
39 | */
40 | bool create(int flag = 0);
41 |
42 | /**
43 | * @brief 向工作列表中添加条目
44 | * @param fd 目标文件描述符
45 | * @param data 添加到事件的数据指针
46 | * @param event 触发何种事件类型时进入就绪(epoll_event.events)
47 | * @return 成功则返回true, 失败则返回 false
48 | * @note 该函数调用的内部函数在错误时会设置 errno
49 | */
50 | bool add(int fd, void* data, int event);
51 |
52 | /**
53 | * @brief 修改工作列表中的条目
54 | * @param fd 目标文件描述符
55 | * @param data 添加到事件的数据指针
56 | * @param event 触发何种事件类型时进入就绪(epoll_event.events)
57 | * @return 成功则返回true, 失败则返回 false
58 | * @note 该函数调用的内部函数在错误时会设置 errno
59 | */
60 | bool modify(int fd, void* data, int event);
61 |
62 | /**
63 | * @brief 删除工作列表中的特定条目
64 | * @param fd 目标文件描述符
65 | * @return 成功则返回true, 失败则返回 false
66 | * @note 该函数调用的内部函数在错误时会设置 errno
67 | */
68 | bool del(int fd);
69 |
70 | /**
71 | * @brief 等待事件到来
72 | * @param timeout 最大超时时间,单位毫秒. -1 则设置永久等待
73 | * @return 返回处于就绪状态的文件描述符个数,出错时返回 -1
74 | * @note 该函数调用的内部函数在错误时会设置 errno
75 | */
76 | int wait(int timeout);
77 |
78 | /**
79 | * @brief 释放当前 epoll 实例
80 | */
81 | void destroy();
82 |
83 | /**
84 | * @brief 获取事件数组中的对应事件
85 | * @param index 事件数组中的索引
86 | * @note 调用者必须确保 index 不能越界.
87 | */
88 | epoll_event getEvent(size_t index);
89 |
90 | private:
91 | static const size_t MAX_EVENTS = 1024;
92 |
93 | int epoll_fd_;
94 | epoll_event events_[MAX_EVENTS];
95 | };
96 |
97 | #endif
--------------------------------------------------------------------------------
/ThreadPool.h:
--------------------------------------------------------------------------------
1 | #ifndef THREADPOOL_H
2 | #define THREADPOOL_H
3 |
4 | #include
5 | #include
6 |
7 | #include "Condition.h"
8 | #include "MutexLock.h"
9 |
10 | using namespace std;
11 |
12 | class ThreadPool
13 | {
14 | public:
15 | // 线程池摧毁时,当前正在工作的线程是等待工作完成后退出(graceful) 还是直接退出(immediate)
16 | enum ShutdownMode { GRACEFUL_QUIT, IMMEDIATE_SHUTDOWN } ;
17 | /***
18 | * @brief 创建线程池
19 | * @param threadNum 线程池线程个数
20 | * @param shutdown_mode 当前线程池的摧毁方案
21 | * @param maxQueueSize 线程池事件队列最大大小, 默认不设限制(-1)
22 | */
23 | ThreadPool( size_t threadNum,
24 | ShutdownMode shutdown_mode = GRACEFUL_QUIT,
25 | size_t maxQueueSize = -1
26 | );
27 |
28 | /***
29 | * @brief 销毁线程池
30 | */
31 | ~ThreadPool();
32 |
33 | /***
34 | * @brief 将当前task加入至线程池中
35 | * @param task 待处理的 task
36 | * @return 返回添加结果, true 表示添加成功, false 表示队列已满, 添加失败
37 | * @note 这里的 arguments 指针指向的对象,将 **不会** 在子线程内部事件执行完成后自动释放
38 | * 也就是说,外部调用者需要自己考虑到内存释放
39 | */
40 | bool appendTask(void (*function)(void*), void* arguments);
41 |
42 | // /**
43 | // * @brief 声明一些获取线程池属性的方法.不管有没有用到,实现一下接口总是没错的.
44 | // */
45 | // size_t getThreadNum() { return threadNum_; }
46 | // size_t getWorkingThreadNum() { return workingThreadNum_; }
47 | // size_t getIdleThreadNum() { return idleThreadNum_; }
48 | // size_t getStartedThreadNum() { return startedThreadNum_; }
49 |
50 | private:
51 | /**
52 | * @brief 每个子线程所要执行的函数, 在该函数中轮询事件队列
53 | * @param pool 当前线程所属的线程池
54 | */
55 | static void* TaskForWorkerThreads_(void* arg);
56 |
57 | /***
58 | * 每个线程的基本事件单元
59 | */
60 | struct ThreadpoolTask
61 | {
62 | void (*function)(void*);
63 | void* arguments;
64 | };
65 |
66 | size_t threadNum_; // 线程个数
67 |
68 | // size_t workingThreadNum_; // 正在工作的线程个数
69 | // size_t idleThreadNum_; // 空闲线程个数
70 | // size_t startedThreadNum_; // 已经启动的线程个数,注意已经启动的线程分为 正在工作 和 空闲 两类
71 |
72 | size_t maxQueueSize_; // 事件队列最大长度,超出则停止添加新事件
73 | queue task_queue_; // 事件队列
74 |
75 | vector threads_; // 线程的标识符
76 |
77 | MutexLock threadpool_mutex_; // 线程池的锁,保证每次最多只能有一个线程正在操作该线程池
78 | Condition threadpool_cond_; // 线程池的条件变量,对于来新task时,唤醒空闲线程
79 |
80 | ShutdownMode shutdown_mode_; // 线程池析构时,剩余工作线程的处理方式
81 |
82 | };
83 |
84 | #endif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebServer-1.1
2 |
3 | ## 一、概述
4 |
5 | WebServer-1.0 简单实现了一个基础的 **多并发网络服务程序** 。在该版本中,主要实现了以下重要内容:
6 |
7 | - 线程互斥锁 & 条件变量的封装
8 |
9 | - 线程池的设计,以支持并发
10 |
11 | - 基础网络连接的实现
12 |
13 | - http 协议的简略支持
14 |
15 | - 支持部分常用 HTTP 报文
16 | - 200 OK
17 | - 400 Bad Request
18 | - 500 Internal Server Error
19 | - 501 Not Implemented
20 | - 505 HTTP Version Not Supported
21 | - 支持 HTTP GET 请求
22 | - 支持 HTTP/1.1 **持续连接** 特性
23 |
24 | WebServer-1.0运行时截图:
25 |
26 | 
27 |
28 | > 1.0版本的项目代码位于 [Kiprey/WebServer CommitID: 6473f5d - github](https://github.com/Kiprey/WebServer/tree/6473f5d512097f235ab209b13b53e28d7946a0f6)
29 | >
30 | > 可以使用`git checkout v1.0`命令来切换版本。
31 |
32 | WebServer-1.1 在原先 1.0 版本的基础上大量重构了代码,相对于旧版本来说,新版本主要更新了以下内容:
33 |
34 | - 替换并发方式,从**多线程并发** 更换为 **epoll 并发**
35 | - HTTP报文处理添加 POST 和 HEAD 方式的处理
36 | - 支持自定义 WebServer 的 www 目录路径
37 | - 使用 timerfd API,对每个 HTTP/1.1 Keep-Alive 的 TCP 链接设置了超时时间,超时后若还没有请求,则强制关闭该连接。
38 | - 支持 Post 请求使用 CGI 程序。其中CGI程序是可执行文件(例如shell脚本,附带 `#!` 的 python 脚本, ELF可执行文件等等)。
39 | - 支持自定义 www 目录路径,不再限制为当前工作目录。
40 | - 支持互斥 pretty LOG 输出
41 | - 支持更多的 Http 错误报文
42 | - 404 Not Found
43 | - 411 Length Required
44 | - 支持 Address Sanitizer 检测当前程序的潜在漏洞
45 | - 更多的功能等待发现......
46 |
47 | WebServer-1.1 运行时截图:
48 |
49 | 
50 |
51 | > 可以使用`git checkout v1.1`命令来切换版本
52 |
53 | > 注意:该程序的实现大量参考了 [linyacool/WebServer - github](https://github.com/linyacool/WebServer) 的代码。
54 |
55 | ## 二、编译、运行与调试
56 |
57 | - 使用以下指令编译:
58 |
59 | ```bash
60 | make
61 | ```
62 |
63 | - WebServer-1.0使用以下指令运行
64 |
65 | ```bash
66 | ./WebServer
67 | ```
68 |
69 | > 注意一些**特殊端口**的绑定需要使用 root 权限,例如 80 端口。
70 |
71 | WebServer-1.1 使用以下指令执行
72 |
73 | ```bash
74 | ./WebServer []
75 | ```
76 |
77 | - 使用 GDB 进行调试。
78 |
79 | ## 三、技术文档
80 |
81 | 请点击 [此处 WebServer-1](docs/WebServer-1.md) 跳转至更加详细的WebServer1.0技术文档。
82 |
83 | WebServer1.1技术文档因为时间原因暂时没有完成,但主要的技术细节已经以大量注释的形式写入了源代码中,可以直接阅读源代码来理解。
84 |
85 | ## 四、测试方式
86 |
87 | - 单个测试
88 |
89 | ```bash
90 | # GET 请求
91 | curl http://localhost:8012/html/index.html
92 | curl -d http://localhost:8012/html/CGI/base64script
93 | # POST 请求
94 |
95 | ```
96 |
97 | - 使用 apache 测试工具 `ab` 来进行大批量测试
98 |
99 | ```bash
100 | # -c 并发数
101 | # -n 总请求数
102 | # -s 单个请求的超时时间
103 |
104 | # GET 测试
105 | ab -c 500 -n 10000 -s 300 http://127.0.0.1:8012/html/index.html
106 | # POST 测试
107 | ab -c 500 -n 10000 -s 300 -p ignore_post.txt http://127.0.0.1:8012/html/CGI/base64script
108 | ```
--------------------------------------------------------------------------------
/Utils.h:
--------------------------------------------------------------------------------
1 | #ifndef UTILS_H
2 | #define UTILS_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | using std::cout;
9 | using std::cerr;
10 | using std::endl;
11 | using std::string;
12 | using std::ostream;
13 |
14 | /**
15 | * @brief 绑定一个端口号并返回一个 fd
16 | * @param port 目标端口号
17 | * @return 运行正常则返回 fd, 否则返回 -1
18 | * @note 该函数在错误时会生成 errno
19 | */
20 | int socket_bind_and_listen(int port);
21 |
22 | /**
23 | * @brief 设置传入的文件描述符为非阻塞模式
24 | * @param fd 传入的目标套接字
25 | * @return true表示设置成功, false表示设置失败
26 | * @note fcntl函数在错误时会生成 errno
27 | */
28 | bool setFdNoBlock(int fd);
29 |
30 | /**
31 | * @brief 设置socket禁用 nagle算法
32 | * @param fd 目标套接字
33 | * @return true 表示设置成功, false 表示设置失败
34 | * @note setsockopt函数在错误时会生成 errno
35 | */
36 | bool setSocketNoDelay(int fd);
37 |
38 | /**
39 | * @brief 非阻塞模式 read 的wrapper
40 | * @param fd 源文件描述符
41 | * @param buf 缓冲区地址
42 | * @param len 目标读取的字节个数
43 | * @return 成功读取的长度
44 | * @note 内部函数在错误时会生成 errno
45 | * @note 非阻塞模式下,无论有没有读取到数据,都会马上返回
46 | * @note readn 不能用于替代 recv
47 | * 因为当 readn 返回0时,调用者无法知道是连接关闭,还是当前暂时无数据可读
48 | */
49 | ssize_t readn(int fd, void* buf, size_t len);
50 |
51 | /**
52 | * @brief write/send的wrapper
53 | * @param fd 源文件描述符
54 | * @param buf 缓冲区地址
55 | * @param len 目标读取的字节个数
56 | * @param isWrite 启用 write 函数
57 | * @return 成功读取的长度
58 | * @note 内部函数在错误时会生成 errno
59 | * @note 该函数将 **阻塞** 写入数据, 除非有其他错误发生
60 | * @note writen 可以用于替代 send 进行阻塞写入操作
61 | */
62 | ssize_t writen(int fd, const void* buf, size_t len, bool isWrite = false);
63 |
64 | /**
65 | * @brief 忽略 SIGPIPE信号
66 | * @note 当远程主机强迫关闭socket时,Server端会产生 SIGPIPE 信号
67 | * 但SIGPIPE信号默认关闭当前进程,因此在Server端处需要忽略该信号
68 | */
69 | void handleSigpipe();
70 |
71 | /**
72 | * @brief 将当前client_fd_对应的连接信息,以 LOG(INFO) 的形式输出
73 | * @param client_fd_ 待输出信息的 fd
74 | * @param prefix 输出信息的前缀,例如 ": "
75 | */
76 | void printConnectionStatus(int client_fd_, string prefix);
77 |
78 | /**
79 | * @brief 将传入的字符串转义成终端可以直接显示的输出
80 | * @param str 待输出的字符串
81 | * @param MAXBUF 最长能输出的字符串长度
82 | * @return 转义后的字符串
83 | * @note 是将 '\r' 等无法在终端上显示的字符,转义成 "\r"字符串 输出
84 | */
85 | string escapeStr(const string& str, size_t MAXBUF);
86 |
87 | /**
88 | * @brief 判断字符串是否全为数字
89 | * @return true 表示字符串全为数字, 否则返回false
90 | */
91 | bool isNumericStr(string str);
92 |
93 | /**
94 | * @brief 清空当前剩余尚未 accept 的连接
95 | * @param listen_fd 当前所监听的文件描述符
96 | * @param idle_fd 空闲的文件描述符
97 | * @return 返回清空的连接数量
98 | */
99 | size_t closeRemainingConnect(int listen_fd, int* idle_fd);
100 |
101 | /**
102 | * @brief 检测两个 path 是否包含从属关系,以防止目录穿越漏洞
103 | * @param root_dir 最外层的路径
104 | * @param child_dir 内层路径
105 | * @return 返回从属关系
106 | */
107 | bool is_path_parent(const string& parent_path, const string& child_path);
108 |
109 | #endif
--------------------------------------------------------------------------------
/ThreadPool.cpp:
--------------------------------------------------------------------------------
1 | #include "Log.h"
2 | #include "ThreadPool.h"
3 | #include "Utils.h"
4 |
5 | ThreadPool::ThreadPool(size_t threadNum, ShutdownMode shutdown_mode, size_t maxQueueSize)
6 | : threadNum_(threadNum),
7 | maxQueueSize_(maxQueueSize),
8 | // 使用 类成员变量 threadpool_mutex_ 来初始化 threadpool_cond_
9 | threadpool_cond_(threadpool_mutex_),
10 | shutdown_mode_(shutdown_mode)
11 | {
12 | // 开始循环创建线程
13 | while(threads_.size() < threadNum_)
14 | {
15 | pthread_t thread;
16 | // 如果线程创建成功,则将其压入栈内存中
17 | if(!pthread_create(&thread, nullptr, TaskForWorkerThreads_, this))
18 | {
19 | threads_.push_back(thread);
20 | // // 注意这里只修改已启动的线程数量
21 | // startedThreadNum_++;
22 | }
23 | }
24 | }
25 |
26 | ThreadPool::~ThreadPool()
27 | {
28 | // 向任务队列中添加退出线程事件,注意上锁
29 | // 注意在 cond 使用之前一定要上 mutex
30 | {
31 | // 操作 task_queue_ 时一定要上锁
32 | MutexLockGuard guard(threadpool_mutex_);
33 | // 如果需要立即关闭当前的线程池,则
34 | if(shutdown_mode_ == IMMEDIATE_SHUTDOWN)
35 | // 先将当前队列清空
36 | while(!task_queue_.empty())
37 | task_queue_.pop();
38 |
39 | // 往任务队列中添加退出线程任务
40 | for(size_t i = 0; i < threadNum_; i++)
41 | {
42 | auto pthreadExit = [](void*) { pthread_exit(0); };
43 | ThreadpoolTask task = { pthreadExit, nullptr };
44 | task_queue_.push(task);
45 | }
46 | // 唤醒所有线程以执行退出操作
47 | threadpool_cond_.notifyAll();
48 | }
49 | for(size_t i = 0; i < threadNum_; i++)
50 | {
51 | // 回收线程资源
52 | pthread_join(threads_[i], nullptr);
53 | }
54 | }
55 |
56 | bool ThreadPool::appendTask(void (*function)(void*), void* arguments)
57 | {
58 | // 由于会操作事件队列,因此需要上锁
59 | MutexLockGuard guard(threadpool_mutex_);
60 | // 如果队列长度过长,则将当前task丢弃
61 | if(task_queue_.size() > maxQueueSize_)
62 | return false;
63 | else
64 | {
65 | // 添加task至列表中
66 | ThreadpoolTask task = { function, arguments };
67 | task_queue_.push(task);
68 | // 每当有新事件进入之时,只唤醒一个等待线程
69 | threadpool_cond_.notify();
70 | return true;
71 | }
72 | }
73 |
74 | void* ThreadPool::TaskForWorkerThreads_(void* arg)
75 | {
76 | ThreadPool* pool = (ThreadPool*)arg;
77 | // 启动当前线程
78 | ThreadpoolTask task;
79 | // 对于子线程来说,事件循环开始
80 | for(;;)
81 | {
82 | // 首先获取事件
83 | {
84 | // 获取事件时需要上个锁
85 | MutexLockGuard guard(pool->threadpool_mutex_);
86 |
87 | /**
88 | * 如果好不容易获得到锁了,但是没有事件可以执行
89 | * 则陷入沉睡,释放锁,并等待唤醒
90 | * NOTE: 注意, pthread_cond_signal 会唤醒至少一个线程
91 | * 也就是说,可能存在被唤醒的线程仍然没有事件处理的情况
92 | * 这时只需循环wait即可.
93 | */
94 | while(pool->task_queue_.size() == 0)
95 | pool->threadpool_cond_.wait();
96 | // 唤醒后一定有事件
97 | assert(pool->task_queue_.size() != 0);
98 | task = pool->task_queue_.front();
99 | pool->task_queue_.pop();
100 | }
101 | // 执行事件
102 | (task.function)(task.arguments);
103 | }
104 | // 注意: UNREACHABLE, 控制流不可能会到达此处
105 | // 因为线程的退出不会走这条控制流,而是执行退出事件
106 | UNREACHABLE();
107 | return nullptr;
108 | }
--------------------------------------------------------------------------------
/Utils.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #include "Log.h"
13 | #include "MutexLock.h"
14 | #include "Utils.h"
15 |
16 | int socket_bind_and_listen(int port)
17 | {
18 | int listen_fd = 0;
19 | // 开始创建 socket, 注意这是阻塞模式的socket
20 | // AF_INET : IPv4 Internet protocols
21 | // SOCK_STREAM : TCP socket
22 | if((listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)) == -1)
23 | return -1;
24 |
25 | // 绑定端口
26 | sockaddr_in server_addr;
27 | // 初始化一下
28 | memset(&server_addr, '\0', sizeof(server_addr));
29 | // 设置一下基本操作
30 | server_addr.sin_family = AF_INET;
31 | server_addr.sin_port = htons((unsigned short)port);
32 | server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
33 | // 端口复用
34 | int opt = 1;
35 | if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
36 | return -1;
37 | // 试着bind
38 | if(bind(listen_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1)
39 | return -1;
40 | // 试着listen, 设置最大队列长度为 1024
41 | if(listen(listen_fd, 1024) == -1)
42 | return -1;
43 |
44 | return listen_fd;
45 | }
46 |
47 | bool setFdNoBlock(int fd)
48 | {
49 | // 获取fd对应的flag
50 | int flag = fcntl(fd, F_GETFD);
51 | if(flag == -1)
52 | return -1;
53 | flag |= O_NONBLOCK;
54 | if(fcntl(fd, F_SETFL, flag) == -1)
55 | return false;
56 | return true;
57 | }
58 |
59 | bool setSocketNoDelay(int fd)
60 | {
61 | int enable = 1;
62 | if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&enable, sizeof(enable)) == -1)
63 | return false;
64 | return true;
65 | }
66 |
67 | ssize_t readn(int fd, void* buf, size_t len)
68 | {
69 | // 这里将 void* 转换成 char* 是为了在下面进行自增操作
70 | char *pos = (char*)buf;
71 | size_t leftNum = len;
72 | ssize_t readNum = 0;
73 | while(leftNum > 0)
74 | {
75 | // 尝试循环读取,如果报错,则进行判断
76 | // 注意, read 的返回值为0则表示读取到 EOF,是正常现象
77 | ssize_t tmpRead = read(fd, pos, leftNum);
78 |
79 | if(tmpRead < 0)
80 | {
81 | if(errno == EINTR)
82 | tmpRead = 0;
83 | // 如果始终读取不到数据,则提前返回,因为这个取决于远程 fd,无法预测要等多久
84 | else if (errno == EAGAIN)
85 | return readNum;
86 | else
87 | return -1;
88 | }
89 | // 读取的0,则说明远程连接已被关闭
90 | if(tmpRead == 0)
91 | break;
92 | readNum += tmpRead;
93 | pos += tmpRead;
94 |
95 | leftNum -= tmpRead;
96 | }
97 | return readNum;
98 | }
99 |
100 | ssize_t writen(int fd, const void* buf, size_t len, bool isWrite)
101 | {
102 | // 这里将 void* 转换成 char* 是为了在下面进行自增操作
103 | char *pos = (char*)buf;
104 | size_t leftNum = len;
105 | ssize_t writtenNum = 0;
106 | while(leftNum > 0)
107 | {
108 | ssize_t tmpWrite = 0;
109 |
110 | if(isWrite)
111 | tmpWrite = write(fd, pos, leftNum);
112 | else
113 | tmpWrite = send(fd, pos, leftNum, 0);
114 |
115 | // 尝试循环写入,如果报错,则进行判断
116 | // 注意,write返回0属于异常现象,因此判断时需要包含
117 | if(tmpWrite < 0)
118 | {
119 | // 与read不同的是,如果 EAGAIN,则继续重复写入,因为写入操作是有Server这边决定的
120 | if(errno == EINTR || errno == EAGAIN)
121 | tmpWrite = 0;
122 | else
123 | return -1;
124 | }
125 | if(tmpWrite == 0)
126 | break;
127 | writtenNum += tmpWrite;
128 | pos += tmpWrite;
129 | leftNum -= tmpWrite;
130 | }
131 | return writtenNum;
132 | }
133 |
134 | void handleSigpipe()
135 | {
136 | struct sigaction sa;
137 | memset(&sa, '\0', sizeof(sa));
138 | sa.sa_handler = SIG_IGN;
139 | sa.sa_flags = 0;
140 | if(sigaction(SIGPIPE, &sa, NULL) == -1)
141 | ERROR("Ignore SIGPIPE failed! (%s)", strerror(errno));
142 | }
143 |
144 | void printConnectionStatus(int client_fd_, string prefix)
145 | {
146 | // 输出连接信息 [Server]IP:PORT <---> [Client]IP:PORT
147 | sockaddr_in serverAddr, peerAddr;
148 | socklen_t serverAddrLen = sizeof(serverAddr);
149 | socklen_t peerAddrLen = sizeof(peerAddr);
150 |
151 | if((getsockname(client_fd_, (struct sockaddr *)&serverAddr, &serverAddrLen) != -1)
152 | && (getpeername(client_fd_, (struct sockaddr *)&peerAddr, &peerAddrLen) != -1))
153 | INFO("%s: (socket %d) [Server] %s:%d <---> [Client] %s:%d",
154 | prefix.c_str(), client_fd_,
155 | inet_ntoa(serverAddr.sin_addr), ntohs(serverAddr.sin_port),
156 | inet_ntoa(peerAddr.sin_addr), ntohs(peerAddr.sin_port));
157 | else
158 | ERROR("printConnectionStatus failed ! (%s)", strerror(errno));
159 | }
160 |
161 | string escapeStr(const string& str, size_t MAXBUF)
162 | {
163 | string msg = str;
164 | // 遍历所有字符
165 | for(size_t i = 0; i < msg.length(); i++)
166 | {
167 | char ch = msg[i];
168 | // 如果当前字符无法打印,则转义
169 | if(!isprint(ch))
170 | {
171 | // 这里只对\r\n做特殊处理
172 | string substr;
173 | if(ch == '\r')
174 | substr = "\\r";
175 | else if(ch == '\n')
176 | substr = "\\n";
177 | else
178 | {
179 | char hex[10];
180 | // 注意这里要设置成 unsigned,即零扩展
181 | snprintf(hex, 10, "\\x%02x", static_cast(ch));
182 | substr = hex;
183 | }
184 | msg.replace(i, 1, substr);
185 | }
186 | }
187 | // 将读取到的数据输出
188 | if(msg.length() > MAXBUF)
189 | return msg.substr(0, MAXBUF) + " ... ... ";
190 | else
191 | return msg;
192 | }
193 |
194 | bool isNumericStr(string str)
195 | {
196 | for(size_t i = 0; i < str.length(); i++)
197 | if(!isdigit(str[i]))
198 | return false;
199 | return true;
200 | }
201 |
202 | size_t closeRemainingConnect(int listen_fd, int* idle_fd) {
203 | close(*idle_fd);
204 |
205 | size_t count = 0;
206 | for(;;) {
207 | int client_fd = accept4(listen_fd, nullptr, nullptr,
208 | SOCK_NONBLOCK | SOCK_CLOEXEC);
209 | if(client_fd == -1 && errno == EAGAIN)
210 | break;
211 | close(client_fd);
212 | ++count;
213 | }
214 | // 重新恢复空闲描述符
215 | *idle_fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
216 | return count;
217 | }
218 |
219 | bool is_path_parent(const string& parent_path, const string& child_path) {
220 | bool result = false;
221 | char* parent_p = nullptr, *child_p = nullptr;
222 | char separator;
223 |
224 | parent_p = canonicalize_file_name(parent_path.c_str());
225 | if(!parent_p) {
226 | ERROR("is_path_parent failed, cannot get parent path [%s] (%s)",
227 | parent_path.c_str(),
228 | strerror(errno));
229 | goto clean_parent;
230 | }
231 |
232 | child_p = canonicalize_file_name(child_path.c_str());
233 | if(!child_p) {
234 | ERROR("is_path_parent failed, cannot get child path [%s] (%s)",
235 | child_path.c_str(),
236 | strerror(errno));
237 | goto clean_child;
238 | }
239 |
240 | // INFO("resolved parent path: %s", parent_p);
241 | INFO("resolved path: %s", child_p);
242 |
243 | /* 判断是否存在目录穿越漏洞,判断条件:
244 | 1. parent_path 是否在 child_path 的起始位置,
245 | 例如 parent: /usr/class/html
246 | 与 child: /usr/class/html/index.html
247 | 2. 判断 parent_path 末尾是否分割符
248 | 例如 parent: /usr/class/html
249 | 与 child: /usr/class/htmlflag/../../../../flag
250 | ----------------------------A--------------------
251 | 这里没有在 html 后面加 /,说明两个路径不对应
252 | */
253 | if(child_p == strstr(child_p, parent_p)) {
254 | // parent 在 child 中,因此 child[parent.len] 不会越界
255 | separator = child_p[strlen(parent_p)];
256 | if (separator == '\0' || separator == '/')
257 | return true;
258 | }
259 |
260 | free(child_p);
261 | clean_child:
262 | free(parent_p);
263 | clean_parent:
264 | return result;
265 | }
266 |
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "Epoll.h"
10 | #include "HttpHandler.h"
11 | #include "Log.h"
12 | #include "ThreadPool.h"
13 | #include "Utils.h"
14 |
15 | using namespace std;
16 |
17 | /**
18 | * @brief 处理新的连接
19 | * @param epoll 存放新连接的Epoll类实例
20 | * @param listen_fd 新连接所对应的 listen 描述符
21 | */
22 | void handleNewConnections(Epoll* epoll, int listen_fd, int* idle_fd)
23 | {
24 | // 注意:可能会有很多个 connect 动作,但只会有一个 event
25 | sockaddr_in client_addr;
26 | socklen_t client_addr_len = 0;
27 |
28 | /**
29 | * 如果
30 | * 1. accept 没有发生错误
31 | * 2. accppt 发生了 EINTR 错误
32 | * 3. accept 发生了 ECONNABORTED 错误(该错误是远程连接被中断)
33 | * 则重新循环. 其中第三点, 若发生了 aborted 错误,则继续循环接受下一个socket 的请求
34 | */
35 | for(;;) {
36 | int client_fd = accept4(listen_fd, (sockaddr*)&client_addr, &client_addr_len,
37 | SOCK_NONBLOCK | SOCK_CLOEXEC);
38 | // accept 的错误处理
39 | if(client_fd == -1) {
40 | // 如果是因为一些无关的错误所阻断,则继续 accept
41 | if(errno == EINTR || errno == ECONNABORTED)
42 | continue;
43 | // 正常情况下,如果处理了所有的 accept后, errno == EAGAIN,则直接退出
44 | else if (errno == EAGAIN)
45 | break;
46 | // 如果由于文件描述符不够用了,则会返回 EMFILE,此时清空全部的尚未 accept 连接
47 | else if(errno == EMFILE) {
48 | int closed_conn_num = closeRemainingConnect(listen_fd, idle_fd);
49 | WARN("No reliable pipes in new connection, close %d conns", closed_conn_num);
50 | break;
51 | }
52 | // 如果是其他的错误,则输出信息
53 | else
54 | ERROR("Accept Error! (%s)", strerror(errno));
55 | }
56 | // 如果 accept 正常
57 | else {
58 | /** 构建一个新的 HttpHandler,并放入 epoll 实例中
59 | * 注意这里使用了 ONESHOT, 每个套接字只会在 边缘触发,可读时处于就绪状态
60 | * 且每个套接字只会被一个线程处理
61 | * NOTE: 每个 client_fd 只会在 HttpHandler 中被 close + 下面的 timer 异常处理中被关闭
62 | * 每个 client_handler 也只会在 setConnectionClosed 之后, 执行完 RunEventLoop 函数结束时被释放
63 | * 每个 Timer 在此处创建, 在 HttpHandler 中被释放
64 | * 可以看出,现在指针已经满天飞了 2333
65 | */
66 | Timer* timer = new Timer(TFD_NONBLOCK | TFD_CLOEXEC);
67 | // 如果timer创建失败,则清空当前所有尚未 accept 的连接,因为文件描述符满
68 | if(!timer->isValid())
69 | {
70 | delete timer;
71 | // 直接关闭,告诉远程这里放不下了
72 | close(client_fd);
73 |
74 | int closed_conn_num = closeRemainingConnect(listen_fd, idle_fd);
75 | WARN("No reliable pipes in new connection, close %d conns", closed_conn_num);
76 | break;
77 | }
78 | HttpHandler* client_handler = new HttpHandler(epoll, client_fd, timer);
79 | /**
80 | * @brief EPOLLRDHUP EPOLLHUP 不同点,前者是半关闭连接时出发,后者是完全关闭后触发
81 | * @ref tcp 源码 https://elixir.bootlin.com/linux/v4.19/source/net/ipv4/tcp.c#L524
82 | * @ref TCP: When is EPOLLHUP generated? https://stackoverflow.com/questions/52976152/tcp-when-is-epollhup-generated
83 | */
84 | bool ret1 = epoll->add(client_fd, client_handler->getClientEpollEvent(), client_handler->getClientTriggerCond());
85 | // 设置定时器以边缘-单次触发方式
86 | bool ret2 = epoll->add(timer->getFd(), client_handler->getTimerEpollEvent(), client_handler->getTimerTriggerCond());
87 | assert(ret1 && ret2);
88 | // 输出相关信息
89 | printConnectionStatus(client_fd, "-------->>>>> New Connection");
90 | }
91 | }
92 | }
93 |
94 | /**
95 | * @brief 处理旧的连接
96 | * @param epoll 被唤醒的 epoll
97 | * @param fd 被唤醒的文件描述符
98 | * @param thread_pool 目标线程池
99 | * @param event 待处理的事件
100 | */
101 | void handleOldConnection(Epoll* epoll, int fd, ThreadPool* thread_pool, epoll_event* event)
102 | {
103 | EpollEvent* curr_epoll_event = static_cast(event->data.ptr);
104 | HttpHandler* handler = static_cast(curr_epoll_event->ptr);
105 | // 处理一些错误事件
106 | int events_ = event->events;
107 | // 如果远程关闭了当前连接
108 | if ((events_ & EPOLLHUP) || (events_ & EPOLLRDHUP)) {
109 | INFO("Socket(%d) was closed by peer.", handler->getClientFd());
110 | // 当某个 handler 无法使用时,一定要销毁内存
111 | delete handler;
112 | // 之后重新开始遍历新的事件.
113 | return;
114 | }
115 | // 如果当前 socket / events_ 存在错误
116 | else if ((events_ & EPOLLERR) || !(events_ & EPOLLIN)) {
117 | ERROR("Socket(%d) error.", handler->getClientFd());
118 | // 当某个 handler 无法使用时,一定要销毁内存
119 | delete handler;
120 | // 之后重新开始遍历新的事件.
121 | return;
122 | }
123 | // 如果没有错误发生
124 | // 1. 如果是因为超时
125 | if(fd == handler->getTimer()->getFd())
126 | {
127 | INFO("-------->>>>> "
128 | "New Message: socket(%d) - timerfd(%d) timeout."
129 | " <<<<<--------",
130 | handler->getClientFd(), handler->getTimer()->getFd());
131 | /* 这里不像下面需要从epoll中关闭 timer fd
132 | 因为 timer将会在HttpHandler的析构函数中从epoll内部删除 */
133 | // 删除 handler 实例
134 | delete handler;
135 | }
136 | // 2. 如果不是因为超时
137 | else
138 | {
139 | // 则从epoll中关闭 timer, 防止条件竞争
140 | epoll->modify(handler->getTimer()->getFd(), nullptr, 0);
141 | // 并将其放入线程池中并行执行
142 | thread_pool->appendTask(
143 | // lambda 函数
144 | [](void* arg)
145 | {
146 | HttpHandler* handler = static_cast(arg);
147 |
148 | printConnectionStatus(handler->getClientFd(), "-------->>>>> New Message");
149 |
150 | // 如果出现无法恢复的错误,则直接释放该实例以及对应的 client_fd
151 | if(!(handler->RunEventLoop()))
152 | delete handler;
153 | },
154 | handler);
155 | }
156 | }
157 |
158 | int main(int argc, char* argv[])
159 | {
160 | // 获取传入的参数
161 | if (argc < 2 || !isNumericStr(argv[1]))
162 | {
163 | ERROR("usage: %s []", argv[0]);
164 | exit(EXIT_FAILURE);
165 | }
166 | int port = atoi(argv[1]);
167 | if(argc > 2)
168 | HttpHandler::setWWWPath(argv[2]);
169 | // 输出当前进程的 PID,便于调试
170 | INFO("PID: %d", getpid());
171 | // 忽略 SIGPIPE 信号
172 | handleSigpipe();
173 | // 创建线程池
174 | ThreadPool thread_pool(8);
175 |
176 | // 空闲 fd,用于关闭溢出的文件描述符
177 | int idle_fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
178 | int listen_fd = -1;
179 | if((listen_fd = socket_bind_and_listen(port)) == -1)
180 | {
181 | ERROR("Bind %d port failed ! (%s)", port, strerror(errno));
182 | exit(EXIT_FAILURE);
183 | }
184 |
185 | // 声明一个 epoll 实例,该实例将在整个main函数结束时被释放
186 | Epoll epoll(EPOLL_CLOEXEC);
187 | assert(epoll.isEpollValid());
188 | // 将 listen_fd 添加进 epoll 实例
189 | EpollEvent* listen_epollevent = new EpollEvent{listen_fd, nullptr};
190 | epoll.add(listen_fd, listen_epollevent, EPOLLET | EPOLLIN);
191 |
192 | // 开始事件循环
193 | for(;;)
194 | {
195 | // 阻塞等待新的事件
196 | int event_num = epoll.wait(-1);
197 | // 如果报错
198 | if(event_num < 0)
199 | {
200 | // 表示该错误一定不是因为无效的 epoll 导致的
201 | assert(event_num != -2);
202 | // 如果只是中断,则直接重新循环
203 | if(errno == EINTR)
204 | continue;
205 | // 如果是其他异常,则输出信息并终止.
206 | else
207 | FATAL("epoll_wait fail! (%s)", strerror(errno));
208 | }
209 | // 如果什么也没读到,则可能是因为 signal 导致的.例如 SIGINT XD
210 | else if(event_num == 0)
211 | continue;
212 |
213 | // 遍历获取到的事件
214 | for(int i = 0; i < event_num; i++)
215 | {
216 | // 获取事件相关的信息
217 | epoll_event&& event = epoll.getEvent(static_cast(i));
218 | EpollEvent* curr_epoll_event = static_cast(event.data.ptr);
219 |
220 | int fd = curr_epoll_event->fd;
221 |
222 | // 如果当前文件描述符是 listen_fd, 则建立连接
223 | if(fd == listen_fd)
224 | handleNewConnections(&epoll, listen_fd, &idle_fd);
225 | else
226 | handleOldConnection(&epoll, fd, &thread_pool, &event);
227 | }
228 | }
229 | delete listen_epollevent;
230 |
231 | return 0;
232 | }
--------------------------------------------------------------------------------
/HttpHandler.h:
--------------------------------------------------------------------------------
1 | #ifndef HTTPHANDLER_H
2 | #define HTTPHANDLER_H
3 |
4 | #include
5 | #include