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/KEventLoop.cpp b/httpserver/KEventLoop.cpp new file mode 100644 index 0000000..1e01d43 --- /dev/null +++ b/httpserver/KEventLoop.cpp @@ -0,0 +1,87 @@ +#include "KEventLoop.h" +#include "poller/KChannel.h" +#include "poller/KDefaultPoller.h" +#include +#include + +using namespace kback; + +const int kPollTimeMs = 3000; + +EventLoop::EventLoop() + : looping_(false), + quit_(false), + poller_(Poller::newDefaultPoller(this)) +{ + std::cout << "LOG_TRACE: " + << "EventLoop created " << this << std::endl; +} + +EventLoop::~EventLoop() +{ + assert(!looping_); +} + +void EventLoop::loop() +{ + assert(!looping_); + looping_ = true; + quit_ = false; + + while (!quit_) + { + activeChannels_.clear(); + pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); + for (auto it = activeChannels_.begin(); it != activeChannels_.end(); ++it) + { + (*it)->handleEvent(pollReturnTime_); + } + doPendingFunctors(); + } + std::cout << "LOG_TRACE: " + << "EventLoop " << this << " stop looping" << std::endl; + looping_ = false; +} + +void EventLoop::quit() +{ + quit_ = true; +} + +// channel 中注册了事件,需要更新 +// 将更新的工作转交给loop, 然后在loop中 同步poll 中的channel +void EventLoop::updateChannel(Channel *channel) +{ + assert(channel->ownerLoop() == this); + // EventLoop 实际上不负责更新channel + poller_->updateChannel(channel); +} + +void EventLoop::removeChannel(Channel *channel) +{ + assert(channel->ownerLoop() == this); + poller_->removeChannel(channel); +} + +// 执行装载的的回调函数 +void EventLoop::doPendingFunctors() +{ + // 遍历执行回调函数 + for (auto &functor : pendingFunctors_) + { + functor(); + } + pendingFunctors_.clear(); +} + +// !这个函数是在loop启动之前调用的,用于设置channel +void EventLoop::runInLoop(const Functor &cb) +{ + cb(); +} + +// queueInLoop 是public的,不一定要被runInLoop调用,也可以被直接调用 +void EventLoop::queueInLoop(const Functor &cb) +{ + pendingFunctors_.push_back(cb); +} diff --git a/httpserver/KEventLoop.h b/httpserver/KEventLoop.h new file mode 100644 index 0000000..ccb4a9f --- /dev/null +++ b/httpserver/KEventLoop.h @@ -0,0 +1,50 @@ +#pragma once + +#include "utils/Knoncopyable.h" +#include "utils/KTimestamp.h" +#include +#include +#include +#include +#include + +namespace kback +{ + +class Channel; +class Poller; + +class EventLoop : noncopyable +{ +public: + typedef std::function Functor; + + EventLoop(); + ~EventLoop(); + + void loop(); + void quit(); + + void runInLoop(const Functor &cb); + void queueInLoop(const Functor &cb); + + // internal use only + void updateChannel(Channel *channel); + void removeChannel(Channel *channel); + +private: + bool looping_; + + typedef std::vector ChannelList; + ChannelList activeChannels_; + + std::unique_ptr poller_; + + bool quit_; // atomic + Timestamp pollReturnTime_; + + void doPendingFunctors(); + std::vector pendingFunctors_; + +}; +} // namespace kback diff --git a/httpserver/chatserver.cpp b/httpserver/chatserver.cpp deleted file mode 100755 index f6967b8..0000000 --- a/httpserver/chatserver.cpp +++ /dev/null @@ -1,286 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include // contains the system call(alarm) -#include -#include -#include -#include -#include -#include -#include -#include "hpserver/ThreadPool.h" -#include "hpserver/lst_timer.h" -#include - - -#define FD_LIMIT 65535 // 文件描述符数量限制 -#define MAX_EVENT_NUMBER 1024 -#define TIMESLOT 5 -#define NUMBER_TIME 8 -#define USER_LIMIT // 最大用户数量 - -static int pipefd[2]; -static sort_time_lst timer_lst; -static int epollfd = 0; - -static std::vector active_user; - -int setnonblocking(int fd){ - int old_option = fcntl(fd, F_GETFL); - int new_option = old_option | O_NONBLOCK; - fcntl(fd, F_SETFL, new_option); - return old_option; -} - -// 边缘触发方式一定要采用非阻塞io -int addfd(int epollfd, int fd){ - epoll_event event; - event.data.fd = fd; - event.events = EPOLLIN | EPOLLET; - epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); - setnonblocking(fd); -} - -void sig_handler(int sig){ - int save_errno = errno; - int msg = sig; - send(pipefd[1], (char *)&msg, 1, 0); - errno = save_errno; -} - -// -void addsig(int sig){ - struct sigaction sa; - memset(&sa, '\0', sizeof(sa)); - sa.sa_handler = sig_handler; - sa.sa_flags |= SA_RESTART; - sigfillset(&sa.sa_mask); - assert(sigaction(sig, &sa, NULL) != -1); -} - -void timer_handler(){ - // 定时处理任务,实际上就是调用tick函数 - timer_lst.tick(); - // 因为一次alarm调用只会引起一次SIGALRM信号,所以我们需要重新定时,以不断触发SIGALRM信号 - alarm(TIMESLOT); -} - -// 定时器回调函数,它删除非活动连接socket上的注册事件,并关闭之 -void cb_func(client_data* user_data){ - epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); - assert(user_data); - close(user_data->sockfd); - printf("close fd %d\n", user_data->sockfd); -} - -ThreadPool pool(10); -std::vector< std::future > results; - -int main(int argc, char * argv[]) -{ - if( argc <= 2 ) - { - printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); - return 1; - } - const char* ip = argv[1]; - int port = atoi(argv[2]); - - int ret = 0; - struct sockaddr_in address; - bzero( &address, sizeof( address ) ); - address.sin_family = AF_INET; - inet_pton( AF_INET, ip, &address.sin_addr ); - address.sin_port = htons( port ); - - int listenfd = socket( PF_INET, SOCK_STREAM, 0 ); - assert( listenfd >= 0 ); - - ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) ); - assert( ret != -1 ); - - ret = listen( listenfd, 5 ); - assert( ret != -1 ); - - epoll_event events[MAX_EVENT_NUMBER]; - int epollfd = epoll_create(5); - assert(epollfd != -1); - addfd(epollfd, listenfd); - - ret = socketpair( PF_UNIX, SOCK_STREAM, 0, pipefd ); - assert( ret != -1 ); - setnonblocking( pipefd[1] ); - addfd( epollfd, pipefd[0] ); - - // add all the interesting signals here - addsig( SIGALRM ); - addsig( SIGTERM ); - bool stop_server = false; - - client_data* users = new client_data[FD_LIMIT]; - bool timeout = false; - alarm( TIMESLOT ); - - while( !stop_server ) - { - int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 ); - if ( ( number < 0 ) && ( errno != EINTR ) ) - { - printf( "epoll failure\n" ); - break; - } - - for ( int i = 0; i < number; i++ ) - { - int sockfd = events[i].data.fd; - // 如果就绪的fd是listenfd, 则处理新的连接 - if( sockfd == listenfd ) - { - struct sockaddr_in client_address; - socklen_t client_addrlength = sizeof( client_address ); - int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength ); - - if(connfd < 0){ - printf("errno is: %d\n", errno); - continue; - } - - // 记录当前处理的连接 - active_user.push_back(connfd); - - // 如果请求太多,则关闭新到的连接 - - addfd( epollfd, connfd ); - users[connfd].address = client_address; - users[connfd].sockfd = connfd; - util_timer* timer = new util_timer; - timer->user_data = &users[connfd]; - timer->cb_func = cb_func; - time_t cur = time( NULL ); - timer->expire = cur + NUMBER_TIME * TIMESLOT; - users[connfd].timer = timer; - timer_lst.add_timer( timer ); - } - else if( ( sockfd == pipefd[0] ) && ( events[i].events & EPOLLIN ) ) - { - int sig; - char signals[1024]; - ret = recv( pipefd[0], signals, sizeof( signals ), 0 ); - if( ret == -1 ) - { - // handle the error - continue; - } - else if( ret == 0 ) - { - continue; - } - else - { - for( int i = 0; i < ret; ++i ) - { - switch( signals[i] ) - { - case SIGALRM: - { - timeout = true; - break; - } - case SIGTERM: - { - stop_server = true; - } - } - } - } - } - // 处理客户连接上接收到的数据 - else if( events[i].events & EPOLLIN ) - { - memset( users[sockfd].buf, '\0', BUFFER_SIZE ); - ret = recv( sockfd, users[sockfd].buf, BUFFER_SIZE-1, 0 ); - printf( "get %d bytes of client data %s from %d\n", ret, users[sockfd].buf, sockfd ); - util_timer* timer = users[sockfd].timer; - if( ret < 0 ) - { - // 如果发生读错误,则关闭连接,并移除其对应的的定时器 - if( errno != EAGAIN ) - { - cb_func( &users[sockfd] ); - for(int j = 0; j < active_user.size(); j++){ - if(active_user[j] == sockfd) active_user[j] = -1; - } - if( timer ) - { - timer_lst.del_timer( timer ); - } - } - } - else if( ret == 0 ) - { - // 如果对方已经关闭连接,则我们也关闭连接,并移除对应的定时器 - cb_func( &users[sockfd] ); - for(int j = 0; j < active_user.size(); j++){ - if(active_user[j] == sockfd) active_user[j] = -1; - } - if( timer ) - { - timer_lst.del_timer( timer ); - } - } - else - { - // send( sockfd, users[sockfd].buf, BUFFER_SIZE-1, 0 ); - // 拷贝当前信息作为临时变量,避免中途存在user加入,也会收到消息 - char* tempbuf = users[sockfd].buf; - std::vector temp_active_user = active_user; - results.emplace_back( - pool.enqueue([sockfd, tempbuf, temp_active_user]{ - for(int j = 0; j < temp_active_user.size(); j++){ - if(temp_active_user[j] == sockfd || temp_active_user[j] == -1) continue; - write(temp_active_user[j], tempbuf, BUFFER_SIZE-1); - } - return sockfd; - }) - ); - - // 如果某个客户连接上有数据可读,则我们需要调整该连接对应的定时器,以延迟该连接被关闭的时间 - if( timer ) - { - time_t cur = time( NULL ); - timer->expire = cur + NUMBER_TIME * TIMESLOT; - printf( "adjust timer once\n" ); - timer_lst.adjust_timer( timer ); - } - - } - } - else - { - // others - } - } - - if( timeout ) - { - timer_handler(); - timeout = false; - } - } - - for (auto && result : results) { - std::cout << result.get() << ' ' << std::endl; - } - - close( listenfd ); - close( pipefd[1] ); - close( pipefd[0] ); - delete [] users; - return 0; - -} \ No newline at end of file diff --git a/httpserver/hpserver/KInetAddress.cpp b/httpserver/hpserver/KInetAddress.cpp deleted file mode 100644 index e04ce53..0000000 --- a/httpserver/hpserver/KInetAddress.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "KInetAddress.h" -#include // memset - -using namespace kb; - - -InetAddress::InetAddress(uint16_t port) -{ - memset(&addr_, 0, sizeof addr_); - addr_.sin_family = AF_INET; - addr_.sin_addr.s_addr = INADDR_ANY; - addr_.sin_port = htons(port); -} - diff --git a/httpserver/hpserver/KInetAddress.h b/httpserver/hpserver/KInetAddress.h deleted file mode 100644 index 126e943..0000000 --- a/httpserver/hpserver/KInetAddress.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include - -namespace kb -{ -class InetAddress -{ -private: - struct sockaddr_in addr_; - -public: - explicit InetAddress(uint16_t port); - - // InetAddress(const std::string &ip, uint16_t port); - - // InetAddress(const struct sockaddr_in &addr) : addr_(addr) {} - - // 获取或者设置 InetAddress 所管理的 网络地址和端口 - const struct sockaddr_in &getSockAddrInet() const { return addr_; } - void setSockAddrInet(const struct sockaddr_in &addr) { addr_ = addr; } -}; - -} // namespace kb \ No newline at end of file diff --git a/httpserver/hpserver/KSocket.cpp b/httpserver/hpserver/KSocket.cpp deleted file mode 100644 index 0dcf6eb..0000000 --- a/httpserver/hpserver/KSocket.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "KSocket.h" -#include // close -#include "KInetAddress.h" -#include // memset - -using namespace kb; - -void kb::setNonBlockAndCloseOnExec(int sockfd) -{ - // non-block - int flags = ::fcntl(sockfd, F_GETFL, 0); - flags |= O_NONBLOCK; - int ret = ::fcntl(sockfd, F_SETFL, flags); - - // close-on-exec - flags = ::fcntl(sockfd, F_GETFD, 0); - flags |= FD_CLOEXEC; - ret = ::fcntl(sockfd, F_SETFD, flags); -} - - -int kb::createTcpSocket() -{ - int sockfd = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sockfd < 0) - { - std::cout << "LOG_SYSFATAL: " - << "Socket::createTcpSocket" << std::endl; - } - return sockfd; -} - -Socket::~Socket() -{ - if (::close(sockfd_) < 0) - { - std::cout << "LOG_SYSERR: " - << "Socket::close" << std::endl; - } -} - -void Socket::bindAddress(const InetAddress &localaddr) -{ - struct sockaddr_in myaddr = localaddr.getSockAddrInet(); - int ret = ::bind(sockfd_, (sockaddr *)&myaddr, sizeof myaddr); - if (ret < 0) - { - std::cout << "LOG_SYSFATAL: " - << "Socket::bindAddress" << std::endl; - } -} - -void Socket::listen() -{ - int ret = ::listen(sockfd_, SOMAXCONN); - if (ret < 0) - { - std::cout << "LOG_SYSFATAL: " - << "Socket::listen" << std::endl; - } -} - -// 完成accpet -int Socket::accept(InetAddress *peeraddr) -{ - struct sockaddr_in caddr; // client addr - socklen_t caddr_len = sizeof caddr; - memset(&caddr, 0, caddr_len); - int connfd = ::accept(sockfd_, (struct sockaddr *)&caddr, &caddr_len); - if (connfd < 0) - { - std::cout << "LOG_SYSERR: " << "Socket::accept" << std::endl; - } - if(connfd >= 0) - { - peeraddr->setSockAddrInet(caddr); - } - return connfd; -} \ No newline at end of file diff --git a/httpserver/hpserver/KSocket.h b/httpserver/hpserver/KSocket.h deleted file mode 100644 index 9a66860..0000000 --- a/httpserver/hpserver/KSocket.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -namespace kb -{ - -void setNonBlockAndCloseOnExec(int sockfd); - -int createTcpSocket(); - -class InetAddress; - -// 新建了一个Socket类,利用RAII方法将socket文件描述符对象的生命期严格绑定, -// 使得Tcp socket的创建的销毁更加简单 -class Socket -{ -private: - const int sockfd_; - -public: - explicit Socket(int sockfd) : sockfd_(sockfd) {} - ~Socket(); - - int fd() const { return sockfd_; } - - // 服务端建立连接的三个流程 - void bindAddress(const InetAddress &localaddr); - void listen(); - - int accept(InetAddress *peeraddr); - // Tcp的一些参数设置 -}; - -} // namespace kb \ 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/http/KHttpContext.cpp b/httpserver/http/KHttpContext.cpp index b693687..7b737d7 100644 --- a/httpserver/http/KHttpContext.cpp +++ b/httpserver/http/KHttpContext.cpp @@ -1,7 +1,7 @@ -#include "../hpserver/KBuffer.h" +#include "../tcp/KBuffer.h" #include "KHttpContext.h" -using namespace kb; +using namespace kback; bool HttpContext::processRequestLine(const char *begin, const char *end) { diff --git a/httpserver/http/KHttpContext.h b/httpserver/http/KHttpContext.h index d0a8f3b..1f8d391 100644 --- a/httpserver/http/KHttpContext.h +++ b/httpserver/http/KHttpContext.h @@ -4,7 +4,7 @@ // 一个用于解析Http内容的类 -namespace kb +namespace kback { class Buffer; diff --git a/httpserver/http/KHttpRequest.h b/httpserver/http/KHttpRequest.h index cc180cc..0d9479c 100644 --- a/httpserver/http/KHttpRequest.h +++ b/httpserver/http/KHttpRequest.h @@ -7,7 +7,7 @@ #include #include -namespace kb +namespace kback { class HttpRequest : public copyable @@ -188,4 +188,4 @@ class HttpRequest : public copyable std::map headers_; }; -} // namespace kb +} // namespace kback diff --git a/httpserver/http/KHttpResponse.cpp b/httpserver/http/KHttpResponse.cpp index 3a2cfa1..0c77af5 100644 --- a/httpserver/http/KHttpResponse.cpp +++ b/httpserver/http/KHttpResponse.cpp @@ -1,9 +1,9 @@ #include "KHttpResponse.h" -#include "../hpserver/KBuffer.h" +#include "../tcp/KBuffer.h" #include -using namespace kb; +using namespace kback; void HttpResponse::appendToBuffer(Buffer *output) const { diff --git a/httpserver/http/KHttpResponse.h b/httpserver/http/KHttpResponse.h index 23152b6..389e389 100644 --- a/httpserver/http/KHttpResponse.h +++ b/httpserver/http/KHttpResponse.h @@ -3,7 +3,7 @@ #include -namespace kb +namespace kback { class Buffer; @@ -72,4 +72,4 @@ class HttpResponse : public copyable string body_; }; -} // namespace kb +} // namespace kback diff --git a/httpserver/http/KHttpServer.cpp b/httpserver/http/KHttpServer.cpp new file mode 100644 index 0000000..07861cc --- /dev/null +++ b/httpserver/http/KHttpServer.cpp @@ -0,0 +1,80 @@ +#include "KHttpServer.h" +#include +#include "../utils/KCallbacks.h" + +#include "KHttpContext.h" +#include "KHttpRequest.h" +#include "KHttpResponse.h" + +using namespace kback; + +void defaultHttpCallback(const HttpRequest &, HttpResponse *resp) +{ + resp->setStatusCode(HttpResponse::k404NotFound); + resp->setStatusMessage("Not Found"); + resp->setCloseConnection(true); +} + +HttpServer::HttpServer(EventLoop *loop, + const InetAddress &listenAddr, + const string &name) : server_(loop, listenAddr, name), + httpCallback_(defaultHttpCallback) +{ + server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, _1)); + server_.setMessageCallback(std::bind(&HttpServer::onMessage, this, _1, _2, _3)); +} + +void HttpServer::start() +{ + std::cout << "LOG_WARN: " + << "HttpServer[" << server_.name() + << "] starts listenning on " << server_.ipPort() << std::endl; + // 启动TcpServer, 开始监听端口 + server_.start(); +} + +void HttpServer::onConnection(const TcpConnectionPtr &conn) +{ + if (conn->connected()) + { + conn->setContext(HttpContext()); + } +} + +// 设置为TcpConnection的messageCallback_ +void HttpServer::onMessage(const TcpConnectionPtr &conn, + Buffer *buf, + Timestamp receiveTime) +{ + HttpContext *context = boost::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 string &connection = req.getHeader("Connection"); + bool close = connection == "close" || + (req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive"); + HttpResponse response(close); + httpCallback_(req, &response); + Buffer buf; + response.appendToBuffer(&buf); + // conn->send(&buf); + string temp = buf.retrieveAsString(); + conn->send(temp); + if (response.closeConnection()) + { + conn->shutdown(); + } +} \ No newline at end of file diff --git a/httpserver/http/KHttpServer.h b/httpserver/http/KHttpServer.h new file mode 100644 index 0000000..594f0bc --- /dev/null +++ b/httpserver/http/KHttpServer.h @@ -0,0 +1,47 @@ +#pragma once +#include "../tcp/KTcpServer.h" +#include "../utils/Knoncopyable.h" + +namespace kback +{ + +class HttpRequest; +class HttpResponse; + +/// A simple embeddable HTTP server designed for report status of a program. +/// It is not a fully HTTP 1.1 compliant server, but provides minimum features +/// that can communicate with HttpClient and Web browser. +/// It is synchronous, just like Java Servlet. +class HttpServer : noncopyable +{ +public: + typedef std::function + HttpCallback; + + HttpServer(EventLoop *loop, + const InetAddress &listenAddr, + const string &name); + + EventLoop *getLoop() const { return server_.getLoop(); } + + /// Not thread safe, callback be registered before calling start(). + void setHttpCallback(const HttpCallback &cb) + { + httpCallback_ = cb; + } + + 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 kback \ No newline at end of file diff --git a/httpserver/utils/KIcon.h b/httpserver/http/KIcons.h similarity index 99% rename from httpserver/utils/KIcon.h rename to httpserver/http/KIcons.h index e3eff89..97325f6 100644 --- a/httpserver/utils/KIcon.h +++ b/httpserver/http/KIcons.h @@ -1,6 +1,6 @@ #pragma once -char favicon[555] = { +extern char favicon[555] = { '\x89', 'P', 'N', 'G', '\xD', '\xA', '\x1A', '\xA', '\x0', '\x0', '\x0', '\xD', 'I', 'H', 'D', 'R', '\x0', '\x0', '\x0', '\x10', '\x0', '\x0', '\x0', '\x10', diff --git a/httpserver/http/test_HttpServer b/httpserver/http/test_HttpServer new file mode 100755 index 0000000..81e3fdf Binary files /dev/null and b/httpserver/http/test_HttpServer differ diff --git a/httpserver/httpserver b/httpserver/httpserver deleted file mode 100755 index f40778a..0000000 Binary files a/httpserver/httpserver and /dev/null differ diff --git a/httpserver/httpserver.cpp b/httpserver/httpserver.cpp deleted file mode 100644 index e3422fc..0000000 --- a/httpserver/httpserver.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#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("Codestin Search App" - "

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/httpserver/httpserverET b/httpserver/httpserverET deleted file mode 100755 index 91a6408..0000000 Binary files a/httpserver/httpserverET and /dev/null differ diff --git a/httpserver/httpserverET.cpp b/httpserver/httpserverET.cpp deleted file mode 100644 index b6dfad0..0000000 --- a/httpserver/httpserverET.cpp +++ /dev/null @@ -1,227 +0,0 @@ -#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" -#include - -using namespace kb; - -/// Http server 版本-1 using ET /// - -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("Codestin Search App" - "

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; - for (;;) - { - // 全部委托给新的线程,注意销毁 - // 处理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 if (str_len < 0) - { - assert(errno == saveErrno); - if (saveErrno == EAGAIN) - { - break; - } - } - 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(); - } - } - } - std::cout << "Quit tid: " << std::this_thread::get_id() - << std::endl; -} - -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); - - setNonBlockAndCloseOnExec(sockfd); - // 然后设置所要关注事件的参数 - 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); - setNonBlockAndCloseOnExec(clntfd); - event.data.fd = clntfd; - event.events = EPOLLIN | EPOLLET; - 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/httpserver/poller/KChannel.cpp b/httpserver/poller/KChannel.cpp new file mode 100644 index 0000000..22451ef --- /dev/null +++ b/httpserver/poller/KChannel.cpp @@ -0,0 +1,108 @@ + +#include "../KEventLoop.h" +#include "KChannel.h" + +#include +#include +#include +#include "../utils/KTypes.h" + +using namespace kback; + +const int Channel::kNoneEvent = 0; +const int Channel::kReadEvent = POLLIN | POLLPRI; +const int Channel::kWriteEvent = POLLOUT; + +Channel::Channel(EventLoop *loop, int fdArg) + : loop_(CheckNotNull(loop)), + fd_(fdArg), + events_(0), + revents_(0), + index_(-1), + eventHandling_(false) +{ + // std::cout << "Init channel: " << fd_ << std::endl; +} + +Channel::~Channel() +{ + // 确保channel被析构时不是在事件处理期间 + assert(!eventHandling_); +} + +void Channel::update() +{ + loop_->updateChannel(this); +} + +void Channel::handleEvent(Timestamp receiveTime) +{ + eventHandling_ = true; + if (revents_ & POLLNVAL) // invalid polling request + { + // LOG_WARN << "Channel::handle_event() POLLNVAL"; + std::cout << "LOG_WARN: " + << "Channel::handle_event() POLLNVAL" << std::endl; + } + if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) + { + // LOG_WARN << "Channel::handle_event() POLLHUP"; + std::cout << "LOG_WARN: " + << "Channel::handle_event() POLLHUP" << std::endl; + if (closeCallback_) + closeCallback_(); + } + if (revents_ & (POLLERR | POLLNVAL)) + { + if (errorCallback_) + { + errorCallback_(); + } + } + + if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) // POLLRDHUP ??? + { + std::cout << "LOG_WARN: " + << "Channel::handle_event() POLLIN" << std::endl; + if (readCallback_) + { + readCallback_(receiveTime); + } + } + + if (revents_ & POLLOUT) + { + if (writeCallback_) + { + writeCallback_(); + } + } + eventHandling_ = false; +} + +string Channel::eventsToString() const +{ + return eventsToString(fd_, events_); +} + +string Channel::eventsToString(int fd, int ev) +{ + std::ostringstream oss; + oss << fd << ": "; + if (ev & POLLIN) + oss << "IN "; + if (ev & POLLPRI) + oss << "PRI "; + if (ev & POLLOUT) + oss << "OUT "; + if (ev & POLLHUP) + oss << "HUP "; + if (ev & POLLRDHUP) + oss << "RDHUP "; + if (ev & POLLERR) + oss << "ERR "; + if (ev & POLLNVAL) + oss << "NVAL "; + + return oss.str(); +} \ No newline at end of file diff --git a/httpserver/poller/KChannel.h b/httpserver/poller/KChannel.h new file mode 100644 index 0000000..25ae54d --- /dev/null +++ b/httpserver/poller/KChannel.h @@ -0,0 +1,102 @@ +#pragma once +#include "../utils/Knoncopyable.h" +#include "../utils/KTimestamp.h" +#include + +namespace kback +{ +class EventLoop; + +// A selectable I/O channel. +// 每个Channel对象都只属于某一个IO线程,每个channel +// 对象自始至终只负责一个文件描述符fd的IO事件分发, +// 但它不会拥有这个fd,也不会在析构的时候关闭这个fd +class Channel : noncopyable +{ +public: + typedef std::function EventCallback; + typedef std::function ReadEventCallback; + + Channel(EventLoop *loop, int fdArg); + ~Channel(); + + // handleEvent是Channel的核心,它由EventLoop::loop()调用 + // 它的功能是根据revents_的值分别调用不同的用户回调 + void handleEvent(Timestamp receiveTime); + void setReadCallback(const ReadEventCallback &cb) + { + readCallback_ = cb; + } + void setWriteCallback(const EventCallback &cb) + { + writeCallback_ = cb; + } + void setErrorCallback(const EventCallback &cb) + { + errorCallback_ = cb; + } + void setCloseCallback(const EventCallback &cb) + { + closeCallback_ = cb; + } + + int fd() const { return fd_; } + int events() const { return events_; }; + void set_revents(int revt) { revents_ = revt; } + bool isNoneEvent() const { return events_ == kNoneEvent; } + + // 注册可读事件 注意update()函数 + void enableReading() + { + events_ |= kReadEvent; + update(); + } + void enableWriting() + { + events_ |= kWriteEvent; + } + void disableWriting() + { + events_ &= (-kWriteEvent); + update(); + } + void disableAll() + { + events_ = kNoneEvent; + update(); + } + + bool isWriting() const { return events_ & kWriteEvent; } + // for poller + int index() { return index_; } + void set_index(int idx) { index_ = idx; } + + // for debug + string eventsToString() const; + + EventLoop *ownerLoop() { return loop_; } + +private: + static string eventsToString(int fd, int ev); + void update(); + + // 每个channel对象 共享相同的常量值,所以定义为static + static const int kNoneEvent; + static const int kReadEvent; + static const int kWriteEvent; + + EventLoop *loop_; + const int fd_; + int events_; // Channel类关心的IO事件 + int revents_; // 目前活动的事件 + int index_; // used by Poller + + bool eventHandling_; + + ReadEventCallback readCallback_; + EventCallback writeCallback_; + EventCallback errorCallback_; + EventCallback closeCallback_; +}; + +} // namespace kback diff --git a/httpserver/poller/KDefaultPoller.h b/httpserver/poller/KDefaultPoller.h new file mode 100644 index 0000000..31117b4 --- /dev/null +++ b/httpserver/poller/KDefaultPoller.h @@ -0,0 +1,21 @@ +#pragma once + +#include "KPoller.h" +#include "KPollPoller.h" +#include "KEPollPoller.h" + +#include + +using namespace kback; + +Poller *Poller::newDefaultPoller(EventLoop *loop) +{ + if (::getenv("MUDUO_USE_POLL")) + { + return new EPollPoller(loop); + } + else + { + return new PollPoller(loop); + } +} \ No newline at end of file diff --git a/httpserver/poller/KEPollPoller.cpp b/httpserver/poller/KEPollPoller.cpp new file mode 100644 index 0000000..4971e36 --- /dev/null +++ b/httpserver/poller/KEPollPoller.cpp @@ -0,0 +1,204 @@ +#include "KEPollPoller.h" +#include "KChannel.h" +#include +#include +#include +#include +#include +#include + +using namespace kback; + +// On Linux, the constants of poll(2) and epoll(4) +// are expected to be the same. +static_assert(EPOLLIN == POLLIN, "epoll uses same flag values as poll"); +static_assert(EPOLLPRI == POLLPRI, "epoll uses same flag values as poll"); +static_assert(EPOLLOUT == POLLOUT, "epoll uses same flag values as poll"); +static_assert(EPOLLRDHUP == POLLRDHUP, "epoll uses same flag values as poll"); +static_assert(EPOLLERR == POLLERR, "epoll uses same flag values as poll"); +static_assert(EPOLLHUP == POLLHUP, "epoll uses same flag values as poll"); + +namespace +{ +const int kNew = -1; +const int kAdded = 1; +const int kDeleted = 2; +} // namespace + +EPollPoller::EPollPoller(EventLoop *loop) + : Poller(loop), + epollfd_(::epoll_create1(EPOLL_CLOEXEC)), + events_(kInitEventListSize) +{ + if (epollfd_ < 0) + { + std::cout << "LOG_SYSFATAL" + << "EPollPoller::EPollPoller" << std::endl; + } +} + +EPollPoller::~EPollPoller() +{ + ::close(epollfd_); +} + +Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels) +{ + std::cout << "LOG_TRACE" + << "fd total count " << channels_.size() << std::endl; + int numEvents = ::epoll_wait(epollfd_, + &*events_.begin(), + static_cast(events_.size()), + timeoutMs); + int savedErrno = errno; + Timestamp now(Timestamp::now()); + if (numEvents > 0) + { + std::cout << "LOG_TRACE" << numEvents << " events happended" << std::endl; + fillActiveChannels(numEvents, activeChannels); + if (implicit_cast(numEvents) == events_.size()) + { + events_.resize(events_.size() * 2); + } + } + else if (numEvents == 0) + { + std::cout << "LOG_TRACE" + << " nothing happended" << std::endl; + } + else + { + // error happens, log uncommon ones + if (savedErrno != EINTR) + { + errno = savedErrno; + // LOG_SYSERR << "EPollPoller::poll()"; + std::cout << "LOG_SYSERR" + << "EPollPoller::poll()" << std::endl; + } + } + return now; +} + +void EPollPoller::fillActiveChannels(int numEvents, + ChannelList *activeChannels) const +{ + assert(implicit_cast(numEvents) <= events_.size()); + for (int i = 0; i < numEvents; ++i) + { + Channel *channel = static_cast(events_[i].data.ptr); +#ifndef NDEBUG + int fd = channel->fd(); + ChannelMap::const_iterator it = channels_.find(fd); + assert(it != channels_.end()); + assert(it->second == channel); +#endif + channel->set_revents(events_[i].events); + activeChannels->push_back(channel); + } +} + +void EPollPoller::updateChannel(Channel *channel) +{ + const int index = channel->index(); + std::cout << "LOG_TRACE" + << "fd = " << channel->fd() + << " events = " << channel->events() << " index = " << index << std::endl; + if (index == kNew || index == kDeleted) + { + // a new one, add with EPOLL_CTL_ADD + int fd = channel->fd(); + if (index == kNew) + { + assert(channels_.find(fd) == channels_.end()); + channels_[fd] = channel; + } + else // index == kDeleted + { + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); + } + + channel->set_index(kAdded); + update(EPOLL_CTL_ADD, channel); + } + else + { + // update existing one with EPOLL_CTL_MOD/DEL + int fd = channel->fd(); + (void)fd; + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); + assert(index == kAdded); + if (channel->isNoneEvent()) + { + update(EPOLL_CTL_DEL, channel); + channel->set_index(kDeleted); + } + else + { + update(EPOLL_CTL_MOD, channel); + } + } +} + +void EPollPoller::removeChannel(Channel *channel) +{ + int fd = channel->fd(); + std::cout << "LOG_TRACE: " + << "fd = " << fd << std::endl; + assert(channels_.find(fd) != channels_.end()); + assert(channels_[fd] == channel); + assert(channel->isNoneEvent()); + int index = channel->index(); + assert(index == kAdded || index == kDeleted); + size_t n = channels_.erase(fd); + (void)n; + assert(n == 1); + + if (index == kAdded) + { + update(EPOLL_CTL_DEL, channel); + } + channel->set_index(kNew); +} + +void EPollPoller::update(int operation, Channel *channel) +{ + struct epoll_event event; + memZero(&event, sizeof event); + event.events = channel->events(); + event.data.ptr = channel; + int fd = channel->fd(); + std::cout << "LOG_TRACE" << "epoll_ctl op = " << operationToString(operation) + << " fd = " << fd << " event = { " << channel->eventsToString() << " }"; + if (::epoll_ctl(epollfd_, operation, fd, &event) < 0) + { + if (operation == EPOLL_CTL_DEL) + { + std::cout << "LOG_SYSERR" + << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd << std::endl; + } + else + { + std::cout << "LOG_SYSFATAL" + << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd; + } + } +} + +const char *EPollPoller::operationToString(int op) +{ + switch (op) + { + case EPOLL_CTL_ADD: + return "ADD"; + case EPOLL_CTL_DEL: + return "DEL"; + case EPOLL_CTL_MOD: + return "MOD"; + default: + assert(false && "ERROR op"); + return "Unknown Operation"; + } +} \ No newline at end of file diff --git a/httpserver/poller/KEPollPoller.h b/httpserver/poller/KEPollPoller.h new file mode 100644 index 0000000..b9da3a2 --- /dev/null +++ b/httpserver/poller/KEPollPoller.h @@ -0,0 +1,39 @@ +#pragma once + +#include "KPoller.h" +#include + +struct epoll_event; + +namespace kback +{ + +/// +/// IO Multiplexing with epoll(4). +/// +class EPollPoller : public Poller +{ +public: + EPollPoller(EventLoop *loop); + ~EPollPoller() override; + + Timestamp poll(int timeoutMs, ChannelList *activeChannels) override; + void updateChannel(Channel *channel) override; + void removeChannel(Channel *channel) override; + +private: + static const int kInitEventListSize = 16; + + static const char *operationToString(int op); + + void fillActiveChannels(int numEvents, + ChannelList *activeChannels) const; + void update(int operation, Channel *channel); + + typedef std::vector EventList; + + int epollfd_; + EventList events_; +}; + +} // namespace kback diff --git a/httpserver/poller/KPollPoller.cpp b/httpserver/poller/KPollPoller.cpp new file mode 100644 index 0000000..2b1e975 --- /dev/null +++ b/httpserver/poller/KPollPoller.cpp @@ -0,0 +1,142 @@ +#include "KPollPoller.h" + +#include +#include "KChannel.h" + +#include +#include +#include + +using namespace kback; + +PollPoller::PollPoller(EventLoop *loop) + : Poller(loop) +{ +} + +PollPoller::~PollPoller() = default; + +Timestamp PollPoller::poll(int timeoutMs, ChannelList *activeChannels) +{ + // XXX pollfds_ shouldn't change + // ::poll是为了区分Poller中的poll, ::表示全局作用域 + // pollfds - vector + // C++标准保证std::vector的元素排列跟数组一样 + int numEvents = ::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs); + int saveErrno = errno; + Timestamp now(Timestamp::now()); + if (numEvents > 0) + { + std::cout << "LOG_TRACE: " << numEvents << " events happended" << std::endl; + fillActiveChannels(numEvents, activeChannels); + } + else if (numEvents == 0) + { + std::cout << "LOG_TRACE: " << numEvents << " nothing happended" << std::endl; + } + else + { + // report error and abort + if (saveErrno != EINTR) + { + std::cout << "LOG_SYSERR: " + << "PollPoller::poll()" << std::endl; + } + } + return now; +} + +// 函数功能: 遍历pollfds_, 找出有活动事件的fd, 把它对应的Channel填入对应的activeChannels, +// 这个函数的复杂度是O(N), 其中N是pollfds_的长度,即文件描述符的长度 +void PollPoller::fillActiveChannels(int numEvents, + ChannelList *activeChannels) const +{ + for (PollFdList::const_iterator pfd = pollfds_.begin(); + pfd != pollfds_.end() && numEvents > 0; ++pfd) + { + if (pfd->revents > 0) + { + --numEvents; + ChannelMap::const_iterator ch = channels_.find(pfd->fd); + assert(ch != channels_.end()); + Channel *channel = ch->second; + assert(channel->fd() == pfd->fd); + channel->set_revents(pfd->revents); + activeChannels->push_back(channel); + } + } +} + +// 感觉这个函数名不应该叫做updateChannel +// 个人理解,用户通过channel监视事件,然后管理事件的回调 +// 显然channel是没有监视事件的功能的,因此,监视这件事要交给poller来做, poller主要监视pollfds_中的文件描述符 +// 这里是将channel 与 pollfds_ 同步,起到让poller"监视"channel的作用 + +void PollPoller::updateChannel(Channel *channel) +{ + std::cout << "LOG_TRACE: " + << "fd = " << channel->fd() << " events = " << channel->events() << std::endl; + if (channel->index() < 0) + { + // a new one, add to pollfds_ + assert(channels_.find(channel->fd()) == channels_.end()); + struct pollfd pfd; + pfd.fd = channel->fd(); + pfd.events = static_cast(channel->events()); + pfd.revents = 0; + pollfds_.push_back(pfd); + int idx = static_cast(pollfds_.size()) - 1; + channel->set_index(idx); + channels_[pfd.fd] = channel; + } + else + { + // update existing one + assert(channels_.find(channel->fd()) != channels_.end()); + assert(channels_[channel->fd()] == channel); + int idx = channel->index(); + assert(0 <= idx && idx < static_cast(pollfds_.size())); + struct pollfd &pfd = pollfds_[idx]; + assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd() - 1); + pfd.fd = channel->fd(); + pfd.events = static_cast(channel->events()); + pfd.revents = 0; + if (channel->isNoneEvent()) + { + // ignore this pollfd + pfd.fd = -channel->fd() - 1; + } + } +} + +void PollPoller::removeChannel(Channel *channel) +{ + std::cout << "LOG_TRACE: " + << "fd = " << channel->fd() << std::endl; + assert(channels_.find(channel->fd()) != channels_.end()); + assert(channels_[channel->fd()] == channel); + assert(channel->isNoneEvent()); + int idx = channel->index(); + assert(0 <= idx && idx < static_cast(pollfds_.size())); + const struct pollfd &pfd = pollfds_[idx]; + (void)pfd; + assert(pfd.fd == -channel->fd() - 1 && pfd.events == channel->events()); + size_t n = channels_.erase(channel->fd()); + assert(n == 1); + (void)n; + if (implicit_cast(idx) == pollfds_.size() - 1) + { + pollfds_.pop_back(); + } + else + { + int channelAtEnd = pollfds_.back().fd; + iter_swap(pollfds_.begin() + idx, pollfds_.end() - 1); + if (channelAtEnd < 0) + { + channelAtEnd = -channelAtEnd - 1; + } + channels_[channelAtEnd]->set_index(idx); + pollfds_.pop_back(); + } +} \ No newline at end of file diff --git a/httpserver/poller/KPollPoller.h b/httpserver/poller/KPollPoller.h new file mode 100644 index 0000000..2d2384d --- /dev/null +++ b/httpserver/poller/KPollPoller.h @@ -0,0 +1,29 @@ +#pragma once + +#include "KPoller.h" +#include + +// Poller中并没有include, 所以要使用pollfd, 需要前向声明 +struct pollfd; + +namespace kback +{ +class PollPoller : public Poller +{ +public: + PollPoller(EventLoop *loop); + ~PollPoller() override; + + Timestamp poll(int timeoutMs, ChannelList *activeChannels) override; + void updateChannel(Channel *channel) override; + void removeChannel(Channel *channel) override; + +private: + void fillActiveChannels(int numEvents, + ChannelList *activeChannels) const; + + typedef std::vector PollFdList; + PollFdList pollfds_; +}; + +} // namespace kback diff --git a/httpserver/poller/KPoller.cpp b/httpserver/poller/KPoller.cpp new file mode 100644 index 0000000..7691dcd --- /dev/null +++ b/httpserver/poller/KPoller.cpp @@ -0,0 +1,17 @@ +#include "KPoller.h" +#include "KChannel.h" + +using namespace kback; + +Poller::Poller(EventLoop *loop) + : ownerLoop_(loop) +{ +} + +Poller::~Poller() = default; + +bool Poller::hasChannel(Channel *channel) const +{ + auto it = channels_.find(channel->fd()); + return it != channels_.end() && it->second == channel; +} \ No newline at end of file diff --git a/httpserver/poller/KPoller.h b/httpserver/poller/KPoller.h new file mode 100644 index 0000000..ca379b7 --- /dev/null +++ b/httpserver/poller/KPoller.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include "../utils/Knoncopyable.h" +#include "../utils/KTimestamp.h" +#include "../KEventLoop.h" + +namespace kback +{ +class Channel; + +// IO复用的抽象基类 +// Poller类同样不拥有Channel类 + +class Poller : noncopyable +{ +public: + typedef std::vector ChannelList; + + Poller(EventLoop *loop); + virtual ~Poller(); + + /// Polls the I/O events. + /// Must be called in the loop thread. + virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels) = 0; + + // update 关注的IO事件 + virtual void updateChannel(Channel *channel) = 0; + // 当channel析构时,移除channel + virtual void removeChannel(Channel *channel) = 0; + + virtual bool hasChannel(Channel *channel) const; + + static Poller *newDefaultPoller(EventLoop *loop); + +protected: + typedef std::map ChannelMap; + ChannelMap channels_; // 从fd到channel*的映射 + +private: + EventLoop *ownerLoop_; +}; + +} // namespace kback diff --git a/httpserver/runHttpServer b/httpserver/runHttpServer new file mode 100755 index 0000000..960fc54 Binary files /dev/null and b/httpserver/runHttpServer differ diff --git a/httpserver/runHttpServer.cpp b/httpserver/runHttpServer.cpp new file mode 100644 index 0000000..b61eda4 --- /dev/null +++ b/httpserver/runHttpServer.cpp @@ -0,0 +1,67 @@ +#include "KEventLoop.h" +#include "http/KHttpServer.h" +#include "http/KHttpRequest.h" +#include "http/KHttpResponse.h" +#include "http/KIcons.h" +#include +#include + +using namespace kback; + +bool benchmark = false; + +void onRequest(const HttpRequest &req, HttpResponse *resp) +{ + std::cout << "Headers " << req.methodString() << " " << req.path() << std::endl; + if (!benchmark) + { + const std::map &headers = req.headers(); + for (const auto &header : headers) + { + std::cout << header.first << ": " << header.second << std::endl; + } + } + + 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("Codestin Search App" + "

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); + } +} + +int main(int argc, char *argv[]) +{ + EventLoop loop; + HttpServer server(&loop, InetAddress(9981), "dummy"); + server.setHttpCallback(onRequest); + server.start(); + loop.loop(); +} diff --git a/httpserver/tcp/KAcceptor.cpp b/httpserver/tcp/KAcceptor.cpp new file mode 100644 index 0000000..57f302f --- /dev/null +++ b/httpserver/tcp/KAcceptor.cpp @@ -0,0 +1,63 @@ +#include "KAcceptor.h" + +#include "../KEventLoop.h" +#include "KInetAddress.h" +#include "KSocketsOps.h" + +using namespace kback; + +Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr) + : loop_(loop), + acceptSocket_(sockets::createNonblockingOrDie()), + acceptChannel_(loop, acceptSocket_.fd()), + listenning_(false) +{ + acceptSocket_.setReuseAddr(true); + + acceptSocket_.bindAddress(listenAddr); + acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this)); +} + +Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport) + : loop_(loop), + acceptSocket_(sockets::createNonblockingOrDie()), + acceptChannel_(loop, acceptSocket_.fd()), + listenning_(false) +{ + if (reuseport == true) + { + acceptSocket_.setReuseAddr(true); + } + acceptSocket_.bindAddress(listenAddr); + acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this)); +} + +// 在TCP编程中 +// 在这里 要把接受端口请求的事件注册到accepchannel 可读时间的回调函数中 +void Acceptor::listen() +{ + // loop_->assertInLoopThread(); + listenning_ = true; + acceptSocket_.listen(); + acceptChannel_.enableReading(); +} + +void Acceptor::handleRead() +{ + // loop_->assertInLoopThread(); + // 利用一个 InetAddress 来管理 sockaddr_in + InetAddress peerAddr(0); + // loop until no more + int connfd = acceptSocket_.accept(&peerAddr); + if (connfd >= 0) + { + if (newConnectionCallback_) + { + newConnectionCallback_(connfd, peerAddr); + } + else + { + sockets::close(connfd); + } + } +} \ No newline at end of file diff --git a/httpserver/tcp/KAcceptor.h b/httpserver/tcp/KAcceptor.h new file mode 100644 index 0000000..66bacc5 --- /dev/null +++ b/httpserver/tcp/KAcceptor.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "../utils/Knoncopyable.h" +#include "KSocket.h" +#include "../poller/KChannel.h" + +namespace kback +{ +class EventLoop; +class InetAddress; + +/// +/// Acceptor of incoming TCP connections. +/// +class Acceptor : noncopyable +{ +public: + typedef std::function NewConnectionCallback; + Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport); + Acceptor(EventLoop *loop, const InetAddress &listenAddr); + + void setNewConnectionCallback(const NewConnectionCallback &cb) + { + newConnectionCallback_ = cb; + } + + bool listenning() const { return listenning_; } + void listen(); + +private: + // 具备loop和channel + void handleRead(); + + EventLoop *loop_; + + // 成员函数的初始化顺序与他们在类定义中的出现顺序一致 + // acceptSocket_必须在acceptChannel_之前定义, + // 因为构造函数列表初始化的时候 acceptChannel_要使用acceptSocket_的成员初始化 + Socket acceptSocket_; + Channel acceptChannel_; + NewConnectionCallback newConnectionCallback_; + bool listenning_; +}; + +} // namespace kback diff --git a/httpserver/hpserver/KBuffer.cpp b/httpserver/tcp/KBuffer.cpp similarity index 96% rename from httpserver/hpserver/KBuffer.cpp rename to httpserver/tcp/KBuffer.cpp index bc54019..716463c 100644 --- a/httpserver/hpserver/KBuffer.cpp +++ b/httpserver/tcp/KBuffer.cpp @@ -1,12 +1,12 @@ #include "KBuffer.h" -// #include "KSocketsOps.h" +#include "KSocketsOps.h" // #include "logging/KLogging.h" #include "../utils/KTypes.h" #include #include #include -using namespace kb; +using namespace kback; const char Buffer::kCRLF[] = "\r\n"; // 回车换行 diff --git a/httpserver/hpserver/KBuffer.h b/httpserver/tcp/KBuffer.h similarity index 98% rename from httpserver/hpserver/KBuffer.h rename to httpserver/tcp/KBuffer.h index f603ed4..589508d 100644 --- a/httpserver/hpserver/KBuffer.h +++ b/httpserver/tcp/KBuffer.h @@ -1,14 +1,14 @@ #pragma once -#include "../utils/Kcopyable.h" #include #include #include - #include +#include "../utils/Kcopyable.h" + //#include // ssize_t -namespace kb +namespace kback { /// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer @@ -21,7 +21,7 @@ namespace kb /// | | | | /// 0 <= readerIndex <= writerIndex <= size /// @endcode -class Buffer : public kb::copyable +class Buffer : public kback::copyable { public: static const size_t kCheapPrepend = 8; @@ -214,4 +214,4 @@ class Buffer : public kb::copyable static const char kCRLF[]; }; -} // namespace kb \ No newline at end of file +} // namespace kback \ No newline at end of file diff --git a/httpserver/tcp/KInetAddress.cpp b/httpserver/tcp/KInetAddress.cpp new file mode 100644 index 0000000..835a219 --- /dev/null +++ b/httpserver/tcp/KInetAddress.cpp @@ -0,0 +1,33 @@ +#include "KInetAddress.h" + +#include +#include + +#include "KSocketsOps.h" +#include "../utils/KTypes.h" + + +using namespace kback; + +static const in_addr_t kInaddrAny = INADDR_ANY; + +InetAddress::InetAddress(uint16_t port) +{ + memZero(&addr_, sizeof addr_); + addr_.sin_family = AF_INET; + addr_.sin_addr.s_addr = sockets::hostToNetwork32(kInaddrAny); + addr_.sin_port = sockets::hostToNetwork16(port); +} + +InetAddress::InetAddress(const std::string &ip, uint16_t port) +{ + memZero(&addr_, sizeof addr_); + sockets::fromHostPort(ip.c_str(), port, &addr_); +} + +std::string InetAddress::toHostPort() const +{ + char buf[32]; + sockets::toHostPort(buf, sizeof buf, addr_); + return buf; +} \ No newline at end of file diff --git a/httpserver/tcp/KInetAddress.h b/httpserver/tcp/KInetAddress.h new file mode 100644 index 0000000..85daec9 --- /dev/null +++ b/httpserver/tcp/KInetAddress.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../utils/Kcopyable.h" +#include +#include + +namespace kback +{ +/// +/// Wrapper of sockaddr_in. +/// POD: plain old data structure +/// This is an POD interface class. +class InetAddress : public copyable +{ +public: + // Constructs an endpoint with given port number. + // 经常使用在 TcpServer listening上 + explicit InetAddress(uint16_t port); + + /// Constructs an endpoint with given ip and port. + /// @c ip should be "1.2.3.4" + InetAddress(const std::string &ip, uint16_t port); + + /// Constructs an endpoint with given struct @c sockaddr_in + /// Mostly used when accepting new connections + InetAddress(const struct sockaddr_in &addr) + : addr_(addr) + { + } + + std::string toHostPort() const; + + // default copy/assignment are Okay + const struct sockaddr_in &getSockAddrInet() const { return addr_; } + void setSockAddrInet(const struct sockaddr_in &addr) { addr_ = addr; } + +private: + struct sockaddr_in addr_; +}; +} // namespace kback \ No newline at end of file diff --git a/httpserver/tcp/KSocket.cpp b/httpserver/tcp/KSocket.cpp new file mode 100644 index 0000000..c33a1de --- /dev/null +++ b/httpserver/tcp/KSocket.cpp @@ -0,0 +1,73 @@ +#include "KSocket.h" + +#include "KInetAddress.h" +#include "KSocketsOps.h" + +#include +#include + +#include "../utils/KTypes.h" // memZero + +using namespace kback; + +Socket::~Socket() +{ + sockets::close(sockfd_); +} + +void Socket::bindAddress(const InetAddress& addr) +{ + sockets::bindOrDie(sockfd_, addr.getSockAddrInet()); +} + +void Socket::listen() +{ + sockets::listenOrDie(sockfd_); +} + +int Socket::accept(InetAddress* peeraddr) +{ + struct sockaddr_in addr; + memZero(&addr, sizeof addr); + int connfd = sockets::accept(sockfd_, &addr); + if(connfd >= 0) + { + peeraddr->setSockAddrInet(addr); + } + return connfd; +} + +// 设置调用close(socket)后,仍可继续重用该socket。 +// 调用close(socket)一般不会立即关闭socket,而要经历TIME_WAIT的过程。 +// BOOL bReuseaddr = TRUE; +// setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) ) +void Socket::setReuseAddr(bool on) +{ + int optval = on ? 1 : 0; + ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, + &optval, sizeof optval); + // FIXME CHECK +} + +void Socket::shutdownWrite() +{ + sockets::shutdownWrite(sockfd_); +} + +// 都是设置套接字的功能 setTcpNoDelay和setKeepAlive + +void Socket::setTcpNoDelay(bool on) +{ + int optval = on ? 1 : 0; + ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, + &optval, sizeof optval); + // FIXME CHECK +} + +void Socket::setKeepAlive(bool on) +{ + int optval = on ? 1 : 0; + ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, + &optval, static_cast(sizeof optval)); + // FIXME CHECK +} \ No newline at end of file diff --git a/httpserver/tcp/KSocket.h b/httpserver/tcp/KSocket.h new file mode 100644 index 0000000..1f7e2d9 --- /dev/null +++ b/httpserver/tcp/KSocket.h @@ -0,0 +1,59 @@ +#pragma once + +#include "../utils/Knoncopyable.h" +#include + +namespace kback +{ + +class InetAddress; + +// socket fd 的封装 +// 在析构时关闭sockfd +// 线程安全,所有操作都转交给OS + +class Socket : noncopyable +{ +public: + explicit Socket(int sockfd) : sockfd_(sockfd) { + // std::cout << "sockfd_ is: " << sockfd_ << std::endl; + } + + ~Socket(); + + int fd() const + { + return sockfd_; + } + + // 服务端建立连接的三个流程 + /// abort if address in use + void bindAddress(const InetAddress &localaddr); + /// abort if address in use + void listen(); + + /// On success, returns a non-negative integer that is + /// a descriptor for the accepted socket, which has been + /// set to non-blocking and close-on-exec. *peeraddr is assigned. + /// On error, -1 is returned, and *peeraddr is untouched. + int accept(InetAddress *peeraddr); + + /// + /// Enable/disable SO_REUSEADDR + /// + void setReuseAddr(bool on); + + void shutdownWrite(); + + /// TCP 的一些操作,也要记住 + /// Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm). + /// + void setTcpNoDelay(bool on); + + void setKeepAlive(bool on); + +private: + const int sockfd_; +}; + +} // namespace kback \ No newline at end of file diff --git a/httpserver/tcp/KSocketsOps.cpp b/httpserver/tcp/KSocketsOps.cpp new file mode 100644 index 0000000..0104d76 --- /dev/null +++ b/httpserver/tcp/KSocketsOps.cpp @@ -0,0 +1,212 @@ +#include "KSocketsOps.h" + +#include "../utils/KTypes.h" + +#include +#include + +#include +#include // + +using namespace kback; + +namespace +{ +typedef struct sockaddr SA; + +// 这里是将sockaddr_in 转化为 sockaddr +const SA *sockaddr_cast(const struct sockaddr_in *addr) +{ + return static_cast(implicit_cast(addr)); +} + +SA *sockaddr_cast(struct sockaddr_in *addr) +{ + return static_cast(implicit_cast(addr)); +} + +void setNonBlockAndCloseOnExec(int sockfd) +{ + // non-block + int flags = ::fcntl(sockfd, F_GETFL, 0); + flags |= O_NONBLOCK; + int ret = ::fcntl(sockfd, F_SETFL, flags); + + // close-on-exec + flags = ::fcntl(sockfd, F_GETFD, 0); + flags |= FD_CLOEXEC; + ret = ::fcntl(sockfd, F_SETFD, flags); +} + +} // namespace + +int sockets::createNonblockingOrDie() +{ + + int sockfd = ::socket(AF_INET, + SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, + IPPROTO_TCP); + if (sockfd < 0) + { + std::cout << "LOG_SYSFATAL: " + << "sockets::createNonblockingOrDie" << std::endl; + } + return sockfd; +} + +int sockets::connect(int sockfd, const struct sockaddr_in &addr) +{ + return ::connect(sockfd, sockaddr_cast(&addr), sizeof addr); +} + +void sockets::bindOrDie(int sockfd, const struct sockaddr_in &addr) +{ + int ret = ::bind(sockfd, sockaddr_cast(&addr), sizeof addr); + if (ret < 0) + { + std::cout << "LOG_SYSFATAL: " + << "sockets::bindOrDie" << std::endl; + } +} + +void sockets::listenOrDie(int sockfd) +{ + int ret = ::listen(sockfd, SOMAXCONN); + if (ret < 0) + { + std::cout << "LOG_SYSFATAL: " + << "sockets::listenOrDie" << std::endl; + } +} + +int sockets::accept(int sockfd, struct sockaddr_in *addr) +{ + socklen_t addrlen = sizeof *addr; + + int connfd = ::accept4(sockfd, sockaddr_cast(addr), + &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC); + if (connfd < 0) + { + int savedErrno = errno; + // LOG_SYSERR << "Socket::accept"; + std::cout << "LOG_SYSERR: " + << "Socket::accept" << std::endl; + switch (savedErrno) + { + case EAGAIN: + 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 + std::cout << "LOG_FATAL: " + << "unexpected error of ::accept " << std::endl; + break; + default: + std::cout << "LOG_FATAL: " + << "unknown error of ::accept" << std::endl; + break; + } + } + return connfd; +} + +void sockets::close(int sockfd) +{ + if (::close(sockfd) < 0) + { + std::cout << "LOG_SYSERR: " + << "sockets::close" << std::endl; + } +} + +void sockets::shutdownWrite(int sockfd) +{ + if (::shutdown(sockfd, SHUT_WR) < 0) + { + std::cout << "LOG_SYSERR: " + << "sockets::shutdownWrite" << std::endl; + } +} + +void sockets::toHostPort(char *buf, size_t size, + const struct sockaddr_in &addr) +{ + char host[INET_ADDRSTRLEN] = "INVALID"; + ::inet_ntop(AF_INET, &addr.sin_addr, host, sizeof host); + uint16_t port = sockets::networkToHost16(addr.sin_port); + snprintf(buf, size, "%s:%u", host, port); +} + +void sockets::fromHostPort(const char *ip, uint16_t port, + struct sockaddr_in *addr) +{ + addr->sin_family = AF_INET; + addr->sin_port = hostToNetwork16(port); + if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0) + { + std::cout << "LOG_SYSERR: " + << "sockets::fromHostPort" << std::endl; + } +} + +struct sockaddr_in sockets::getLocalAddr(int sockfd) +{ + struct sockaddr_in localaddr; + memZero(&localaddr, sizeof localaddr); + socklen_t addrlen = sizeof(localaddr); + if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0) + { + std::cout << "LOG_SYSERR: " + << "sockets::getLocalAddr" << std::endl; + } + return localaddr; +} + +struct sockaddr_in sockets::getPeerAddr(int sockfd) +{ + struct sockaddr_in peeraddr; + memZero(&peeraddr, sizeof peeraddr); + socklen_t addrlen = sizeof(peeraddr); + // + if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0) + { + std::cout << "LOG_SYSERR: " + << "sockets::getPeerAddr" << std::endl; + } + return peeraddr; +} + +int sockets::getSocketError(int sockfd) +{ + int optval; + socklen_t optlen = sizeof optval; + + if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) + { + return errno; + } + else + { + return optval; + } +} + +bool sockets::isSelfConnect(int sockfd) +{ + struct sockaddr_in localaddr = getLocalAddr(sockfd); + struct sockaddr_in peeraddr = getPeerAddr(sockfd); + return localaddr.sin_port == peeraddr.sin_port && localaddr.sin_addr.s_addr == peeraddr.sin_addr.s_addr; +} \ No newline at end of file diff --git a/httpserver/tcp/KSocketsOps.h b/httpserver/tcp/KSocketsOps.h new file mode 100644 index 0000000..60feae0 --- /dev/null +++ b/httpserver/tcp/KSocketsOps.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +namespace kback +{ +namespace sockets +{ +inline uint64_t hostToNetwork64(uint64_t host64) +{ + return htobe64(host64); +} + +inline uint32_t hostToNetwork32(uint32_t host32) +{ + return htonl(host32); +} + +inline uint16_t hostToNetwork16(uint16_t host16) +{ + return htons(host16); +} + +inline uint64_t networkToHost64(uint64_t net64) +{ + return be64toh(net64); +} + +inline uint32_t networkToHost32(uint32_t net32) +{ + return ntohl(net32); +} + +inline uint16_t networkToHost16(uint16_t net16) +{ + return ntohs(net16); +} + +// 创建一个非阻塞socket文件描述符 +int createNonblockingOrDie(); + +int connect(int sockfd, const struct sockaddr_in &addr); +void bindOrDie(int sockfd, const struct sockaddr_in &addr); +void listenOrDie(int sockfd); +int accept(int sockfd, struct sockaddr_in *addr); +void close(int sockfd); +void shutdownWrite(int sockfd); + +void toHostPort(char *buf, size_t size, + const struct sockaddr_in &addr); +void fromHostPort(const char *ip, uint16_t port, + struct sockaddr_in *addr); + +struct sockaddr_in getLocalAddr(int sockfd); +struct sockaddr_in getPeerAddr(int sockfd); + +int getSocketError(int sockfd); +bool isSelfConnect(int sockfd); + +} // namespace sockets + +} // namespace kback diff --git a/httpserver/tcp/KTcpConnection.cpp b/httpserver/tcp/KTcpConnection.cpp new file mode 100644 index 0000000..4f2ddeb --- /dev/null +++ b/httpserver/tcp/KTcpConnection.cpp @@ -0,0 +1,255 @@ +#include "KTcpConnection.h" + +#include "../poller/KChannel.h" +#include "../KEventLoop.h" +#include "KSocket.h" +#include "KSocketsOps.h" +#include "../utils/KTypes.h" +#include +#include +#include + +using namespace kback; + +TcpConnection::TcpConnection(EventLoop *loop, + const std::string &nameArg, + int sockfd, + const InetAddress &localAddr, + const InetAddress &peerAddr) + : loop_(CheckNotNull(loop)), + name_(nameArg), + state_(kConnecting), + socket_(new Socket(sockfd)), + channel_(new Channel(loop, sockfd)), + localAddr_(localAddr), + peerAddr_(peerAddr) +{ + // LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this + // << " fd=" << sockfd; + std::cout << "LOG_DEBUG: " + << "TcpConnection::ctor[" << name_ << "] at " << this + << " fd=" << sockfd << std::endl; + 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)); +} + +TcpConnection::~TcpConnection() +{ + // LOG_DEBUG << "TcpConnection::dtor[" << name_ << "] at " << this + // << " fd=" << channel_->fd(); + std::cout << "LOG_DEBUG: " + << "TcpConnection::dtor[" << name_ << "] at " << this + << " fd=" << channel_->fd() << std::endl; +} + +void TcpConnection::send(const std::string &message) +{ + if (state_ == kConnected) + { + // if (loop_->isInLoopThread()) + // { + sendInLoop(message); + // } + // else + // { + // loop_->runInLoop(std::bind(&TcpConnection::sendInLoop, this, message)); + // } + } +} + +void TcpConnection::sendInLoop(const std::string &message) +{ + // sendInLoop() 会先尝试直接发送数据,如果一次发送完毕,就不会启用writeCallback + // 如果只发送了部分数据,则把剩余的数据放入outputBuffer_, 并开始关注writable事件, + // 以后在handlewrite中发送剩余的数据, 如果当前outputBuffer_已经有待发送的数据, + // 那么就不能先尝试发送了,因为会造成数据乱序 + // loop_->assertInLoopThread(); + ssize_t nwrote = 0; + // if no thing in output queue, try writing directly + if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) + { + nwrote = ::write(channel_->fd(), message.data(), message.size()); + if (nwrote >= 0) + { + if (implicit_cast(nwrote) < message.size()) + { + std::cout << "LOG_TRACE: " + << "I am going to write more data" << std::endl; + } + else if (writeCompleteCallback_) + { + loop_->queueInLoop( + std::bind(writeCompleteCallback_, shared_from_this())); + } + } + else + { + nwrote = 0; + if (errno != EWOULDBLOCK) + { + std::cout << "LOG_SYSERR: " + << "TcpConnection::sendInLoop" << std::endl; + } + } + } + + assert(nwrote >= 0); + if (implicit_cast(nwrote) < message.size()) + { + outputBuffer_.append(message.data() + nwrote, message.size() - nwrote); + // 如果没有关注writable事件,则开始关注 + if (!channel_->isWriting()) + { + channel_->enableWriting(); + } + } +} + +void TcpConnection::shutdown() +{ + // FIXME: use compare and swap + if (state_ == kConnected) + { + setState(kDisconnecting); + // FIXME: shared_from_this()? + loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop, this)); + } +} + +// 没有直接关闭TCP connection, 数据写完成后,只关闭写 +void TcpConnection::shutdownInLoop() +{ + if (!channel_->isWriting()) + { + // we are not writing + socket_->shutdownWrite(); + } +} + +// 禁用Nagle算法,避免连续发包出现的延迟,这对编写低延迟网络服务很重要 +void TcpConnection::setTcpNoDelay(bool on) +{ + socket_->setTcpNoDelay(on); +} + +// tcp connection 处理连接建立的过程 +// 1. 利用state_变量 标志 连接的状态 +// 2. 掉用channel_->enableReading() 将channel负责的文件描述符中的可读事件注册到loop中 +// 并用IO复用机制poller类来监视文件描述符 +// 3. 最后调用connectioncallback函数 (注意其中使用的shared_from_this()) +void TcpConnection::connectEstablished() +{ + // loop_->assertInLoopThread(); + assert(state_ == kConnecting); + setState(kConnected); + channel_->enableReading(); + + connectionCallback_(shared_from_this()); +} + +void TcpConnection::connectDestroyed() +{ + // loop_->assertInLoopThread(); + assert(state_ == kConnected); + setState(kDisconnected); + channel_->disableAll(); + connectionCallback_(shared_from_this()); + + loop_->removeChannel(get_pointer(channel_)); +} + +void TcpConnection::handleRead(Timestamp receiveTime) +{ + int savedErrno = 0; + ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno); + if (n > 0) + { + messageCallback_(shared_from_this(), &inputBuffer_, receiveTime); + } + else if (n == 0) + { + handleClose(); + } + else + { + errno = savedErrno; + std::cout << "LOG_SYSERR: " + << "TcpConnection::handleRead" << std::endl; + handleError(); + } +} + +void TcpConnection::handleWrite() +{ + // loop_->assertInLoopThread(); + if (channel_->isWriting()) + { + ssize_t n = ::write(channel_->fd(), outputBuffer_.peek(), outputBuffer_.readableBytes()); + + if (n > 0) + { + outputBuffer_.retrieve(n); + if (outputBuffer_.readableBytes() == 0) + { + channel_->disableWriting(); + if (writeCompleteCallback_) + { + loop_->queueInLoop( + std::bind(writeCompleteCallback_, shared_from_this())); + } + + if (state_ = kDisconnecting) + { + shutdownInLoop(); + } + } + else + { + // LOG_TRACE << "I am going to write more data"; + std::cout << "LOG_TRACE: " + << "I am going to write more data" << std::endl; + } + } + else + { + // LOG_SYSERR << "TcpConnection::handleWrite"; + std::cout << "LOG_SYSERR: " + << "TcpConnection::handleWrite" << std::endl; + } + } + else + { + // LOG_TRACE << "Connection is down, no more writing"; + std::cout << "LOG_TRACE: " + << "Connection is down, no more writing" << std::endl; + } +} + +void TcpConnection::handleClose() +{ + // loop_->assertInLoopThread(); + std::cout << "LOG_TRACE: " + << "TcpConnection::handleClose state = " << state_ << std::endl; + + assert(state_ == kConnected || state_ == kDisconnecting); + // we don't close fd, leave it to dtor, so we can find leaks easily. + channel_->disableAll(); + // must be the last line + closeCallback_(shared_from_this()); +} + +void TcpConnection::handleError() +{ + int err = sockets::getSocketError(channel_->fd()); + // LOG_ERROR << "TcpConnection::handleError [" << name_ + // << "] - SO_ERROR = " << err << " " << strerror_tl(err); + std::cout << "LOG_ERROR: " + << "TcpConnection::handleError [" << name_ + << "] - SO_ERROR = " << err << " " << std::endl; +} \ No newline at end of file diff --git a/httpserver/tcp/KTcpConnection.h b/httpserver/tcp/KTcpConnection.h new file mode 100644 index 0000000..1781b7a --- /dev/null +++ b/httpserver/tcp/KTcpConnection.h @@ -0,0 +1,118 @@ +#pragma once + +#include "KBuffer.h" +#include "KInetAddress.h" +#include "../utils/KCallbacks.h" +#include "../utils/Knoncopyable.h" +#include "../utils/KTimestamp.h" +#include +#include +#include + +namespace kback +{ +class Channel; +class EventLoop; +class Socket; + +// Tcp connection是唯一默认使用shared_ptr来管理的class, 也是唯一继承enable_shared_from_this的calss +// ??? 这源于其模糊的生命期 +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(); + + EventLoop *getLoop() const { return loop_; } + const std::string &name() const { return name_; } + const InetAddress &localAddress() { return localAddr_; } + const InetAddress &peerAddress() { return peerAddr_; } + bool connected() const { return state_ == kConnected; } + + void send(const std::string &message); + // void send(Buffer *buf); + void shutdown(); + void setTcpNoDelay(bool on); + + /// ============= Http ============ /// + void setContext(const boost::any &context) + { + context_ = context; + } + + const boost::any &getContext() const + { + return context_; + } + + boost::any *getMutableContext() + { + return &context_; + } + /// ============= Http ============ /// + + void setConnectionCallback(const ConnectionCallback &cb) + { + connectionCallback_ = cb; + } + + void setMessageCallback(const MessageCallback &cb) + { + messageCallback_ = cb; + } + + void setWriteCompleteCallback(const WriteCompleteCallback &cb) + { + writeCompleteCallback_ = cb; + } + + /// Internal use only. + void setCloseCallback(const CloseCallback &cb) + { + closeCallback_ = cb; + } + + // called when TcpServer accepts a new connection + void connectEstablished(); // should be called only once + // called when TcpServer has removed me from its map + void connectDestroyed(); // should be called only once + +private: + enum StateE + { + kConnecting, + kConnected, + kDisconnecting, + kDisconnected, + }; + StateE state_; + void setState(StateE s) { state_ = s; } + void handleRead(Timestamp receiveTime); + void handleWrite(); + void handleClose(); + void handleError(); + void sendInLoop(const std::string &message); + void shutdownInLoop(); + + EventLoop *loop_; + std::string name_; + + std::unique_ptr socket_; + std::unique_ptr channel_; + + InetAddress localAddr_; + InetAddress peerAddr_; + ConnectionCallback connectionCallback_; + MessageCallback messageCallback_; + WriteCompleteCallback writeCompleteCallback_; + CloseCallback closeCallback_; + Buffer inputBuffer_; + Buffer outputBuffer_; + + boost::any context_; +}; + +} // namespace kback diff --git a/httpserver/tcp/KTcpServer.cpp b/httpserver/tcp/KTcpServer.cpp new file mode 100644 index 0000000..a2212f1 --- /dev/null +++ b/httpserver/tcp/KTcpServer.cpp @@ -0,0 +1,89 @@ +#include "KTcpServer.h" + +#include "../KEventLoop.h" +#include "KAcceptor.h" +#include "KSocketsOps.h" +#include "../utils/KTypes.h" + +using namespace kback; + +TcpServer::TcpServer(EventLoop *loop, const InetAddress &listenAddr) + : loop_(CheckNotNull(loop)), + name_(listenAddr.toHostPort()), + acceptor_(new Acceptor(loop, listenAddr)), + started_(false), + nextConnId_(1) +{ + acceptor_->setNewConnectionCallback( + std::bind(&TcpServer::newConnection, this, _1, _2)); +} + +TcpServer::TcpServer(EventLoop *loop, const InetAddress &listenAddr, const string &nameArg) + : loop_(CheckNotNull(loop)), + ipPort_(listenAddr.toHostPort()), + name_(nameArg), + acceptor_(new Acceptor(loop, listenAddr)), + started_(false), + nextConnId_(1) +{ + acceptor_->setNewConnectionCallback( + std::bind(&TcpServer::newConnection, this, _1, _2)); +} + +TcpServer::~TcpServer() +{ +} + +void TcpServer::start() +{ + if (!started_) + { + started_ = true; + } + if (!acceptor_->listenning()) + { + loop_->runInLoop(std::bind(&Acceptor::listen, get_pointer(acceptor_))); + } +} + +void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr) +{ + char buf[32]; + snprintf(buf, sizeof buf, "#%d", nextConnId_); + ++nextConnId_; + std::string connName = name_ + buf; + + std::cout << "LOG_INFO: " + << "TcpServer::newConnection [" << name_ + << "] - new connection [" << connName + << "] from " << peerAddr.toHostPort() << std::endl; + + InetAddress localAddr(sockets::getLocalAddr(sockfd)); + TcpConnectionPtr conn(new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr)); + + // 新建Tcp Connection, 此时新的Tcp connection是shared_ptr, 离开作用域后仅被connections_持有 + connections_[connName] = conn; + conn->setConnectionCallback(connectionCallback_); + conn->setMessageCallback(messageCallback_); + conn->setWriteCompleteCallback(writeCompleteCallback_); + conn->setCloseCallback( + std::bind(&TcpServer::removeConnection, this, _1)); + conn->connectEstablished(); +} + + +void TcpServer::removeConnection(const TcpConnectionPtr &conn) +{ + std::cout << "LOG_INFO: " + << "TcpServer::removeConnection [" << name_ + << "] - connection " << conn->name() << std::endl; + // 此时,conn 对象被其本身还有 connections_ 对象持有, + // 当把conn从 connections_ 中移除时引用计数降到1, 不做处理的话,离开作用域后就会被销毁 + // 最后使用了 std::bind 让TcpConnection的生命期延长到connectDestroyed 调用完成时 + size_t n = connections_.erase(conn->name()); + assert(n == 1); + (void)n; + + loop_->queueInLoop( + std::bind(&TcpConnection::connectDestroyed, conn)); +} \ No newline at end of file diff --git a/httpserver/tcp/KTcpServer.h b/httpserver/tcp/KTcpServer.h new file mode 100644 index 0000000..ad8792b --- /dev/null +++ b/httpserver/tcp/KTcpServer.h @@ -0,0 +1,75 @@ +#pragma once + +#include "../utils/KCallbacks.h" +#include "../utils/Knoncopyable.h" +#include "KTcpConnection.h" +#include "KInetAddress.h" +#include +#include +#include + +namespace kback +{ +class Acceptor; +class EventLoop; +// class EventLoopThreadPool; + +// TcpServer class 的功能是管理accept 获得的TcpConnection +class TcpServer : noncopyable +{ +public: + TcpServer(EventLoop *loop, const InetAddress &listenAddr); + TcpServer(EventLoop *loop, const InetAddress &listenAddr, const string &nameArg); + ~TcpServer(); + + const string &ipPort() const { return ipPort_; } + const string &name() const { return name_; } + + // 启动server + void start(); + + EventLoop *getLoop() const { return loop_; } + + // void setThreadNum(int numThreads); + + // + void setConnectionCallback(const ConnectionCallback &cb) + { + connectionCallback_ = cb; + } + + void setMessageCallback(const MessageCallback &cb) + { + messageCallback_ = cb; + } + + void setWriteCompleteCallback(const WriteCompleteCallback &cb) + { + writeCompleteCallback_ = cb; + } + +private: + // not thread safe, but in loop + void newConnection(int sockfd, const InetAddress &peerAddr); + void removeConnection(const TcpConnectionPtr &conn); + + // void removeConnectionInLoop(const TcpConnectionPtr &conn); + + typedef std::map ConnectionMap; + + EventLoop *loop_; // the acceptor loop + const string ipPort_; + const string name_; + std::unique_ptr acceptor_; + ConnectionCallback connectionCallback_; + + MessageCallback messageCallback_; + WriteCompleteCallback writeCompleteCallback_; + bool started_; + int nextConnId_; // always in loop thread + ConnectionMap connections_; + + // std::unique_ptr threadPool_; +}; + +} // namespace kback \ No newline at end of file diff --git a/httpserver/test_epoll b/httpserver/test_epoll deleted file mode 100755 index 13bc857..0000000 Binary files a/httpserver/test_epoll and /dev/null differ diff --git a/httpserver/test_epollv1.cpp b/httpserver/test_epollv1.cpp deleted file mode 100644 index b523905..0000000 --- a/httpserver/test_epollv1.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// epoll LT 程序 - -#include -#include "hpserver/KInetAddress.h" -#include "hpserver/KSocket.h" -#include "hpserver/KBuffer.h" -#include -#include -#include -#include - -using namespace kb; - -int main() -{ - 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); - - const int timeoutMs = 1000; - - Buffer buffer_; - 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) - { - // 处理client即可 - int saveErrno = 0; - int str_len = buffer_.readFd(events[i].data.fd, &saveErrno); - if(str_len == 0) - { - epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, nullptr); - close(events[i].data.fd); - clntfd = -1; - std::cout << "close client " << events[i].data.fd << std::endl; - } - else - { - // echo事件 - int nw = ::write(events[i].data.fd, buffer_.peek(), buffer_.readableBytes()); - if(nw > 0) - { - buffer_.retrieve(nw); - } - std::cout << "write data " << events[i].data.fd << std::endl; - } - } - - } - } - 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/httpserver/test_epollv2.cpp b/httpserver/test_epollv2.cpp deleted file mode 100644 index cdf4e65..0000000 --- a/httpserver/test_epollv2.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// 将client端的IO事件交由线程池来处理 -#include -#include -#include "hpserver/KInetAddress.h" -#include "hpserver/KSocket.h" -#include "hpserver/KBuffer.h" -#include "thread/KThreadPool.h" -#include -#include -#include -#include - -using namespace kb; - -const int timeoutMs = 5000; - -void httpOnRequest(int epfd, int clntfd) -{ - // 全部委托给新的线程,注意销毁 - // 处理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请求 - } -} - -int main() -{ - - // 添加线程池 来处理http请求 - ThreadPool pool("Test"); - pool.start(1); - - 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) - { - pool.run(std::bind(httpOnRequest, epfd, clntfd)); - } - } - } - 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/httpserver/thread/KThreadPool.cpp b/httpserver/thread/KThreadPool.cpp deleted file mode 100644 index 61abc99..0000000 --- a/httpserver/thread/KThreadPool.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "KThreadPool.h" -#include "assert.h" - -ThreadPool::ThreadPool(const std::string &name) - : name_(name), - running_(false) -{ -} - -ThreadPool::~ThreadPool() -{ - if (running_) - { - stop(); - } -} - -void ThreadPool::start(int numThreads) -{ - assert(threads_.empty()); - running_ = true; - threads_.reserve(numThreads); - for (size_t i = 0; i < numThreads; ++i) - { - threads_.push_back(std::thread( - std::bind(&ThreadPool::runInThread, this))); - } -} - -// 定义stop -void ThreadPool::stop() -{ - { - std::unique_lock lock(mutex_); - running_ = false; - cond_.notify_all(); - } - for (std::thread &th : threads_) - { - th.join(); - } -} - -// 添加任务 -void ThreadPool::run(const Task &task) -{ - if (threads_.empty()) - { - task(); - } - else - { - std::unique_lock lock(mutex_); - queue_.push_back(task); - cond_.notify_one(); - } -} - -ThreadPool::Task ThreadPool::take() -{ - - std::unique_lock lock(mutex_); - cond_.wait(lock, [this] { - return (!running_ || !queue_.empty()); - }); - Task task; - if (!this->queue_.empty()) - { - task = queue_.front(); - queue_.pop_front(); - } - - return task; -} - -void ThreadPool::runInThread() -{ - try - { - while (running_ == true) - { - Task task(take()); - if (task) - { - task(); - } - } - } - catch (...) - { - // deal with error - } -} \ No newline at end of file diff --git a/httpserver/thread/KThreadPool.h b/httpserver/thread/KThreadPool.h deleted file mode 100644 index 612fd97..0000000 --- a/httpserver/thread/KThreadPool.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - - -class ThreadPool -{ -public: - typedef std::function Task; - - explicit ThreadPool(const std::string &name = std::string()); - ~ThreadPool(); - - // 启动线程 - void start(int numThreads); - // 终止线程 - void stop(); - - // 添加任务 - void run(const Task &task); - -private: - // - void runInThread(); - Task take(); - - std::mutex mutex_; - std::condition_variable cond_; - std::vector threads_; - std::deque queue_; - bool running_; - - // 可以作为线程池标识 - std::string name_; -}; diff --git a/httpserver/thread/test_ThreadPool b/httpserver/thread/test_ThreadPool deleted file mode 100755 index 7bccfb6..0000000 Binary files a/httpserver/thread/test_ThreadPool and /dev/null differ diff --git a/httpserver/thread/test_ThreadPool.cpp b/httpserver/thread/test_ThreadPool.cpp deleted file mode 100644 index a875e6a..0000000 --- a/httpserver/thread/test_ThreadPool.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "KThreadPool.h" -#include -#include - - -void print() -{ - std::cout << "tid: " << std::this_thread::get_id() - << std::endl; -} - -void printString(const std::string &str) -{ - std::cout << "tid: " << std::this_thread::get_id() - << " str = " << str << std::endl; -} - -int main() -{ - ThreadPool pool("Test"); - pool.start(5); - - pool.run(print); - pool.run(print); - - for (int i = 0; i < 100; ++i) - { - pool.run(std::bind(printString, std::to_string(i))); - } - - std::this_thread::sleep_for(std::chrono::seconds(3)); - pool.stop(); - - return 0; -} \ No newline at end of file diff --git a/httpserver/utils/KCallbacks.h b/httpserver/utils/KCallbacks.h new file mode 100644 index 0000000..de533b6 --- /dev/null +++ b/httpserver/utils/KCallbacks.h @@ -0,0 +1,43 @@ +#pragma once + +#include "KTimestamp.h" +#include +#include + + +using std::placeholders::_1; +using std::placeholders::_2; +using std::placeholders::_3; + + +template +inline T* get_pointer(const std::shared_ptr& ptr) +{ + return ptr.get(); +} + +template +inline T* get_pointer(const std::unique_ptr& ptr) +{ + return ptr.get(); +} + +namespace kback +{ + +// All client visible callbacks go here. +class Buffer; +class TcpConnection; + +typedef std::shared_ptr TcpConnectionPtr; + +typedef std::function TimerCallback; +typedef std::function ConnectionCallback; + +typedef std::function MessageCallback; + +typedef std::function WriteCompleteCallback; + +typedef std::function CloseCallback; + +} // namespace kback \ No newline at end of file diff --git a/httpserver/utils/KTimestamp.cpp b/httpserver/utils/KTimestamp.cpp index 72d0df1..98b4888 100644 --- a/httpserver/utils/KTimestamp.cpp +++ b/httpserver/utils/KTimestamp.cpp @@ -9,9 +9,9 @@ #include -using namespace kb; +using namespace kback; -// static_assert(sizeof(Timestamp) == sizeof(int64_t), "Timestamp is same size as int64_t"); +static_assert(sizeof(Timestamp) == sizeof(int64_t), "Timestamp is same size as int64_t"); string Timestamp::toString() const { diff --git a/httpserver/utils/KTimestamp.h b/httpserver/utils/KTimestamp.h index f2ccacb..00de511 100644 --- a/httpserver/utils/KTimestamp.h +++ b/httpserver/utils/KTimestamp.h @@ -4,7 +4,7 @@ #include -namespace kb +namespace kback { /// @@ -113,4 +113,4 @@ inline Timestamp addTime(Timestamp timestamp, double seconds) return Timestamp(timestamp.microSecondsSinceEpoch() + delta); } -} // namespace kb \ No newline at end of file +} // namespace kback \ No newline at end of file diff --git a/httpserver/utils/KTypes.h b/httpserver/utils/KTypes.h index 47f3ad0..fd692ef 100644 --- a/httpserver/utils/KTypes.h +++ b/httpserver/utils/KTypes.h @@ -16,7 +16,7 @@ /// /// The most common stuffs. /// -namespace kb +namespace kback { template @@ -145,4 +145,4 @@ inline To down_cast(From *f) // so we only accept pointers return static_cast(f); } -} // namespace kb +} // namespace kback diff --git a/httpserver/utils/Kcopyable.h b/httpserver/utils/Kcopyable.h index 1eda7ce..5666b3b 100644 --- a/httpserver/utils/Kcopyable.h +++ b/httpserver/utils/Kcopyable.h @@ -1,6 +1,6 @@ #pragma once -namespace kb +namespace kback { class copyable { @@ -8,4 +8,4 @@ class copyable copyable() = default; ~copyable() = default; }; -} // namespace kb \ No newline at end of file +} // namespace kback \ No newline at end of file diff --git a/httpserver/utils/Knoncopyable.h b/httpserver/utils/Knoncopyable.h new file mode 100644 index 0000000..2c4265c --- /dev/null +++ b/httpserver/utils/Knoncopyable.h @@ -0,0 +1,16 @@ +#pragma once + +namespace kback +{ +class noncopyable +{ +public: + noncopyable(const noncopyable &) = delete; + void operator=(const noncopyable &) = delete; + +protected: + noncopyable() = default; + ~noncopyable() = default; +}; + +} // namespace kback 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..5d01f44 --- /dev/null +++ "b/\346\224\271\350\277\233\346\200\235\350\267\257.md" @@ -0,0 +1,73 @@ +## Http version 1 + +对于每一个http请求(client),会有一个fd去处理,v1版本的做法是,每当请求client到达时(即epoll检测到活动事件时),将此活动fd的任务转交给线程池中的线程去处理 + +版本一的框架如下图所示: + +serverarch1 + +## Http version 2 + +这里每次有新任务到达时,就将新任务转交给线程,然后把程序的控制权转交到Epoll上,但是当在高并发状态,有很多活动的事件fd, 这是频繁的转交线程也会造成很大的开销 + +所以Version的初步设想是,每当有一个client到达时,利用线程池中的线程去负责这个client的fd, 然后由主线程中的epoll去通知这个线程何时开始处理任务,这样单个线程就可长期监视一个fd, 主线程就不用像Version 1一样,需要频繁地分配任务 + +> Note: 主线程应该如何通知单个线程要开始处理任务了呢? +> +> 1. 也就是采用线程间的通信方式(临界区、信号量、事件信号、互斥量) +> 2. 采用eventfd 的可读事件作为线程间的唤醒机制(类似于无名管道?) + +由此,初步设想已经完成,但是细想发现又不是很现实,比如,一个线程去负责一个client,那在高并发状态下,这是不可能完成的任务, + +简单想一想,让一个线程负责多个文件描述符很简单,用一个map维护即可,这样就可让单个线程 负责 多个文件描述符 + +此时的框架图如下图所示: + +serverarch2_0 + +但是直接这样就 单个线程负责多个文件描述符 是不现实的,比如,A线程负责了 a3和a4两个文件描述符,如果主线程应该如何通知 A线程 去处理 a3和a4两个活动fd呢,这里虽然可以想办法实现(将发生活动事件的fd 作为数据写入eventfd,然后再唤醒线程),但是有没有更好的办法呢。 + +进一步的思考时 如何让单个线程彻头彻尾的负责多个文件描述符,彻尾很简单,负责文件描述符的close即可。彻头就是要去负责监视文件描述符的活动事件,也就是由主线程通知A线程,你应该负责a3 这个文件描述符,A线程中也应该创建epoll对象,将a3需要监听的事件加入进来。往后,可继续添加多个文件描述符。 其实这一步相当于对之前的思路做了一个调整,之前是想让主线程去监听所有事件,现在主线程只负责accept请求,然后将accept的对象的控制权转交给其余线程,至此,版本二的设想已经基本完成 + +版本二的基本框架如下图所示: + +![serverarch2](file/serverarch2.png) + +为了简化处理,接下来的版本V2_1 将完成单线程的http server + +和多线程的http server相比,区别主要在与单线程的Server在Acceptor后并不会转交给其他线程,而是在主线程内添加需要监视的文件描述符,所以在完成单线程server的基础上,只要加上 “转交” 的代码即可修改成多线程的server + +单线程Server(v2_0)的主要代码可见v2_0 分支,接下来的 Server(v2_1)主要基于v2_0修改,v2_0已经是一个很基础的单线程http server了。 + + + +### version 2_1 (单线程) + +在版本v2_0中,代码太过于集中,不利于维护和修改,因此才有了版本v2_1 + +v2_0主要的功能是Epoll监听活动事件,Acceptor接受新的连接,新创建的client fd的IO事件管理 + +所以V2_1 打算新建一个Epoll类用于 IO复用,一个Acceptor用于建立新连接,TcpConnection用于管理接收到的连接,同时建立一个TcpServer,用于管理Acceptor和TcpConnection + +对于每一个文件描述符,这里按照muduo的思路,新建了一个Channel类来管理 IO事件,然后用一个loop对象来管理整个程序 + +``````cpp +// loop对象的主要代码 +while (!quit_) + { + activeChannels_.clear(); + pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); + for (auto it = activeChannels_.begin(); it != activeChannels_.end(); ++it) + { + (*it)->handleEvent(pollReturnTime_); + } + // 在单线程server中, 直接用上面的handleEvent 是不能处理所有的任务回调的, 比如无法处理 Channel的析构,所以析构要在下面的doPendingFunctors中处理 + doPendingFunctors(); + } +`````` + + + +### version log (日志) + +在V2_1单线程版本的server已经完成,为了实现一个多线程的http server,首先得实现一个Log类,在这里主要是便于多线程的调试,而且实际环境中肯定是要有一个Log用于记录服务器的运行状态的 \ No newline at end of file