├── .gitignore ├── code ├── Buffer.cpp ├── Buffer.h ├── Epoll.cpp ├── Epoll.h ├── HttpRequest.cpp ├── HttpRequest.h ├── HttpResponse.cpp ├── HttpResponse.h ├── HttpServer.cpp ├── HttpServer.h ├── ThreadPool.cpp ├── ThreadPool.h ├── Timer.cpp ├── Timer.h ├── Utils.cpp ├── Utils.h ├── main.cpp ├── makefile └── run.sh ├── doc ├── img │ ├── CPU信息.png │ ├── CPU占用情况.png │ ├── 一千并发六十秒八线程.png │ ├── 一千并发六十秒四线程.png │ └── 代码统计.png ├── 主要函数.md ├── 并发模型.md ├── 并发测试.md ├── 整体架构.md ├── 核心类.md ├── 相关知识.md ├── 遇到的问题.md └── 项目目的.md ├── readme.md ├── test ├── makefile ├── test_thread_pool.cpp ├── test_timer.cpp └── webbench-1.5 │ ├── COPYRIGHT │ ├── ChangeLog │ ├── Makefile │ ├── debian │ ├── changelog │ ├── control │ ├── copyright │ ├── dirs │ └── rules │ ├── socket.c │ ├── tags │ ├── webbench │ ├── webbench.1 │ └── webbench.c └── www ├── index.html ├── spring.jpg └── tencent.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | log/ 3 | *.o 4 | server 5 | -------------------------------------------------------------------------------- /code/Buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "Buffer.h" 2 | 3 | #include // perror 4 | #include 5 | 6 | #include // write 7 | #include // readv 8 | 9 | using namespace swings; 10 | 11 | ssize_t Buffer::readFd(int fd, int* savedErrno) 12 | { 13 | // 保证一次读到足够多的数据 14 | char extrabuf[65536]; 15 | struct iovec vec[2]; 16 | const size_t writable = writableBytes(); 17 | vec[0].iov_base = __begin() + writerIndex_; 18 | vec[0].iov_len = writable; 19 | vec[1].iov_base = extrabuf; 20 | vec[1].iov_len = sizeof(extrabuf); 21 | const ssize_t n = ::readv(fd, vec, 2); 22 | if(n < 0) { 23 | printf("[Buffer:readFd]fd = %d readv : %s\n", fd, strerror(errno)); 24 | *savedErrno = errno; 25 | } 26 | else if(static_cast(n) <= writable) 27 | writerIndex_ += n; 28 | else { 29 | writerIndex_ = buffer_.size(); 30 | append(extrabuf, n - writable); 31 | } 32 | 33 | return n; 34 | } 35 | 36 | ssize_t Buffer::writeFd(int fd, int* savedErrno) 37 | { 38 | size_t nLeft = readableBytes(); 39 | char* bufPtr = __begin() + readerIndex_; 40 | ssize_t n; 41 | if((n = ::write(fd, bufPtr, nLeft)) <= 0) { 42 | if(n < 0 && n == EINTR) 43 | return 0; 44 | else { 45 | printf("[Buffer:writeFd]fd = %d write : %s\n", fd, strerror(errno)); 46 | *savedErrno = errno; 47 | return -1; 48 | } 49 | } else { 50 | readerIndex_ += n; 51 | return n; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /code/Buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef __BUFFER_H__ 2 | #define __BUFFER_H__ 3 | 4 | #include 5 | #include 6 | #include // copy 7 | #include 8 | 9 | #include 10 | 11 | #define INIT_SIZE 1024 12 | 13 | namespace swings { 14 | 15 | class Buffer { 16 | public: 17 | Buffer() 18 | : buffer_(INIT_SIZE), 19 | readerIndex_(0), 20 | writerIndex_(0) 21 | { 22 | assert(readableBytes() == 0); 23 | assert(writableBytes() == INIT_SIZE); 24 | } 25 | ~Buffer() {} 26 | 27 | // 默认拷贝构造函数和赋值函数可用 28 | 29 | size_t readableBytes() const // 可读字节数 30 | { return writerIndex_ - readerIndex_; } 31 | 32 | size_t writableBytes() const // 可写字节数 33 | { return buffer_.size() - writerIndex_; } 34 | 35 | size_t prependableBytes() const // readerIndex_前面的空闲缓冲区大小 36 | { return readerIndex_; } 37 | 38 | const char* peek() const // 第一个可读位置 39 | { return __begin() + readerIndex_; } 40 | 41 | void retrieve(size_t len) // 取出len个字节 42 | { 43 | assert(len <= readableBytes()); 44 | readerIndex_ += len; 45 | } 46 | 47 | void retrieveUntil(const char* end) // 取出数据直到end 48 | { 49 | assert(peek() <= end); 50 | assert(end <= beginWrite()); 51 | retrieve(end - peek()); 52 | } 53 | 54 | void retrieveAll() // 取出buffer内全部数据 55 | { 56 | readerIndex_ = 0; 57 | writerIndex_ = 0; 58 | } 59 | 60 | std::string retrieveAsString() // 以string形式取出全部数据 61 | { 62 | std::string str(peek(), readableBytes()); 63 | retrieveAll(); 64 | return str; 65 | } 66 | 67 | void append(const std::string& str) // 插入数据 68 | { append(str.data(), str.length()); } 69 | 70 | void append(const char* data, size_t len) // 插入数据 71 | { 72 | ensureWritableBytes(len); 73 | std::copy(data, data + len, beginWrite()); 74 | hasWritten(len); 75 | } 76 | 77 | void append(const void* data, size_t len) // 插入数据 78 | { append(static_cast(data), len); } 79 | 80 | void append(const Buffer& otherBuff) // 把其它缓冲区的数据添加到本缓冲区 81 | { append(otherBuff.peek(), otherBuff.readableBytes()); } 82 | 83 | void ensureWritableBytes(size_t len) // 确保缓冲区有足够空间 84 | { 85 | if(writableBytes() < len) { 86 | __makeSpace(len); 87 | } 88 | assert(writableBytes() >= len); 89 | } 90 | 91 | char* beginWrite() // 可写char指针 92 | { return __begin() + writerIndex_; } 93 | 94 | const char* beginWrite() const 95 | { return __begin() + writerIndex_; } 96 | 97 | void hasWritten(size_t len) // 写入数据后移动writerIndex_ 98 | { writerIndex_ += len; } 99 | 100 | ssize_t readFd(int fd, int* savedErrno); // 从套接字读到缓冲区 101 | ssize_t writeFd(int fd, int* savedErrno); // 缓冲区写到套接字 102 | 103 | const char* findCRLF() const 104 | { 105 | const char CRLF[] = "\r\n"; 106 | const char* crlf = std::search(peek(), beginWrite(), CRLF, CRLF+2); 107 | return crlf == beginWrite() ? nullptr : crlf; 108 | } 109 | 110 | const char* findCRLF(const char* start) const 111 | { 112 | assert(peek() <= start); 113 | assert(start <= beginWrite()); 114 | const char CRLF[] = "\r\n"; 115 | const char* crlf = std::search(start, beginWrite(), CRLF, CRLF + 2); 116 | return crlf == beginWrite() ? nullptr : crlf; 117 | } 118 | 119 | private: 120 | char* __begin() // 返回缓冲区头指针 121 | { return &*buffer_.begin(); } 122 | 123 | const char* __begin() const // 返回缓冲区头指针 124 | { return &*buffer_.begin(); } 125 | 126 | void __makeSpace(size_t len) // 确保缓冲区有足够空间 127 | { 128 | if(writableBytes() + prependableBytes() < len) { 129 | buffer_.resize(writerIndex_ + len); 130 | } 131 | else { 132 | size_t readable = readableBytes(); 133 | std::copy(__begin() + readerIndex_, 134 | __begin() + writerIndex_, 135 | __begin()); 136 | readerIndex_ = 0; 137 | writerIndex_ = readerIndex_ + readable; 138 | assert(readable == readableBytes()); 139 | } 140 | } 141 | 142 | private: 143 | 144 | std::vector buffer_; 145 | size_t readerIndex_; 146 | size_t writerIndex_; 147 | }; // class Buffer 148 | 149 | } // namespace swings 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /code/Epoll.cpp: -------------------------------------------------------------------------------- 1 | #include "Epoll.h" 2 | #include "HttpRequest.h" 3 | #include "ThreadPool.h" 4 | 5 | #include 6 | #include 7 | #include // perror 8 | 9 | #include // close 10 | 11 | using namespace swings; 12 | 13 | Epoll::Epoll() 14 | : epollFd_(::epoll_create1(EPOLL_CLOEXEC)), 15 | events_(MAXEVENTS) 16 | { 17 | assert(epollFd_ >= 0); 18 | } 19 | 20 | Epoll::~Epoll() 21 | { 22 | ::close(epollFd_); 23 | } 24 | 25 | int Epoll::add(int fd, HttpRequest* request, int events) 26 | { 27 | struct epoll_event event; 28 | event.data.ptr = (void*)request; // XXX 使用cast系列函数 29 | event.events = events; 30 | int ret = ::epoll_ctl(epollFd_, EPOLL_CTL_ADD, fd, &event); 31 | return ret; 32 | } 33 | 34 | int Epoll::mod(int fd, HttpRequest* request, int events) 35 | { 36 | struct epoll_event event; 37 | event.data.ptr = (void*)request; // XXX 使用cast系列函数 38 | event.events = events; 39 | int ret = ::epoll_ctl(epollFd_, EPOLL_CTL_MOD, fd, &event); 40 | return ret; 41 | } 42 | 43 | int Epoll::del(int fd, HttpRequest* request, int events) 44 | { 45 | struct epoll_event event; 46 | event.data.ptr = (void*)request; // XXX 使用cast系列函数 47 | event.events = events; 48 | int ret = ::epoll_ctl(epollFd_, EPOLL_CTL_DEL, fd, &event); 49 | return ret; 50 | } 51 | 52 | int Epoll::wait(int timeoutMs) 53 | { 54 | int eventsNum = ::epoll_wait(epollFd_, &*events_.begin(), static_cast(events_.size()), timeoutMs); 55 | if(eventsNum == 0) { 56 | // printf("[Epoll::wait] nothing happen, epoll timeout\n"); 57 | } else if(eventsNum < 0) { 58 | printf("[Epoll::wait] epoll : %s\n", strerror(errno)); 59 | } 60 | 61 | return eventsNum; 62 | } 63 | 64 | void Epoll::handleEvent(int listenFd, std::shared_ptr& threadPool, int eventsNum) 65 | { 66 | assert(eventsNum > 0); 67 | for(int i = 0; i < eventsNum; ++i) { 68 | HttpRequest* request = (HttpRequest*)(events_[i].data.ptr); // XXX 使用cast系列函数 69 | int fd = request -> fd(); 70 | 71 | if(fd == listenFd) { 72 | // 新连接回调函数 73 | onConnection_(); 74 | } else { 75 | // 排除错误事件 76 | if((events_[i].events & EPOLLERR) || 77 | (events_[i].events & EPOLLHUP) || 78 | (!events_[i].events & EPOLLIN)) { 79 | request -> setNoWorking(); 80 | // 出错则关闭连接 81 | onCloseConnection_(request); 82 | } else if(events_[i].events & EPOLLIN) { 83 | request -> setWorking(); 84 | threadPool -> pushJob(std::bind(onRequest_, request)); 85 | } else if(events_[i].events & EPOLLOUT) { 86 | request -> setWorking(); 87 | threadPool -> pushJob(std::bind(onResponse_, request)); 88 | } else { 89 | printf("[Epoll::handleEvent] unexpected event\n"); 90 | } 91 | } 92 | } 93 | return; 94 | } 95 | -------------------------------------------------------------------------------- /code/Epoll.h: -------------------------------------------------------------------------------- 1 | #ifndef __EPOLL_H__ 2 | #define __EPOLL_H__ 3 | 4 | #include // function 5 | #include // vector 6 | #include // shared_ptr 7 | 8 | #include // epoll_event 9 | 10 | #define MAXEVENTS 1024 11 | 12 | namespace swings { 13 | 14 | class HttpRequest; 15 | class ThreadPool; 16 | 17 | class Epoll { 18 | public: 19 | using NewConnectionCallback = std::function; 20 | using CloseConnectionCallback = std::function; 21 | using HandleRequestCallback = std::function; 22 | using HandleResponseCallback = std::function; 23 | 24 | Epoll(); 25 | ~Epoll(); 26 | int add(int fd, HttpRequest* request, int events); // 注册新描述符 27 | int mod(int fd, HttpRequest* request, int events); // 修改描述符状态 28 | int del(int fd, HttpRequest* request, int events); // 从epoll中删除描述符 29 | int wait(int timeoutMs); // 等待事件发生, 返回活跃描述符数量 30 | void handleEvent(int listenFd, std::shared_ptr& threadPool, int eventsNum); // 调用事件处理函数 31 | void setOnConnection(const NewConnectionCallback& cb) { onConnection_ = cb; } // 设置新连接回调函数 32 | void setOnCloseConnection(const CloseConnectionCallback& cb) { onCloseConnection_ = cb; } // 设置关闭连接回调函数 33 | void setOnRequest(const HandleRequestCallback& cb) { onRequest_ = cb; } // 设置处理请求回调函数 34 | void setOnResponse(const HandleResponseCallback& cb) { onResponse_ = cb; } // 设置响应请求回调函数 35 | 36 | private: 37 | using EventList = std::vector; 38 | 39 | int epollFd_; 40 | EventList events_; 41 | NewConnectionCallback onConnection_; 42 | CloseConnectionCallback onCloseConnection_; 43 | HandleRequestCallback onRequest_; 44 | HandleResponseCallback onResponse_; 45 | }; // class Epoll 46 | 47 | } // namespace swings 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /code/HttpRequest.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpRequest.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using namespace swings; 9 | 10 | HttpRequest::HttpRequest(int fd) 11 | : fd_(fd), 12 | working_(false), 13 | timer_(nullptr), 14 | state_(ExpectRequestLine), 15 | method_(Invalid), 16 | version_(Unknown) 17 | { 18 | assert(fd_ >= 0); 19 | } 20 | 21 | HttpRequest::~HttpRequest() 22 | { 23 | close(fd_); 24 | } 25 | 26 | int HttpRequest::read(int* savedErrno) 27 | { 28 | int ret = inBuff_.readFd(fd_, savedErrno); 29 | return ret; 30 | } 31 | 32 | int HttpRequest::write(int* savedErrno) 33 | { 34 | int ret = outBuff_.writeFd(fd_, savedErrno); 35 | return ret; 36 | } 37 | 38 | bool HttpRequest::parseRequest() 39 | { 40 | bool ok = true; 41 | bool hasMore = true; 42 | 43 | while(hasMore) { 44 | if(state_ == ExpectRequestLine) { 45 | // 处理请求行 46 | const char* crlf = inBuff_.findCRLF(); 47 | if(crlf) { 48 | ok = __parseRequestLine(inBuff_.peek(), crlf); 49 | if(ok) { 50 | inBuff_.retrieveUntil(crlf + 2); 51 | state_ = ExpectHeaders; 52 | } else { 53 | hasMore = false; 54 | } 55 | } else { 56 | hasMore = false; 57 | } 58 | } else if(state_ == ExpectHeaders) { 59 | // 处理报文头 60 | const char* crlf = inBuff_.findCRLF(); 61 | if(crlf) { 62 | const char* colon = std::find(inBuff_.peek(), crlf, ':'); 63 | if(colon != crlf) { 64 | __addHeader(inBuff_.peek(), colon, crlf); 65 | } else { 66 | state_ = GotAll; 67 | hasMore = false; 68 | } 69 | inBuff_.retrieveUntil(crlf + 2); 70 | } else { 71 | hasMore = false; 72 | } 73 | } else if(state_ == ExpectBody) { 74 | // TODO 处理报文体 75 | } 76 | } 77 | 78 | return ok; 79 | } 80 | 81 | bool HttpRequest::__parseRequestLine(const char* begin, const char* end) 82 | { 83 | bool succeed = false; 84 | const char* start = begin; 85 | const char* space = std::find(start, end, ' '); 86 | if(space != end && __setMethod(start, space)) { 87 | start = space + 1; 88 | space = std::find(start, end, ' '); 89 | if(space != end) { 90 | const char* question = std::find(start, space, '?'); 91 | if(question != space) { 92 | __setPath(start, question); 93 | __setQuery(question, space); 94 | } else { 95 | __setPath(start, space); 96 | } 97 | start = space + 1; 98 | succeed = end - start == 8 && std::equal(start, end - 1, "HTTP/1."); 99 | if(succeed) { 100 | if(*(end - 1) == '1') 101 | __setVersion(HTTP11); 102 | else if(*(end - 1) == '0') 103 | __setVersion(HTTP10); 104 | else 105 | succeed = false; 106 | } 107 | } 108 | } 109 | 110 | return succeed; 111 | } 112 | 113 | bool HttpRequest::__setMethod(const char* start, const char* end) 114 | { 115 | std::string m(start, end); 116 | if(m == "GET") 117 | method_ = Get; 118 | else if(m == "POST") 119 | method_ = Post; 120 | else if(m == "HEAD") 121 | method_ = Head; 122 | else if(m == "PUT") 123 | method_ = Put; 124 | else if(m == "DELETE") 125 | method_ = Delete; 126 | else 127 | method_ = Invalid; 128 | 129 | return method_ != Invalid; 130 | } 131 | 132 | void HttpRequest::__addHeader(const char* start, const char* colon, const char* end) 133 | { 134 | std::string field(start, colon); 135 | ++colon; 136 | while(colon < end && *colon == ' ') 137 | ++colon; 138 | std::string value(colon, end); 139 | while(!value.empty() && value[value.size() - 1] == ' ') 140 | value.resize(value.size() - 1); 141 | 142 | headers_[field] = value; 143 | } 144 | 145 | std::string HttpRequest::getMethod() const 146 | { 147 | std::string res; 148 | if(method_ == Get) 149 | res = "GET"; 150 | else if(method_ == Post) 151 | res = "POST"; 152 | else if(method_ == Head) 153 | res = "HEAD"; 154 | else if(method_ == Put) 155 | res = "Put"; 156 | else if(method_ == Delete) 157 | res = "DELETE"; 158 | 159 | return res; 160 | } 161 | 162 | std::string HttpRequest::getHeader(const std::string& field) const 163 | { 164 | std::string res; 165 | auto itr = headers_.find(field); 166 | if(itr != headers_.end()) 167 | res = itr -> second; 168 | return res; 169 | } 170 | 171 | bool HttpRequest::keepAlive() const 172 | { 173 | std::string connection = getHeader("Connection"); 174 | bool res = connection == "Keep-Alive" || 175 | (version_ == HTTP11 && connection != "close"); 176 | 177 | return res; 178 | } 179 | 180 | void HttpRequest::resetParse() 181 | { 182 | state_ = ExpectRequestLine; // 报文解析状态 183 | method_ = Invalid; // HTTP方法 184 | version_ = Unknown; // HTTP版本 185 | path_ = ""; // URL路径 186 | query_ = ""; // URL参数 187 | headers_.clear(); // 报文头部 188 | } 189 | -------------------------------------------------------------------------------- /code/HttpRequest.h: -------------------------------------------------------------------------------- 1 | #ifndef __HTTP_REQUEST_H__ 2 | #define __HTTP_REQUEST_H__ 3 | 4 | #include "Buffer.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #define STATIC_ROOT "../www" 11 | 12 | namespace swings { 13 | 14 | class Timer; 15 | 16 | class HttpRequest { 17 | public: 18 | enum HttpRequestParseState { // 报文解析状态 19 | ExpectRequestLine, 20 | ExpectHeaders, 21 | ExpectBody, 22 | GotAll 23 | }; 24 | 25 | enum Method { // HTTP方法 26 | Invalid, Get, Post, Head, Put, Delete 27 | }; 28 | 29 | enum Version { // HTTP版本 30 | Unknown, HTTP10, HTTP11 31 | }; 32 | 33 | HttpRequest(int fd); 34 | ~HttpRequest(); 35 | 36 | int fd() { return fd_; } // 返回文件描述符 37 | int read(int* savedErrno); // 读数据 38 | int write(int* savedErrno); // 写数据 39 | 40 | void appendOutBuffer(const Buffer& buf) { outBuff_.append(buf); } 41 | int writableBytes() { return outBuff_.readableBytes(); } 42 | 43 | void setTimer(Timer* timer) { timer_ = timer; } 44 | Timer* getTimer() { return timer_; } 45 | 46 | void setWorking() { working_ = true; } 47 | void setNoWorking() { working_ = false; } 48 | bool isWorking() const { return working_; } 49 | 50 | bool parseRequest(); // 解析Http报文 51 | bool parseFinish() { return state_ == GotAll; } // 是否解析完一个报文 52 | void resetParse(); // 重置解析状态 53 | std::string getPath() const { return path_; } 54 | std::string getQuery() const { return query_; } 55 | std::string getHeader(const std::string& field) const; 56 | std::string getMethod() const; 57 | bool keepAlive() const; // 是否长连接 58 | 59 | private: 60 | // 解析请求行 61 | bool __parseRequestLine(const char* begin, const char* end); 62 | // 设置HTTP方法 63 | bool __setMethod(const char* begin, const char* end); 64 | // 设置URL路径 65 | void __setPath(const char* begin, const char* end) 66 | { 67 | std::string subPath; 68 | subPath.assign(begin, end); 69 | if(subPath == "/") 70 | subPath = "/index.html"; 71 | path_ = STATIC_ROOT + subPath; 72 | } 73 | // 设置URL参数 74 | void __setQuery(const char* begin, const char* end) 75 | { query_.assign(begin, end); } 76 | // 设置HTTP版本 77 | void __setVersion(Version version) 78 | { version_ = version; } 79 | // 增加报文头 80 | void __addHeader(const char* start, const char* colon, const char* end); 81 | 82 | private: 83 | // 网络通信相关 84 | int fd_; // 文件描述符 85 | Buffer inBuff_; // 读缓冲区 86 | Buffer outBuff_; // 写缓冲区 87 | bool working_; // 若正在工作,则不能被超时事件断开连接 88 | 89 | // 定时器相关 90 | Timer* timer_; 91 | 92 | // 报文解析相关 93 | HttpRequestParseState state_; // 报文解析状态 94 | Method method_; // HTTP方法 95 | Version version_; // HTTP版本 96 | std::string path_; // URL路径 97 | std::string query_; // URL参数 98 | std::map headers_; // 报文头部 99 | }; // class HttpRequest 100 | } // namespace swings 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /code/HttpResponse.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpResponse.h" 2 | #include "Buffer.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include // open 10 | #include // close 11 | #include // stat 12 | #include // mmap, munmap 13 | 14 | using namespace swings; 15 | 16 | const std::map HttpResponse::statusCode2Message = { 17 | {200, "OK"}, 18 | {400, "Bad Request"}, 19 | {403, "Forbidden"}, 20 | {404, "Not Found"} 21 | }; 22 | 23 | const std::map HttpResponse::suffix2Type = { 24 | {".html", "text/html"}, 25 | {".xml", "text/xml"}, 26 | {".xhtml", "application/xhtml+xml"}, 27 | {".txt", "text/plain"}, 28 | {".rtf", "application/rtf"}, 29 | {".pdf", "application/pdf"}, 30 | {".word", "application/nsword"}, 31 | {".png", "image/png"}, 32 | {".gif", "image/gif"}, 33 | {".jpg", "image/jpeg"}, 34 | {".jpeg", "image/jpeg"}, 35 | {".au", "audio/basic"}, 36 | {".mpeg", "video/mpeg"}, 37 | {".mpg", "video/mpeg"}, 38 | {".avi", "video/x-msvideo"}, 39 | {".gz", "application/x-gzip"}, 40 | {".tar", "application/x-tar"}, 41 | {".css", "text/css"} 42 | }; 43 | 44 | Buffer HttpResponse::makeResponse() 45 | { 46 | Buffer output; 47 | 48 | if(statusCode_ == 400) { 49 | doErrorResponse(output, "Swings can't parse the message"); 50 | return output; 51 | } 52 | 53 | struct stat sbuf; 54 | // 文件找不到错误 55 | if(::stat(path_.data(), &sbuf) < 0) { 56 | statusCode_ = 404; 57 | doErrorResponse(output, "Swings can't find the file"); 58 | return output; 59 | } 60 | // 权限错误 61 | if(!(S_ISREG(sbuf.st_mode) || !(S_IRUSR & sbuf.st_mode))) { 62 | statusCode_ = 403; 63 | doErrorResponse(output, "Swings can't read the file"); 64 | return output; 65 | } 66 | 67 | // 处理静态文件请求 68 | doStaticRequest(output, sbuf.st_size); 69 | return output; 70 | } 71 | 72 | // TODO 还要填入哪些报文头部选项 73 | void HttpResponse::doStaticRequest(Buffer& output, long fileSize) 74 | { 75 | assert(fileSize >= 0); 76 | 77 | auto itr = statusCode2Message.find(statusCode_); 78 | if(itr == statusCode2Message.end()) { 79 | statusCode_ = 400; 80 | doErrorResponse(output, "Unknown status code"); 81 | return; 82 | } 83 | 84 | // 响应行 85 | output.append("HTTP/1.1 " + std::to_string(statusCode_) + " " + itr -> second + "\r\n"); 86 | // 报文头 87 | if(keepAlive_) { 88 | output.append("Connection: Keep-Alive\r\n"); 89 | output.append("Keep-Alive: timeout=" + std::to_string(CONNECT_TIMEOUT) + "\r\n"); 90 | } else { 91 | output.append("Connection: close\r\n"); 92 | } 93 | output.append("Content-type: " + __getFileType() + "\r\n"); 94 | output.append("Content-length: " + std::to_string(fileSize) + "\r\n"); 95 | // TODO 添加头部Last-Modified: ? 96 | output.append("Server: Swings\r\n"); 97 | output.append("\r\n"); 98 | 99 | // 报文体 100 | int srcFd = ::open(path_.data(), O_RDONLY, 0); 101 | // 存储映射IO 102 | void* mmapRet = ::mmap(NULL, fileSize, PROT_READ, MAP_PRIVATE, srcFd, 0); 103 | ::close(srcFd); 104 | if(mmapRet == (void*) -1) { 105 | munmap(mmapRet, fileSize); 106 | output.retrieveAll(); 107 | statusCode_ = 404; 108 | doErrorResponse(output, "Swings can't find the file"); 109 | return; 110 | } 111 | char* srcAddr = static_cast(mmapRet); 112 | output.append(srcAddr, fileSize); 113 | 114 | munmap(srcAddr, fileSize); 115 | } 116 | 117 | std::string HttpResponse::__getFileType() 118 | { 119 | int idx = path_.find_last_of('.'); 120 | std::string suffix; 121 | // 找不到文件后缀,默认纯文本 122 | if(idx == std::string::npos) { 123 | return "text/plain"; 124 | } 125 | 126 | suffix = path_.substr(idx); 127 | auto itr = suffix2Type.find(suffix); 128 | // 未知文件后缀,默认纯文本 129 | if(itr == suffix2Type.end()) { 130 | return "text/plain"; 131 | } 132 | return itr -> second; 133 | } 134 | 135 | // TODO 还要填入哪些报文头部选项 136 | void HttpResponse::doErrorResponse(Buffer& output, std::string message) 137 | { 138 | std::string body; 139 | 140 | auto itr = statusCode2Message.find(statusCode_); 141 | if(itr == statusCode2Message.end()) { 142 | return; 143 | } 144 | 145 | body += "Swings Error"; 146 | body += ""; 147 | body += std::to_string(statusCode_) + " : " + itr -> second + "\n"; 148 | body += "

