├── FishWebServer ├── BlockingQueue.h ├── Buffer.cpp ├── Buffer.h ├── CallBacks.cpp ├── CallBacks.h ├── ConnectionPool.cpp ├── ConnectionPool.h ├── HttpConnection.cpp ├── HttpConnection.h ├── HttpServer.cpp ├── HttpServer.h ├── LICENSE ├── Log.h ├── README.md ├── Semaphore.h ├── TcpConnection.cpp ├── TcpConnection.h ├── TcpServer.cpp ├── TcpServer.h ├── ThreadPool.h ├── TimeWheel.h ├── html │ ├── ChangePassword.html │ ├── ChangePasswordError.html │ ├── HTMLPage.html │ ├── SignIn.html │ ├── SignInError.html │ ├── SignUp.html │ ├── SignUpError.html │ └── favicon.ico └── picture │ ├── image-20200704114632971.png │ └── image-20200704120608103.png └── Interview experience └── 字节跳动面经.md /FishWebServer/BlockingQueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "CallBacks.h" 5 | #include 6 | 7 | template 8 | class BlockingQueue 9 | { 10 | typedef std::lock_guard LockGuard; 11 | typedef std::unique_lock uniqueLock; 12 | public: 13 | BlockingQueue(int size):maxSize(size) 14 | {} 15 | void append(const T& events) { 16 | uniqueLock lock(mutexLock); 17 | while (messageQue.size() == maxSize) { 18 | unFull.wait(lock); 19 | } 20 | messageQue.push_back(events); 21 | unEmpty.notify_one(); 22 | } 23 | void append(T&& events) { 24 | uniqueLock lock(mutexLock); 25 | while (messageQue.size() == maxSize) { 26 | unFull.wait(lock); 27 | } 28 | messageQue.push_back(std::move(events)); 29 | unEmpty.notify_one(); 30 | } 31 | T take() { 32 | uniqueLock lock(mutexLock); 33 | while (messageQue.empty()) 34 | { 35 | unEmpty.wait(lock); 36 | } 37 | T target(std::move(messageQue.front())); 38 | messageQue.pop_front(); 39 | 40 | unFull.notify_one(); 41 | return target; 42 | } 43 | size_t size() const{ 44 | LockGuard lock(mutexLock); 45 | return messageQue.size(); 46 | } 47 | bool full() const{ 48 | LockGuard lock(mutexLock); 49 | return maxSize == messageQue.size(); 50 | } 51 | bool empty()const { 52 | LockGuard lock(mutexLock); 53 | return messageQue.empty(); 54 | } 55 | int getMaxSize() { 56 | return maxSize; 57 | } 58 | ~BlockingQueue(){} 59 | private: 60 | int maxSize; 61 | std::list messageQue; 62 | std::condition_variable unEmpty; 63 | std::condition_variable unFull; 64 | mutable std::mutex mutexLock; 65 | }; 66 | 67 | template<> 68 | class BlockingQueue { 69 | typedef std::lock_guard LockGuard; 70 | typedef std::unique_lock uniqueLock; 71 | public: 72 | BlockingQueue(size_t size) :maxSize(size) 73 | {} 74 | bool append(const TcpConnectionPtr& events) { 75 | LockGuard lock(mutexLock); 76 | if (messageQue.size() == maxSize) return false; 77 | messageQue.push_back(events); 78 | unEmpty.notify_one(); 79 | return true; 80 | } 81 | TcpConnectionPtr take() { 82 | uniqueLock lock(mutexLock); 83 | while (messageQue.empty()) 84 | { 85 | unEmpty.wait(lock); 86 | } 87 | TcpConnectionPtr target(std::move(messageQue.front())); 88 | messageQue.pop_front(); 89 | return target; 90 | } 91 | size_t size() const { 92 | LockGuard lock(mutexLock); 93 | return messageQue.size(); 94 | } 95 | bool full() const { 96 | LockGuard lock(mutexLock); 97 | return maxSize == messageQue.size(); 98 | } 99 | bool empty() const { 100 | LockGuard lock(mutexLock); 101 | return messageQue.empty(); 102 | } 103 | ~BlockingQueue() {} 104 | private: 105 | size_t maxSize; 106 | std::list messageQue; 107 | std::condition_variable unEmpty; 108 | mutable std::mutex mutexLock; 109 | }; 110 | -------------------------------------------------------------------------------- /FishWebServer/Buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "Buffer.h" 2 | #include 3 | #include 4 | #include 5 | void Buffer::append(const char* data, size_t len) 6 | { 7 | if (writableBytes() < len) enlargeSpace(len); 8 | std::copy(data, data + len, begin() + writerIndex); 9 | writerIndex += len; 10 | } 11 | 12 | bool Buffer::readFd() 13 | { 14 | char extrabuf[65536]; 15 | struct iovec vec[2]; 16 | int bytes_read = 0; 17 | while (true) 18 | { 19 | const size_t writable = writableBytes(); 20 | vec[0].iov_base = begin() + writerIndex; 21 | vec[0].iov_len = writable; 22 | vec[1].iov_base = extrabuf; 23 | vec[1].iov_len = sizeof extrabuf; 24 | const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1; 25 | bytes_read = ::readv(socketfd, vec, iovcnt); 26 | 27 | if (bytes_read == -1) 28 | { 29 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 30 | break; 31 | } 32 | 33 | return false; 34 | } 35 | else if (bytes_read == 0) return false; 36 | else if (static_cast(bytes_read) <= writable) 37 | { 38 | writerIndex += bytes_read; 39 | } 40 | else { 41 | writerIndex = charVec.size(); 42 | append(extrabuf, bytes_read - writable); 43 | } 44 | } 45 | return true; 46 | } 47 | 48 | void Buffer::enlargeSpace(size_t len) 49 | { 50 | charVec.resize(writerIndex + len); 51 | assert(len <= writableBytes()); 52 | } 53 | 54 | std::string Buffer::getMessageAsString() 55 | { 56 | std::string message(begin(), readableBytes()); 57 | clear(); 58 | return message; 59 | } 60 | 61 | void Buffer::clear() 62 | { 63 | writerIndex = 0; 64 | readerIndex = 0; 65 | } 66 | -------------------------------------------------------------------------------- /FishWebServer/Buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | class Buffer 6 | { 7 | public: 8 | static const size_t kInitialSize = 1024; 9 | public: 10 | explicit Buffer(size_t initialSize = kInitialSize) : charVec(initialSize), 11 | readerIndex(0), 12 | writerIndex(0) 13 | { 14 | assert(readableBytes() == 0); 15 | assert(writableBytes() == initialSize); 16 | } 17 | void setSocketfd(int fd) { socketfd = fd; } 18 | size_t readableBytes() const 19 | { 20 | return writerIndex - readerIndex; 21 | } 22 | size_t writableBytes() const 23 | { 24 | return charVec.size() - writerIndex; 25 | } 26 | char* begin() 27 | { 28 | return &*charVec.begin(); 29 | } 30 | 31 | const char* begin() const 32 | { 33 | return &*charVec.begin(); 34 | } 35 | char* beginRead() 36 | { 37 | return begin() + readerIndex; 38 | } 39 | char* end() 40 | { 41 | return &*charVec.end(); 42 | } 43 | const char* end() const 44 | { 45 | return &*charVec.end(); 46 | } 47 | const char* beginRead() const 48 | { 49 | return begin() + readerIndex; 50 | } 51 | void retrieve(size_t len) { 52 | assert(len <= readableBytes()); 53 | if (len < readableBytes()) readerIndex += len; 54 | else clear(); 55 | } 56 | void append(const char* data, size_t len); 57 | bool readFd(); 58 | void enlargeSpace(size_t len); 59 | std::string getMessageAsString(); 60 | void clear(); 61 | private: 62 | std::vector charVec; 63 | size_t readerIndex; 64 | size_t writerIndex; 65 | int socketfd; 66 | }; 67 | 68 | -------------------------------------------------------------------------------- /FishWebServer/CallBacks.cpp: -------------------------------------------------------------------------------- 1 | #include "CallBacks.h" 2 | #include "sys/epoll.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Log.h" 8 | 9 | static int masterSocketfd = 0; 10 | static int slaveSocketfd = 0; 11 | 12 | int sockets::setnonblocking(int fd) 13 | { 14 | int old_option = fcntl(fd, F_GETFL); 15 | int new_option = old_option | O_NONBLOCK; 16 | fcntl(fd, F_SETFL, new_option); 17 | return old_option; 18 | } 19 | 20 | void sockets::update(int operation, int socketfd,int epollfd) 21 | { 22 | epoll_event event; 23 | event.data.fd = socketfd; 24 | event.events = operation; 25 | epoll_ctl(epollfd, EPOLL_CTL_MOD, socketfd, &event); 26 | } 27 | 28 | int sockets::createSocketfd(int type) 29 | { 30 | int socketfd = ::socket(PF_INET,type, IPPROTO_IP); 31 | assert(socketfd > 0); 32 | LOG_DEBUG("events::create socketfd,id is : %d", socketfd); 33 | return socketfd; 34 | } 35 | 36 | int sockets::accept(int fd, sockaddr_in& addr) 37 | { 38 | socklen_t addrlen = static_cast(sizeof(addr)); 39 | int connfd = accept4(fd, (sockaddr*)&addr, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC); 40 | if (connfd < 0) { 41 | LOG_FATAL("events::accept error! errno %s", strerror(errno)); 42 | } 43 | return connfd; 44 | } 45 | 46 | void sockets::bind(int listenfd, sockaddr_in& addr) 47 | { 48 | int ret = ::bind(listenfd, (struct sockaddr*) & addr, sizeof(addr)); 49 | if (ret < 0) { 50 | LOG_FATAL("events::bind error! errno %s", strerror(errno)); 51 | } 52 | } 53 | 54 | void sockets::listen(int socketfd) 55 | { 56 | int ret = ::listen(socketfd, SOMAXCONN); 57 | if (ret < 0) { 58 | LOG_FATAL("events::listen error! errno %s", strerror(errno)); 59 | } 60 | } 61 | 62 | //将内核事件表注册读事件,ET = 0/LT = 1模式,选择开启EPOLLONESHOT 63 | void sockets::addfd(int epollfd, int fd, bool one_shot,bool flag) 64 | { 65 | epoll_event event; 66 | event.data.fd = fd; 67 | event.events = flag == 0 ? EPOLLIN | EPOLLET | EPOLLRDHUP : EPOLLIN | EPOLLRDHUP; 68 | 69 | if (one_shot) 70 | event.events |= EPOLLONESHOT; 71 | epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); 72 | } 73 | 74 | void sockets::socketpair(int domain, int type, int protocol, int ptr[2]) 75 | { 76 | int ret = ::socketpair(domain, type, protocol, ptr); 77 | if (ret == -1) 78 | LOG_ERROR("socketpair create failed ! errno %s", strerror(errno)); 79 | ::masterSocketfd = ptr[0]; 80 | ::slaveSocketfd = ptr[1]; 81 | } 82 | 83 | void sockets::addsig(int sig, void(handler)(int), bool restart) 84 | { 85 | struct sigaction sa; 86 | memset(&sa, '\0', sizeof(sa)); 87 | sa.sa_handler = handler; 88 | if (restart) 89 | sa.sa_flags |= SA_RESTART; 90 | sigfillset(&sa.sa_mask); 91 | assert(sigaction(sig, &sa, NULL) != -1); 92 | } 93 | 94 | void sockets::sig_handler(int sig) 95 | { 96 | //为保证函数的可重入性,保留原来的errno 97 | int save_errno = errno; 98 | int msg = sig; 99 | send(::slaveSocketfd, (char *)&msg, 1, 0); 100 | errno = save_errno; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /FishWebServer/CallBacks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class TcpConnection; 10 | class EventNode; 11 | 12 | namespace sockets{ 13 | int setnonblocking(int fd); 14 | void update(int operation, int socketfd,int epollfd); 15 | int createSocketfd(int type); 16 | int accept(int fd,sockaddr_in&); 17 | void bind(int socketfd, sockaddr_in&); 18 | void listen(int socketfd); 19 | //将内核事件表注册读事件,ET = 0/LT = 1模式,选择开启EPOLLONESHOT 20 | void addfd(int epollfd, int fd, bool one_shot = false, bool flag = false); 21 | void socketpair(int domain, int type, int protocol, int ptr[2]); 22 | 23 | void addsig(int sig, void(handler)(int), bool restart = true); 24 | void sig_handler(int sig); 25 | } 26 | 27 | 28 | typedef std::shared_ptr NodePtr; 29 | typedef std::weak_ptr WeakNodePtr; 30 | typedef std::shared_ptr TcpConnectionPtr; 31 | typedef std::function handleEventCallBack; 32 | typedef std::weak_ptr WeakTcpConnectionPtr; 33 | typedef std::unordered_set Bucket; 34 | typedef boost::circular_buffer timeWheel; 35 | -------------------------------------------------------------------------------- /FishWebServer/ConnectionPool.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionPool.h" 2 | #include "Log.h" 3 | 4 | 5 | void ConnectionPool::init(std::string host, int port, std::string passward) 6 | { 7 | for (int i = 0; i < contextQue.getMaxSize(); ++i) { 8 | contextQue.append(std::make_shared(host,port)); 9 | } 10 | 11 | } 12 | 13 | ConnectionPool::ConnectionPool() :contextQue(4) 14 | { 15 | } 16 | ConnectionPool::~ConnectionPool() 17 | { 18 | while (!contextQue.empty()) { 19 | auto conn = contextQue.take(); 20 | } 21 | 22 | } 23 | 24 | void ConnectionPool::push(RedisContextPtr& ctx) 25 | { 26 | contextQue.append(ctx); 27 | } 28 | 29 | ConnectionPool::RedisContextPtr ConnectionPool::take() 30 | { 31 | return contextQue.take(); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /FishWebServer/ConnectionPool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "BlockingQueue.h" 3 | #include 4 | #include 5 | struct RedisContext; 6 | class ConnectionPool 7 | { 8 | typedef std::shared_ptr RedisContextPtr; 9 | public: 10 | static ConnectionPool& getInstance() { 11 | static ConnectionPool pool; 12 | return pool; 13 | } 14 | 15 | void init(std::string host, int port, std::string passward); 16 | void push(RedisContextPtr&); 17 | RedisContextPtr take(); 18 | private: 19 | ConnectionPool(); 20 | ~ConnectionPool(); 21 | 22 | BlockingQueue contextQue; 23 | }; 24 | 25 | struct RedisContext { 26 | RedisContext(std::string host, int port) { 27 | context = redisConnect(host.c_str(), port); 28 | std::cout << "connect sucess!" << std::endl; 29 | } 30 | ~RedisContext() { redisFree(context); std::cout << "free sucess!" << std::endl; 31 | } 32 | redisContext* context; 33 | }; 34 | -------------------------------------------------------------------------------- /FishWebServer/HttpConnection.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpConnection.h" 2 | #include 3 | 4 | bool HttpConnection::setHearMethod(std::string& str, strIter begin, strIter end) 5 | { 6 | //这个函数有点东西,最简单的就是用if-else语句疯狂判断,但是效率可能会有点差。 7 | //我想到如果用到reflex + function overloading,可能会好一些,但是CPP没有reflex,怎么办?用map + decltype? 8 | //现在暂时交由以后处理,暂时先全部返回True 9 | requestHeader[str] = std::string(begin, end); 10 | std::cout << str << "\t" << requestHeader[str]; 11 | return true; 12 | } 13 | 14 | HttpConnection::HTTP_CODE HttpConnection::httpMessageDecode() 15 | { 16 | while ((checkState == CHECK_STATE_CONTENT and lineStatus == LINE_OK) or (lineStatus = parseLine()) == LINE_OK) 17 | { 18 | switch (checkState) 19 | { 20 | case CHECK_STATE_REQUESTLINE: 21 | retStatus = parseRequestLine(); 22 | if (retStatus == BAD_REQUEST) return BAD_REQUEST; 23 | break; 24 | case CHECK_STATE_HEADER: 25 | retStatus = parseHeader(); 26 | if (retStatus == BAD_REQUEST) return BAD_REQUEST; 27 | break; 28 | case CHECK_STATE_CONTENT: 29 | retStatus = parseContent(); 30 | if (retStatus == BAD_REQUEST) return BAD_REQUEST; 31 | lineStatus = LINE_END; 32 | break; 33 | default: 34 | return INTERNAL_ERROR; 35 | break; 36 | } 37 | } 38 | return NO_REQUEST; 39 | } 40 | HttpConnection::LINE_STATUS HttpConnection::parseLine() 41 | { 42 | auto currCheckPos = start; 43 | auto index = start; 44 | for (; index < message.end(); ++index) { 45 | currCheckPos++; 46 | if (*currCheckPos == '\r') { 47 | //达到最后一个字符,则inputBuffer消息不完整。 48 | if (currCheckPos + 1 == message.end()) 49 | return LINE_OPEN; 50 | else if (*(currCheckPos + 1) == '\n') { 51 | end = currCheckPos + 2; 52 | return LINE_OK; 53 | } 54 | return LINE_BAD; 55 | } 56 | //上次读取到\r就到buffer末尾了,没有接收完整,再次接收时会出现这种情况 57 | else if (*currCheckPos == '\n') { 58 | if (currCheckPos > message.begin() and *(currCheckPos - 1) == '\r') { 59 | end = currCheckPos + 1; 60 | return LINE_OK; 61 | } 62 | return LINE_BAD; 63 | } 64 | } 65 | 66 | //没有\r\n,inputBuffer消息不完整 67 | return LINE_OPEN; 68 | } 69 | 70 | HttpConnection::HTTP_CODE HttpConnection::parseRequestLine() 71 | { 72 | auto space = std::find(start, end, ' '); 73 | //找不到空格,说明请求行出错。 74 | if (space == end) return BAD_REQUEST; 75 | requestMethod = getMethod(start, space); 76 | //出现无效模式, 请求行出错 77 | if (requestMethod == INVALID) return BAD_REQUEST; 78 | 79 | start = space + 1; 80 | space = std::find(start, end, ' '); 81 | auto question = std::find(start, space, '?'); 82 | if (question != space) { 83 | requestPath.assign(start, question); 84 | requestQuery.assign(question, space); 85 | } 86 | else { 87 | requestPath.assign(start, question); 88 | } 89 | 90 | start = space + 1; 91 | // HTTP/1.X\r\n 需要8个字节,小于8个则请求行出错。 92 | if (end - start < 10 and !std::equal(start, end, "HTTP/1.")) return BAD_REQUEST; 93 | if (*(end - 3) == '1') { 94 | version = Http11; 95 | } 96 | else if (*(end - 3) == '0') { 97 | version = Http10; 98 | } 99 | else /*未知HTTP协议类型,则请求行出错*/ return BAD_REQUEST; 100 | 101 | //Request hear 的开头 102 | start += 10; 103 | checkState = CHECK_STATE_HEADER; 104 | 105 | return NO_REQUEST; 106 | } 107 | 108 | HttpConnection::HTTP_CODE HttpConnection::parseHeader() 109 | { 110 | auto space = std::find(start, end, ':'); 111 | //若没找到":" 112 | if (space == end) { 113 | //到达空行 114 | if (std::string(start + 1, end) == "\r\n") { 115 | //下一行 116 | 117 | start = end; 118 | checkState = CHECK_STATE_CONTENT; 119 | return GET_REQUEST; 120 | } 121 | return BAD_REQUEST; 122 | } 123 | //滤除":" 判断 124 | std::string headRequest = std::string(start, space); 125 | space = space + 1; 126 | 127 | setHearMethod(headRequest, space, end - 2); 128 | 129 | //下一行 130 | start = end; 131 | return NO_REQUEST; 132 | } 133 | 134 | HttpConnection::HTTP_CODE HttpConnection::parseContent() 135 | { 136 | requestBody = std::string(start, end); 137 | std::cout << requestBody.empty() << std::endl; 138 | return requestBody.empty() ? NO_REQUEST : GET_REQUEST; 139 | } 140 | 141 | std::string& HttpConnection::getRequestPath() 142 | { 143 | return requestPath; 144 | } 145 | -------------------------------------------------------------------------------- /FishWebServer/HttpConnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TcpConnection.h" 3 | #include 4 | class HttpConnection : public TcpConnection 5 | { 6 | typedef std::string::iterator strIter; 7 | public: 8 | enum HttpStatusCode 9 | { 10 | kUnknown, 11 | k200Ok = 200, 12 | k301MovedPermanently = 301, 13 | k400BadRequest = 400, 14 | k401Unauthorized = 401, 15 | k40Forbidden = 403, 16 | k404NotFound = 404, 17 | k500InternalServerError = 500, 18 | k503ServerUnavailable = 503 19 | }; 20 | 21 | enum HttpVersion 22 | { 23 | Unknown, Http10, Http11 24 | }; 25 | enum HTTP_CODE 26 | { 27 | NO_REQUEST, 28 | GET_REQUEST, 29 | BAD_REQUEST, 30 | NO_RESOURCE, 31 | FORBIDDEN_REQUEST, 32 | FILE_REQUEST, 33 | INTERNAL_ERROR, 34 | CLOSED_CONNECTION 35 | }; 36 | enum CHECK_STATE 37 | { 38 | CHECK_STATE_HEADER = 0, 39 | CHECK_STATE_REQUESTLINE, 40 | CHECK_STATE_CONTENT 41 | }; 42 | enum LINE_STATUS 43 | { 44 | LINE_OK = 0, 45 | LINE_BAD, 46 | LINE_OPEN, 47 | LINE_END 48 | }; 49 | enum CONNECTION_STATE { 50 | KEEPALIVE = 0, 51 | CLOSE, 52 | UNKNOWN 53 | }; 54 | enum METHOD 55 | { 56 | INVALID = 0, 57 | GET, 58 | POST, 59 | HEAD, 60 | PUT, 61 | DELETE, 62 | TRACE, 63 | OPTIONS, 64 | CONNECT, 65 | }; 66 | LINE_STATUS getlineStatus() { 67 | return lineStatus; 68 | } 69 | HTTP_CODE getretStatus() { 70 | return retStatus; 71 | } 72 | HttpConnection(int fd, int epoll) 73 | :processLength(0), checkState(CHECK_STATE_REQUESTLINE), 74 | lineStatus(LINE_BAD),linger(true), contentLength(0) 75 | , requestMethod(INVALID), version(HttpVersion::Unknown), connectionState(UNKNOWN) 76 | ,TcpConnection(fd,epoll), statusCode(HttpStatusCode::kUnknown), retStatus(NO_REQUEST){ 77 | message.clear(); 78 | } 79 | //进入此函数,应是读事件 80 | void processMessage() { 81 | message += inputBuffer.getMessageAsString(); 82 | start = message.begin(); 83 | end = message.end(); 84 | std::cout << message << std::endl; 85 | httpMessageDecode(); 86 | sockets::update(EPOLLOUT | EPOLLONESHOT | EPOLLRDHUP, getSocketfd(), epollfd); 87 | } 88 | std::string& getRequestPath(); 89 | void clear() { 90 | checkState = CHECK_STATE_REQUESTLINE; 91 | processLength = 0; 92 | requestMethod = INVALID; 93 | lineStatus = LINE_BAD; 94 | linger = true; 95 | contentLength = 0; 96 | connectionState = UNKNOWN; 97 | statusCode = kUnknown; 98 | retStatus = NO_REQUEST; 99 | message.clear(); 100 | start = message.begin(); 101 | end = message.end(); 102 | } 103 | private: 104 | METHOD getMethod(std::string::iterator begin, std::string::iterator end) { 105 | assert(requestMethod == INVALID); 106 | std::string method(begin, end); 107 | if (method == "GET") requestMethod = GET; 108 | else if (method == "POST") requestMethod = POST; 109 | else if (method == "HEAD") requestMethod = HEAD; 110 | else if (method == "PUT") requestMethod = PUT; 111 | else if (method == "DELETE") requestMethod = DELETE; 112 | else if (method == "CONNECT") requestMethod = CONNECT; 113 | else if (method == "TRACE") requestMethod = TRACE; 114 | else if (method == "OPTIONS") requestMethod = OPTIONS; 115 | else requestMethod = INVALID; 116 | 117 | return requestMethod; 118 | } 119 | bool setHearMethod(std::string& str, strIter begin, strIter end); 120 | std::string message; 121 | int processLength; 122 | 123 | 124 | LINE_STATUS lineStatus; 125 | HTTP_CODE retStatus; 126 | CHECK_STATE checkState; 127 | HTTP_CODE httpMessageDecode(); 128 | //bool httpMessageWrite(); //此函数将结果copy to outputBuffer 129 | bool linger; 130 | std::string::iterator start; 131 | std::string::iterator end; 132 | 133 | LINE_STATUS parseLine(); 134 | //请求模式 135 | METHOD requestMethod; 136 | 137 | 138 | HttpVersion version; //Http版本记录 139 | int contentLength; //以8进制表示的请求体的长度 140 | CONNECTION_STATE connectionState; //客户端(浏览器)想要优先使用的连接类型 keep-alive or close 141 | HttpStatusCode statusCode; 142 | //std::string cookie; // 由之前服务器通过Set-Cookie设置的一个HTTP协议Cookie 143 | //std::string cookieControl; //是否可以被缓存(public可以;private和no - cache不可以;max - age表示可被缓存的时间长) 144 | 145 | //std::string from; //发起此请求的用户的邮件地址 146 | //std::string host; //表示服务器的域名以及服务器所监听的端口号。如果所请求的端口是对应的服务的标准端口(80),则端口号可以省略。 147 | //std::string acceptLanguage; //可接受的响应内容语言列表。 148 | //std::string acceptDateTime; //可接受的按照时间来表示的响应内容版本 149 | //std::string acceptEncoding; //可接受的响应内容的编码方式。 150 | //std::string acceptCharset; //可接受的字符集 151 | //std::string accept; //可接受的响应内容类型(Content-Types)。 152 | //std::string authorization; //用于表示HTTP协议中需要认证资源的认证信息 153 | //std::string userAgent; //浏览器的身份标识字符串 154 | //std::string warning; //一个一般性的警告,表示在实体内容体中可能存在错误。 155 | //std::string via; //告诉服务器,这个请求是由哪些代理发出的。 156 | //std::string upGrade; //浏览器的身份标识字符串 157 | 158 | //std::string expires; //过期时间,优先级低于cache-control中的max-age。 159 | //std::string lastModified; //文件的上一次/最近一次的修改时间。 160 | std::string requestBody; 161 | std::map requestHeader; 162 | size_t age; //从原始服务器到代理缓存形成的估算时间(以秒计,非负) 163 | //是否启用cgi 164 | int cgi; 165 | HTTP_CODE parseRequestLine(); 166 | HTTP_CODE parseHeader(); 167 | HTTP_CODE doRequest(); 168 | HTTP_CODE parseContent(); 169 | 170 | 171 | 172 | std::string requestPath; 173 | std::string requestQuery; 174 | }; 175 | 176 | -------------------------------------------------------------------------------- /FishWebServer/HttpServer.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpServer.h" 2 | #include 3 | #include "CallBacks.h" 4 | #include 5 | void HttpServer::handleRead(TcpConnectionPtr ptr) 6 | { 7 | ptr->read(); 8 | ptr->processMessage(); 9 | } 10 | const char* blank = "\r\n"; 11 | void HttpServer::handleWrite(TcpConnectionPtr ptr) 12 | { 13 | auto upCastptr = dynamic_cast(ptr.get()); 14 | char buf[32]; 15 | snprintf(buf, sizeof buf, "HTTP/1.1 %d ", upCastptr->statusCode); 16 | upCastptr->write(buf); 17 | upCastptr->write("OK"); 18 | upCastptr->write("\r\n"); 19 | upCastptr->write("Connection: "); 20 | upCastptr->write(upCastptr->getHeaderMessage("Connection")); 21 | upCastptr->write("\r\n"); 22 | //std::string b = "Connection: " + upCastptr->getHeaderMessage("Connection")+blank; 23 | //std::cout << "upCastptr->getretStatus() is " << upCastptr->getretStatus() << std::endl; 24 | //std::cout << upCastptr->getRequestPath() << std::endl; 25 | switch (upCastptr->getretStatus()) 26 | { 27 | case HttpConnection::NO_REQUEST: 28 | { 29 | //GET 30 | if (upCastptr->getRequestPath() == "/") { 31 | responseInfo("/home/pi/projects/HttpServerForLinux/html/SignIn.html", "text/html", upCastptr); 32 | } 33 | else { 34 | responseInfo(upCastptr->getRequestPath().c_str(), upCastptr->getContentType(), upCastptr); 35 | } 36 | break; 37 | } 38 | case HttpConnection::GET_REQUEST: 39 | { 40 | //POST 41 | std::string& content = upCastptr->getRequestBody(); 42 | 43 | switch (content[1]) 44 | { 45 | //sign in 46 | case 'i': 47 | signIn(content, upCastptr); 48 | break; 49 | //sign up 50 | case 'u': 51 | signUp(content, upCastptr); 52 | break; 53 | //change password 54 | case 'p': 55 | changePassword(content, upCastptr); 56 | break; 57 | default: 58 | //never came here 59 | break; 60 | } 61 | break; 62 | } 63 | case HttpConnection::BAD_REQUEST: 64 | //协议错了 65 | 66 | break; 67 | default: 68 | //NO_RESOURCE, 69 | //FORBIDDEN_REQUEST, 70 | //FILE_REQUEST, 71 | //INTERNAL_ERROR, 72 | //CLOSED_CONNECTION 73 | upCastptr->send("Http/1.1 404 Not Found\r\n\r\n"); 74 | break; 75 | } 76 | 77 | 78 | upCastptr->send(); 79 | 80 | if (upCastptr->getHeaderMessage("Connection") == "close") { 81 | upCastptr->closeSocket(); 82 | } 83 | else { 84 | sockets::update(EPOLLIN | EPOLLONESHOT | EPOLLRDHUP, upCastptr->getSocketfd(), upCastptr->getepollfd()); 85 | upCastptr->clearAll(); 86 | } 87 | } 88 | 89 | void HttpServer::signUp(std::string &word, HttpConnection* upCastptr) 90 | { 91 | auto iterFirst = word.find_first_of('&'); 92 | auto iterSecond = word.find_last_of('&'); 93 | auto userIter = std::find(word.begin(), word.begin() + iterFirst, '='); 94 | std::string username = std::string(userIter + 1, word.begin() + iterFirst); 95 | 96 | auto connRedis = ConnectionPool::getInstance().take(); 97 | redisReply* r = (redisReply*)redisCommand(connRedis->context, "GET %s", username.c_str()); 98 | 99 | char buffer[100]; 100 | snprintf(buffer, 100, "select password from user_tab where id = '%s'", username.c_str()); 101 | std::string mysqlWord = std::string(buffer); 102 | auto curStr = mySqlCommand(mysqlWord); 103 | if (curStr.empty()) { 104 | auto passwordIter = std::find(word.begin() + iterFirst + 1, word.begin() + iterSecond, '='); 105 | std::string password = std::string(passwordIter + 1, word.begin() + iterSecond); 106 | std::string email = std::string(word.begin() + iterSecond + 1, word.end()); 107 | snprintf(buffer, 100, "insert into user_tab(id,password,email) values('%s','%s','%s')", username.c_str(), password.c_str(), email.c_str()); 108 | mysqlWord = std::string(buffer); 109 | mySqlCommand(mysqlWord); 110 | redisReply* reply = (redisReply*)redisCommand(connRedis->context, "SET %s %s", username.c_str(), password.c_str()); 111 | freeReplyObject(reply); 112 | responseInfo("/home/pi/projects/HttpServerForLinux/html/SignIn.html", "text/html", upCastptr); 113 | } 114 | else { 115 | responseInfo("/home/pi/projects/HttpServerForLinux/html/SignUpError.html", "text/html", upCastptr); 116 | } 117 | 118 | 119 | freeReplyObject(r); 120 | ConnectionPool::getInstance().push(connRedis); 121 | } 122 | 123 | void HttpServer::signIn(std::string &word, HttpConnection* upCastptr) 124 | { 125 | auto iter = word.find('&'); 126 | auto userIter = std::find(word.begin(), word.begin() + iter, '='); 127 | std::string username = std::string(userIter + 1, word.begin() + iter); 128 | auto passwordIter = std::find(word.begin() + iter + 1, word.end(), '='); 129 | std::string password = std::string(passwordIter + 1, word.end()); 130 | auto connRedis = ConnectionPool::getInstance().take(); 131 | redisReply* r = (redisReply*)redisCommand(connRedis->context, "GET %s", username.c_str()); 132 | if (r->str == nullptr) { 133 | char buffer[100]; 134 | snprintf(buffer, 100, "select password from user_tab where id = '%s'", username.c_str()); 135 | std::string mysqlWord = std::string(buffer); 136 | 137 | if (mySqlCommand(mysqlWord) == password) 138 | responseInfo("/home/pi/projects/HttpServerForLinux/html/HTMLPage.html", "text/html", upCastptr); 139 | else 140 | responseInfo("/home/pi/projects/HttpServerForLinux/html/SignInError.html", "text/html", upCastptr); 141 | redisReply* raw_reply = (redisReply*)redisCommand(connRedis->context, "SET %s %s", username.c_str(), password.c_str()); 142 | freeReplyObject(raw_reply); 143 | } 144 | else if (std::string(r->str) != password) { 145 | responseInfo("/home/pi/projects/HttpServerForLinux/html/SignInError.html", "text/html", upCastptr); 146 | } 147 | else { 148 | responseInfo("/home/pi/projects/HttpServerForLinux/html/HTMLPage.html", "text/html", upCastptr); 149 | } 150 | 151 | freeReplyObject(r); 152 | ConnectionPool::getInstance().push(connRedis); 153 | } 154 | 155 | void HttpServer::changePassword(std::string &word, HttpConnection* upCastptr) 156 | { 157 | auto iterFirst = word.find_first_of('&'); 158 | auto iterSecond = word.find_last_of('&'); 159 | auto userIter = std::find(word.begin(), word.begin() + iterFirst, '='); 160 | std::string username = std::string(userIter + 1, word.begin() + iterFirst); 161 | 162 | auto oldpasswordIter = std::find(word.begin() + iterFirst + 1, word.begin() + iterSecond, '='); 163 | std::string oldpassword = std::string(oldpasswordIter + 1, word.begin() + iterSecond); 164 | 165 | auto connRedis = ConnectionPool::getInstance().take(); 166 | redisReply* r = (redisReply*)redisCommand(connRedis->context, "GET %s", username.c_str()); 167 | 168 | 169 | if (r->str == nullptr) { 170 | char buffer[100]; 171 | snprintf(buffer, 100, "select password from user_tab where id = '%s'", username.c_str()); 172 | std::string mysqlWord = std::string(buffer); 173 | if (mySqlCommand(mysqlWord).empty()) { 174 | responseInfo("/home/pi/projects/HttpServerForLinux/html/ChangePasswordError.html", "text/html", upCastptr); 175 | } 176 | else { 177 | auto newpasswordIter = std::find(word.begin() + iterSecond + 1, word.end(), '='); 178 | std::string newpassword = std::string(newpasswordIter + 1, word.end()); 179 | 180 | redisReply* reply = (redisReply*)redisCommand(connRedis->context, "SET %s %s", username.c_str(), newpassword.c_str()); 181 | 182 | char buffer[100]; 183 | snprintf(buffer, 100, "update user_tab set password = '%s' where id = '%s'", newpassword.c_str(), username.c_str()); 184 | std::string mysqlWord = std::string(buffer); 185 | mySqlCommand(mysqlWord); 186 | 187 | 188 | freeReplyObject(reply); 189 | responseInfo("/home/pi/projects/HttpServerForLinux/html/SignIn.html", "text/html", upCastptr); 190 | } 191 | } 192 | else if (std::string(r->str) != oldpassword) { 193 | responseInfo("/home/pi/projects/HttpServerForLinux/html/ChangePasswordError.html", "text/html", upCastptr); 194 | } 195 | else { 196 | auto newpasswordIter = std::find(word.begin() + iterSecond + 1, word.end(), '='); 197 | std::string newpassword = std::string(newpasswordIter + 1, word.end()); 198 | redisReply* reply = (redisReply*)redisCommand(connRedis->context, "SET %s %s", username.c_str(), newpassword.c_str()); 199 | 200 | char buffer[100]; 201 | snprintf(buffer, 100, "update user_tab set password = '%s' where id = '%s'", newpassword.c_str(), username.c_str()); 202 | std::string mysqlWord = std::string(buffer); 203 | mySqlCommand(mysqlWord); 204 | 205 | 206 | freeReplyObject(reply); 207 | responseInfo("/home/pi/projects/HttpServerForLinux/html/SignIn.html", "text/html", upCastptr); 208 | } 209 | freeReplyObject(r); 210 | ConnectionPool::getInstance().push(connRedis); 211 | } 212 | 213 | void HttpServer::responseInfo(const std::string & path, const std::string &type, HttpConnection* upCastptr) 214 | { 215 | std::string content; 216 | char tempBuf[200]; 217 | int fd = open(path.c_str(), O_RDONLY); 218 | int ret; 219 | while ((ret = read(fd, tempBuf, 200)) > 0) { 220 | content += std::string(tempBuf, tempBuf + ret); 221 | } 222 | upCastptr->write("Content-Length: "); 223 | upCastptr->write(std::to_string(content.size())); 224 | upCastptr->write("\r\n"); 225 | upCastptr->write("Content-Type: "); 226 | upCastptr->write(type); 227 | upCastptr->write("\r\n"); 228 | upCastptr->write(blank); 229 | upCastptr->write(content); 230 | } 231 | 232 | std::string & HttpServer::mySqlCommand(std::string & mysqlWord) 233 | { 234 | auto conn = ConnectionPool::getInstance().take(); 235 | std::string res = ""; 236 | if (mysql_query(conn->context, mysqlWord.c_str())) { 237 | LOG_ERROR("%s\n", mysql_error(conn->context)); 238 | return res; 239 | } 240 | 241 | MYSQL_RES *result = mysql_store_result(conn->context); 242 | MYSQL_ROW sql_row; 243 | sql_row = mysql_fetch_row(result); 244 | if (sql_row != nullptr) 245 | res = std::string(sql_row[0]); 246 | mysql_free_result(result); 247 | ConnectionPool::getInstance().push(conn); 248 | return res; 249 | } 250 | -------------------------------------------------------------------------------- /FishWebServer/HttpServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TcpServer.h" 3 | #include "ConnectionPool.h" 4 | 5 | class HttpServer 6 | { 7 | public: 8 | HttpServer(std::string ipAddr, uint16_t port) :server(ipAddr, port) { 9 | server.sethandleRead(std::bind(&HttpServer::handleRead, this, std::placeholders::_1)); 10 | server.sethandleWrite(std::bind(&HttpServer::handleWrite, this, std::placeholders::_1)); 11 | } 12 | void start() { server.start(); } 13 | void handleRead(TcpConnectionPtr); 14 | void handleWrite(TcpConnectionPtr); 15 | private: 16 | void signUp(std::string&, HttpConnection*); 17 | void signIn(std::string&, HttpConnection*); 18 | void changePassword(std::string&, HttpConnection*); 19 | void responseInfo(const std::string&, const std::string&, HttpConnection*); 20 | std::string& mySqlCommand(std::string&); 21 | TcpServer server; 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /FishWebServer/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 fishfishfishh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /FishWebServer/Log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "BlockingQueue.h" 13 | #include "CallBacks.h" 14 | class Log 15 | { 16 | public: 17 | enum LogLevel 18 | { 19 | ALL, 20 | TRACE, 21 | DEBUG, 22 | INFO, 23 | WARNNING, 24 | ERROR, 25 | FATAL, 26 | OFF, 27 | }; 28 | static Log& getInstance() { 29 | static Log instance(10000); 30 | return instance; 31 | } 32 | static void threadFunc() { 33 | Log::getInstance().run(); 34 | } 35 | void setLogLevel(LogLevel level) { logLevel = level; } 36 | Log::LogLevel getLogLevel() { return logLevel; } 37 | 38 | void write(Log::LogLevel level , const char *format, ...) { 39 | char s[16] = { 0 }; 40 | switch (level) 41 | { 42 | case 0: 43 | strcpy(s, "[ALL]:"); 44 | break; 45 | case 1: 46 | strcpy(s, "[TRACE]:"); 47 | break; 48 | case 2: 49 | strcpy(s, "[DEBUG]:"); 50 | break; 51 | case 3: 52 | strcpy(s, "[INFO]:"); 53 | break; 54 | case 4: 55 | strcpy(s, "[WARNNING]:"); 56 | break; 57 | case 5: 58 | strcpy(s, "[ERROR]:"); 59 | break; 60 | case 6: 61 | strcpy(s, "[FATAL]:"); 62 | break; 63 | default: 64 | strcpy(s, "[OFF]:"); 65 | break; 66 | } 67 | char str_tmp[128]; 68 | timeval now = { 0, 0 }; 69 | gettimeofday(&now, nullptr); 70 | time_t lt = now.tv_sec; 71 | tm *curTime = localtime(<); 72 | //判断时间,若日期改变,则换.log文件 73 | 74 | judgementTime(curTime); 75 | 76 | //写入的具体时间内容格式 77 | int n = snprintf(str_tmp, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ", 78 | curTime->tm_year + 1900, curTime->tm_mon + 1, curTime->tm_mday, 79 | curTime->tm_hour, curTime->tm_min, curTime->tm_sec, now.tv_usec,s ); 80 | 81 | va_list vArgList; //定义一个va_list型的变量,这个变量是指向参数的指针. 82 | va_start(vArgList, format); //用va_start宏初始化变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数 83 | int i = vsnprintf(str_tmp + n, 128, format, vArgList); 84 | str_tmp[n + i] = '\n'; 85 | str_tmp[n + i + 1] = '\0'; 86 | va_end(vArgList); 87 | 88 | logQue.append(std::string(str_tmp)); 89 | } 90 | private: 91 | void judgementTime(tm *time) { 92 | if (loggingTime->tm_mday == time->tm_mday) return; 93 | //更换log文件 94 | { 95 | std::lock_guard lckGuard(lock); 96 | if (loggingTime->tm_mday == time->tm_mday) return; 97 | writeFile.close(); 98 | initIostream(time, writeFile); 99 | loggingTime = time; 100 | } 101 | } 102 | void init() { 103 | //init logging thread 104 | std::thread writeThread(std::bind(&Log::threadFunc)); 105 | writeThread.detach(); 106 | } 107 | void run() { 108 | while (!stop) { 109 | std::string curLog(std::move(logQue.take())); 110 | 111 | { 112 | std::lock_guard lckGuard(lock); 113 | //每次都flash性能会出现问题,如何解决? 114 | writeFile << curLog; 115 | writeFile.flush(); 116 | } 117 | 118 | } 119 | } 120 | void initIostream(tm* time, std::ofstream& stream) { 121 | std::string&& fileName = std::to_string(time->tm_year + 1900) + "-" 122 | + std::to_string(time->tm_mon + 1) + "-" 123 | + std::to_string(time->tm_mday) + ".log"; 124 | stream.open(fileName, std::ios::out); 125 | if (stream.fail()) { 126 | std::cerr << "log file could not be opened!" << std::endl; 127 | exit(EXIT_FAILURE); 128 | } 129 | } 130 | Log(size_t maxSize) :logQue(maxSize),stop(false) { 131 | //init logging time 132 | time_t lt = time(nullptr); 133 | loggingTime = localtime(<); 134 | initIostream(loggingTime, writeFile); 135 | init(); 136 | 137 | } 138 | ~Log() { stop = true; writeFile.close(); } 139 | BlockingQueue logQue; 140 | LogLevel logLevel; 141 | bool stop; 142 | std::thread logThread; 143 | 144 | std::mutex lock; 145 | tm* loggingTime; 146 | std::ofstream writeFile; 147 | }; 148 | 149 | #define LOG_ALL(format, ...) if(Log::getInstance().getLogLevel() <= Log::ALL) \ 150 | Log::getInstance().write(Log::ALL, format, ##__VA_ARGS__) 151 | #define LOG_TRACE(format, ...) if(Log::getInstance().getLogLevel() <= Log::TRACE) \ 152 | Log::getInstance().write(Log::TRACE, format, ##__VA_ARGS__) 153 | #define LOG_DEBUG(format, ...) if(Log::getInstance().getLogLevel() <= Log::DEBUG) \ 154 | Log::getInstance().write(Log::DEBUG, format, ##__VA_ARGS__) 155 | #define LOG_INFO(format, ...) if(Log::getInstance().getLogLevel() <= Log::INFO) \ 156 | Log::getInstance().write(Log::INFO, format, ##__VA_ARGS__) 157 | #define LOG_WARNNING(format, ...) Log::getInstance().write(Log::WARNNING, format, ##__VA_ARGS__)) 158 | #define LOG_ERROR(format, ...) Log::getInstance().write(Log::ERROR, format, ##__VA_ARGS__) 159 | #define LOG_FATAL(format, ...) Log::getInstance().write(Log::FATAL, format, ##__VA_ARGS__) 160 | -------------------------------------------------------------------------------- /FishWebServer/README.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | 4 | 5 | 6 | 7 | # FishWebServer 8 | 9 | it is a toy webserver based on reactor model 10 | 11 | 12 | # 项目模型 13 | 在处理web请求时,通常有两种体系结构,分别为:thread-based architecture(基于线程)、event-driven architecture(事件驱动),本项目使用的是第一种 14 | ## thread-based architcture 15 | 基于线程的体系结构通常会使用多线程来处理客户端的请求,每当接收到一个请求,便开启一个独立的线程来处理。 16 | 这种方式虽然是直观的,但是仅适用于并发访问量不大的场景,因为线程需要占用一定的内存资源,且操作系统在线程之间的切换也需要一定的开销,当线程数过多时显然会降低web服务器的性能。并且,当线程在处理I/O操作,在等待输入的这段时间线程处于空闲的状态,同样也会造成cpu资源的浪费(是如此吗?)。一个典型的设计如下: 17 | ![img](https://upload-images.jianshu.io/upload_images/10345180-faaebf9335592620.png?imageMogr2/auto-orient/strip|imageView2/2/w/661/format/webp) 18 | 19 | # 为什么TCP服务器的监听套接字要设置为非阻塞 20 | 我们一般使用IO复用来实现并发模型,如果我们默认监听套接字为阻塞模式,假设一种场景如下: 21 | 22 | 客户通过connect向TCP服务器发起三次握手 23 | 三次握手完成后,触发TCP服务器监听套接字的可读事件,IO复用返回(select、poll、epoll_wait) 24 | 客户通过RST报文取消连接 25 | 26 | TCP服务器调用accept接受连接,此时发现内核已连接队列为空(因为唯一的连接已被客户端取消) 27 | 程序阻塞在accept调用,无法响应其它已连接套接字的事件 28 | 29 | 为了防止出现上面的场景,我们需要把监听套接字设置为非阻塞 30 | 31 | # HTTP的状态机模型 32 | 该部分引用与 微信公众号"两猿社" 33 | ![img](https://mmbiz.qpic.cn/mmbiz_jpg/6OkibcrXVmBH2ZO50WrURwTiaNKTH7tCia3AR4WeKu2EEzSgKibXzG4oa4WaPfGutwBqCJtemia3rc5V1wupvOLFjzQ/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 34 | 35 | # 用 Timing Wheel 踢掉空闲的链接 36 | ![img](https://upload-images.jianshu.io/upload_images/2116753-4fac9916aef57694.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp) 37 | Timeing Wheel的主循环是一个循环队列,可以自己实现,也可以使用std::deque来实现,这里我是用的是boost::circular_buffer来做的。 38 | 39 | 每个slot中的数据结构可以用双向list,也可以用unordered_set来管理链接。 40 | 41 | 这里有一个小技巧,用智能指针管理链接的周期。具体实现方法如下: 42 | ~~~ 43 | struct EventNode 44 | { 45 | explicit EventNode(const WeakTcpConnectionPtr& ptr) 46 | :weakPtr(ptr) 47 | { } 48 | ~EventNode() 49 | { 50 | auto ptr = weakPtr.lock(); 51 | if (ptr != nullptr) { 52 | ptr->closeSocket(); 53 | } 54 | } 55 | WeakTcpConnectionPtr weakPtr; 56 | }; 57 | ~~~ 58 | timeWheel管理的是每一个EventNode的shared_ptr,每一个链接有且只有一个唯一的shared_ptr 59 | 60 | 然后我们在EventNode的析构函数中处理链接即可。 61 | 62 | 因此我们就面临一个循环嵌套的问题,及在TcpConnection中含有EventNode的shared_ptr,然后再EventNode中也含有TcpConnection的shared_ptr,所以必须有一个是weak_ptr,改哪一个好呢? 63 | 64 | TcpConnection中的EventNode一定是Weak_ptr,若是shared_ptr,则TimeWheel就不能删除任何一个链接,因为EventNode永远存在(主动关闭连接除外)。 65 | 66 | EventNode的TcpConnection呢?若是shared_ptr,那么主动关闭连接的时候,所有的TcpConnection不能正常析构,只有在TimeWheel转动到时间了,才正常析构。 67 | 68 | 所以,最好的方式就是两个都是Weak_ptr包含在对方的Class。 69 | # 日志 70 | ## 如何实现Log日志的输出级别调整 71 | 该部分参考 陈硕先生muduo里的Logging.h中的实现手法。 72 | 使用 73 | ~~~ 74 | #define if(a) / b 75 | ~~~ 76 | 这种形式就可以简单的实现输出级别的调整。 77 | ## 如何实现日志写入文件按时间更换? 78 | 我使用的是加锁。每次写入前判断当前日期与创建日期是否相同,若相同则什么都不干,若不相同则换文件。 79 | 80 | 相对应的,写入线程也需要加锁。在更改时不能写入。 81 | 82 | 具体实现如下: 83 | 在写函数中判断。 84 | ~~~ 85 | void judgementTime(tm *time) { 86 | if (loggingTime->tm_mday == time->tm_mday) return; 87 | //更换log文件 88 | { 89 | std::lock_guard lckGuard(lock); 90 | if (loggingTime->tm_mday == time->tm_mday) return; 91 | writeFile.close(); 92 | initIostream(time, writeFile); 93 | loggingTime = time; 94 | } 95 | } 96 | void initIostream(tm* time, std::ofstream& stream) { 97 | std::string&& fileName = std::to_string(time->tm_year + 1900) + "-" 98 | + std::to_string(time->tm_mon + 1) + "-" 99 | + std::to_string(time->tm_mday) + ".log"; 100 | stream.open(fileName, std::ios::out); 101 | if (stream.fail()) { 102 | std::cerr << "log file could not be opened!" << std::endl; 103 | exit(EXIT_FAILURE); 104 | } 105 | } 106 | ~~~ 107 | 在读函数中加锁。 108 | ~~~ 109 | void run() { 110 | while (!stop) { 111 | std::string curLog(std::move(logQue.take())); 112 | 113 | { 114 | std::lock_guard lckGuard(lock); 115 | //每次都flash性能会出现问题,如何解决? 116 | writeFile << curLog; 117 | writeFile.flush(); 118 | } 119 | 120 | } 121 | ~~~ 122 | 我认为这里judgement有必要使用双锁。 123 | 124 | 因为是多线程写入,因此如果不加双锁,就会出现多次调用initIostream()的情况,双锁可以解决这一问题。 125 | 126 | # Redis缓存和MySQL数据一致性方案 127 | 128 | 一个基本的解决方案如下: 129 | 130 | ## 查找流程: 131 | 132 | ![image-20200704114632971](https://github.com/fishfishfishh/FishWebServer/blob/master/FishWebServer/picture/image-20200704114632971.png?raw=true) 133 | 134 | 135 | 136 | ## 更改流程: 137 | 138 | ![image-20200704120608103](https://github.com/fishfishfishh/FishWebServer/blob/master/FishWebServer/picture/image-20200704114632971.png?raw=true) 139 | 140 | 读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题。 141 | 142 | 143 | 144 | 不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举一个例子: 145 | 1.如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。 146 | 2.如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。 147 | 因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。 148 | 如来解决?这里给出两个解决方案,先易后难,结合业务和技术代价选择使用。(暂时没有实现) 149 | 150 | 151 | 152 | 1.第一种方案:采用延时双删策略 153 | 154 | 在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。 155 | 伪代码如下: 156 | 157 | ~~~ 158 | public void write(String key,Object data){ redis.delKey(key); 159 | db.updateData(data); Thread.sleep(500); redis.delKey(key); } 160 | ~~~ 161 | 162 | 163 | 164 | 具体的步骤就是: 165 | 166 | 先删除缓存; 167 | 再写数据库; 168 | 休眠500毫秒; 169 | 再次删除缓存。 170 | 那么,这个500毫秒怎么确定的,具体该休眠多久呢? 171 | 需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。 172 | 当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。 173 | 174 | 设置缓存过期时间 175 | 从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。 176 | 177 | 该方案的弊端 178 | 结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。 179 | 180 | 2、第二种方案:异步更新缓存(基于订阅binlog的同步机制) 181 | 182 | 技术整体思路: 183 | MySQL binlog增量订阅消费+消息队列+增量数据更新到redis 184 | 读Redis:热数据基本都在Redis 185 | 写MySQL:增删改都是操作MySQL 186 | 更新Redis数据:MySQ的数据操作binlog,来更新到Redis 187 | 188 | Redis更新 189 | 1)数据操作主要分为两大块: 190 | 一个是全量(将全部数据一次写入到redis) 191 | 一个是增量(实时更新) 192 | 这里说的是增量,指的是mysql的update、insert、delate变更数据。 193 | 194 | 2)读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。 195 | 这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。 196 | 其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。 197 | 198 | 这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。 199 | 200 | 当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。 201 | 202 | 以上就是Redis和MySQL数据一致性详解。 203 | 204 | # 目前尚未解决的问题 205 | 1. HTTP协议尚未完工。 基本完工 206 | 2. 尚未与数据库连接池进行连接测试(计划用mySQL储存,Redis缓存) 基本完工 207 | 3. 压力测试(完成) 208 | 4. 因为本人在使用Visual Studio 2017 的SSH远程连接树莓派4B进行编码工作,因此暂时不提供makeFile文件 - =,以后加上~。 209 | 210 | # 更新日志 211 | 2020/6/11 加入了http的解析。
212 | 2020/6/15 加入了html文件,完善了http的解析工作。
213 | 2020/6/19 加入了压力测试,完善了一下httpServer.
214 | 215 | 2020/6/24 完善了后台逻辑,暂时用Redis充当数据库,后面开始完成MySql和Redis的协同工作.
216 | 217 | 2020/7/2 完善了后台逻辑,完成简易的MySql和Redis的协同工作.
218 | 219 | ## 服务器压力测试 220 | 221 | ### 测试平台 222 | 223 | 树莓派4B 4GB 版本 224 | 225 | 226 | 227 | ### 测试系统 228 | 229 | Linux Raspberry 4.19 230 | 231 | 232 | 233 | ### 测试对比 234 | 235 | nginx 和 本项目 236 | 237 | 238 | 239 | ### 测试工具 240 | 241 | webbench 242 | 243 | nginx采用系统默认配置启动,统一压测时长为5s , 协议为http/1.1 244 | 245 | 246 | 247 | ### 测试结果 248 | 249 | 250 | 251 | ngnix 252 | 253 | | clients | 10 | 30 | 50 | 70 | 100 | 200 | 400 | 800 | 1600 | 3200 | 254 | | -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | 255 | | Requests | 1327 | 1350 | 1247 | 1951 | 2247 | 1925 | 1336 | 2642 | 2348 | 1558 | 256 | 257 | 258 | 259 | 我的 260 | 261 | | clients | 10 | 30 | 50 | 70 | 100 | 200 | 400 | 800 | 1600 | 3200 | 262 | | -------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | 263 | | Requests | 1592 | 2070 | 2020 | 1498 | 1444 | 1675 | 1386 | 1559 | 1392 | 744 | 264 | 265 | 266 | 267 | 差距还是有的,尤其是在2k clients以上,差距很明显,我觉得主要原因有一下几点: 268 | 269 | 1. timewheel 算法虽然简单,但是带来了大量的冗余weak_ptr,处理也需要时间啊! 270 | 2. 我用了大量 的 std::string操作,耗时间。 271 | 3. 虽然read可以用mmap加快速度,但是感觉还需谨慎。 272 | 4. 我觉得是最重要的,nginx的架构要比我的更完美。 273 | 274 | # 参考的开源项目以及书籍、Blog 275 | 1. https://github.com/chenshuo/muduo 276 | 2. https://github.com/qinguoyi/TinyWebServer 277 | 3. https://www.jianshu.com/p/eb3e5ec98a66 278 | 4. Linux多线程服务器编程 陈硕 著 279 | 5. STL源码剖析 侯捷 著 280 | 6. 后台开发核心技术与应用实践 徐晓鑫 著 281 | 7. https://blog.csdn.net/zhwenx3/article/details/88107428 282 | 8. https://blog.csdn.net/ChenRui_yz/article/details/85057439 283 | -------------------------------------------------------------------------------- /FishWebServer/Semaphore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | class Semaphore 5 | { 6 | public: 7 | Semaphore(int count_ = 0) 8 | : count(count_) {} 9 | 10 | inline void notify() 11 | { 12 | std::unique_lock lock(mtx); 13 | count++; 14 | cv.notify_one(); 15 | } 16 | 17 | inline void wait() 18 | { 19 | std::unique_lock lock(mtx); 20 | 21 | while (count == 0) { 22 | cv.wait(lock); 23 | } 24 | count--; 25 | } 26 | 27 | private: 28 | std::mutex mtx; 29 | std::condition_variable cv; 30 | int count; 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /FishWebServer/TcpConnection.cpp: -------------------------------------------------------------------------------- 1 | #include "TcpConnection.h" 2 | 3 | void TcpConnection::read() 4 | { 5 | if (inputBuffer.readFd()){ 6 | std::cout << "socket id: "<< socketfd << " received "<< inputBuffer.readableBytes() << " successfully!" << std::endl; 7 | } 8 | else { 9 | //若timeWheel调用closeSocket(),而此时工作线程正在read.则调用了两遍closeSocket(); 10 | closeSocket(); 11 | } 12 | } 13 | 14 | void TcpConnection::write(std::string& str) 15 | { 16 | outputBuffer.append(str.c_str(), str.size()); 17 | } 18 | 19 | void TcpConnection::closeSocket() 20 | { 21 | handleClose(); 22 | closecb(shared_from_this()); 23 | } 24 | 25 | void TcpConnection::handleClose() 26 | { 27 | epoll_ctl(epollfd, EPOLL_CTL_DEL, socketfd, 0); 28 | close(socketfd); 29 | } 30 | -------------------------------------------------------------------------------- /FishWebServer/TcpConnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include"Buffer.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "CallBacks.h" 10 | #include "Log.h" 11 | class TcpConnection : public std::enable_shared_from_this 12 | { 13 | public: 14 | typedef std::function CloseCallback; 15 | TcpConnection(int fd, int epoll) : epollfd(epoll){ 16 | socketfd = sockets::accept(fd, client_address); 17 | LOG_INFO("New connection have accepted, ip address: %s", inet_ntoa(client_address.sin_addr)); 18 | inputBuffer.setSocketfd(socketfd); 19 | outputBuffer.setSocketfd(socketfd); 20 | } 21 | int getSocketfd() { return socketfd; } 22 | void setCloseCallback(const CloseCallback& cb) { closecb = cb; } 23 | //bool read() { return inputBuffer.readFd(); } 24 | void read(); 25 | void write(std::string&); 26 | void closeSocket(); 27 | //没有经过outputbuffer 28 | void send(std::string& msg) { 29 | ::write(socketfd, msg.c_str(), msg.size()); 30 | } 31 | //经过了outputbuffer 32 | void send() { 33 | ::write(socketfd, outputBuffer.begin(), outputBuffer.readableBytes()); 34 | std::cout << "socket id: " << socketfd << " have send " << outputBuffer.readableBytes() << " successfully!" << std::endl; 35 | outputBuffer.clear(); 36 | sockets::update(EPOLLIN | EPOLLONESHOT | EPOLLRDHUP, getSocketfd(), epollfd); 37 | } 38 | void setHandleEvents(const handleEventCallBack& callback) { 39 | cb = callback; 40 | 41 | } 42 | void setWeakNodePtr(const NodePtr& ptr) { weakTimerPtr = ptr; } 43 | NodePtr getNodePtr() { return weakTimerPtr.lock(); } 44 | void handleEvents() { cb(shared_from_this()); } 45 | virtual void processMessage() = 0; 46 | virtual ~TcpConnection(){ 47 | LOG_INFO("Ip address: %s has close connection", inet_ntoa(client_address.sin_addr)); 48 | } 49 | protected: 50 | Buffer inputBuffer; 51 | Buffer outputBuffer; 52 | int epollfd; 53 | TcpConnection(){} 54 | private: 55 | void handleClose(); 56 | int socketfd; 57 | sockaddr_in client_address; 58 | CloseCallback closecb; 59 | handleEventCallBack cb; 60 | WeakNodePtr weakTimerPtr; 61 | }; 62 | -------------------------------------------------------------------------------- /FishWebServer/TcpServer.cpp: -------------------------------------------------------------------------------- 1 | #include "TcpServer.h" 2 | #include 3 | #include 4 | #include 5 | #include "Log.h" 6 | 7 | TcpServer::TcpServer(std::string ipAddr, uint16_t port) 8 | : events(initEventVectorSize), start_loop(true) , timeWheel(30 * TIMESLOT + 1) 9 | { 10 | int ret = 0; 11 | bzero(&address, sizeof(address)); 12 | address.sin_family = AF_INET; 13 | ret = inet_pton(AF_INET, ipAddr.c_str(), &address.sin_addr); 14 | assert(ret > 0); 15 | address.sin_port = htons(port); 16 | m_epollfd = initialSocketfd(); 17 | handleEventsPool.init(); 18 | } 19 | 20 | int TcpServer::initialSocketfd() 21 | { 22 | int ret; 23 | //创建套接字 24 | m_listenfd = sockets::createSocketfd(SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC); 25 | //int listenfd = socket(PF_INET, SOCK_STREAM, 0); 26 | //assert(listenfd >= 0); 27 | //m_listenfd = listenfd; 28 | 29 | 30 | // 设置端口复用 31 | //一般不会立即关闭而经历TIME_WAIT的过程 后想继续重用该socket 32 | int iReuseaddr = 1; 33 | setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &iReuseaddr, sizeof(iReuseaddr)); 34 | 35 | //绑定端口 36 | sockets::bind(m_listenfd, address); 37 | 38 | //somaxconn参数 39 | //定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数,默认值为128 40 | sockets::listen(m_listenfd); 41 | 42 | //在 epoll_create() 的最初实现版本时, size参数的作用是创建epoll实例时候告诉内核需要使用多少个文件描述符。 43 | //内核会使用 size 的大小去申请对应的内存(如果在使用的时候超过了给定的size, 内核会申请更多的空间)。 44 | //现在,这个size参数不再使用了(内核会动态的申请需要的内存)。 45 | //但要注意的是,这个size必须要大于0,为了兼容旧版的linux 内核的代码。 46 | int epollfd = epoll_create(5); 47 | assert(epollfd != -1); 48 | // listenfd采用LT模式,否则accpet要使用循环 49 | sockets::addfd(epollfd, m_listenfd,false,true); 50 | 51 | //初始化timer 52 | 53 | sockets::socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, socketPair); 54 | //sockets::setnonblocking(socketPair[1]); 55 | sockets::addfd(epollfd, socketPair[0], false); 56 | 57 | sockets::addsig(SIGPIPE, SIG_IGN); 58 | sockets::addsig(SIGALRM, sockets::sig_handler, false); 59 | sockets::addsig(SIGTERM, sockets::sig_handler, false); 60 | 61 | 62 | 63 | alarm(TIMESLOT); 64 | return epollfd; 65 | } 66 | 67 | void TcpServer::newConnection(int socketfd) 68 | { 69 | TcpConnectionPtr newConn(new HttpConnection(socketfd, m_epollfd)); 70 | 71 | if (newConn->getSocketfd() < 0) { 72 | //std::cout << newConn->getSocketfd() << "\t" << " connection failed!" << std::endl; 73 | return; 74 | } 75 | else { 76 | //std::cout << newConn->getSocketfd() << "\t" << "success connection!" << std::endl; 77 | } 78 | int newSocketfd = newConn->getSocketfd(); 79 | 80 | assert(userArray[socketfd].get() == nullptr); 81 | userArray[newSocketfd] = newConn; 82 | sockets::addfd(m_epollfd, newSocketfd); 83 | newConn->setCloseCallback(std::bind((void(TcpServer::*)(const TcpConnectionPtr))&TcpServer::removeConnection, this, std::placeholders::_1)); 84 | 85 | //设置timer 86 | NodePtr node(new EventNode(newConn)); 87 | timeWheel.push(node); 88 | newConn->setWeakNodePtr(node); 89 | } 90 | 91 | void TcpServer::removeConnection(int socketfd) 92 | { 93 | assert(userArray[socketfd].get() != nullptr); 94 | userArray[socketfd]->closeSocket(); 95 | } 96 | 97 | void TcpServer::removeConnection(const TcpConnectionPtr ptr) 98 | { 99 | userArray[ptr->getSocketfd()] = TcpConnectionPtr(); 100 | } 101 | 102 | void TcpServer::processEvents(int sockfd, const handleEventCallBack& events) 103 | { 104 | //判断是否被timer断了链接。 105 | if (userArray[sockfd].get() == nullptr) return; 106 | else { 107 | timeWheel.push(userArray[sockfd]->getNodePtr()); 108 | userArray[sockfd]->setHandleEvents(events); 109 | if (!handleEventsPool.append(WeakTcpConnectionPtr(userArray[sockfd]))) removeConnection(sockfd); 110 | } 111 | } 112 | 113 | void TcpServer::start() 114 | { 115 | while (start_loop) 116 | { 117 | int eventsNum = epoll_wait(m_epollfd, &*events.begin(), static_cast(events.size()), -1); 118 | if (eventsNum < 0 && errno != EINTR) 119 | { 120 | LOG_FATAL("epoll failed! errno: errno: %s",strerror(errno)); 121 | break; 122 | } 123 | for (int i = 0; i < eventsNum; ++i) 124 | { 125 | int sockfd = events[i].data.fd; 126 | if (sockfd == m_listenfd) { 127 | newConnection(sockfd); 128 | } 129 | //Tcp链接断开 130 | else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { 131 | //std::cout << "removeConnection " << userMap[sockfd].use_count() << " id :" << userMap[sockfd]->getSocketfd() << std::endl; 132 | removeConnection(sockfd); 133 | } 134 | //处理信号 135 | else if ((sockfd == socketPair[0]) && (events[i].events & EPOLLIN)) 136 | { 137 | char signals[1024]; 138 | int ret = recv(socketPair[0], signals, sizeof(signals), 0); 139 | timeWheel.tick(); 140 | alarm(TIMESLOT); 141 | } 142 | //处理客户连接上接收到的数据 143 | else if (events[i].events & EPOLLIN) { 144 | //std::cout << "read " << userMap[sockfd].use_count() << " id :" << userMap[sockfd]->getSocketfd() << std::endl; 145 | processEvents(sockfd, handleRead); 146 | } 147 | //发送数据 148 | else if (events[i].events & EPOLLOUT) { 149 | //std::cout << "write " << userMap[sockfd].use_count() << " id :" << userMap[sockfd]->getSocketfd() << std::endl; 150 | processEvents(sockfd, handleWrite); 151 | } 152 | 153 | } 154 | if (static_cast(eventsNum) == events.size()) 155 | { 156 | events.resize(events.size() * 2); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /FishWebServer/TcpServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define ET 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "HttpConnection.h" 18 | #include "CallBacks.h" 19 | #include "ThreadPool.h" 20 | #include 21 | #include "TimeWheel.h" 22 | 23 | 24 | const int TIMESLOT = 1; //最小超时单位 25 | 26 | 27 | class HttpConnection; 28 | class TcpServer 29 | { 30 | public: 31 | typedef std::vector EventVector; 32 | typedef std::map TcpMap; 33 | typedef std::array TcpArray; 34 | TcpServer(std::string ipAddr, uint16_t port); 35 | void sethandleRead(const handleEventCallBack& cb) { handleRead = cb; } 36 | void sethandleWrite(const handleEventCallBack& cb) { handleWrite = cb; } 37 | void start(); 38 | ~TcpServer() 39 | { 40 | close(m_listenfd); 41 | close(socketPair[0]); 42 | close(socketPair[1]); 43 | } 44 | private: 45 | int initialSocketfd(); 46 | private: 47 | handleEventCallBack handleRead; 48 | handleEventCallBack handleWrite; 49 | void newConnection(int socketfd); 50 | void removeConnection(int socketfd); 51 | void removeConnection(const TcpConnectionPtr); 52 | void processEvents(int, const handleEventCallBack&); 53 | static const uint32_t initEventVectorSize = 100; 54 | sockaddr_in address; 55 | EventVector events; 56 | int m_epollfd; 57 | int m_listenfd; 58 | int start_loop; 59 | //TcpMap userMap; 60 | TcpArray userArray; 61 | ThreadPool handleEventsPool; 62 | 63 | //timer 64 | int socketPair[2]; 65 | TimeWheel timeWheel; 66 | }; 67 | 68 | -------------------------------------------------------------------------------- /FishWebServer/ThreadPool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "CallBacks.h" 10 | #include "Semaphore.h" 11 | #include "BlockingQueue.h" 12 | 13 | 14 | class ThreadPool : boost::noncopyable 15 | { 16 | class connection_pool; 17 | typedef std::vector ThreadPtrVector; 18 | typedef BlockingQueue messageQueue; 19 | public: 20 | ThreadPool(int thread_number = 4, int max_requests = 10000); 21 | 22 | //最好不要再初始化里注册回调函数,有可能出问题。 23 | void init(); 24 | void append(WeakTcpConnectionPtr& events); 25 | void append(WeakTcpConnectionPtr&& events); 26 | void run(); 27 | ~ThreadPool(); 28 | 29 | private: 30 | bool stop; 31 | //std::mutex mutexLock; 32 | ThreadPtrVector thread_pool; 33 | messageQueue workQue; 34 | //connection_pool* conn_pool; 35 | //Semaphore queuestate; 36 | }; 37 | 38 | 39 | inline ThreadPool::ThreadPool(int thread_number, int max_requests): 40 | thread_pool(thread_number), stop(false), workQue(max_requests) 41 | { 42 | } 43 | 44 | 45 | inline void ThreadPool::init() 46 | { 47 | for (int i = 0; i < thread_pool.size(); ++i) { 48 | thread_pool[i] = new std::thread(std::bind(&ThreadPool::run, this)); 49 | assert(thread_pool[i] != nullptr); 50 | thread_pool[i]->detach(); 51 | } 52 | } 53 | 54 | 55 | inline void ThreadPool::append(WeakTcpConnectionPtr& events) 56 | { 57 | workQue.append(events); 58 | } 59 | 60 | inline void ThreadPool::append(WeakTcpConnectionPtr&& events) 61 | { 62 | workQue.append(std::move(events)); 63 | } 64 | 65 | inline void ThreadPool::run() 66 | { 67 | while (!stop) 68 | { 69 | auto req(std::move(workQue.take())); 70 | auto ptr = req.lock(); 71 | if(ptr != nullptr) ptr->handleEvents(); 72 | } 73 | } 74 | 75 | 76 | inline ThreadPool::~ThreadPool() 77 | { 78 | //如何安全退出? 79 | stop = true; 80 | for (int i = 0; i < thread_pool.size(); ++i) delete thread_pool[i]; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /FishWebServer/TimeWheel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CallBacks.h" 4 | #include "TcpConnection.h" 5 | 6 | struct EventNode 7 | { 8 | explicit EventNode(const WeakTcpConnectionPtr& ptr) 9 | :weakPtr(ptr) 10 | { } 11 | ~EventNode() 12 | { 13 | auto ptr = weakPtr.lock(); 14 | if (ptr != nullptr) { 15 | ptr->closeSocket(); 16 | } 17 | } 18 | WeakTcpConnectionPtr weakPtr; 19 | }; 20 | 21 | class TimeWheel 22 | { 23 | public: 24 | explicit TimeWheel(int bucketNum) :wheel(bucketNum) { 25 | } 26 | void tick() { 27 | wheel.push_back(Bucket()); 28 | } 29 | void push(const NodePtr& ptr) { 30 | wheel.back().insert(ptr); 31 | } 32 | ~TimeWheel() { 33 | } 34 | 35 | private: 36 | timeWheel wheel; 37 | }; 38 | 39 | 40 | -------------------------------------------------------------------------------- /FishWebServer/html/ChangePassword.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Change Password 9 | 71 | 72 | 73 |
74 |

