【基于C++HTTP 服务器的epoll 改造】

news2025/7/14 2:22:53

打印模块

Log.hpp 方便使用

#pragma once

#include <iostream>
#include <string>
#include <ctime>

#define INFO    1
#define WARNING 2
#define ERROR   3
#define FATAL   4

#define LOG(level, message) Log(#level, message, __FILE__, __LINE__)

void Log(std::string level, std::string message, std::string file_name, int line)
{
    std::cout<<"["<<level<<"]["<<time(nullptr)<<"]["<<message<<"]["<<file_name<<"]["<<line<<"]"<<std::endl;
}

TcpServer.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"

#define BACKLOG 5

class TcpServer{
    private:
        int _port; //端口号
        int _listen_sock; //监听套接字
        static TcpServer* _svr;
        int epollfd;
    private:
        TcpServer(int port)
            :_port(port)
            ,_listen_sock(-1)
        {}
        TcpServer(const TcpServer&)
        {}
    public:
        static TcpServer* GetInstance(int port)
        {
            static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
            if(_svr == nullptr){
                pthread_mutex_lock(&lock);
                if(_svr == nullptr){
                    _svr = new TcpServer(port);
                    _svr->InitServer();
                }
                pthread_mutex_unlock(&lock);
            }
            return _svr;
        }
        void InitServer()
        {
            Socket();
            Bind();
            Listen();
            LOG(INFO, "tcp_server init ... success");
        }
        void Socket()
        {
            _listen_sock = socket(AF_INET, SOCK_STREAM, 0);
            if(_listen_sock < 0){
                LOG(FATAL, "socket error!");
                exit(1);
            }
            int opt = 1;
            setsockopt(_listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
            LOG(INFO, "create socket ... success");
        }
        void Bind()
        {
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY; //云服务器不能直接绑定公网IP
            if(bind(_listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
                LOG(FATAL, "bind error!");
                exit(2);
            }
            LOG(INFO, "bind socket ... success");
        }
        void Listen()
        {
            if(listen(_listen_sock, BACKLOG) < 0){
                LOG(FATAL, "listen error!");
                exit(3);
            }
            LOG(INFO, "listen socket ... success");
        }
        int Sock()
        {
            return _listen_sock;
        }
        ~TcpServer()
        {
            if(_listen_sock >= 0){
                close(_listen_sock);
            }
        }
};
TcpServer* TcpServer::_svr = nullptr;

HttpServer.hpp

#pragma once

#include <iostream>
#include <sys/epoll.h>
#include <signal.h>
#include "TcpServer.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#include "Log.hpp"

#define PORT 8081

#define MAX_EVENT_NUMBER 1024

class HttpServer{
    private:
        int _port;
        bool _stop;
        int epollfd;
				struct epoll_event events[MAX_EVENT_NUMBER];
    public:
        HttpServer(int port)
            :_port(port)
        {
        	  _stop = false;
        		epollfd = -1;
        }
	      void InitServer()
	      {
	          signal(SIGPIPE, SIG_IGN); //忽略SIGPIPE信号,防止写入时崩溃
	      }

		
		int setnonblocking(int fd)
		{
			int option = fcntl(fd, F_GETFL) |  O_NONBLOCK;
			fcntl(fd, F_SETFD, option);
			return fcntl(fd, F_GETFL);
		}

		void addfd(int fd, bool enable_et){
			struct epoll_event ev;
			ev.data.fd = fd;
			ev.events = EPOLLIN;
			if(enable_et){
				ev.events |= EPOLLET;
			}
			epoll_ctl(this->epollfd, EPOLL_CTL_ADD, fd, &ev);
			setnonblocking(fd);
		}
  
        void work(int nread, int listenfd)
        {
              for(int i = 0; i < nread; i++)
              {
                int sockfd = events[i].data.fd;
                if(sockfd == listenfd){
                    struct sockaddr_in peer;
                    memset(&peer, 0, sizeof(peer));
                    socklen_t len = sizeof(peer);
                    int connfd = accept(listenfd, (struct sockaddr*)&peer, &len);
                    addfd(connfd, true);
                }else if(events[i].events &  EPOLLIN){
                    LOG(INFO, "event trigger once");
                    LOG(INFO, "get a new link");
                    Task task(sockfd);
                    ThreadPool::GetInstance()->PushTask(task);   
                }else{
                    LOG(INFO, "something else happened");
                }
              }  
        }

        void Loop()
        {
            LOG(INFO, "loop begin");
            TcpServer* tsvr = TcpServer::GetInstance(_port);
            int listen_sock = tsvr->Sock();
            epollfd = epoll_create(5);
						addfd(listen_sock, true);
            while(!_stop){

                int nread = epoll_wait(this->epollfd, events, MAX_EVENT_NUMBER, -1);
                if(nread < 0){
                    continue;
                }
                // struct sockaddr_in peer;
                // memset(&peer, 0, sizeof(peer));
                // socklen_t len = sizeof(peer);
                // int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
                // if(sock < 0){
                //     continue;
                // }
                // LOG(INFO, "get a new link");
                // Task task(sock);
                // ThreadPool::GetInstance()->PushTask(task);
                //lt(nread, listen_sock);
                work(nread, listen_sock);

                //int* p = new int(sock);
                //pthread_t tid;
                //pthread_create(&tid, nullptr, Entrance::HandlerRequest, (void*)p);
                //pthread_detach(tid);
            }
        }
        ~HttpServer()
        {}
};

Task.hpp

#pragma once

#include <iostream>
#include <unistd.h>
#include "Protocol.hpp"

class Task{
    private:
        int _sock;
        CallBack _handler; //回调
    public:
        Task()
        {}
        Task(int sock)
            :_sock(sock)
        {}
        //处理任务
        void ProcessOn()
        {
            _handler(_sock); //调用CallBack的仿函数
        }
        ~Task()
        {}
};

ThreadPool.hpp

#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
#include "Log.hpp"

#define NUM 6

class ThreadPool{
    private:
        std::queue<Task> _task_queue; //任务队列
        int _num;
        bool _stop;
        pthread_mutex_t _mutex;
        pthread_cond_t _cond;
        static ThreadPool* _inst;
    private:
        //构造函数私有
        ThreadPool(int num = NUM)
            :_num(num)
            ,_stop(false)
        {
            pthread_mutex_init(&_mutex, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }
        //拷贝构造函数私有或删除
        ThreadPool(const ThreadPool&)=delete;

        bool IsEmpty()
        {
            return _task_queue.empty();
        }
        bool IsStop()
        {
            return _stop;
        }
        void LockQueue()
        {
            pthread_mutex_lock(&_mutex);
        }
        void UnLockQueue()
        {
            pthread_mutex_unlock(&_mutex);
        }
        void ThreadWait()
        {
            pthread_cond_wait(&_cond, &_mutex);
        }
        void ThreadWakeUp()
        {
            pthread_cond_signal(&_cond);
        }
    public:
        static ThreadPool* GetInstance()
        {
            static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
            if(_inst == nullptr){
                pthread_mutex_lock(&mtx);
                if(_inst == nullptr){
                    _inst = new ThreadPool();
                    _inst->InitThreadPool();
                }
                pthread_mutex_unlock(&mtx);
            }
            return _inst;
        }
        static void* ThreadRoutine(void* arg)
        {
            pthread_detach(pthread_self());
            ThreadPool* tp = (ThreadPool*)arg;
            while(true){
                tp->LockQueue();
                while(tp->IsEmpty()){
                    tp->ThreadWait();
                }
                Task task;
                tp->PopTask(task);
                tp->UnLockQueue();

                task.ProcessOn(); //处理任务
            }
        }
        bool InitThreadPool()
        {
            pthread_t tid;
            for(int i = 0;i < _num;i++){
                if(pthread_create(&tid, nullptr, ThreadRoutine, this) != 0){
                    LOG(FATAL, "create thread pool error!");
                    return false;
                }
            }
            LOG(INFO, "create thread pool success!");
            return true;
        }
        void PushTask(const Task& task)
        {
            LockQueue();
            _task_queue.push(task);
            UnLockQueue();
            ThreadWakeUp();
        }
        void PopTask(Task& task)
        {
            task = _task_queue.front();
            _task_queue.pop();
        }
        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_cond);
        }
};
ThreadPool* ThreadPool::_inst = nullptr;

Util.hpp

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>

//工具类
class Util{
    public:
        static int ReadLine(int sock, std::string& out)
        {
            char ch = 'X'; //随便设置一个字符,只要不是\n即可
            while(ch != '\n'){
                ssize_t size = recv(sock, &ch, 1, 0);
                if(size > 0){
                    if(ch == '\r'){
                        //窥探
                        recv(sock, &ch, 1, MSG_PEEK);
                        if(ch == '\n'){
                            //窥探成功
                            //\r\n->\n
                            recv(sock, &ch, 1, 0);
                        }
                        else{
                            //\r->\n
                            ch = '\n';
                        }
                    }
                    //普通字符或\n
                    out.push_back(ch);
                }
                else if(size == 0){
                    return 0;
                }
                else{
                    return -1;
                }
            }
            return out.size();
        }
        static bool CutString(std::string& target, std::string& sub1_out, std::string& sub2_out, std::string sep)
        {
            size_t pos = target.find(sep, 0);
            if(pos != std::string::npos){
                sub1_out = target.substr(0, pos);
                sub2_out = target.substr(pos + sep.size());
                return true;
            }
            return false;
        }
};

Protocol.hpp

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <algorithm>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "Util.hpp"
#include "Log.hpp"

#define SEP ": "
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_400 "400.html"
#define PAGE_404 "404.html"
#define PAGE_500 "500.html"

#define OK 200
#define BAD_REQUEST 400
#define NOT_FOUND 404
#define SERVER_ERROR 500

static std::string CodeToDesc(int code)
{
    std::string desc;
    switch(code){
        case 200:
            desc = "OK";
            break;
        case 404:
            desc = "Not Found";
            break;
        default:
            break;
    }
    return desc;
}
static std::string SuffixToDesc(const std::string& suffix)
{
    static std::unordered_map<std::string, std::string> suffix_to_desc = {
        {".html", "text/html"},
        {".css", "text/css"},
        {".js", "application/x-javascript"},
        {".jpg", "application/x-jpg"},
        {".xml", "text/xml"}
    };
    auto iter = suffix_to_desc.find(suffix);
    if(iter != suffix_to_desc.end()){
        return iter->second;
    }
    return "text/html";
}

class HttpRequest{
    public:
        std::string _request_line; //请求行
        std::vector<std::string> _request_header; //请求报头
        std::string _blank; //空行
        std::string _request_body; //请求正文