" + message + "

"; 149 | body += "
Swings web server"; 150 | 151 | // 响应行 152 | output.append("HTTP/1.1 " + std::to_string(statusCode_) + " " + itr -> second + "\r\n"); 153 | // 报文头 154 | output.append("Server: Swings\r\n"); 155 | output.append("Content-type: text/html\r\n"); 156 | output.append("Connection: close\r\n"); 157 | output.append("Content-length: " + std::to_string(body.size()) + "\r\n\r\n"); 158 | // 报文体 159 | output.append(body); 160 | } 161 | -------------------------------------------------------------------------------- /code/HttpResponse.h: -------------------------------------------------------------------------------- 1 | #ifndef __HTTP_RESPONSE_H__ 2 | #define __HTTP_RESPONSE_H__ 3 | 4 | #include 5 | #include 6 | 7 | #define CONNECT_TIMEOUT 500 // 非活跃连接500ms断开 8 | 9 | namespace swings { 10 | 11 | class Buffer; 12 | 13 | class HttpResponse { 14 | public: 15 | static const std::map statusCode2Message; 16 | static const std::map suffix2Type; 17 | 18 | HttpResponse(int statusCode, std::string path, bool keepAlive) 19 | : statusCode_(statusCode), 20 | path_(path), 21 | keepAlive_(keepAlive) 22 | {} 23 | 24 | ~HttpResponse() {} 25 | 26 | Buffer makeResponse(); 27 | void doErrorResponse(Buffer& output, std::string message); 28 | void doStaticRequest(Buffer& output, long fileSize); 29 | 30 | private: 31 | std::string __getFileType(); 32 | 33 | private: 34 | std::map headers_; // 响应报文头部 35 | int statusCode_; // 响应状态码 36 | std::string path_; // 请求资源路径 37 | bool keepAlive_; // 长连接 38 | }; // class HttpResponse 39 | 40 | } // namespace swings 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /code/HttpServer.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpServer.h" 2 | #include "HttpRequest.h" 3 | #include "HttpResponse.h" 4 | #include "Utils.h" 5 | #include "Epoll.h" 6 | #include "ThreadPool.h" 7 | #include "Timer.h" 8 | 9 | #include 10 | #include // bind 11 | #include // assert 12 | #include // bzero 13 | 14 | #include // close, read 15 | #include // accept 16 | #include // sockaddr_in 17 | 18 | using namespace swings; 19 | 20 | HttpServer::HttpServer(int port, int numThread) 21 | : port_(port), 22 | listenFd_(utils::createListenFd(port_)), 23 | listenRequest_(new HttpRequest(listenFd_)), 24 | epoll_(new Epoll()), 25 | threadPool_(new ThreadPool(numThread)), 26 | timerManager_(new TimerManager()) 27 | { 28 | assert(listenFd_ >= 0); 29 | } 30 | 31 | HttpServer::~HttpServer() 32 | { 33 | } 34 | 35 | void HttpServer::run() 36 | { 37 | // 注册监听套接字到epoll(可读事件,ET模式) 38 | epoll_ -> add(listenFd_, listenRequest_.get(), (EPOLLIN | EPOLLET)); 39 | // 注册新连接回调函数 40 | epoll_ -> setOnConnection(std::bind(&HttpServer::__acceptConnection, this)); 41 | // 注册关闭连接回调函数 42 | epoll_ -> setOnCloseConnection(std::bind(&HttpServer::__closeConnection, this, std::placeholders::_1)); 43 | // 注册请求处理回调函数 44 | epoll_ -> setOnRequest(std::bind(&HttpServer::__doRequest, this, std::placeholders::_1)); 45 | // 注册响应处理回调函数 46 | epoll_ -> setOnResponse(std::bind(&HttpServer::__doResponse, this, std::placeholders::_1)); 47 | 48 | // 事件循环 49 | while(1) { 50 | int timeMS = timerManager_ -> getNextExpireTime(); 51 | // 等待事件发生 52 | // int eventsNum = epoll_ -> wait(TIMEOUTMS); 53 | int eventsNum = epoll_ -> wait(timeMS); 54 | 55 | if(eventsNum > 0) { 56 | // 分发事件处理函数 57 | epoll_ -> handleEvent(listenFd_, threadPool_, eventsNum); 58 | } 59 | timerManager_ -> handleExpireTimers(); 60 | } 61 | } 62 | 63 | // ET 64 | void HttpServer::__acceptConnection() 65 | { 66 | while(1) { 67 | int acceptFd = ::accept4(listenFd_, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC); 68 | if(acceptFd == -1) { 69 | if(errno == EAGAIN) 70 | break; 71 | printf("[HttpServer::__acceptConnection] accept : %s\n", strerror(errno)); 72 | break; 73 | } 74 | // 为新的连接套接字分配HttpRequest资源 75 | HttpRequest* request = new HttpRequest(acceptFd); 76 | timerManager_ -> addTimer(request, CONNECT_TIMEOUT, std::bind(&HttpServer::__closeConnection, this, request)); 77 | // 注册连接套接字到epoll(可读,边缘触发,保证任一时刻只被一个线程处理) 78 | epoll_ -> add(acceptFd, request, (EPOLLIN | EPOLLONESHOT)); 79 | } 80 | } 81 | 82 | void HttpServer::__closeConnection(HttpRequest* request) 83 | { 84 | int fd = request -> fd(); 85 | if(request -> isWorking()) { 86 | return; 87 | } 88 | // printf("[HttpServer::__closeConnection] connect fd = %d is closed\n", fd); 89 | timerManager_ -> delTimer(request); 90 | epoll_ -> del(fd, request, 0); 91 | // 释放该套接字占用的HttpRequest资源,在析构函数中close(fd) 92 | delete request; 93 | request = nullptr; 94 | } 95 | 96 | // LT模式 97 | void HttpServer::__doRequest(HttpRequest* request) 98 | { 99 | timerManager_ -> delTimer(request); 100 | assert(request != nullptr); 101 | int fd = request -> fd(); 102 | 103 | int readErrno; 104 | int nRead = request -> read(&readErrno); 105 | 106 | // read返回0表示客户端断开连接 107 | if(nRead == 0) { 108 | request -> setNoWorking(); 109 | __closeConnection(request); 110 | return; 111 | } 112 | 113 | // 非EAGAIN错误,断开连接 114 | if(nRead < 0 && (readErrno != EAGAIN)) { 115 | request -> setNoWorking(); 116 | __closeConnection(request); 117 | return; 118 | } 119 | 120 | // EAGAIN错误则释放线程使用权,并监听下次可读事件epoll_ -> mod(...) 121 | if(nRead < 0 && readErrno == EAGAIN) { 122 | epoll_ -> mod(fd, request, (EPOLLIN | EPOLLONESHOT)); 123 | request -> setNoWorking(); 124 | timerManager_ -> addTimer(request, CONNECT_TIMEOUT, std::bind(&HttpServer::__closeConnection, this, request)); 125 | return; 126 | } 127 | 128 | // 解析报文,出错则断开连接 129 | if(!request -> parseRequest()) { 130 | // 发送400报文 131 | HttpResponse response(400, "", false); 132 | request -> appendOutBuffer(response.makeResponse()); 133 | 134 | // XXX 立刻关闭连接了,所以就算没写完也只能写一次? 135 | int writeErrno; 136 | request -> write(&writeErrno); 137 | request -> setNoWorking(); 138 | __closeConnection(request); 139 | return; 140 | } 141 | 142 | // 解析完成 143 | if(request -> parseFinish()) { 144 | HttpResponse response(200, request -> getPath(), request -> keepAlive()); 145 | request -> appendOutBuffer(response.makeResponse()); 146 | epoll_ -> mod(fd, request, (EPOLLIN | EPOLLOUT | EPOLLONESHOT)); 147 | } 148 | } 149 | 150 | // LT模式 151 | void HttpServer::__doResponse(HttpRequest* request) 152 | { 153 | timerManager_ -> delTimer(request); 154 | assert(request != nullptr); 155 | int fd = request -> fd(); 156 | 157 | int toWrite = request -> writableBytes(); 158 | 159 | if(toWrite == 0) { 160 | epoll_ -> mod(fd, request, (EPOLLIN | EPOLLONESHOT)); 161 | request -> setNoWorking(); 162 | timerManager_ -> addTimer(request, CONNECT_TIMEOUT, std::bind(&HttpServer::__closeConnection, this, request)); 163 | return; 164 | } 165 | 166 | int writeErrno; 167 | int ret = request -> write(&writeErrno); 168 | 169 | if(ret < 0 && writeErrno == EAGAIN) { 170 | epoll_ -> mod(fd, request, (EPOLLIN | EPOLLOUT | EPOLLONESHOT)); 171 | return; 172 | } 173 | 174 | // 非EAGAIN错误,断开连接 175 | if(ret < 0 && (writeErrno != EAGAIN)) { 176 | request -> setNoWorking(); 177 | __closeConnection(request); 178 | return; 179 | } 180 | 181 | if(ret == toWrite) { 182 | if(request -> keepAlive()) { 183 | request -> resetParse(); 184 | epoll_ -> mod(fd, request, (EPOLLIN | EPOLLONESHOT)); 185 | request -> setNoWorking(); 186 | timerManager_ -> addTimer(request, CONNECT_TIMEOUT, std::bind(&HttpServer::__closeConnection, this, request)); 187 | } else { 188 | request -> setNoWorking(); 189 | __closeConnection(request); 190 | } 191 | return; 192 | } 193 | 194 | epoll_ -> mod(fd, request, (EPOLLIN | EPOLLOUT | EPOLLONESHOT)); 195 | request -> setNoWorking(); 196 | timerManager_ -> addTimer(request, CONNECT_TIMEOUT, std::bind(&HttpServer::__closeConnection, this, request)); 197 | return; 198 | } 199 | -------------------------------------------------------------------------------- /code/HttpServer.h: -------------------------------------------------------------------------------- 1 | #ifndef __HTTP_SERVER_H__ 2 | #define __HTTP_SERVER_H__ 3 | 4 | #include // unique_ptr 5 | #include 6 | 7 | #define TIMEOUTMS -1 // epoll_wait超时时间,-1表示不设超时 8 | #define CONNECT_TIMEOUT 500 // 连接默认超时时间 9 | #define NUM_WORKERS 4 // 线程池大小 10 | 11 | namespace swings { 12 | 13 | // 前置声明,不需要包含HttpRequest.h和Epoll.h 14 | class HttpRequest; 15 | class Epoll; 16 | class ThreadPool; 17 | class TimerManager; 18 | 19 | class HttpServer { 20 | public: 21 | HttpServer(int port, int numThread); 22 | ~HttpServer(); 23 | void run(); // 启动HTTP服务器 24 | 25 | private: 26 | void __acceptConnection(); // 接受新连接 27 | void __closeConnection(HttpRequest* request); // 关闭连接 28 | void __doRequest(HttpRequest* request); // 处理HTTP请求报文,这个函数由线程池调用 29 | void __doResponse(HttpRequest* request); 30 | 31 | private: 32 | using ListenRequestPtr = std::unique_ptr; 33 | using EpollPtr = std::unique_ptr; 34 | using ThreadPoolPtr = std::shared_ptr; 35 | using TimerManagerPtr = std::unique_ptr; 36 | 37 | int port_; // 监听端口 38 | int listenFd_; // 监听套接字 39 | ListenRequestPtr listenRequest_; // 监听套接字的HttpRequest实例 40 | EpollPtr epoll_; // epoll实例 41 | ThreadPoolPtr threadPool_; // 线程池 42 | TimerManagerPtr timerManager_; // 定时器管理器 43 | }; // class HttpServer 44 | 45 | } // namespace swings 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /code/ThreadPool.cpp: -------------------------------------------------------------------------------- 1 | #include "ThreadPool.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace swings; 7 | 8 | ThreadPool::ThreadPool(int numWorkers) 9 | : stop_(false) 10 | { 11 | numWorkers = numWorkers <= 0 ? 1 : numWorkers; 12 | for(int i = 0; i < numWorkers; ++i) 13 | threads_.emplace_back([this]() { 14 | while(1) { 15 | JobFunction func; 16 | { 17 | std::unique_lock lock(lock_); 18 | while(!stop_ && jobs_.empty()) 19 | cond_.wait(lock); 20 | if(jobs_.empty() && stop_) { 21 | // printf("[ThreadPool::ThreadPool] threadid = %lu return\n", pthread_self()); 22 | return; 23 | } 24 | // if(!jobs_.empty()) { 25 | func = jobs_.front(); 26 | jobs_.pop(); 27 | // } 28 | } 29 | if(func) { 30 | // printf("[ThreadPool::ThreadPool] threadid = %lu get a job\n", pthread_self()/*std::this_thread::get_id()*/); 31 | func(); 32 | // printf("[ThreadPool::ThreadPool] threadid = %lu job finish\n", pthread_self()/*std::this_thread::get_id()*/); 33 | } 34 | } 35 | }); 36 | } 37 | 38 | ThreadPool::~ThreadPool() 39 | { 40 | { 41 | std::unique_lock lock(lock_); 42 | stop_ = true; 43 | } 44 | cond_.notify_all(); 45 | for(auto& thread: threads_) 46 | thread.join(); 47 | // printf("[ThreadPool::~ThreadPool] threadpool is remove\n"); 48 | } 49 | 50 | void ThreadPool::pushJob(const JobFunction& job) 51 | { 52 | { 53 | std::unique_lock lock(lock_); 54 | jobs_.push(job); 55 | } 56 | // printf("[ThreadPool::pushJob] push new job\n"); 57 | cond_.notify_one(); 58 | } 59 | -------------------------------------------------------------------------------- /code/ThreadPool.h: -------------------------------------------------------------------------------- 1 | #ifndef __THREAD_POOL__ 2 | #define __THREAD_POOL__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace swings { 12 | 13 | class ThreadPool { 14 | public: 15 | using JobFunction = std::function; 16 | 17 | ThreadPool(int numWorkers); 18 | ~ThreadPool(); 19 | void pushJob(const JobFunction& job); 20 | 21 | private: 22 | std::vector threads_; 23 | std::mutex lock_; 24 | std::condition_variable cond_; 25 | std::queue jobs_; 26 | bool stop_; 27 | }; 28 | 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /code/Timer.cpp: -------------------------------------------------------------------------------- 1 | #include "Timer.h" 2 | #include "HttpRequest.h" 3 | 4 | #include 5 | 6 | using namespace swings; 7 | 8 | void TimerManager::addTimer(HttpRequest* request, 9 | const int& timeout, 10 | const TimeoutCallBack& cb) 11 | { 12 | std::unique_lock lock(lock_); 13 | assert(request != nullptr); 14 | 15 | updateTime(); 16 | Timer* timer = new Timer(now_ + MS(timeout), cb); 17 | timerQueue_.push(timer); 18 | 19 | // 对同一个request连续调用两次addTimer,需要把前一个定时器删除 20 | if(request -> getTimer() != nullptr) 21 | delTimer(request); 22 | 23 | request -> setTimer(timer); 24 | } 25 | 26 | // 这个函数不必上锁,没有线程安全问题 27 | // 若上锁,反而会因为连续两次上锁造成死锁:handleExpireTimers -> runCallBack -> __closeConnection -> delTimer 28 | void TimerManager::delTimer(HttpRequest* request) 29 | { 30 | // std::unique_lock lock(lock_); 31 | assert(request != nullptr); 32 | 33 | Timer* timer = request -> getTimer(); 34 | if(timer == nullptr) 35 | return; 36 | 37 | // 如果这里写成delete timeNode,会使priority_queue里的对应指针变成垂悬指针 38 | // 正确的方法是惰性删除 39 | timer -> del(); 40 | // 防止request -> getTimer()访问到垂悬指针 41 | request -> setTimer(nullptr); 42 | } 43 | 44 | void TimerManager::handleExpireTimers() 45 | { 46 | std::unique_lock lock(lock_); 47 | updateTime(); 48 | while(!timerQueue_.empty()) { 49 | Timer* timer = timerQueue_.top(); 50 | assert(timer != nullptr); 51 | // 定时器被删除 52 | if(timer -> isDeleted()) { 53 | // std::cout << "[TimerManager::handleExpireTimers] timer = " << Clock::to_time_t(timer -> getExpireTime()) 54 | // << " is deleted" << std::endl; 55 | timerQueue_.pop(); 56 | delete timer; 57 | continue; 58 | } 59 | // 优先队列头部的定时器也没有超时,return 60 | if(std::chrono::duration_cast(timer -> getExpireTime() - now_).count() > 0) { 61 | // std::cout << "[TimerManager::handleExpireTimers] there is no timeout timer" << std::endl; 62 | return; 63 | } 64 | // std::cout << "[TimerManager::handleExpireTimers] timeout" << std::endl; 65 | // 超时 66 | timer -> runCallBack(); 67 | timerQueue_.pop(); 68 | delete timer; 69 | } 70 | } 71 | 72 | int TimerManager::getNextExpireTime() 73 | { 74 | std::unique_lock lock(lock_); 75 | updateTime(); 76 | int res = -1; 77 | while(!timerQueue_.empty()) { 78 | Timer* timer = timerQueue_.top(); 79 | if(timer -> isDeleted()) { 80 | timerQueue_.pop(); 81 | delete timer; 82 | continue; 83 | } 84 | res = std::chrono::duration_cast(timer -> getExpireTime() - now_).count(); 85 | res = (res < 0) ? 0 : res; 86 | break; 87 | } 88 | return res; 89 | } 90 | -------------------------------------------------------------------------------- /code/Timer.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIMER_H__ 2 | #define __TIMER_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace swings { 13 | 14 | using TimeoutCallBack = std::function; 15 | using Clock = std::chrono::high_resolution_clock; 16 | using MS = std::chrono::milliseconds; 17 | using Timestamp = Clock::time_point; 18 | 19 | class HttpRequest; 20 | 21 | class Timer { 22 | public: 23 | Timer(const Timestamp& when, const TimeoutCallBack& cb) 24 | : expireTime_(when), 25 | callBack_(cb), 26 | delete_(false) {} 27 | ~Timer() {} 28 | void del() { delete_ = true; } 29 | bool isDeleted() { return delete_; } 30 | Timestamp getExpireTime() const { return expireTime_; } 31 | void runCallBack() { callBack_(); } 32 | 33 | private: 34 | Timestamp expireTime_; 35 | TimeoutCallBack callBack_; 36 | bool delete_; 37 | }; // class Timer 38 | 39 | // 比较函数,用于priority_queue,时间值最小的在队头 40 | struct cmp { 41 | bool operator()(Timer* a, Timer* b) 42 | { 43 | assert(a != nullptr && b != nullptr); 44 | return (a -> getExpireTime()) > (b -> getExpireTime()); 45 | } 46 | }; 47 | 48 | class TimerManager { 49 | public: 50 | TimerManager() 51 | : now_(Clock::now()) {} 52 | ~TimerManager() {} 53 | void updateTime() { now_ = Clock::now(); } 54 | 55 | void addTimer(HttpRequest* request, const int& timeout, const TimeoutCallBack& cb); // timeout单位ms 56 | void delTimer(HttpRequest* request); 57 | void handleExpireTimers(); 58 | int getNextExpireTime(); // 返回超时时间(优先队列中最早超时时间和当前时间差) 59 | 60 | private: 61 | using TimerQueue = std::priority_queue, cmp>; 62 | 63 | TimerQueue timerQueue_; 64 | Timestamp now_; 65 | std::mutex lock_; 66 | }; // class TimerManager 67 | 68 | } // namespace swings 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /code/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.h" 2 | 3 | #include 4 | #include // bzero 5 | 6 | #include // perror 7 | #include // fcntl, close 8 | #include // fcntl 9 | #include // socket, setsockopt, bind, listen 10 | #include // htonl, htons 11 | 12 | using namespace swings; 13 | 14 | int utils::createListenFd(int port) 15 | { 16 | // 处理非法端口 17 | port = ((port <= 1024) || (port >= 65535)) ? 6666 : port; 18 | 19 | // 创建套接字(IPv4,TCP,非阻塞) 20 | int listenFd = 0; 21 | if((listenFd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) { 22 | printf("[Utils::createListenFd]fd = %d socket : %s\n", listenFd, strerror(errno)); 23 | return -1; 24 | } 25 | 26 | // 避免"Address already in use" 27 | int optval = 1; 28 | if(::setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int)) == -1) { 29 | printf("[Utils::createListenFd]fd = %d setsockopt : %s\n", listenFd, strerror(errno)); 30 | return -1; 31 | } 32 | 33 | // 绑定IP和端口 34 | struct sockaddr_in serverAddr; 35 | ::bzero((char*)&serverAddr, sizeof(serverAddr)); 36 | serverAddr.sin_family = AF_INET; 37 | serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY); 38 | serverAddr.sin_port = ::htons((unsigned short)port); 39 | if(::bind(listenFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) { 40 | printf("[Utils::createListenFd]fd = %d bind : %s\n", listenFd, strerror(errno)); 41 | return -1; 42 | } 43 | 44 | // 开始监听,最大监听队列为LISTENQ 45 | if(::listen(listenFd, LISTENQ) == -1) { 46 | printf("[Utils::createListenFd]fd = %d listen : %s\n", listenFd, strerror(errno)); 47 | return -1; 48 | } 49 | 50 | // 关闭无效监听描述符 51 | if(listenFd == -1) { 52 | ::close(listenFd); 53 | return -1; 54 | } 55 | 56 | return listenFd; 57 | } 58 | 59 | int utils::setNonBlocking(int fd) 60 | { 61 | // 获取套接字选项 62 | int flag = ::fcntl(fd, F_GETFL, 0); 63 | if(flag == -1) { 64 | printf("[Utils::setNonBlocking]fd = %d fcntl : %s\n", fd, strerror(errno)); 65 | return -1; 66 | } 67 | // 设置非阻塞 68 | flag |= O_NONBLOCK; 69 | if(::fcntl(fd, F_SETFL, flag) == -1) { 70 | printf("[Utils::setNonBlocking]fd = %d fcntl : %s\n", fd, strerror(errno)); 71 | return -1; 72 | } 73 | 74 | return 0; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /code/Utils.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTILS_H__ 2 | #define __UTILS_H__ 3 | 4 | #define LISTENQ 1024 // 监听队列长度,操作系统默认值为SOMAXCONN 5 | 6 | namespace swings { 7 | 8 | namespace utils { 9 | int createListenFd(int port); // 创建监听描述符 10 | int setNonBlocking(int fd); // 设置非阻塞模式 11 | } 12 | 13 | } // namespace swings 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /code/main.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpServer.h" 2 | 3 | int main(int argc, char** argv) 4 | { 5 | // TODO 读配置文件 6 | int port = 6666; 7 | if(argc >= 2) { 8 | port = atoi(argv[1]); 9 | } 10 | int numThread = 4; 11 | if(argc >= 3) { 12 | numThread = atoi(argv[2]); 13 | } 14 | 15 | swings::HttpServer server(port, numThread); 16 | server.run(); 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /code/makefile: -------------------------------------------------------------------------------- 1 | EXE := server 2 | 3 | # wildcard扫描源文件 4 | sources := ${wildcard *.cpp} 5 | headfile := ${wildcard *.h} 6 | objects := ${sources:.cpp=.o} 7 | 8 | CC := g++ -std=c++11 -g 9 | RM := rm -rf 10 | LIB := -lpthread 11 | 12 | ${EXE}: ${objects} 13 | ${CC} -o $@ $^ ${LIB} 14 | 15 | ${objects}: %.o: %.cpp ${headfile} 16 | ${CC} -o $@ -c $< 17 | 18 | # 伪目标,意味着clean不代表一个真正的文件名 19 | .PHONY: clean cleanall 20 | cleanall: 21 | ${RM} ${EXE} ${objects} 22 | clean: 23 | ${RM} ${objects} 24 | -------------------------------------------------------------------------------- /code/run.sh: -------------------------------------------------------------------------------- 1 | mkdir ../log 2 | logfile=$(date "+%Y-%m-%d-%H:%M:%S") 3 | ./server 8888 4 | tee "../log/"${logfile}".log" 4 | -------------------------------------------------------------------------------- /doc/img/CPU信息.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/doc/img/CPU信息.png -------------------------------------------------------------------------------- /doc/img/CPU占用情况.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/doc/img/CPU占用情况.png -------------------------------------------------------------------------------- /doc/img/一千并发六十秒八线程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/doc/img/一千并发六十秒八线程.png -------------------------------------------------------------------------------- /doc/img/一千并发六十秒四线程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/doc/img/一千并发六十秒四线程.png -------------------------------------------------------------------------------- /doc/img/代码统计.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/doc/img/代码统计.png -------------------------------------------------------------------------------- /doc/主要函数.md: -------------------------------------------------------------------------------- 1 | ## 主要函数 2 | 3 | --- 4 | 5 | #### 1. HttpServer.cpp 6 | - 启动HTTP服务器:void run(); 7 | - 接受新连接:void acceptConnection(); 8 | - 关闭连接:void closeConnection(HttpRequest* request); 9 | - 读事件处理:void doRequest(HttpRequest* request); 10 | - 写事件处理:void doResponse(HttpRequest* request); 11 | 12 | #### 2. HttpRequest.cpp 13 | - 读数据:int read(int* savedErrno); 14 | - 写数据:int write(int* savedErrno); 15 | - 解析HTTP报文:bool parseRequest(); 16 | 17 | #### 3. HttpResponse.cpp 18 | - 创建HTTP响应报文:Buffer makeResponse(); 19 | - 创建错误响应报文:void doErrorResponse(Buffer& output, std::string message); 20 | - 处理静态资源请求:void doStaticRequest(Buffer& output, long fileSize); 21 | 22 | #### 4. Epoll.cpp 23 | - 等待事件发生, 返回活跃描述符数量:int wait(int timeoutMs); 24 | - 分发事件处理函数:void handleEvent(int listenFd,std::shared_ptr& threadPool, int eventsNum); 25 | 26 | #### 5. ThreadPool.cpp 27 | - 添加任务:void pushJob(const JobFunction& job); 28 | 29 | #### 6. Timer.cpp 30 | - 添加定时器:void addTimer(HttpRequest* request, const int& timeout, const TimeoutCallBack& cb); 31 | - 删除定时器:void delTimer(HttpRequest* request); 32 | - 处理超时定时器:void handleExpireTimers(); 33 | - 获取超时时间:int getNextExpireTime(); -------------------------------------------------------------------------------- /doc/并发模型.md: -------------------------------------------------------------------------------- 1 | ## 并发模型 2 | 3 | --- 4 | 5 | ### 三大类并发模型 6 | 并发模型可分为多进程模型、多线程模型和事件驱动模型三大类: 7 | - 多进程模型 8 | - 每接受一个连接就fork一个子进程,在该子进程中处理该连接的请求 9 | - 特点是多进程占用系统资源多,进程切换的系统开销大,Linux下最大进程数有限制,不利于处理大并发 10 | - 多线程模型 11 | - 每接受一个连接就create一个子线程,利用子线程处理这个连接的请求 12 | - Linux下有最大线程数限制(进程虚拟地址空间有限),进程频繁创建和销毁造成系统开销,同样不利于处理大并发 13 | - 事件驱动模型 14 | - Linux下基于select、poll或epoll实现 15 | - 程序的基本结构是一个事件循环结合非阻塞IO,以事件驱动和事件回调的方式实现业务逻辑,目前在高性能的网络程序中,使用得最广泛的就是这种并发模型 16 | - 结合线程池,避免线程频繁创建和销毁的开销,能很好地处理高并发 17 | 18 | ### I/O多路复用 19 | I/O多路复用使得程序能同时监听多个文件描述符,在一个或更多文件描述符就绪前始终处于睡眠状态。Linux下的I/O复用方案有select、poll和epoll 20 | - select 21 | ```cpp 22 | #include 23 | #include 24 | #include 25 | int select(int n, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout); 26 | ``` 27 | - 监听的文件描述符分为三类,分别等待可读(readfds)、可写(writefds)和异常事件(exceptfds) 28 | - 成功返回时,每个集合只包含对应类型的I/O就绪的文件描述符,下次调用时需要重新填充readfds、writefds和exceptfds 29 | - 监听的文件描述符数量最大值是1024 30 | - poll 31 | ```cpp 32 | #include 33 | int poll(struct pollfd* fds, unsigned int nfds, int timeout); 34 | struct pollfd { 35 | int fd; 36 | short events; 37 | short revents; 38 | }; 39 | ``` 40 | - 与select使用的三个基于位掩码的文件描述符集合不同,poll使用一个简单的nfds个pollfd结构体构成的数组,fds指向该数组 41 | - 每个pollfd结构体指定监听单一的文件描述符。events是要监听的文件描述符事件的一组位掩码,revents字段则是发生在该文件描述符上的事件的位掩码。内核在poll返回时设置revents字段 42 | - 每次返回时,需要遍历nfds数组,寻找活跃的文件描述符并处理,与select相比,下次调用poll时不需要重新填充监听的文件描述符 43 | - epoll 44 | ```cpp 45 | #include 46 | int epoll_create(int size); 47 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event); 48 | int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout); 49 | struct epoll_event { 50 | __u32 events; 51 | union { 52 | void* ptr; 53 | int fd; 54 | __u32 u32; 55 | __u64 u64; 56 | } data; 57 | }; 58 | ``` 59 | - epoll_create用于创建epoll实例 60 | - epoll_ctl用于向指定的epoll实例加入或删除或修改文件描述符,op的有效值是EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL 61 | - epoll_wait等待给定epoll实例关联的文件描述符上的事件,通过events数组返回活跃的文件描述符,因此处理时不需要遍历全部的监听描述符 62 | 63 | 本服务器选用了事件驱动模型,核心是epoll,在创建好了epoll之后: 64 | - 注册监听描述符的读事件 65 | - 监听描述符可读,epoll_wait返回,accept新连接,并注册连接描述符的可读事件 66 | - 连接描述符可读,epoll_wait返回,唤醒线程池中等待任务的线程来处理连接描述符上的I/O事件 67 | 68 | 之所以选择事件驱动模型和epoll是因为适合大并发下的I/O密集场景,而HTTP服务器最核心的任务就是响应请求的数据,涉及大量I/O请求 69 | 70 | ## Reactor和Preactor 71 | 本服务器采用了同步非阻塞I/O模型Reactor,与Reactor相对应的是异步非阻塞I/O模型Preactor,这两种模型的工作步骤(读事件)如下 72 | 73 | - Reactor 74 | - 应用程序注册读就需事件和相关联的事件处理器。 75 | - 事件分离器等待事件的发生。 76 | - 当发生读就需事件的时候,事件分离器调用第一步注册的事件处理器。 77 | - 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理。 78 | - Preactor 79 | - 应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。 80 | - 事件分离器等待读取操作完成事件。 81 | - 在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作,并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,在Proactor中,应用程序需要传递缓存区。 82 | - 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。 83 | 84 | 可以看出,Reactor与Preactor的区别在于事件分离器通知的是I/O就绪事件还是I/O完成事件,这也是同步I/O模型与异步I/O模型的区别。Preactor模型需要操作系统的支持,并不常用。 -------------------------------------------------------------------------------- /doc/并发测试.md: -------------------------------------------------------------------------------- 1 | ## 并发测试 2 | 3 | --- 4 | 5 | #### 1、测试环境 6 | - 测试环境:笔记本,本地,CPU信息如下 7 | ![](./img/CPU信息.png) 8 | - 测试工具:webbench,1000并发连接,时间60s 9 | 10 | #### 2、测试结果 11 | - 线程池大小为4 12 | ![](./img/一千并发六十秒四线程.png) 13 | 14 | - 线程池大小为8 15 | ![](./img/一千并发六十秒八线程.png) 16 | 17 | - CPU占用情况 18 | ![](./img/CPU占用情况.png) 19 | 20 | - 可以看到八线程的成功请求数还略有下降,这也说明CPU核心数是相对合适的线程数 -------------------------------------------------------------------------------- /doc/整体架构.md: -------------------------------------------------------------------------------- 1 | ## 整体架构 2 | 3 | --- 4 | 5 | #### 1、初始化 6 | - 创建监听描述符 7 | - 绑定地址和端口,创建非阻塞监听描述符 8 | 9 | - 创建Epoll实例 10 | 11 | - 创建线程池 12 | - 初始化线程池参数 13 | - 创建线程 14 | - 子线程运行以下代码: 15 | - 对mutex加锁 16 | - 通过condition_variable判断是否任务队列是否有待处理任务,如果没有任务则阻塞并对mutex解锁 17 | - 如有待处理任务,取出任务队列的第一个节点 18 | - 对线程池解锁 19 | - 执行任务 20 | 21 | - 创建定时器管理器 22 | - 更新当前时间 23 | 24 | 25 | #### 2、运行服务器 26 | - 注册监听描述符的可读事件 27 | 28 | - 注册新连接、可读事件、可写事件和关闭连接的事件处理函数 29 | 30 | - 进入事件循环 31 | - 通过定时器管理器获取下一个超时时间点与现在的时间差timeMs 32 | - 设置超时时间timeMs,调用epoll_ -> wait(),阻塞等待监听事件发生 33 | - 调用epoll_ -> wait(),分配事件处理函数,新连接处理函数和断开连接处理函数在I/O线程调用,连接描述符的读写事件处理函数在线程池调用 34 | - 处理超时事件 35 | 36 | #### 3、接受连接 37 | - 监听描述符设置为ET模式,所以需要循环读监听描述符直到返回EAGAIN错误 38 | 39 | - 调用accept4接受新连接,accept4函数可以直接设置新连接描述符为非阻塞模式 40 | 41 | - 为新连接分配一个HttpRequest对象,设置定时器和注册可读事件到epoll 42 | 43 | #### 4、断开连接 44 | - 判断该连接是否活跃,若活跃则退出,不断开连接 45 | 46 | - 若不活跃,删除定时器,从epoll的监听描述符中删除该文件描述符,回收HttpRequest对象,断开连接 47 | 48 | #### 5、可读事件 49 | - 删除文件描述符的超时定时器 50 | 51 | - 从文件描述符中读数据,根据read的返回值处理 52 | - 返回0,断开连接 53 | - EAGAIN错误,对文件描述符进行epoll的MOD操作,注册可读事件(因为使用了EPOLLONESHOT),设置超时定时器并返回 54 | - 其它错误,断开连接 55 | - 返回值大于0,解析报文,若解析报文出错,则返回400响应报文并断开连接 56 | - 若解析报文完成,则通过HttpResponse类构造响应报文,并注册文件描述符可写事件(使用了LT模式) 57 | - 解析报文未完成,对文件描述符进行epoll的MOD操作,注册可读事件(因为使用了EPOLLONESHOT) 58 | 59 | #### 6、可写事件 60 | - 删除文件描述符的超时定时器 61 | 62 | - 若文件描述符的输出缓冲区为空,设置超时定时器,直接返回 63 | 64 | - 往文件描述符中写数据,根据write的返回值处理 65 | - EAGAIN错误,对文件描述符进行epoll的MOD操作,注册可写事件(因为使用了EPOLLONESHOT),返回 66 | - 其它错误,断开连接 67 | - 缓冲区的数据写完,如果是HTTP长连接,重置HTTP解析状态,对文件描述符进行epoll的MOD操作,注册可读事件(因为使用了EPOLLONESHOT),设置超时定时器,返回;不是HTTP长连接则断开连接 68 | - 缓冲区的数据没有写完,对文件描述符进行epoll的MOD操作,注册可读事件(因为使用了EPOLLONESHOT),设置超时定时器,返回 69 | 70 | #### 7、线程池 71 | - ThreadPool类的数据成员如下 72 | ```cpp 73 | using JobFunction = std::function; 74 | std::vector threads_; 75 | std::mutex lock_; 76 | std::condition_variable cond_; 77 | std::queue jobs_; 78 | bool stop_; 79 | ``` 80 | 81 | - 外部对线程池的操作只有添加任务,添加任务流程如下 82 | - 对互斥量加锁 83 | - 把任务push进任务队列 84 | - 解锁 85 | - 通过condition_variable唤醒一个阻塞线程 86 | 87 | #### 8、定时器 88 | - 定时器包含两个类Timer和TimerManager 89 | 90 | - Timer类是内部类,不对外部开放,外部直接使用TimerManger类 91 | 92 | - TimerManager类用小根堆管理Timer,根据超时时间排序 93 | 94 | - TimerManager的关键函数是addTimer、delTimer、handleExpireTimers和getNextExpireTime 95 | 96 | - addTimer用于添加定时器,流程如下 97 | - 对定时器管理器加锁 98 | - 更新当前时间 99 | - 创建一个Timer对象并添加进小根堆 100 | - 使HttpRequest对象的timer_指向该Timer对象 101 | 102 | - delTimer用于删除定时器(懒删除) 103 | - 通过HttpRequest对象的timer_指针对该定时器设置删除标志,并设置timer_为空 104 | 105 | - handleExpireTimers用于调用超时定时器的超时回调函数 106 | - 遍历小根堆获取定时器,直到小根堆的根节点不超时 107 | - 若定时器被设置删除标志,则回收定时器资源,继续下一次遍历 108 | - 定时器超时,调用超时回调函数 109 | 110 | - getNextExpireTime用于获取最近的超时时间,用于设置epoll_wait超时时间 111 | - 遍历小根堆获取定时器 112 | - 若小根堆为空,返回-1 113 | - 若定时器被设置删除标志,则回收定时器资源,继续下一次遍历 114 | - 若定时器已超时,返回0 115 | - 若定时器未超时,返回定时器的超时时间 - 当前时间 116 | 117 | #### 9、缓冲区 118 | - 对muduo缓冲区做了修改,基本原理相似,具体参见muduo缓冲区 119 | -------------------------------------------------------------------------------- /doc/核心类.md: -------------------------------------------------------------------------------- 1 | ## 核心类 2 | 3 | --- 4 | 5 | 1. HTTP请求信息类(HttpRequest.h) 6 | - 用于管理HTTP连接和解析HTTP请求报文 7 | - 数据成员有三类 8 | - 网络通信相关 9 | - 文件描述符、输入缓冲区、输出缓冲区 10 | - 定时器相关 11 | - 指向定时器的指针 12 | - 报文解析相关 13 | - 一些报文解析状态 14 | 15 | 2. HTTP响应类(HttpResponse.h) 16 | - 用于创建HTTP响应报文 17 | 18 | 3. Epoll类(Epoll.h) 19 | - 对epoll的封装 20 | - 核心函数是wait和handleEvent,分别用于等待事件和分发事件处理函数 21 | 22 | 4. HttpServer类(HttpServer.h) 23 | - HTTP服务器类,拥有监听描述符、线程池、定时器管理器和Epoll 24 | - 核心函数是acceptConnection、closeConnection、doRequest和doResponse,作用如函数名所示,其中后两个函数放在线程池中运行,需要注意线程安全问题 25 | 26 | 5. ThreadPool线程池类(ThreadPool.h) 27 | - 使用C++11的thread、mutex和condition_variable实现的线程池 28 | 29 | 6. TimerManager定时器管理器类(Timer.h) 30 | - 使用C++的priority_queue写的一个定时器管理器,priority_queue的底层是二叉堆 31 | - 使用惰性删除的方式处理被删除的定时器 32 | 33 | 7. Buffer缓冲区类(Buffer.h) 34 | - 参考muduo的Buffer类实现的缓冲区,与其不同的是没有在缓冲区头部预留空闲空间 35 | - 底层的数据结构用vector 36 | -------------------------------------------------------------------------------- /doc/相关知识.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/doc/相关知识.md -------------------------------------------------------------------------------- /doc/遇到的问题.md: -------------------------------------------------------------------------------- 1 | ## 遇到的问题 2 | 3 | --- 4 | 5 | #### 1. 出现大量Bad file descriptor错误 6 | - 这个bug是调试时间最长的,原因是在HttpRequest的析构函数中关闭了连接描述符,但是在delete一个HttpRequest对象后又错误地再关闭一次文件描述符。在低并发时,这个bug没有表现出来,而高并发时,析构函数中关闭的连接描述符立刻被accept4分配给了新的HTTP连接,而这时再次调用close(fd)就错误地把新连接关闭了,这时候对新连接进行操作就会触发Bad file descriptor错误,在webbench压测时会有大量的失败请求 7 | 8 | #### 2. 段错误 9 | - 段错误会生成core文件(ubuntu默认不生成,需要进行一些设置),通过gdb可打开core文件,随后通过bt命令查看函数调用栈,分析函数调用栈即可debug 10 | 11 | #### 3. 处理超时事件和处理I/O事件的顺序 12 | - 由于超时事件会断开连接,因此如果先处理超时事件,有可能断开了即将要在I/O事件处理函数中处理的连接,因此需要先处理I/O事件再处理超时事件 13 | -------------------------------------------------------------------------------- /doc/项目目的.md: -------------------------------------------------------------------------------- 1 | ## 项目目的 2 | --- 3 | SWING是个人的C++网络编程项目,在开始做这个项目之前,花时间读了一些C++后台方向的书,包括 4 | - 《C++Primer》 5 | - 《深度探索C++对象模型》 6 | - 《Effective C++》 7 | - 《STL源码剖析》 8 | - 《UNIX环境高级编程》 9 | - 《TCP/IP网络编程》 10 | - 《Linux系统编程》 11 | - 《Linux高性能服务器编程》 12 | - 《Linux多线程服务器编程》 13 | 14 | 纸上得来终觉浅,读了这些书后觉得知识点很杂很破碎,于是想要做一个练手项目,希望能通过这个项目能把所学知识串成一个整体。这个项目的可扩展性必须要强,能够随时新增一些功能,达到边做项目边学习的目的。 15 | 16 | 考虑C++和网络编程的技术栈,最终选择了静态Web服务器这个题目。 17 | 18 | 开发该项目时需要做到以下几点: 19 | - 使用最基本、最常见的开发工具,如g++、make、gdb 20 | - 代码只使用C++标准库,不使用第三方库如boost等 21 | - 尽量用上C++11的新特性,如智能指针、函数对象、thread线程库、chrono时间库等 22 | - 能处理静态文件的访问,支持HTTP长连接和超时断开,处理常见的400,403和404错误 23 | - 统一在Linux平台下开发,通过git进行版本控制 -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## SWINGS WebServer 2 | 3 | --- 4 | 5 | ## 简介 6 | **SWINGS是用C++11实现的高性能Web服务器,可处理静态资源请求,支持HTTP长连接及超时断开** 7 | 8 | ## 代码统计 9 | ![](./doc/img/代码统计.png) 10 | 11 | ## 开发工具 12 | - **操作系统**: Ubuntu18.04LTS 13 | - **编译器**: vim + vscode + sublime 14 | - **编译器**: g++ 7.3.0 15 | - **版本控制**: git 16 | - **工程构建**: make 17 | - **调试工具**: gdb 18 | - **压测工具**: webbench 19 | 20 | ## 使用方式 21 | ```shell 22 | git clone git@github.com:zhangwenxiao/swings.git 23 | cd code 24 | make 25 | sh run.sh 26 | ``` 27 | 28 | ## 技术要点 29 | - 并发模型为Reactor 30 | - 使用Epoll水平触发+EPOLLONESHOT,非阻塞IO 31 | - 为充分利用多核CPU的性能,以多线程的形式实现服务器,并实现线程池避免线程频繁创建销毁造成的系统开销 32 | - 实现基于小根堆的定时器,用于断开超时连接 33 | - 实现可以自动增长的缓冲区,作为HTTP连接的输入和输出缓冲区 34 | 35 | ## 文档 36 | | Part I | Part II | Part III | Part IV | Part V | Part VI | Part VIII | Part IX | 37 | | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | 38 | |[项目目的](./doc/项目目的.md)|[并发模型](./doc/并发模型.md)|[核心类](./doc/核心类.md)|[整体架构](./doc/整体架构.md)|[主要函数](./doc/主要函数.md)|[遇到的问题](./doc/遇到的问题.md)|[并发测试](./doc/并发测试.md)|[相关知识](./doc/相关知识.md)| 39 | -------------------------------------------------------------------------------- /test/makefile: -------------------------------------------------------------------------------- 1 | SRC := ../code/HttpRequest.cpp ../code/Timer.cpp ../code/Buffer.cpp 2 | HEADER := ../code/HttpRequest.h ../code/Timer.h ../code/Buffer.h 3 | 4 | test_timer: test_timer.cpp ${SRC} ${HEADER} 5 | g++ -std=c++11 -I ../code -o test_timer test_timer.cpp ${SRC} -lpthread 6 | 7 | test_thread_pool: test_thread_pool.cpp ../code/ThreadPool.cpp ../code/ThreadPool.h 8 | g++ -std=c++11 -I ../code -o test_thread_pool test_thread_pool.cpp ../code/ThreadPool.cpp -lpthread 9 | -------------------------------------------------------------------------------- /test/test_thread_pool.cpp: -------------------------------------------------------------------------------- 1 | #include "ThreadPool.h" 2 | 3 | #include 4 | 5 | using namespace swings; 6 | 7 | void func(int n) { 8 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 9 | printf("%d\n", n); 10 | } 11 | 12 | int main() 13 | { 14 | ThreadPool tp(4); 15 | for(int i = 0; i < 10; ++i) { 16 | tp.pushJob(std::bind(&func, i)); 17 | } 18 | return 0; 19 | } -------------------------------------------------------------------------------- /test/test_timer.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpRequest.h" 2 | #include "Timer.h" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | using namespace swings; 10 | 11 | void func(HttpRequest* req) { 12 | // std::cout << "timeout id=" << req -> fd() << std::endl; 13 | return; 14 | } 15 | 16 | int main() 17 | { 18 | HttpRequest req1(2), req2(4), req3(9), req4(7); 19 | TimerManager timerManager; 20 | timerManager.addTimer(&req1, 2000, std::bind(&func, &req1)); 21 | timerManager.addTimer(&req2, 4000, std::bind(&func, &req2)); 22 | timerManager.addTimer(&req3, 9000, std::bind(&func, &req3)); 23 | // std::cout << "[main] add timer finish" << std::endl; 24 | int epollFd = ::epoll_create1(EPOLL_CLOEXEC); 25 | // std::cout << "[main] create epoll finish" << std::endl; 26 | 27 | int flag = 0; 28 | 29 | while(1) { 30 | int time = timerManager.getNextExpireTime(); 31 | // std::cout << "wait " << time << " ms ..." << std::endl; 32 | if(time == -1) 33 | break; 34 | epoll_event events[1]; 35 | epoll_wait(epollFd, events, 1, time); 36 | 37 | timerManager.handleExpireTimers(); 38 | if(flag == 0) { 39 | // 测试删除定时器 40 | timerManager.delTimer(&req2); 41 | // 测试连续两次添加定时器 42 | timerManager.addTimer(&req4, 5000, std::bind(&func, &req4)); 43 | timerManager.addTimer(&req4, 6000, std::bind(&func, &req4)); 44 | flag = 1; 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /test/webbench-1.5/COPYRIGHT: -------------------------------------------------------------------------------- 1 | debian/copyright -------------------------------------------------------------------------------- /test/webbench-1.5/ChangeLog: -------------------------------------------------------------------------------- 1 | debian/changelog -------------------------------------------------------------------------------- /test/webbench-1.5/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS?= -Wall -ggdb -W -O 2 | CC?= gcc 3 | LIBS?= 4 | LDFLAGS?= 5 | PREFIX?= /usr/local 6 | VERSION=1.5 7 | TMPDIR=/tmp/webbench-$(VERSION) 8 | 9 | all: webbench tags 10 | 11 | tags: *.c 12 | -ctags *.c 13 | 14 | install: webbench 15 | install -s webbench $(DESTDIR)$(PREFIX)/bin 16 | install -m 644 webbench.1 $(DESTDIR)$(PREFIX)/man/man1 17 | install -d $(DESTDIR)$(PREFIX)/share/doc/webbench 18 | install -m 644 debian/copyright $(DESTDIR)$(PREFIX)/share/doc/webbench 19 | install -m 644 debian/changelog $(DESTDIR)$(PREFIX)/share/doc/webbench 20 | 21 | webbench: webbench.o Makefile 22 | $(CC) $(CFLAGS) $(LDFLAGS) -o webbench webbench.o $(LIBS) 23 | 24 | clean: 25 | -rm -f *.o webbench *~ core *.core tags 26 | 27 | tar: clean 28 | -debian/rules clean 29 | rm -rf $(TMPDIR) 30 | install -d $(TMPDIR) 31 | cp -p Makefile webbench.c socket.c webbench.1 $(TMPDIR) 32 | install -d $(TMPDIR)/debian 33 | -cp -p debian/* $(TMPDIR)/debian 34 | ln -sf debian/copyright $(TMPDIR)/COPYRIGHT 35 | ln -sf debian/changelog $(TMPDIR)/ChangeLog 36 | -cd $(TMPDIR) && cd .. && tar cozf webbench-$(VERSION).tar.gz webbench-$(VERSION) 37 | 38 | webbench.o: webbench.c socket.c Makefile 39 | 40 | .PHONY: clean install all tar 41 | -------------------------------------------------------------------------------- /test/webbench-1.5/debian/changelog: -------------------------------------------------------------------------------- 1 | webbench (1.5) unstable; urgency=low 2 | 3 | * allow building with both Gnu and BSD make 4 | 5 | -- Radim Kolar Fri, Jun 25 12:00:20 CEST 2004 6 | 7 | webbench (1.4) unstable; urgency=low 8 | 9 | * check if url is not too long 10 | * report correct program version number 11 | * use yield() when waiting for test start 12 | * corrected error codes 13 | * check availability of test server first 14 | * do not abort test if first request failed 15 | * report when some childrens are dead. 16 | * use alarm, not time() for lower syscal use by bench 17 | * use mode 644 for installed doc 18 | * makefile cleaned for better freebsd ports integration 19 | 20 | -- Radim Kolar Thu, 15 Jan 2004 11:15:52 +0100 21 | 22 | webbench (1.3) unstable; urgency=low 23 | 24 | * Build fixes for freeBSD 25 | * Default benchmark time 60 -> 30 26 | * generate tar with subdirectory 27 | * added to freeBSD ports collection 28 | 29 | -- Radim Kolar Mon, 12 Jan 2004 17:00:24 +0100 30 | 31 | webbench (1.2) unstable; urgency=low 32 | 33 | * Only debian-related bugfixes 34 | * Updated Debian/rules 35 | * Adapted to fit new directory system 36 | * moved from debstd to dh_* 37 | 38 | -- Radim Kolar Fri, 18 Jan 2002 12:33:04 +0100 39 | 40 | webbench (1.1) unstable; urgency=medium 41 | 42 | * Program debianized 43 | * added support for multiple methods (GET, HEAD, OPTIONS, TRACE) 44 | * added support for multiple HTTP versions (0.9 -- 1.1) 45 | * added long options 46 | * added multiple clients 47 | * wait for start of second before test 48 | * test time can be specified 49 | * better error checking when reading reply from server 50 | * FIX: tests was one second longer than expected 51 | 52 | -- Radim Kolar Thu, 16 Sep 1999 18:48:00 +0200 53 | 54 | Local variables: 55 | mode: debian-changelog 56 | End: 57 | -------------------------------------------------------------------------------- /test/webbench-1.5/debian/control: -------------------------------------------------------------------------------- 1 | Source: webbench 2 | Section: web 3 | Priority: extra 4 | Maintainer: Radim Kolar 5 | Build-Depends: debhelper (>> 3.0.0) 6 | Standards-Version: 3.5.2 7 | 8 | Package: webbench 9 | Architecture: any 10 | Depends: ${shlibs:Depends} 11 | Description: Simple forking Web benchmark 12 | webbench is very simple program for benchmarking WWW or Proxy servers. 13 | Uses fork() for simulating multiple clients load. Can use HTTP 0.9 - 1.1 14 | requests, but Keep-Alive connections are not supported. 15 | -------------------------------------------------------------------------------- /test/webbench-1.5/debian/copyright: -------------------------------------------------------------------------------- 1 | Webbench was written by Radim Kolar 1997-2004 (hsn@netmag.cz). 2 | 3 | UNIX sockets code (socket.c) taken from popclient 1.5 4/1/94 4 | public domain code, created by Virginia Tech Computing Center. 5 | 6 | Copyright: GPL (see /usr/share/common-licenses/GPL) 7 | -------------------------------------------------------------------------------- /test/webbench-1.5/debian/dirs: -------------------------------------------------------------------------------- 1 | usr/bin -------------------------------------------------------------------------------- /test/webbench-1.5/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # Sample debian/rules that uses debhelper. 3 | # GNU copyright 1997 to 1999 by Joey Hess. 4 | 5 | # Uncomment this to turn on verbose mode. 6 | #export DH_VERBOSE=1 7 | 8 | # This is the debhelper compatability version to use. 9 | export DH_COMPAT=3 10 | 11 | configure: configure-stamp 12 | configure-stamp: 13 | dh_testdir 14 | touch configure-stamp 15 | 16 | build: configure-stamp build-stamp 17 | build-stamp: 18 | dh_testdir 19 | $(MAKE) 20 | touch build-stamp 21 | 22 | clean: 23 | dh_testdir 24 | rm -f build-stamp configure-stamp 25 | 26 | # Add here commands to clean up after the build process. 27 | -$(MAKE) clean 28 | 29 | dh_clean 30 | 31 | install: build 32 | dh_testdir 33 | dh_testroot 34 | dh_clean -k 35 | dh_installdirs 36 | 37 | # Add here commands to install the package into debian/webbench. 38 | $(MAKE) install DESTDIR=$(CURDIR)/debian/webbench 39 | 40 | 41 | # Build architecture-independent files here. 42 | binary-indep: build install 43 | # We have nothing to do by default. 44 | 45 | # Build architecture-dependent files here. 46 | binary-arch: build install 47 | dh_testdir 48 | dh_testroot 49 | dh_installdocs 50 | dh_installman webbench.1 51 | dh_installchangelogs 52 | dh_link 53 | dh_strip 54 | dh_compress 55 | dh_fixperms 56 | # dh_makeshlibs 57 | dh_installdeb 58 | dh_shlibdeps 59 | dh_gencontrol 60 | dh_md5sums 61 | dh_builddeb 62 | 63 | binary: binary-indep binary-arch 64 | .PHONY: build clean binary-indep binary-arch binary install configure 65 | -------------------------------------------------------------------------------- /test/webbench-1.5/socket.c: -------------------------------------------------------------------------------- 1 | /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ 2 | * 3 | * This module has been modified by Radim Kolar for OS/2 emx 4 | */ 5 | 6 | /*********************************************************************** 7 | module: socket.c 8 | program: popclient 9 | SCCS ID: @(#)socket.c 1.5 4/1/94 10 | programmer: Virginia Tech Computing Center 11 | compiler: DEC RISC C compiler (Ultrix 4.1) 12 | environment: DEC Ultrix 4.3 13 | description: UNIX sockets code. 14 | ***********************************************************************/ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | int Socket(const char *host, int clientPort) 30 | { 31 | int sock; 32 | unsigned long inaddr; 33 | struct sockaddr_in ad; 34 | struct hostent *hp; 35 | 36 | memset(&ad, 0, sizeof(ad)); 37 | ad.sin_family = AF_INET; 38 | 39 | inaddr = inet_addr(host); 40 | if (inaddr != INADDR_NONE) 41 | memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); 42 | else 43 | { 44 | hp = gethostbyname(host); 45 | if (hp == NULL) 46 | return -1; 47 | memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); 48 | } 49 | ad.sin_port = htons(clientPort); 50 | 51 | sock = socket(AF_INET, SOCK_STREAM, 0); 52 | if (sock < 0) 53 | return sock; 54 | if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) 55 | return -1; 56 | return sock; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /test/webbench-1.5/tags: -------------------------------------------------------------------------------- 1 | !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ 2 | !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ 3 | !_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ 4 | !_TAG_PROGRAM_NAME Exuberant Ctags // 5 | !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ 6 | !_TAG_PROGRAM_VERSION 5.9~svn20110310 // 7 | METHOD_GET webbench.c 35;" d file: 8 | METHOD_HEAD webbench.c 36;" d file: 9 | METHOD_OPTIONS webbench.c 37;" d file: 10 | METHOD_TRACE webbench.c 38;" d file: 11 | PROGRAM_VERSION webbench.c 39;" d file: 12 | REQUEST_SIZE webbench.c 50;" d file: 13 | Socket socket.c /^int Socket(const char *host, int clientPort)$/;" f 14 | alarm_handler webbench.c /^static void alarm_handler(int signal)$/;" f file: 15 | bench webbench.c /^static int bench(void)$/;" f file: 16 | benchcore webbench.c /^void benchcore(const char *host,const int port,const char *req)$/;" f 17 | benchtime webbench.c /^int benchtime=30;$/;" v 18 | build_request webbench.c /^void build_request(const char *url)$/;" f 19 | bytes webbench.c /^int bytes=0;$/;" v 20 | clients webbench.c /^int clients=1;$/;" v 21 | failed webbench.c /^int failed=0;$/;" v 22 | force webbench.c /^int force=0;$/;" v 23 | force_reload webbench.c /^int force_reload=0;$/;" v 24 | host webbench.c /^char host[MAXHOSTNAMELEN];$/;" v 25 | http10 webbench.c /^int http10=1; \/* 0 - http\/0.9, 1 - http\/1.0, 2 - http\/1.1 *\/$/;" v 26 | long_options webbench.c /^static const struct option long_options[]=$/;" v typeref:struct:option file: 27 | main webbench.c /^int main(int argc, char *argv[])$/;" f 28 | method webbench.c /^int method=METHOD_GET;$/;" v 29 | mypipe webbench.c /^int mypipe[2];$/;" v 30 | proxyhost webbench.c /^char *proxyhost=NULL;$/;" v 31 | proxyport webbench.c /^int proxyport=80;$/;" v 32 | request webbench.c /^char request[REQUEST_SIZE];$/;" v 33 | speed webbench.c /^int speed=0;$/;" v 34 | timerexpired webbench.c /^volatile int timerexpired=0;$/;" v 35 | usage webbench.c /^static void usage(void)$/;" f file: 36 | -------------------------------------------------------------------------------- /test/webbench-1.5/webbench: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/test/webbench-1.5/webbench -------------------------------------------------------------------------------- /test/webbench-1.5/webbench.1: -------------------------------------------------------------------------------- 1 | .TH WEBBENCH 1 "14 Jan 2004" 2 | .\" NAME should be all caps, SECTION should be 1-8, maybe w/ subsection 3 | .\" other parms are allowed: see man(7), man(1) 4 | .SH NAME 5 | webbench \- simple forking web benchmark 6 | .SH SYNOPSIS 7 | .B webbench 8 | .I "[options] URL" 9 | .br 10 | .SH "AUTHOR" 11 | This program and manual page was written by Radim Kolar, 12 | for the 13 | .B Supreme Personality of Godhead 14 | (but may be used by others). 15 | .SH "DESCRIPTION" 16 | .B webbench 17 | is simple program for benchmarking HTTP servers or any 18 | other servers, which can be accessed via HTTP proxy. Unlike others 19 | benchmarks, 20 | .B webbench 21 | uses multiple processes for simulating traffic 22 | generated by multiple users. This allows better operating 23 | on SMP systems and on systems with slow or buggy implementation 24 | of select(). 25 | .SH OPTIONS 26 | The programs follow the usual GNU command line syntax, with long 27 | options starting with two dashes (`-'). 28 | A summary of options are included below. 29 | .TP 30 | .B \-?, \-h, \-\-help 31 | Show summary of options. 32 | .TP 33 | .B \-v, \-\-version 34 | Show version of program. 35 | .TP 36 | .B \-f, \-\-force 37 | Do not wait for any response from server. Close connection after 38 | request is send. This option produce quite a good denial of service 39 | attack. 40 | .TP 41 | .B \-9, \-\-http09 42 | Use HTTP/0.9 protocol, if possible. 43 | .TP 44 | .B \-1, \-\-http10 45 | Use HTTP/1.0 protocol, if possible. 46 | .TP 47 | .B \-2, \-\-http11 48 | Use HTTP/1.1 protocol (without 49 | .I Keep-Alive 50 | ), if possible. 51 | .TP 52 | .B \-r, \-\-reload 53 | Forces proxy to reload document. If proxy is not 54 | set, option has no effect. 55 | .TP 56 | .B \-t, \-\-time 57 | Run benchmark for 58 | .I 59 | seconds. Default value is 30. 60 | .TP 61 | .B \-p, \-\-proxy 62 | Send request via proxy server. Needed for supporting others protocols 63 | than HTTP. 64 | .TP 65 | .B \-\-get 66 | Use GET request method. 67 | .TP 68 | .B \-\-head 69 | Use HEAD request method. 70 | .TP 71 | .B \-\-options 72 | Use OPTIONS request method. 73 | .TP 74 | .B \-\-trace 75 | Use TRACE request method. 76 | .TP 77 | .B \-c, \-\-clients 78 | Use 79 | .I 80 | multiple clients for benchmark. Default value 81 | is 1. 82 | .SH "EXIT STATUS" 83 | .TP 84 | 0 - sucess 85 | .TP 86 | 1 - benchmark failed, can not connect to server 87 | .TP 88 | 2 - bad command line argument(s) 89 | .TP 90 | 3 - internal error, i.e. fork failed 91 | .SH "TODO" 92 | Include support for using 93 | .I Keep-Alive 94 | HTTP/1.1 connections. 95 | .SH "COPYING" 96 | Webbench is distributed under GPL. Copyright 1997-2004 97 | Radim Kolar (hsn@netmag.cz). 98 | UNIX sockets code taken from popclient 1.5 4/1/94 99 | public domain code, created by Virginia Tech Computing Center. 100 | .BR 101 | This man page is public domain. 102 | -------------------------------------------------------------------------------- /test/webbench-1.5/webbench.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (C) Radim Kolar 1997-2004 3 | * This is free software, see GNU Public License version 2 for 4 | * details. 5 | * 6 | * Simple forking WWW Server benchmark: 7 | * 8 | * Usage: 9 | * webbench --help 10 | * 11 | * Return codes: 12 | * 0 - sucess 13 | * 1 - benchmark failed (server is not on-line) 14 | * 2 - bad param 15 | * 3 - internal error, fork failed 16 | * 17 | */ 18 | #include "socket.c" 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /* values */ 28 | volatile int timerexpired=0; 29 | int speed=0; 30 | int failed=0; 31 | int bytes=0; 32 | /* globals */ 33 | int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ 34 | /* Allow: GET, HEAD, OPTIONS, TRACE */ 35 | #define METHOD_GET 0 36 | #define METHOD_HEAD 1 37 | #define METHOD_OPTIONS 2 38 | #define METHOD_TRACE 3 39 | #define PROGRAM_VERSION "1.5" 40 | int method=METHOD_GET; 41 | int clients=1; 42 | int force=0; 43 | int force_reload=0; 44 | int proxyport=80; 45 | char *proxyhost=NULL; 46 | int benchtime=30; 47 | /* internal */ 48 | int mypipe[2]; 49 | char host[MAXHOSTNAMELEN]; 50 | #define REQUEST_SIZE 2048 51 | char request[REQUEST_SIZE]; 52 | 53 | static const struct option long_options[]= 54 | { 55 | {"force",no_argument,&force,1}, 56 | {"reload",no_argument,&force_reload,1}, 57 | {"time",required_argument,NULL,'t'}, 58 | {"help",no_argument,NULL,'?'}, 59 | {"http09",no_argument,NULL,'9'}, 60 | {"http10",no_argument,NULL,'1'}, 61 | {"http11",no_argument,NULL,'2'}, 62 | {"get",no_argument,&method,METHOD_GET}, 63 | {"head",no_argument,&method,METHOD_HEAD}, 64 | {"options",no_argument,&method,METHOD_OPTIONS}, 65 | {"trace",no_argument,&method,METHOD_TRACE}, 66 | {"version",no_argument,NULL,'V'}, 67 | {"proxy",required_argument,NULL,'p'}, 68 | {"clients",required_argument,NULL,'c'}, 69 | {NULL,0,NULL,0} 70 | }; 71 | 72 | /* prototypes */ 73 | static void benchcore(const char* host,const int port, const char *request); 74 | static int bench(void); 75 | static void build_request(const char *url); 76 | 77 | static void alarm_handler(int signal) 78 | { 79 | timerexpired=1; 80 | } 81 | 82 | static void usage(void) 83 | { 84 | fprintf(stderr, 85 | "webbench [option]... URL\n" 86 | " -f|--force Don't wait for reply from server.\n" 87 | " -r|--reload Send reload request - Pragma: no-cache.\n" 88 | " -t|--time Run benchmark for seconds. Default 30.\n" 89 | " -p|--proxy Use proxy server for request.\n" 90 | " -c|--clients Run HTTP clients at once. Default one.\n" 91 | " -9|--http09 Use HTTP/0.9 style requests.\n" 92 | " -1|--http10 Use HTTP/1.0 protocol.\n" 93 | " -2|--http11 Use HTTP/1.1 protocol.\n" 94 | " --get Use GET request method.\n" 95 | " --head Use HEAD request method.\n" 96 | " --options Use OPTIONS request method.\n" 97 | " --trace Use TRACE request method.\n" 98 | " -?|-h|--help This information.\n" 99 | " -V|--version Display program version.\n" 100 | ); 101 | }; 102 | int main(int argc, char *argv[]) 103 | { 104 | int opt=0; 105 | int options_index=0; 106 | char *tmp=NULL; 107 | 108 | if(argc==1) 109 | { 110 | usage(); 111 | return 2; 112 | } 113 | 114 | while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ) 115 | { 116 | switch(opt) 117 | { 118 | case 0 : break; 119 | case 'f': force=1;break; 120 | case 'r': force_reload=1;break; 121 | case '9': http10=0;break; 122 | case '1': http10=1;break; 123 | case '2': http10=2;break; 124 | case 'V': printf(PROGRAM_VERSION"\n");exit(0); 125 | case 't': benchtime=atoi(optarg);break; 126 | case 'p': 127 | /* proxy server parsing server:port */ 128 | tmp=strrchr(optarg,':'); 129 | proxyhost=optarg; 130 | if(tmp==NULL) 131 | { 132 | break; 133 | } 134 | if(tmp==optarg) 135 | { 136 | fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); 137 | return 2; 138 | } 139 | if(tmp==optarg+strlen(optarg)-1) 140 | { 141 | fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); 142 | return 2; 143 | } 144 | *tmp='\0'; 145 | proxyport=atoi(tmp+1);break; 146 | case ':': 147 | case 'h': 148 | case '?': usage();return 2;break; 149 | case 'c': clients=atoi(optarg);break; 150 | } 151 | } 152 | 153 | if(optind==argc) { 154 | fprintf(stderr,"webbench: Missing URL!\n"); 155 | usage(); 156 | return 2; 157 | } 158 | 159 | if(clients==0) clients=1; 160 | if(benchtime==0) benchtime=60; 161 | /* Copyright */ 162 | fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" 163 | "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" 164 | ); 165 | build_request(argv[optind]); 166 | /* print bench info */ 167 | printf("\nBenchmarking: "); 168 | switch(method) 169 | { 170 | case METHOD_GET: 171 | default: 172 | printf("GET");break; 173 | case METHOD_OPTIONS: 174 | printf("OPTIONS");break; 175 | case METHOD_HEAD: 176 | printf("HEAD");break; 177 | case METHOD_TRACE: 178 | printf("TRACE");break; 179 | } 180 | printf(" %s",argv[optind]); 181 | switch(http10) 182 | { 183 | case 0: printf(" (using HTTP/0.9)");break; 184 | case 2: printf(" (using HTTP/1.1)");break; 185 | } 186 | printf("\n"); 187 | if(clients==1) printf("1 client"); 188 | else 189 | printf("%d clients",clients); 190 | 191 | printf(", running %d sec", benchtime); 192 | if(force) printf(", early socket close"); 193 | if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); 194 | if(force_reload) printf(", forcing reload"); 195 | printf(".\n"); 196 | return bench(); 197 | } 198 | 199 | void build_request(const char *url) 200 | { 201 | char tmp[10]; 202 | int i; 203 | 204 | bzero(host,MAXHOSTNAMELEN); 205 | bzero(request,REQUEST_SIZE); 206 | 207 | if(force_reload && proxyhost!=NULL && http10<1) http10=1; 208 | if(method==METHOD_HEAD && http10<1) http10=1; 209 | if(method==METHOD_OPTIONS && http10<2) http10=2; 210 | if(method==METHOD_TRACE && http10<2) http10=2; 211 | 212 | switch(method) 213 | { 214 | default: 215 | case METHOD_GET: strcpy(request,"GET");break; 216 | case METHOD_HEAD: strcpy(request,"HEAD");break; 217 | case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; 218 | case METHOD_TRACE: strcpy(request,"TRACE");break; 219 | } 220 | 221 | strcat(request," "); 222 | 223 | if(NULL==strstr(url,"://")) 224 | { 225 | fprintf(stderr, "\n%s: is not a valid URL.\n",url); 226 | exit(2); 227 | } 228 | if(strlen(url)>1500) 229 | { 230 | fprintf(stderr,"URL is too long.\n"); 231 | exit(2); 232 | } 233 | if(proxyhost==NULL) 234 | if (0!=strncasecmp("http://",url,7)) 235 | { fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); 236 | exit(2); 237 | } 238 | /* protocol/host delimiter */ 239 | i=strstr(url,"://")-url+3; 240 | /* printf("%d\n",i); */ 241 | 242 | if(strchr(url+i,'/')==NULL) { 243 | fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); 244 | exit(2); 245 | } 246 | if(proxyhost==NULL) 247 | { 248 | /* get port from hostname */ 249 | if(index(url+i,':')!=NULL && 250 | index(url+i,':')0) 275 | strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); 276 | if(proxyhost==NULL && http10>0) 277 | { 278 | strcat(request,"Host: "); 279 | strcat(request,host); 280 | strcat(request,"\r\n"); 281 | } 282 | if(force_reload && proxyhost!=NULL) 283 | { 284 | strcat(request,"Pragma: no-cache\r\n"); 285 | } 286 | if(http10>1) 287 | strcat(request,"Connection: close\r\n"); 288 | /* add empty line at end */ 289 | if(http10>0) strcat(request,"\r\n"); 290 | // printf("Req=%s\n",request); 291 | } 292 | 293 | /* vraci system rc error kod */ 294 | static int bench(void) 295 | { 296 | int i,j,k; 297 | pid_t pid=0; 298 | FILE *f; 299 | 300 | /* check avaibility of target server */ 301 | i=Socket(proxyhost==NULL?host:proxyhost,proxyport); 302 | if(i<0) { 303 | fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); 304 | return 1; 305 | } 306 | close(i); 307 | /* create pipe */ 308 | if(pipe(mypipe)) 309 | { 310 | perror("pipe failed."); 311 | return 3; 312 | } 313 | 314 | /* not needed, since we have alarm() in childrens */ 315 | /* wait 4 next system clock tick */ 316 | /* 317 | cas=time(NULL); 318 | while(time(NULL)==cas) 319 | sched_yield(); 320 | */ 321 | 322 | /* fork childs */ 323 | for(i=0;i0) 418 | { 419 | /* fprintf(stderr,"Correcting failed by signal\n"); */ 420 | failed--; 421 | } 422 | return; 423 | } 424 | s=Socket(host,port); 425 | if(s<0) { failed++;continue;} 426 | if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;} 427 | if(http10==0) 428 | if(shutdown(s,1)) { failed++;close(s);continue;} 429 | if(force==0) 430 | { 431 | /* read all available data from socket */ 432 | while(1) 433 | { 434 | if(timerexpired) break; 435 | i=read(s,buf,1500); 436 | /* fprintf(stderr,"%d\n",i); */ 437 | if(i<0) 438 | { 439 | failed++; 440 | close(s); 441 | goto nexttry; 442 | } 443 | else 444 | if(i==0) break; 445 | else 446 | bytes+=i; 447 | } 448 | } 449 | if(close(s)) {failed++;continue;} 450 | speed++; 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome to Swings! 5 | 12 | 13 | 14 |

Welcome to Swings!

15 |

If you see this page, the Swings web server is successfully installed and 16 | working.

17 | 18 |

For online documentation and support please refer to 19 | Swings WebServer.
20 | 21 |

Thank you for using Swings.

22 | 23 | 24 | -------------------------------------------------------------------------------- /www/spring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/www/spring.jpg -------------------------------------------------------------------------------- /www/tencent.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwenxiao/Cpp11WebServer/37b0ac5eb7e310b14aa7247aaadf0dfad3a1dbb6/www/tencent.jpg --------------------------------------------------------------------------------