diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 1c812cf..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "g++ build and debug active file", - "type": "cppdbg", - "request": "launch", - "program": "${fileDirname}/${fileBasenameNoExtension}", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": false, - "MIMode": "gdb", - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ], - "preLaunchTask": "g++ build active file", - "miDebuggerPath": "/usr/bin/gdb" - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index ab1d403..c255bde 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Introduction -本项目实现了一个并发的http服务器 +本项目实现了一个多线程的Web服务器, 支持Http长短连接,可响应Get请求 ## Enviroment @@ -13,8 +13,8 @@ ## 技术要点 * 利用RAII方法封装了socket文件描述符对象,简化了TCP连接的处理 -* IO复用: 使用epoll边沿触发,非阻塞IO +* 使用epoll ET模式+非阻塞IO * 使用C++11中的线程库实现了线程池,利用队列管理任务 -* 实现了基于升序链表的定时器管理非活动连接 +* 实现了基于升序链表的定时器管理非活动连接,超时释放连接资源 * 实现了一个Buffer类处理应用层需要发送与接收的数据 diff --git a/file/serverarch1.png b/file/serverarch1.png new file mode 100755 index 0000000..932750d Binary files /dev/null and b/file/serverarch1.png differ diff --git a/file/serverarch2.png b/file/serverarch2.png new file mode 100755 index 0000000..ecd5005 Binary files /dev/null and b/file/serverarch2.png differ diff --git a/file/serverarch2_0.png b/file/serverarch2_0.png new file mode 100755 index 0000000..bd4e44f Binary files /dev/null and b/file/serverarch2_0.png differ diff --git a/httpserver/.vscode/launch.json b/httpserver/.vscode/launch.json deleted file mode 100644 index 1c812cf..0000000 --- a/httpserver/.vscode/launch.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "g++ build and debug active file", - "type": "cppdbg", - "request": "launch", - "program": "${fileDirname}/${fileBasenameNoExtension}", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": false, - "MIMode": "gdb", - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ], - "preLaunchTask": "g++ build active file", - "miDebuggerPath": "/usr/bin/gdb" - } - ] -} \ No newline at end of file diff --git a/httpserver/.vscode/settings.json b/httpserver/.vscode/settings.json deleted file mode 100644 index 35ed337..0000000 --- a/httpserver/.vscode/settings.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "files.associations": { - "functional": "cpp", - "cctype": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "array": "cpp", - "atomic": "cpp", - "strstream": "cpp", - "*.tcc": "cpp", - "bitset": "cpp", - "chrono": "cpp", - "complex": "cpp", - "condition_variable": "cpp", - "cstdint": "cpp", - "deque": "cpp", - "list": "cpp", - "unordered_map": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "ratio": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "fstream": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "memory": "cpp", - "mutex": "cpp", - "new": "cpp", - "ostream": "cpp", - "numeric": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "cfenv": "cpp", - "cinttypes": "cpp", - "utility": "cpp", - "typeindex": "cpp", - "typeinfo": "cpp" - } -} \ No newline at end of file diff --git a/httpserver/.vscode/tasks.json b/httpserver/.vscode/tasks.json deleted file mode 100644 index e74a96e..0000000 --- a/httpserver/.vscode/tasks.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "tasks": [ - { - "type": "shell", - "label": "g++ build active file", - "command": "/usr/bin/g++", - "args": [ - "-I .", - "-g", - "-std=c++11", - "${file}", - "${workspaceFolder}/hpserver/KInetAddress.cpp", - "${workspaceFolder}/hpserver/KSocket.cpp", - "${workspaceFolder}/hpserver/KBuffer.cpp", - "${workspaceFolder}/utils/KTimestamp.cpp", - "${workspaceFolder}/thread/KThreadPool.cpp", - - // http - "${workspaceFolder}/http/KHttpResponse.cpp", - "${workspaceFolder}/http/KHttpContext.cpp", - - "-o", - "${fileDirname}/${fileBasenameNoExtension}", - "-lpthread" - ], - "options": { - "cwd": "/usr/bin" - } - } - ], - "version": "2.0.0" -} \ No newline at end of file diff --git a/httpserver/hpserver/KLstTimer.h b/httpserver/hpserver/KLstTimer.h new file mode 100644 index 0000000..da5ea49 --- /dev/null +++ b/httpserver/hpserver/KLstTimer.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include +#include +#include + +class util_timer; // 前向声明 + +struct client_data +{ + int sockfd; + util_timer *timer; +}; + +// 定时器类 +class util_timer +{ +public: + util_timer() : prev(nullptr), next(nullptr) {} + +public: + time_t expire; // 任务的超时时间,这里使用绝对时间 + // 定时器管理的对象 + std::function cb_func; + struct client_data *user_data; + util_timer *prev; + util_timer *next; +}; + +// 定时器链表、 他是一个$$$升序$$$的双向链表 +class sortTimeList +{ +public: + sortTimeList() : head(nullptr), tail(nullptr) {} + ~sortTimeList() + { + } + + // 添加节点,将目标定时器timer添加到链表中 + void add_timer(util_timer *timer) + { + if (timer == nullptr) + { + return; + } + if (head == nullptr) + { + head = tail = timer; + return; + } + // 可以直接插入到头部 + if (timer->expire <= head->expire) + { + timer->next = head; + head->prev = timer; + head = timer; + } + else + { + add_timer(timer, head->next); + } + } + + // 当某个定时器任务发生变化时,调整对应的定时器在链表中的位置。 + // 这个函数只考虑被调整的定时器的超时时间延长的情况,即该定时器需要往链表的尾部移动 + void adjust_timer(util_timer *timer) + { + if (timer == nullptr) + { + return; + } + if (timer->next == nullptr || (timer->expire <= timer->next->expire)) + { + return; + } + + if (timer == head) + { + head = head->next; + head->prev = nullptr; + add_timer(timer, head); + } + else + { + timer->prev->next = timer->next; + timer->next->prev = timer->prev; + add_timer(timer, timer->next); + } + } + + // 将目标定时器timer从链表中移除 + void del_timer(util_timer *timer) { + if(timer == nullptr) + { + return; + } + if((timer == head) && (timer == tail)) + { + delete timer; + head = tail = nullptr; + return; + } + if(timer == head) + { + head = head->next; + head->prev = nullptr; + delete timer; + return; + } + if(timer == tail) + { + tail = tail->prev; + tail->next = nullptr; + delete timer; + return; + } + + } + + // SIGALRM 信号每次被触发就在其信号处理函数(如果使用统一事件源,则是主函数)中 + // 执行一次tick函数,以处理链表上到期的任务 + void tick() + { + if (head == nullptr) + { + return; + } + printf("timer tick\n"); + time_t cur = time(nullptr); + util_timer *tmp = head; + while (tmp != nullptr) + { + if (cur < tmp->expire) + { + break; + } + tmp->cb_func(tmp->user_data); + head = tmp->next; + if (head != nullptr) + { + head->prev = nullptr; + } + delete tmp; + tmp = head; + } + } + +private: + // 一个重载的辅助函数,它被公有的add_timer函数和adjust_timer函数调用, + // 该函数表示将目标定时器timer添加到节点watchdog之后的部分链表中 + void add_timer(util_timer *timer, util_timer *watchdog) + { + assert(watchdog != head); + while (watchdog != nullptr) + { + if (timer->expire <= watchdog->expire) + { + /// + watchdog->prev->next = timer; + timer->prev = watchdog->prev; + timer->next = watchdog; + watchdog->prev = timer; + return; + } + watchdog = watchdog->next; + } + + // 特殊 - 插入尾部即可 + if (watchdog == nullptr) + { + tail->next = timer; + timer->prev = tail; + timer->next = nullptr; + tail = timer; + } + } + +private: + util_timer *head; + util_timer *tail; +}; \ No newline at end of file diff --git a/httpserver/hpserver/KLstTimer2.h b/httpserver/hpserver/KLstTimer2.h new file mode 100644 index 0000000..a6856ab --- /dev/null +++ b/httpserver/hpserver/KLstTimer2.h @@ -0,0 +1,263 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "../utils/KTimestamp.h" + +#define BUFFER_SIZE 64 + +extern const int TIMESLOT = 5; +extern const int NUMBER_TIME = 8; + +namespace kb +{ +int createTimerfd() +{ + // GQ 理解timerfd + int timerfd = ::timerfd_create(CLOCK_MONOTONIC, + TFD_NONBLOCK | TFD_CLOEXEC); + if (timerfd < 0) + { + // LOG_SYSFATAL << "Failed in timerfd_create"; + std::cout << "LOG_SYSFATAL: " + << "Failed in timerfd_create" << std::endl; + // abort(); + } + return timerfd; +} + +struct timespec howMuchTimeFromNow(Timestamp when) +{ + int64_t microseconds = when.microSecondsSinceEpoch() - Timestamp::now().microSecondsSinceEpoch(); + if (microseconds < 100) + { + microseconds = 100; + } + struct timespec ts; + ts.tv_sec = static_cast( + microseconds / Timestamp::kMicroSecondsPerSecond); + ts.tv_nsec = static_cast( + (microseconds % Timestamp::kMicroSecondsPerSecond) * 1000); + return ts; +} + +void readTimerfd(int timerfd, Timestamp now) +{ + uint64_t howmany; + ssize_t n = ::read(timerfd, &howmany, sizeof howmany); + // LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString(); + // + std::cout << "LOG_TRACE: " + << "TimerQueue::handleRead() " << n << " at " << now.toString() << std::endl; + if (n != sizeof howmany) + { + // LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8"; + std::cout << "LOG_ERROR: " + << "TimerQueue::handleRead() reads " << n << " bytes instead of 8" << std::endl; + } +} + +void resetTimerfd(int timerfd, Timestamp expiration) +{ + // wake up loop by timerfd_settime() + struct itimerspec newValue; + struct itimerspec oldValue; + bzero(&newValue, sizeof newValue); + bzero(&oldValue, sizeof oldValue); + newValue.it_value = howMuchTimeFromNow(expiration); + // 设置第一个为哨兵值 + int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue); + if (ret) + { + // LOG_SYSERR << "timerfd_settime()"; + std::cout << "LOG_SYSERR: " + << "timerfd_settime()" << std::endl; + } +} +}; // namespace kb + +//class util_timer; /*前向声明*/ + +// 定时器类 +class util_timer +{ +public: + util_timer() : prev(nullptr), next(nullptr) {} + +public: + time_t expire; // 任务的超时时间,这里使用绝对时间 + std::function cb_func; // 任务回调函数,回调函数处理的客户数据,由定时器的执行者传递给回调函数 + + util_timer *prev; // 指向前一个定时器 + util_timer *next; // 指向下一个定时器 +}; + +// 定时器链表、 他是一个$$$升序$$$、双向链表,且带有头节点和尾节点 +class sort_time_lst +{ +public: + sort_time_lst() : head(nullptr), tail(nullptr) {} + + // 链表被销毁时,删除其中所有的定时器 + ~sort_time_lst() + { + util_timer *tmp = head; + while (tmp) + { + head = tmp->next; + delete tmp; + tmp = head; + } + } + + // 添加节点 将目标定时器timer添加到链表中 + void add_timer(util_timer *timer) + { + if (!timer) + { + return; + } + if (!head) + { + head = tail = timer; + return; + } + // 如果目标定时器的超时时间小于当前链表中所有定时器的超时时间,则把该定时器插入链表头部,作为链表新的头节点,否则就调用重载函数add_timer + if (timer->expire < head->expire) + { + timer->next = head; + head->prev = timer; + head = timer; + return; + } + add_timer(timer, head); + } + + // 当某个定时器任务发生变化时,调整对应的定时器在链表中的位置。这个函数只考虑被调整的定时器的超时时间延长的情况,即该定时器需要往链表的尾部移动 + void adjust_timer(util_timer *timer) + { + if (!timer) + { + return; + } + util_timer *tmp = timer->next; + if (!tmp || (timer->expire < tmp->expire)) + { + return; + } + if (timer == head) + { + head = head->next; + head->prev = NULL; + timer->next = NULL; + add_timer(timer, head); + } + else + { + timer->prev->next = timer->next; + timer->next->prev = timer->prev; + add_timer(timer, timer->next); + } + } + + // 将目标定时器timer从链表中删除 + void del_timer(util_timer *timer) + { + if (!timer) + { + return; + } + if ((timer == head) && (timer == tail)) + { + delete timer; + head = NULL; + tail = NULL; + return; + } + if (timer == head) + { + head = head->next; + head->prev = NULL; + delete timer; + return; + } + if (timer == tail) + { + tail = tail->prev; + tail->next = NULL; + delete timer; + return; + } + timer->prev->next = timer->next; + timer->next->prev = timer->prev; + delete timer; + } + + // SIGALRM 信号每次被触发就在其信号处理函数(如果使用统一事件源,则是主函数)中执行一次tick函数,以处理链表上到期的任务 + void tick() + { + if (!head) + { + return; + } + printf("timer tick\n"); + time_t cur = time(NULL); + util_timer *tmp = head; + while (tmp) + { + if (cur < tmp->expire) + { + break; + } + tmp->cb_func(); + head = tmp->next; + if (head) + { + head->prev = NULL; + } + delete tmp; + tmp = head; + } + } + +private: + // 一个重载的辅助函数,它被公有的add_timer函数和adjust_timer函数调用,该函数表示将目标定时器timer添加到节点lst_head之后的部分链表中 + void add_timer(util_timer *timer, util_timer *lst_head) + { + util_timer *prev = lst_head; + util_timer *tmp = prev->next; + // 遍历lst_head节点之后的部分链表,直到找到一个超时时间大于目标定时器的超时时间节点,并将目标定时器插入该节点中 + while (tmp) + { + if (timer->expire < tmp->expire) + { + prev->next = timer; + timer->next = tmp; + tmp->prev = timer; + timer->prev = prev; + break; + } + prev = tmp; + tmp = tmp->next; + } + + // 特殊情况处理 + // 如果遍历完lst_head节点之后的部分链表,仍未找到超时时间大于目标定时器的超时时间的节点,则将目标定时器插入链表尾部,并把它设置为链表的新尾节点 + if (!tmp) // tmp == nullptr + { + prev->next = timer; + timer->prev = prev; + timer->next = NULL; + // 记录尾节点 + tail = timer; + } + } + +private: + util_timer *head; + util_timer *tail; +}; \ No newline at end of file diff --git a/httpserver/hpserver/lst_timer.h b/httpserver/hpserver/lst_timer.h deleted file mode 100644 index 34bc4f5..0000000 --- a/httpserver/hpserver/lst_timer.h +++ /dev/null @@ -1,184 +0,0 @@ -#pragma once - -#include -#include -#include - -#define BUFFER_SIZE 64 -class util_timer; /*前向声明*/ - -struct client_data{ - sockaddr_in address; - int sockfd; - char buf[BUFFER_SIZE]; - util_timer* timer; -}; - - -// 定时器类 -class util_timer{ -public: - util_timer():prev(nullptr), next(nullptr){} - -public: - time_t expire; // 任务的超时时间,这里使用绝对时间 - void (*cb_func)( client_data* ); // 任务回调函数,回调函数处理的客户数据,由定时器的执行者传递给回调函数 - client_data* user_data; - util_timer* prev; // 指向前一个定时器 - util_timer* next; // 指向下一个定时器 -}; - - -// 定时器链表、 他是一个$$$升序$$$、双向链表,且带有头节点和尾节点 -class sort_time_lst{ -public: - sort_time_lst() : head(nullptr), tail(nullptr){} - - // 链表被销毁时,删除其中所有的定时器 - ~sort_time_lst(){ - util_timer* tmp = head; - while(tmp){ - head = tmp->next; - delete tmp; - tmp = head; - } - } - - // 添加节点 将目标定时器timer添加到链表中 - void add_timer(util_timer* timer){ - if(!timer){ - return; - } - if(!head){ - head = tail = timer; - return; - } - // 如果目标定时器的超时时间小于当前链表中所有定时器的超时时间,则把该定时器插入链表头部,作为链表新的头节点,否则就调用重载函数add_timer - if(timer->expire < head->expire){ - timer->next = head; - head->prev = timer; - head = timer; - return; - } - add_timer(timer, head); - } - - // 当某个定时器任务发生变化时,调整对应的定时器在链表中的位置。这个函数只考虑被调整的定时器的超时时间延长的情况,即该定时器需要往链表的尾部移动 - void adjust_timer(util_timer* timer){ - if( !timer ) - { - return; - } - util_timer* tmp = timer->next; - if( !tmp || ( timer->expire < tmp->expire ) ) - { - return; - } - if( timer == head ) - { - head = head->next; - head->prev = NULL; - timer->next = NULL; - add_timer( timer, head ); - } - else - { - timer->prev->next = timer->next; - timer->next->prev = timer->prev; - add_timer( timer, timer->next ); - } - } - - // 将目标定时器timer从链表中删除 - void del_timer(util_timer* timer){ - if( !timer ) - { - return; - } - if( ( timer == head ) && ( timer == tail ) ) - { - delete timer; - head = NULL; - tail = NULL; - return; - } - if( timer == head ) - { - head = head->next; - head->prev = NULL; - delete timer; - return; - } - if( timer == tail ) - { - tail = tail->prev; - tail->next = NULL; - delete timer; - return; - } - timer->prev->next = timer->next; - timer->next->prev = timer->prev; - delete timer; - } - - // SIGALRM 信号每次被触发就在其信号处理函数(如果使用统一事件源,则是主函数)中执行一次tick函数,以处理链表上到期的任务 - void tick(){ - if( !head ) - { - return; - } - printf("timer tick\n"); - time_t cur = time( NULL ); - util_timer* tmp = head; - while( tmp ) - { - if( cur < tmp->expire ) - { - break; - } - tmp->cb_func( tmp->user_data ); - head = tmp->next; - if( head ) - { - head->prev = NULL; - } - delete tmp; - tmp = head; - } - } - -private: - // 一个重载的辅助函数,它被公有的add_timer函数和adjust_timer函数调用,该函数表示将目标定时器timer添加到节点lst_head之后的部分链表中 - void add_timer(util_timer* timer, util_timer* lst_head){ - util_timer* prev = lst_head; - util_timer* tmp = prev->next; - // 遍历lst_head节点之后的部分链表,直到找到一个超时时间大于目标定时器的超时时间节点,并将目标定时器插入该节点中 - while(tmp){ - if(timer->expire < tmp->expire){ - prev->next = timer; - timer->next = tmp; - tmp->prev = timer; - timer->prev = prev; - break; - } - prev = tmp; - tmp = tmp->next; - } - - // 特殊情况处理 - // 如果遍历完lst_head节点之后的部分链表,仍未找到超时时间大于目标定时器的超时时间的节点,则将目标定时器插入链表尾部,并把它设置为链表的新尾节点 - if(!tmp) // tmp == nullptr - { - prev->next = timer; - timer->prev = prev; - timer->next = NULL; - // 记录尾节点 - tail = timer; - } - - } - -private: - util_timer* head; - util_timer* tail; -}; \ No newline at end of file diff --git a/httpserver/httpserver b/httpserver/httpserver index f40778a..2a81fc6 100755 Binary files a/httpserver/httpserver and b/httpserver/httpserver differ diff --git a/httpserver/httpserver.cpp b/httpserver/httpserver.cpp index e3422fc..0cc78de 100644 --- a/httpserver/httpserver.cpp +++ b/httpserver/httpserver.cpp @@ -20,12 +20,18 @@ #include "utils/KIcon.h" +// add timer +#include "utils/KTimestamp.h" +#include "hpserver/KLstTimer2.h" + using namespace kb; /// Http server 版本-1 using LT /// const int timeoutMs = 5000; +static sort_time_lst timer_lst; + void selectResponse(const HttpRequest &req, HttpResponse *resp) { if (req.path() == "/") @@ -124,9 +130,21 @@ void httpOnRequest(int epfd, int clntfd) } } + +// 定时器回调函数,它删除非活动连接socket上的注册事件,并关闭之 +void cb_func(int epfd, int clntfd) +{ + epoll_ctl(epfd, EPOLL_CTL_DEL, clntfd, 0); + close(clntfd); + printf("close fd %d\n", clntfd); +} + int main() { + // 创建Timerfd用于处理定时任务 + int timerfd = createTcpSocket(); + // 添加线程池 来处理http请求 ThreadPool pool("Test"); pool.start(5); @@ -156,6 +174,13 @@ int main() std::cerr << "epoll_ctl error" << std::endl; } + event.data.fd = timerfd; + event.events = EPOLLIN; + if (::epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &event) < 0) + { + std::cerr << "epoll_ctl error" << std::endl; + } + // 保存epoll_wait调用后的活动事件 std::vector events(16); @@ -185,6 +210,14 @@ int main() std::cerr << "epoll_ctl error" << std::endl; } std::cout << "connect client " << clntfd << std::endl; + + /// 添加定时器 + util_timer *timer = new util_timer; + timer->cb_func = std::bind(cb_func, epfd, clntfd); + time_t cur = time(nullptr); + timer->expire = cur + NUMBER_TIME * TIMESLOT; + timer_lst.add_timer(timer); + resetTimerfd(timerfd, Timestamp(NUMBER_TIME * TIMESLOT * 1000000)); } else if (events[i].data.fd >= 0 && events[i].data.fd == clntfd) { @@ -195,6 +228,12 @@ int main() // 版本一设定: 每次触发就是当作一个短期task,执行玩后就退出 // 版本二设定: 利用map 来维护fd和thread的对应关系,thread只有在fd销毁时才算完成任务,否则一直等待 } + else if (events[i].data.fd == timerfd) + { + Timestamp now(Timestamp::now()); + readTimerfd(timerfd, now); + timer_lst.tick(); + } } } else if (numEvents == 0) diff --git a/httpserver/httpserverLT.cpp b/httpserver/httpserverLT.cpp new file mode 100644 index 0000000..e3422fc --- /dev/null +++ b/httpserver/httpserverLT.cpp @@ -0,0 +1,211 @@ +#include +#include + +#include "hpserver/KInetAddress.h" +#include "hpserver/KSocket.h" +#include "hpserver/KBuffer.h" + +#include "thread/KThreadPool.h" + +// for http +#include "http/KHttpContext.h" +#include "http/KHttpRequest.h" +#include "http/KHttpResponse.h" + +#include +#include +#include +#include +#include + +#include "utils/KIcon.h" + +using namespace kb; + +/// Http server 版本-1 using LT /// + +const int timeoutMs = 5000; + +void selectResponse(const HttpRequest &req, HttpResponse *resp) +{ + if (req.path() == "/") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("text/html"); + resp->addHeader("Server", "Muduo"); + string now = Timestamp::now().toFormattedString(); + resp->setBody("This is title" + "

Hello

Now is " + + now + + ""); + } + else if (req.path() == "/favicon.ico") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("image/png"); + resp->setBody(string(favicon, sizeof favicon)); + } + else if (req.path() == "/hello") + { + resp->setStatusCode(HttpResponse::k200Ok); + resp->setStatusMessage("OK"); + resp->setContentType("text/plain"); + resp->addHeader("Server", "Muduo"); + resp->setBody("hello, world!\n"); + } + else + { + resp->setStatusCode(HttpResponse::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); + } +} + +void httpOnRequest(int epfd, int clntfd) +{ + std::cout << "tid: " << std::this_thread::get_id() + << std::endl; + // 全部委托给新的线程,注意销毁 + // 处理client即可 + Buffer buffer_; + int saveErrno = 0; + int str_len = buffer_.readFd(clntfd, &saveErrno); + if (str_len == 0) + { + epoll_ctl(epfd, EPOLL_CTL_DEL, clntfd, NULL); + close(clntfd); + std::cout << "close client " << clntfd << std::endl; + clntfd = -1; + return; + } + else + { + // // echo事件 + // int nw = ::write(clntfd, buffer_.peek(), buffer_.readableBytes()); + // if (nw > 0) + // { + // buffer_.retrieve(nw); + // } + // std::cout << "write data " << clntfd << std::endl; + // 处理http请求 + HttpContext *context = new HttpContext(); + Timestamp receiveTime(Timestamp::now()); + if (!context->parseRequest(&buffer_, receiveTime)) + { + string badReq("HTTP/1.1 400 Bad Request\r\n\r\n"); + int nwrote = ::write(clntfd, badReq.data(), badReq.size()); + assert(nwrote == badReq.size()); + } + + if (context->gotAll()) + { + HttpRequest req = context->request(); + + const string &connection = req.getHeader("Connection"); + // 判断是否是长连接 + bool close = connection == "close" || + (req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive"); + HttpResponse response(close); + + selectResponse(req, &response); + Buffer buf; + response.appendToBuffer(&buf); + string temp = buf.retrieveAsString(); + int nwrote = ::write(clntfd, temp.data(), temp.size()); + assert(nwrote == temp.size()); + if (response.closeConnection()) + { + // + } + context->reset(); + } + } +} + +int main() +{ + + // 添加线程池 来处理http请求 + ThreadPool pool("Test"); + pool.start(5); + + int sockfd = createTcpSocket(); + InetAddress localaddr(9981); + + Socket server(sockfd); + server.bindAddress(localaddr); + server.listen(); + + // 储存客户端地址和fd + int clntfd = -1; + InetAddress peeraddr(0); + + // + int epfd = epoll_create1(EPOLL_CLOEXEC); + + struct epoll_event event; + memset(&event, 0, sizeof event); + + // 然后设置所要关注事件的参数 + event.data.fd = sockfd; + event.events = EPOLLIN; + if (::epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event) < 0) + { + std::cerr << "epoll_ctl error" << std::endl; + } + + // 保存epoll_wait调用后的活动事件 + std::vector events(16); + + for (;;) + { + int numEvents = ::epoll_wait(epfd, events.data(), events.size(), timeoutMs); + + // 事件分发处理 + if (numEvents > 0) + { + std::cout << numEvents << " events happended" << std::endl; + + if (numEvents == events.size()) + { + events.resize(events.size() * 2); + } + + for (int i = 0; i < numEvents; ++i) + { + if (events[i].data.fd == sockfd) + { + clntfd = server.accept(&peeraddr); + event.data.fd = clntfd; + event.events = EPOLLIN; + if (::epoll_ctl(epfd, EPOLL_CTL_ADD, clntfd, &event) < 0) + { + std::cerr << "epoll_ctl error" << std::endl; + } + std::cout << "connect client " << clntfd << std::endl; + } + else if (events[i].data.fd >= 0 && events[i].data.fd == clntfd) + { + std::cout << "assign task to client " << clntfd << std::endl; + pool.run(std::bind(httpOnRequest, epfd, clntfd)); + // httpOnRequest(epfd, clntfd); + // 让新的线程处理读取 然后在 response + // 版本一设定: 每次触发就是当作一个短期task,执行玩后就退出 + // 版本二设定: 利用map 来维护fd和thread的对应关系,thread只有在fd销毁时才算完成任务,否则一直等待 + } + } + } + else if (numEvents == 0) + { + std::cout << "nothing happened" << std::endl; + } + else + { + std::cout << "Error: epoll_wait" << std::endl; + } + } + close(epfd); + return 0; +} diff --git "a/\346\224\271\350\277\233\346\200\235\350\267\257.md" "b/\346\224\271\350\277\233\346\200\235\350\267\257.md" new file mode 100644 index 0000000..725381b --- /dev/null +++ "b/\346\224\271\350\277\233\346\200\235\350\267\257.md" @@ -0,0 +1,36 @@ +### Http version 1 + +对于每一个http请求(client),会有一个fd去处理,v1版本的做法是,每当请求client到达时(即epoll检测到活动事件时),将此活动fd的任务转交给线程池中的线程去处理 + +版本一的框架如下图所示: + +​ ![serverarch1](file/serverarch1.png) + + + +### Http version 2 + +这里每次有新任务到达时,就将新任务转交给线程,然后把程序的控制权转交到Epoll上,但是当在高并发状态,有很多活动的事件fd, 这是频繁的转交线程也会造成很大的开销 + +所以Version的初步设想是,每当有一个client到达时,利用线程池中的线程去负责这个client的fd, 然后由主线程中的epoll去通知这个线程何时开始处理任务,这样单个线程就可长期监视一个fd, 主线程就不用像Version 1一样,需要频繁地分配任务 + +> Note: 主线程应该如何通知单个线程要开始处理任务了呢? +> +> 1. 也就是采用线程间的通信方式(临界区、信号量、事件信号、互斥量) +> 2. 采用eventfd 的可读事件作为线程间的唤醒机制(类似于无名管道?) + +由此,初步设想已经完成,但是细想发现又不是很现实,比如,一个线程去负责一个client,那在高并发状态下,这是不可能完成的任务, + +简单想一想,让一个线程负责多个文件描述符很简单,用一个map维护即可,这样就可让单个线程 负责 多个文件描述符 + +此时的框架图如下图所示: + +![serverarch2_0](file/serverarch2_0.png) + +但是直接这样就 单个线程负责多个文件描述符 是不现实的,比如,A线程负责了 a3和a4两个文件描述符,如果主线程应该如何通知 A线程 去处理 a3和a4两个活动fd呢,这里虽然可以想办法实现(将发生活动事件的fd 作为数据写入eventfd,然后再唤醒线程),但是有没有更好的办法呢。 + +进一步的思考时 如何让单个线程彻头彻尾的负责多个文件描述符,彻尾很简单,负责文件描述符的close即可。彻头就是要去负责监视文件描述符的活动事件,也就是由主线程通知A线程,你应该负责a3 这个文件描述符,A线程中也应该创建epoll对象,将a3需要监听的事件加入进来。往后,可继续添加多个文件描述符。 其实这一步相当于对之前的思路做了一个调整,之前是想让主线程去监听所有事件,现在主线程只负责accept请求,然后将accept的对象的控制权转交给其余线程,至此,版本二的设想已经基本完成 + +版本二的基本框架如下图所示: + +![serverarch2](file/serverarch2.png) \ No newline at end of file