        //解析完毕之后的结果
        std::string _method; //请求方法
        std::string _uri; //URI
        std::string _version; //版本号

        std::unordered_map<std::string, std::string> _header_kv; //请求报头中的键值对
        int _content_length; //正文长度
        std::string _path; //请求资源的路径
        std::string _query_string; //uri中携带的参数
        bool _cgi; //是否需要使用CGI模式
    public:
        HttpRequest()
            :_content_length(0)
            ,_cgi(false)
        {}
        ~HttpRequest()
        {}
};

class HttpResponse{
    public:
        std::string _status_line; //状态行
        std::vector<std::string> _response_header; //响应报头
        std::string _blank; //空行
        std::string _response_body; //响应正文

        int _status_code; //状态码
        int _fd; //响应的文件
        int _size; //响应文件的大小
        std::string _suffix; //响应文件的后缀
    public:
        HttpResponse()
            :_blank(LINE_END)
            ,_status_code(OK)
            ,_fd(-1)
            ,_size(0)
        {}
        ~HttpResponse()
        {}
};

//读取请求、分析请求、构建响应
//IO通信
class EndPoint{
    private:
        int _sock;
        HttpRequest _http_request;
        HttpResponse _http_response;
        bool _stop;
    private:
        //读取请求行
        bool RecvHttpRequestLine()
        {
            auto& line = _http_request._request_line;
            if(Util::ReadLine(_sock, line) > 0){
                line.resize(line.size() - 1);
                LOG(INFO, line);
            }
            else{
                _stop = true; //读取出错,不予处理
            }
            return _stop;
        }
        //读取请求报头和空行
        bool RecvHttpRequestHeader()
        {
            std::string line;
            while(true){
                line.clear(); //每次读取之前清空line
                if(Util::ReadLine(_sock, line) <= 0){
                    _stop = true;
                    break;
                }
                if(line == "\n"){
                    _http_request._blank = line;
                    break;
                }
                line.resize(line.size() - 1);
                _http_request._request_header.push_back(line);
                //LOG(INFO, line);
            }
            return _stop;
        }
        //解析请求行
        void ParseHttpRequestLine()
        {
            auto& line = _http_request._request_line;
            std::stringstream ss(line);
            ss>>_http_request._method>>_http_request._uri>>_http_request._version;
            auto& method = _http_request._method;
            std::transform(method.begin(), method.end(), method.begin(), toupper);
        }
        //解析请求报头
        void ParseHttpRequestHeader()
        {
            std::string key;
            std::string value;
            for(auto& iter : _http_request._request_header){
                if(Util::CutString(iter, key, value, SEP))
                {
                    _http_request._header_kv.insert({key, value});
                }
            }
        }
        //判定是否需要读取请求正文
        bool IsNeedRecvHttpRequestBody()
        {
            auto& method = _http_request._method;
            if(method == "POST"){
                auto& header_kv = _http_request._header_kv;
                auto iter = header_kv.find("Content-Length");
                if(iter != header_kv.end()){
                    _http_request._content_length = atoi(iter->second.c_str());
                    return true;
                }
            }
            return false;
        }
        //读取请求正文
        bool RecvHttpRequestBody()
        {
            if(IsNeedRecvHttpRequestBody()){
                int content_length = _http_request._content_length;
                auto& body = _http_request._request_body;

                char ch = 0;
                while(content_length){
                    ssize_t size = recv(_sock, &ch, 1, 0);
                    if(size > 0){
                        body.push_back(ch);
                        content_length--;
                    }
                    else{
                        _stop = true;
                        break;
                    }
                }
            }
            return _stop;
        }
        //CGI处理
        int ProcessCgi()
        {
            int code = OK;

            auto& bin = _http_request._path; //要让子进程执行的目标程序
            auto& method = _http_request._method;
            //父进程的数据
            auto& query_string = _http_request._query_string; //GET
            auto& request_body = _http_request._request_body; //POST
            int content_length = _http_request._content_length;
            auto& response_body = _http_response._response_body;

            //站在父进程角度
            int input[2];
            int output[2];
            if(pipe(input) < 0){
                LOG(ERROR, "pipe input error!");
                code = SERVER_ERROR;
                return code;
            }
            if(pipe(output) < 0){
                LOG(ERROR, "pipe output error!");
                code = SERVER_ERROR;
                return code;
            }

            pid_t pid = fork();
            if(pid == 0){ //child
                close(input[0]);
                close(output[1]);

                //将请求方法通过环境变量传参
                std::string method_env = "METHOD=";
                method_env += method;
                putenv((char*)method_env.c_str());

                if(method == "GET"){ //通过环境变量传参
                    std::string query_env = "QUERY_STRING=";
                    query_env += query_string;
                    putenv((char*)query_env.c_str());
                    LOG(INFO, "GET Method, Add Query_String env");
                }
                else if(method == "POST"){ //导入正文参数长度
                    std::string content_length_env = "CONTENT_LENGTH=";
                    content_length_env += std::to_string(content_length);
                    putenv((char*)content_length_env.c_str());
                    LOG(INFO, "POST Method, Add Content_Length env");
                }
                else{
                    //Do Nothing
                }

                dup2(output[0], 0);
                dup2(input[1], 1);

                execl(bin.c_str(), bin.c_str(), nullptr);
                exit(1);
            }
            else if(pid < 0){
                LOG(ERROR, "fork error!");
                code = SERVER_ERROR;
                return code;
            }
            else{ //father
                close(input[1]);
                close(output[0]);
                if(method == "POST"){ //将数据写入到管道当中
                    const char* start = request_body.c_str();
                    int total = 0;
                    int size = 0;
                    while(total < content_length && (size = write(output[1], start + total, request_body.size() - total)) > 0){
                        total += size;
                    }
                }
                //std::string test_string = "2021dragon";
                //send(output[1], test_string.c_str(), test_string.size(), 0);
                
                char ch = 0;
                while(read(input[0], &ch, 1) > 0){
                    response_body.push_back(ch);
                } //不会一直读,当另一端关闭后会继续执行下面的代码

                int status = 0;
                pid_t ret = waitpid(pid, &status, 0);
                if(ret == pid){
                    if(WIFEXITED(status)){ //正常退出
                        LOG(INFO, "正常退出");
                        if(WEXITSTATUS(status) == 0){ //结果正确
                            LOG(INFO, "正常退出,结果正确");
                            code = OK;
                        }
                        else{
                            LOG(INFO, "正常退出,结果不正确");
                            code = BAD_REQUEST;
                        }
                    }
                    else{
                        LOG(INFO, "异常退出");
                        code = SERVER_ERROR;
                    }
                }

                //释放文件描述符
                close(input[0]);
                close(output[1]);
            }
            return code;
        }
        //非CGI处理
        int ProcessNonCgi()
        {
            //打开待发送的文件
            _http_response._fd = open(_http_request._path.c_str(), O_RDONLY);
            if(_http_response._fd >= 0){ //打开文件成功再构建
                return OK;
            }
            return NOT_FOUND;
        }
        void BuildOkResponse()
        {
            //构建响应报头
            std::string content_type = "Content-Type: ";
            content_type += SuffixToDesc(_http_response._suffix);
            content_type += LINE_END;
            _http_response._response_header.push_back(content_type);

            std::string content_length = "Content-Length: ";
            if(_http_request._cgi){ //以CGI方式请求
                content_length += std::to_string(_http_response._response_body.size());
            }
            else{ //以非CGI方式请求
                content_length += std::to_string(_http_response._size);
            }
            content_length += LINE_END;
            _http_response._response_header.push_back(content_length);
        }
        void HandlerError(std::string page)
        {
            _http_request._cgi = false; //正常的网页返回,非CGI返回
            _http_response._fd = open(page.c_str(), O_RDONLY);
            std::cout<<page.c_str()<<std::endl;
            if(_http_response._fd > 0){
                std::cout<<page.c_str()<<std::endl;
                //构建响应报头
                struct stat st;
                stat(page.c_str(), &st);
                std::string content_type = "Content-Type: text/html";
                content_type += LINE_END;
                _http_response._response_header.push_back(content_type);

                std::string content_length = "Content-Length: ";
                content_length += std::to_string(st.st_size);
                content_length += LINE_END;
                _http_response._response_header.push_back(content_length);

                _http_response._size = st.st_size;
            }
        }
        void BuildHttpResponseHelp()
        {
            int code = _http_response._status_code;
            //构建状态行
            auto& status_line = _http_response._status_line;
            status_line += HTTP_VERSION;
            status_line += " ";
            status_line += std::to_string(code);
            status_line += " ";
            status_line += CodeToDesc(code);
            status_line += LINE_END;

            //构建响应正文,可能包括响应报头
            std::string path = WEB_ROOT;
            path += "/";
            switch(code){
                case OK:
                    BuildOkResponse();
                    break;
                case NOT_FOUND:
                    path += PAGE_404;
                    HandlerError(path);
                    break;
                case BAD_REQUEST:
                    path += PAGE_400;
                    HandlerError(path);
                    break;
                case SERVER_ERROR:
                    path += PAGE_500;
                    HandlerError(path);
                    break;
                default:
                    break;
            }
        }
    public:
        EndPoint(int sock)
            :_sock(sock)
            ,_stop(false)
        {}
        bool IsStop()
        {
            return _stop;
        }
        //读取请求
        void RecvHttpRequest()
        {
            if(!RecvHttpRequestLine()&&!RecvHttpRequestHeader()){ //短路求值
                ParseHttpRequestLine();
                ParseHttpRequestHeader();
                RecvHttpRequestBody();
            }
        }
        //构建响应
        void BuildHttpResponse()
        {
            auto& code = _http_response._status_code;
            std::string path;
            struct stat st;
            size_t pos = 0;

            if(_http_request._method != "GET"&&_http_request._method != "POST"){
                //非法请求
                LOG(WARNING, "method is not right");
                code = BAD_REQUEST;
                goto END;
            }
            if(_http_request._method == "GET"){
                size_t pos = _http_request._uri.find('?');
                if(pos != std::string::npos){
                    Util::CutString(_http_request._uri, _http_request._path, _http_request._query_string, "?");
                    _http_request._cgi = true; //上传了参数,需要使用CGI模式
                }
                else{
                    _http_request._path = _http_request._uri;
                }
            }
            else if(_http_request._method == "POST"){
                _http_request._path = _http_request._uri;
                _http_request._cgi = true; //上传了参数,需要使用CGI模式
            }
            else{
                //Do Nothing
            }
            path = _http_request._path;
            _http_request._path = WEB_ROOT;
            _http_request._path += path;

            if(_http_request._path[_http_request._path.size() - 1] == '/'){
                _http_request._path += HOME_PAGE;
            }
            
            std::cout<<"debug: "<<_http_request._path.c_str()<<std::endl;
            if(stat(_http_request._path.c_str(), &st) == 0){
                //资源存在
                if(S_ISDIR(st.st_mode)){ //是一个目录,并且不会以/结尾,因为前面已经处理过了
                    _http_request._path += "/";
                    _http_request._path += HOME_PAGE;
                    stat(_http_request._path.c_str(), &st); //path改变,需要重新获取属性
                }
                else if(st.st_mode&S_IXUSR||st.st_mode&S_IXGRP||st.st_mode&S_IXOTH){ //是一个可执行程序,需要特殊处理
                    _http_request._cgi = true; //请求的是一个可执行程序,需要使用CGI模式
                }
                _http_response._size = st.st_size;
            }
            else{
                //资源不存在
                LOG(WARNING, _http_request._path + " NOT_FOUND");
                code = NOT_FOUND;
                goto END;
            }

            pos = _http_request._path.rfind('.');
            if(pos == std::string::npos){
                _http_response._suffix = ".html"; //默认设置
            }
            else{
                _http_response._suffix = _http_request._path.substr(pos);
            }

            if(_http_request._cgi == true){
                code = ProcessCgi(); //以CGI的方式进行处理
            }
            else{
                code = ProcessNonCgi(); //简单的网页返回,返回静态网页
            }
END:
            BuildHttpResponseHelp();
        }
        //发送响应
        void SendHttpResponse()
        {
            //发送状态行
            send(_sock, _http_response._status_line.c_str(), _http_response._status_line.size(), 0);
            //发送响应报头
            for(auto& iter : _http_response._response_header){
                send(_sock, iter.c_str(), iter.size(), 0);
            }
            //发送空行
            send(_sock, _http_response._blank.c_str(), _http_response._blank.size(), 0);
            //发送响应正文
            if(_http_request._cgi){
                auto& response_body = _http_response._response_body;
                const char* start = response_body.c_str();
                size_t size = 0;
                size_t total = 0;
                while(total < response_body.size()&&(size = send(_sock, start + total, response_body.size() - total, 0)) > 0){
                    total += size;
                }
            }
            else{
                sendfile(_sock, _http_response._fd, nullptr, _http_response._size);
                //关闭文件
                close(_http_response._fd);
            }
        }
        ~EndPoint()
        {}
};

class CallBack{
    public:
        CallBack()
        {}
        void operator()(int sock)
        {
            HandlerRequest(sock);
        }
        void HandlerRequest(int sock)
        {
            LOG(INFO, "handler request begin");
            std::cout<<"get a new link..."<<sock<<std::endl;

#ifdef DEBUG
            char buffer[4096];
            recv(sock, buffer, sizeof(buffer), 0);
            std::cout<<"------------------begin------------------"<<std::endl;
            std::cout<<buffer<<std::endl;
            std::cout<<"-------------------end-------------------"<<std::endl;
#else
            EndPoint* ep = new EndPoint(sock);
            ep->RecvHttpRequest();
            if(!ep->IsStop()){
                LOG(INFO, "Recv No Error, Begin Build And Send");
                ep->BuildHttpResponse();
                ep->SendHttpResponse();
            }
            else{
                LOG(WARNING, "Recv Error, Stop Build And Send");
            }

            close(sock);
            delete ep;
#endif
            LOG(INFO, "handler request end");
        }
        ~CallBack()
        {}
};

Makefile

bin=httpserver
cgi=test_cgi
cc=g++
LD_FLAGS=-std=c++11 -lpthread #-DDEBUG=1
curr=$(shell pwd)
src=main.cc

ALL:$(bin) $(cgi)
.PHONY:ALL

$(bin):$(src)
	$(cc) -o $@ $^ $(LD_FLAGS)

$(cgi):cgi/test_cgi.cc
	$(cc) -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f $(bin) $(cgi)
	rm -rf output

.PHONY:output
output:
	mkdir -p output
	cp $(bin) output
	cp -rf wwwroot output
	cp $(cgi) output/wwwroot
	cp ./cgi/shell_cgi.sh output/wwwroot
	cp ./cgi/python_cgi.py output/wwwroot
	cp ./cgi/test.html output/wwwroot

main.cc

#include <iostream>
#include <string>
#include <memory>
#include "HttpServer.hpp"

static void Usage(std::string proc)
{
    std::cout<<"Usage:\n\t"<<proc<<" port"<<std::endl;
}
int main(int argc, char* argv[])
{
    if(argc != 2){
        Usage(argv[0]);
        exit(4);
    }
    int port = atoi(argv[1]);
    std::shared_ptr<HttpServer> svr(new HttpServer(port));
    svr->InitServer();
    svr->Loop();
    return 0;
}

启动服务
结果展示
后续加上post 请求

原文链接: link

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1346025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python特征工程系列】教你利用XGBoost模型分析特征重要性(源码)

这是Python特征工程系列原创文章&#xff0c;我的第186篇原创文章。 一、问题 应用背景介绍&#xff1a; 如果有一个包含数十个甚至数百个特征的数据集&#xff0c;每个特征都可能对你的机器学习模型的性能有所贡献。但是并不是所有的特征都是一样的。有些可能是冗余的…

RK3568测试tdd

RK3568测试tdd 一、门禁取包二、烧录三、跑tdd用例四、查看结果参考资料 一、门禁取包 右键复制链接&#xff0c;粘贴下载&#xff1b;解压到文件夹&#xff1b; 二、烧录 双击\windows\RKDevTool.exe打开烧写工具&#xff0c;工具界面击烧写步骤如图所示&#xff1a; 推荐…

部署es集群

在单机上利用docker容器运行多个es实例来模拟es集群。不过生产环境推荐大家每一台服务节点仅部署一个es的实例。部署es集群可以直接使用docker-compose来完成&#xff0c;但这要求你的Linux虚拟机至少有4G的内存空间 创建es集群 编写docker-compose文件 首先编写一个docker-…

微信公众号没有留言功能,怎么破?

为什么公众号没有留言功能&#xff1f;2018年2月12日&#xff0c;TX新规出台&#xff1a;根据相关规定和平台规则要求&#xff0c;我们暂时调整留言功能开放规则&#xff0c;后续新注册帐号无留言功能。这就意味着2018年2月12日号之后注册的公众号不论个人主体还是组织主体&…

[spark] SaveMode

https://spark.apache.org/docs/latest/api/java/index.html?org/apache/spark/sql/SaveMode.html Overwrite 覆盖模式是指将DataFrame保存到数据源时&#xff0c;如果数据/表已经存在&#xff0c;则现有数据将被DataFrame的内容覆盖。 注意: Overwrite 模式会覆盖已存在的表…

最优轨迹生成(三)—— 无约束BIVP轨迹优化

本系列文章是学习深蓝学院-移动机器人运动规划课程第五章最优轨迹生成 过程中所记录的笔记&#xff0c;本系列文章共包含四篇文章&#xff0c;依次介绍了微分平坦特性、无约束BVP轨迹优化、无约束BIVP轨迹优、 带约束轨迹优化等内容 本系列文章链接如下&#xff1a; 最优轨迹生…

ElasticSearch历史版本/老版本下载

下载地址 https://www.elastic.co/cn/downloads/past-releases#elasticsearch 搜索所需版本并下载

Grafana增加仪表盘

1.Grafana介绍 grafana 是一款采用Go语言编写的开源应用&#xff0c;主要用于大规模指标数据的可视化展现&#xff0c;是网络架构和应用分析中最流行的时序数据展示工具&#xff0c;目前已经支持绝大部分常用的时序数据库。 Grafana下载地址&#xff1a;https://grafana.com/g…

【网络奇遇记】揭秘计算机网络的性能指标:时延带宽积|往返时间|利用率|丢包率

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;上期回顾一. 时延带宽积二. 往返时间三. 利用率四. 丢包率&#x1f4dd;结语 &#x1…

李宏毅 自然语言处理(Speech Recognition) 笔记

李宏毅NLP(自然语言处理)完整课程&#xff0c;强推&#xff01;_哔哩哔哩_bilibili 从Ng那里跑路来学NLP了&#xff0c;之前其实ML入门听的就是宝可梦捕获大师&#xff08;&#xff09;的课&#xff0c;目前计划是&#xff0c;本博文作为上面链接所示课程的笔记记录。听完之后…

sklearn 中matplotlib编制图表

代码 # 导入pandas库&#xff0c;并为其设置别名pd import pandas as pd import matplotlib.pyplot as plt# 使用pandas的read_csv函数读取名为iris.csv的文件&#xff0c;将数据存储在iris_data变量中 iris_data pd.read_csv(data/iris.txt,sep\t)# 使用groupby方法按照&quo…

VMware ESXi常用查看RAID和磁盘信息工具及命令汇总

一、Esxcli 使用 Esxcli 命令可获取有关 vSAN 的信息&#xff0c;以及对您的 vSAN 环境进行故障排除。 可用命令如下&#xff1a; 命令描述esxcli vsan network list确认哪些 VMkernel 适配器可用于 vSAN 通信。esxcli vsan storage list列出由 vSAN 声明的存储磁盘。esxcli…

MIT线性代数笔记-第34讲-左右逆,伪逆

目录 34.左右逆&#xff0c;伪逆左右逆伪逆 打赏 34.左右逆&#xff0c;伪逆 左右逆 之前讲到的逆都是针对可逆方阵而言的&#xff0c;对于长方矩阵&#xff0c;实际上也有广义的逆&#xff0c;那就是左逆和右逆 左逆 当矩阵列满秩&#xff0c;即 r n r n rn时&#xff0c;…

springcloud微服务篇--6.网关Gateway

一、为什么需要网关&#xff1f; 网关功能&#xff1a; 身份认证和权限校验 服务路由、负载均衡 请求限流 在SpringCloud中网关的实现包括两种&#xff1a; gateway zuul Zuul是基于Servlet的实现&#xff0c;属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的Web…

R统计学1 - 基础操作入门问题1-20

R统计学入门基础问题 1. 如何生成100个高斯&#xff08;正态&#xff09;分布随机数 x <- rnorm(100, mean 5, sd 0.1) x # [1] 4.893534 5.046611 5.081097 4.979164 5.181700 5.038192 5.135376 5.173346 4.968877 4.986146 # [11] 4.946258 5.198199 5.055531 4.9430…

在 Linux 中使用 cat 命令

cat 命令用于打印文本文件的文件内容。至少&#xff0c;大多数 Linux 用户都是这么做的&#xff0c;而且没有什么问题。 cat 实际上代表 “连接(concatenate)”&#xff0c;创建它是为了 合并文本文件。但只要有一个参数&#xff0c;它就会打印文件内容。因此&#xff0c;它是用…

轻松实现电脑批量记账,高效管理财务数据

随着生活和工作的日益复杂&#xff0c;财务管理也变得越来越重要。一个好的记账软件可以帮助您更好地管理财务&#xff0c;使您更轻松地跟踪支出、收入和资产。那么&#xff0c;如何选择一款好用的记账软件呢&#xff1f;如何在电脑上进行批量记账呢&#xff1f; 第一步&#x…

51单片机项目(25)——基于51单片机电子秒表的protues仿真

1.功能设计 使用51单片机&#xff0c;完成0-59s的计时&#xff0c;并且实时显示在数码管上。使用一个按键控制计时器的启停。 仿真截图如下&#xff1a; 2.模块介绍 独立按键&#xff1a; 独立按键也称为轻触式开关&#xff0c;是一种简单的电气开关设备。它是一种手动操作Sw…

java并发编程一 并发编程的基本概念

进程与线程 进程 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至 CPU&#xff0c;数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的当一个程序被运行&…

SNMP获取Linux系统信息

snmp测试 snmpwalk -v 2c -c public123 192.168.227.133 system[rootlocalhost ~]# snmpwalk -v 2c -c public123 192.168.227.133 system SNMPv2-MIB::sysDescr.0 STRING: Linux localhost.localdomain 5.10.0-60.18.0.50.oe2203.x86_64 #1 SMP Wed Mar 30 03:12:24 UTC 202…