更改密码

75 |
76 | 77 | 78 | 79 |
80 |
81 |
82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /FishWebServer/html/ChangePasswordError.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Change Password 9 | 74 | 75 | 76 |
77 |

更改密码

78 |
79 | 80 | 81 | 82 |
83 |
84 |
提示:用户名/旧密码输入错误!
85 |
86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /FishWebServer/html/HTMLPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

又是元气满满的一天!

9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /FishWebServer/html/SignIn.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Sign in 8 | 98 | 99 | 100 |
101 |

登陆页面

102 |
103 | 104 | 105 |
106 |
107 |
108 | 109 |
110 |
111 | 112 |
113 |
114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /FishWebServer/html/SignInError.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Sign in 8 | 101 | 102 | 103 |
104 |

登陆页面

105 |
106 | 107 | 108 |
109 |
110 |
111 | 112 |
113 |
114 | 115 |
116 |
提示:用户或密码错误!
117 |
118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /FishWebServer/html/SignUp.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Sign up 8 | 70 | 71 | 72 |
73 |

注册页面

74 |
75 | 76 | 77 | 78 |
79 |
80 | 81 |
82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /FishWebServer/html/SignUpError.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Sign up 7 | 72 | 73 | 74 |
75 |

注册页面

76 |
77 | 78 | 79 | 80 |
81 |
82 |
提示:用户已经被注册!
83 |
84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /FishWebServer/html/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fishfishfishh/FishWebServer/a7b1b00742658dac5c2db12576361ba8ac83b9e0/FishWebServer/html/favicon.ico -------------------------------------------------------------------------------- /FishWebServer/picture/image-20200704114632971.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fishfishfishh/FishWebServer/a7b1b00742658dac5c2db12576361ba8ac83b9e0/FishWebServer/picture/image-20200704114632971.png -------------------------------------------------------------------------------- /FishWebServer/picture/image-20200704120608103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fishfishfishh/FishWebServer/a7b1b00742658dac5c2db12576361ba8ac83b9e0/FishWebServer/picture/image-20200704120608103.png -------------------------------------------------------------------------------- /Interview experience/字节跳动面经.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 总体的面试体验 很不错,面试官都很和善、基本上不会很为难人,面试官也会及时反馈给你他的态度和满意程度,总体上收获很多。 4 | 5 | 6 | 7 | # 字节跳动一面面经 8 | 9 | 面试官人很好,告诉我别太紧张。。。 10 | 11 | 12 | 13 | 自我介绍 14 | 15 | 介绍一下OSI四层网络模型。 16 | 17 | 说一下TCP的拥塞控制(我太紧张听成了超时重传,面试官也不介意。。。) 18 | 19 | 看你的技术站是C/C++,那说一下多态把 20 | 21 | 说下常量指针和指针常量的区别 22 | 23 | 说一下静态多态和动态多态,和他们的实现方式 24 | 25 | 函数重载是怎么实现的? 26 | 27 | 虚函数和虚函数表是属于类的还是属于对象的? 28 | 29 | linux如何把一个文件改成只读? 30 | 31 | fork和vfork有什么区别? 32 | 33 | 说一下进程和线程的区别 34 | 35 | 堆和栈的区别? 36 | 37 | 38 | 39 | ~~~ 40 | 算法题 41 | 题目,一个数组,只含有0,1,2 实现一个算法,输出排好序的数组. 42 | 例如:[2,2,0,1,0,1] 输出[0,0,1,1,2,2] 43 | ~~~ 44 | 45 | 我回答计数排序,他说不能用。 46 | 47 | 最后提示用多个指针。 48 | 49 | 50 | 51 | 写完了还顺便问了一下测试用例的问题,你会用什么样的测试用例来测试你的算法是否鲁棒? 52 | 53 | 54 | 55 | 56 | 57 | # 字节跳动二面面经 58 | 59 | 一面面完大概10分钟,就安排二面了。 60 | 61 | 这个面试官比一面的面试官要严肃一些,问的也深入一些,但是依然态度很好,有及时的反馈他的态度。 62 | 63 | 64 | 65 | 自我介绍 66 | 67 | OSI七层模型(我表示层一紧张给忘了,我就实话实说了,但是说出了表示层的功能。面试官认为我这个知识点掌握了) 68 | 69 | 说一下TCP的三次握手和四次挥手 70 | 71 | 有一条TCP消息,发送给局域网内的某一个电脑,期间发生了什么,说的越详细越好。(这块我答得不是很好) 72 | 73 | 知道HTTP1.X 和HTTP2.X吗,有什么区别?(感觉也答得不太好) 74 | 75 | 知道反向代理,正向代理吗?(我猜到了是什么,但是没敢说。。) 76 | 77 | 如何通过MAC地址获取IP地址 78 | 79 | 路由器的路由表里面有什么? 80 | 81 | 82 | 83 | 接下来问数据库 84 | 85 | Redis 除了能用于缓存,还能干什么用? 86 | 87 | 你刚说了排行榜,用什么实现的?(我直接就说跳表,面试官很诧异。。说Redis有这个数据结构,我马上就反应过来应该是zset,然后面试官说对嘛、这就有的聊了。。。) 88 | 89 | 说一下跳表的实现原理,怎么确定最高的高度。 90 | 91 | zset只有跳表一种数据结构吗? 92 | 93 | 知道Redis集群吗?(我说只在书上看过。。然后他就没深问下去,其实问下去也还是有的说的) 94 | 95 | 说一下Redis持久化,能说多少说多少。 96 | 97 | Redis list用作消息队列比mq好在哪里?(这个我不知道。。难道是可以分布式部署吗?) 98 | 99 | 100 | 101 | mysql引擎了解过吗?说一下。。(我就知道俩。。) 102 | 103 | 说一下聚簇索引和非聚簇索引。 104 | 105 | 聚簇索引用的什么数据结构,有什么特点? 106 | 107 | 事务的四个隔离等级 108 | 109 | MYSQL默认那个隔离等级(我忘了。。猜的可重复读。。结果猜中了) 110 | 111 | 可重复读解决了什么问题? 112 | 113 | 串行化解决了什么问题? 114 | 115 | 116 | 117 | 118 | 119 | ~~~ 120 | 算法题 121 | 122 | 给一个string 形式如下: 123 | 1(2)(3) 代表二叉树的根节点为1 , 左子节点为2 , 右子节点的值为3 124 | 1(#)(3) 代表二叉树的根节点为1 , 左子节点为nullptr , 右子节点的值为3 125 | 可以嵌套下去。。 126 | 1(2(#)(3))(4) 表示 root->val = 1 , root->right->val = 4 , root->left->val = 2 127 | root->left->left = nullptr , root->left->right = 3。 128 | 输入为string,输出为树的根节点。 129 | ~~~ 130 | 131 | 132 | 133 | 134 | 135 | 写完了面试官说没时间改了,想法对的 细节自己下去琢磨一下。 136 | 137 | 138 | 139 | # 三面 140 | 141 | 三面我用的是飞书进行面试,面试官感觉比前面两面的面试官年纪大一些,可能是主管的原因吧。。。 142 | 143 | 自我介绍 144 | 145 | 介绍简历里面的项目 146 | 147 | 介绍一下你的视觉项目 148 | 149 | 介绍一下你的算法 150 | 151 | 你是怎么解决过拟合问题的 152 | 153 | 154 | 155 | 介绍一下正向代理反向代理 156 | 157 | 说一下TCP拥塞控制 158 | 159 | TCP拥塞控制有什么改进的空间吗? 160 | 161 | 说一下select poll epoll 的区别 162 | 163 | 说一下都有什么IO模型 164 | 165 | 166 | 167 | Redis为什么这么快 168 | 169 | 170 | 171 | 你的主力语言是什么? 172 | 173 | C/C++是吧,说一下虚函数指针虚函数表 174 | 175 | 说一下虚继承 176 | 177 | 有虚构造函数吗?有虚析构函数吗? 178 | 179 | 180 | 181 | 来看程序,挑错误 182 | 183 | ~~~c 184 | #include 185 | 186 | char* getMemory(char* p , int num){ 187 | p = (char*)malloc(sizeof(char) * num); 188 | } 189 | int main(){ 190 | char* str = NULL; 191 | getMemory(str,100); 192 | Strcpy(str,"helloword"); 193 | return 0; 194 | } 195 | ~~~ 196 | 197 | 198 | 199 | ~~~c 200 | char* getString(){ 201 | char str[] = "helloworld"; 202 | return str; 203 | } 204 | 205 | int main(){ 206 | char * p = getString(); 207 | return 0; 208 | } 209 | ~~~ 210 | 211 | 212 | 213 | # 四面 214 | 215 | 1. 问项目 216 | 217 | 2. 进程和程序的关系、线程是什么? 218 | 3. shared_ptr weak_ptr unique_ptr 的实现和原理 219 | 4. TCP三次握手 220 | 5. 服务器挂了,客户端知道吗? 221 | 6. 如果此时给服务器发消息,会发生什么?详细说 222 | 223 | 224 | 225 | 码代码 226 | 227 | ~~~ 228 | 二叉树中序遍历结果为1,2,3,4,5,6...N 229 | 一共有多少种可能的组合方式? 230 | ~~~ 231 | 232 | 233 | 234 | 235 | 236 | # 感想 237 | 238 | 说实在的,两个题我都没有完整的AC下来,只是有思路,然后是实现出来了,我感觉面试官注重的是在做题当中你与他的交流是否顺畅,能否理解他的意思,并且快速写出来。。。 239 | 240 | 241 | 242 | 最后的两分钟提问环节,我都问了您对我的面试表现和基础有什么建议。 243 | 244 | 他们都认为如果要在这一行干的好干的深入,基础非常重要,并且都表示我的基础知识还需加强。。。 245 | 246 | 三面面试官面的很多都是我之前回答比较薄弱的环节,最后的编码环节也变成了看程序找错误环节,其实我回答的都还可以,但是都不是很完美。 247 | 248 | 249 | 250 | 综合我的三面来看,对于我这种EE转行CS的人来说,企业看中的还是我个人的基础 和 我的编程思想、 对问题的理解和分析的能力,对于框架这种东西并没有问过我(包括STL)。 251 | 252 | 253 | 254 | 最后希望我能顺利拿到字节跳动的offer,希望各位读者能拿到满意的offer~。 255 | 256 | --------------------------------------------------------------------------------