从零开始实现一个C++高性能服务器框架----Http模块

简介

项目介绍:实现了一个基于协程的服务器框架,支持多线程、多协程协同调度;支持以异步处理的方式提高服务器性能;封装了网络相关的模块,包括socket、http、servlet等,支持快速搭建HTTP服务器或WebSokcet服务器。

详细内容:日志模块,使用宏实现流式输出,支持同步日志与异步日志、自定义日志格式、日志级别、多日志分离等功能。线程模块,封装pthread相关方法,封装常用的锁包括(信号量,读写锁,自旋锁等)。IO协程调度模块,基于ucontext_t实现非对称协程模型,以线程池的方式实现多线程,多协程协同调度,同时依赖epoll实现了事件监听机制。定时器模块,使用最小堆管理定时器,配合IO协程调度模块可以完成基于协程的定时任务调度。hook模块,将同步的系统调用封装成异步操作(accept, recv, send等),配合IO协程调度能够极大的提升服务器性能。Http模块,封装了sokcet常用方法,支持http协议解析,客户端实现连接池发送请求,服务器端实现servlet模式处理客户端请求,支持单Reator多线程,多Reator多线程模式的服务器。

Http模块

1. 主要功能

  • 封装了HTTP请求和响应协议(HttpRequestHttpResponse
  • 使用nodejs的htpp_parser实现了HTTP请求、响应解析(HttpRequestParser和HttpResponseParser)
  • 实现了uri解析(Uri)
  • 封装了HTTP会话,负责服务器接收请求和发送响应(HttpSession)
  • 封装了用于HTTP服务器的相关方法(HttpServer)
  • 封装了客户端发送请求和接收响应及HTTP连接池(HttpConnection和HttpConnectionPool)

2. 功能演示

  • 解析请求头
const char test_request_data[] = "POST /login?aa=bb#sss HTTP/1.1\r\n"
                                 "Host: www.sylar.top\r\n"
                                 "Content-Length: 10\r\n\r\n"
                                 "1234567890";

void test_request(const char *str) {
    johsonli::http::HttpRequestParser parser;
    std::string tmp = str;
    std::cout << "<test_request>:" << std::endl
              << tmp << std::endl;
    parser.execute(&tmp[0], tmp.size());
    if (parser.hasError()) {
        std::cout << "parser execute fail" << std::endl;
    } else {
        johsonli::http::HttpRequest::ptr req = parser.getData();
        std::cout << req->toString() << std::endl;
    }
}

int main() {
	test_request(test_request_data);
}
  • 一个简单的Http服务器
 johnsonli::http::HttpServer::ptr server(new johnsonli::http::HttpServer(true));
 johnsonli::Address::ptr addr = johnsonli::Address::LookupAnyIPAddress("0.0.0.0:8020");
 while(!server->bind(addr)) {
     sleep(2);
 }
server.start();
  • 客户端发送请求
auto r = johnsonli::http::HttpConnection::DoGet("http://www.baidu.com:80", 300, false); 
LOG_INFO(g_logger) << "result=" << r->result
				    << " error=" << r->error
				    << " rsp=" << (r->response ? r->response->toString() : "");
  • 连接池的使用,没1秒发送一个请求
johnsonli::http::HttpConnectionPool::ptr pool(new johnsonli::http::HttpConnectionPool("www.sylar.top", "", 80, false, 10, 1000 * 30, 5));
    
johnsonli::IOManager::GetThis()->addTimer(1000, [pool](){
    auto r = pool->doGet("/blog/", 300);
    LOG_INFO(g_logger) << r->toString();
}, true);

3. 模块介绍

3.1 HttpRequest和HttpResponse

定义了一些请求方式和HTTP响应状态的枚举方式

// 请求方法
#define HTTP_METHOD_MAP(XX)         \
XX(0,  DELETE,      DELETE)       \
XX(1,  GET,         GET)          \
XX(2,  HEAD,        HEAD)         \
XX(3,  POST,        POST)         \
//....

// HTTP方法枚举 {DELETE = 0, GET = 1, ... }
enum class HttpMethod {
#define XX(num, name, string) name = num,
    HTTP_METHOD_MAP(XX)
#undef XX
    INVALID_METHOD
};

// 状态码
#define HTTP_STATUS_MAP(XX)                                                 \
XX(100, CONTINUE,                        Continue)                        \
XX(101, SWITCHING_PROTOCOLS,             Switching Protocols)             \
XX(102, PROCESSING,                      Processing)                      \
XX(200, OK,                              OK)                              \
// ...

// HTTP状态枚举 {CONTINUE = 100, SWITCHING_PROTOCOLS = 101, ...}
enum class HttpStatus {
#define XX(code, name, desc) name = code,
    HTTP_STATUS_MAP(XX)
#undef XX
};

HttpRequest是对HTTP请求协议的封装。包括请求行,请求头,请求消息体等

class HttpRequest {
public:
	// ...
private:
      HttpMethod m_method;        /// HTTP方法
      uint8_t m_version;          /// HTTP版本     
      bool m_close;               /// 是否自动关闭        
      bool m_websocket;           /// 是否为websocket
      uint8_t m_parserParamFlag;        
      std::string m_path;         /// 请求路径        
      std::string m_query;        /// 请求参数       
      std::string m_fragment;     /// 请求fragment        
      std::string m_body;         /// 请求消息体       
      MapType m_headers;          /// 请求头部MAP       
      MapType m_params;           /// 请求参数MAP       
      MapType m_cookies;          /// 请求Cookie MAP
};

HttpResponse是对HTTP响应的封装。包括响应行,响应头,响应消息体

class HttpResponse {
public:
	// ...
private:
	HttpStatus m_status;	/// 响应状态
	uint8_t m_version;		/// 版本
	bool m_close;			/// 是否自动关闭
	bool m_websocket;		/// 是否为websocket
	std::string m_body;		/// 响应消息体
	std::string m_reason;	/// 响应原因
	MapType m_headers;		/// 响应头部MAP
	std::vector<std::string> m_cookies;
};

3.2 HttpRequestParser和HttpResponseParser

  • HTTP解析器基于nodejs/http-parser实现,通过套接字读到HTTP消息后将消息内容传递给解析器,解析器通过回调的形式通知调用方HTTP解析的内容。

3.3 Uri

string uri的解析,具体请参看源码

3.4 HttpSession

继承自SocketStream,实现了在套接字流上读取HTTP请求与发送HTTP响应的功能,在读取HTTP请求时需要借助HTTP解析器,以便于将套接字流上的内容解析成HTTP请求。

class HttpSession : public SocketStream {
public:
    typedef std::shared_ptr<HttpSession> ptr;

    HttpSession(Socket::ptr sock, bool owner = true);

    HttpRequest::ptr recvRequest();					// 接收HTTP请求
    int sendResponse(HttpResponse::ptr rsp);		// 发送HTTP响应
}; 

接收请求recvRequest

    • 循环读,直到请求行,请求头解析完毕
    • 获取content-length,读取请求消息体
    • 返回HttpRequest

发送响应senResponse

int HttpSession::sendResponse(HttpResponse::ptr rsp) {
    std::stringstream ss;
    ss << *rsp;
    std::string data = ss.str();
    return writeFixSize(data.c_str(), data.size());
}

3.5 HttpServer

这里还涉及到Servlet的使用,可以在从零开始实现一个C++高性能服务器框架----Servlet模块了解详细说明。

class HttpServer : public TcpServer 
    {
    public:
        typedef std::shared_ptr<HttpServer> ptr;

        /**
         * @brief 构造函数
         * @param[in] keepalive 是否长连接
         * @param[in] worker 工作调度器
         * @param[in] io_worker 负责连接socket的读写
         * @param[in] accept_worker 接收连接调度器
         */
        HttpServer(bool keepalive = false
                ,johnsonli::IOManager* worker = johnsonli::IOManager::GetThis()
                ,johnsonli::IOManager* io_worker = johnsonli::IOManager::GetThis()
                ,johnsonli::IOManager* accept_worker = johnsonli::IOManager::GetThis());
                

        virtual void setName(const std::string& v) override;

        ServletDispatch::ptr getServletDispatch() const { return m_dispatch;}
        void setServletDispatch(ServletDispatch::ptr v) { m_dispatch = v;}
    
    protected:
        virtual void handleClient(Socket::ptr client) override;

    private:
        bool m_isKeepalive;                 // 是否支持长连接
        ServletDispatch::ptr m_dispatch;    // Servlet分发器
    };
}

重写了handleClient,负责接收连接socket发出的请求,并发出响应。如果是长连接,将会循环执行这个过程。

void HttpServer::handleClient(Socket::ptr client) 
{
    HttpSession::ptr session(new HttpSession(client));
    do {
    	// 接收请求
        auto req = session->recvRequest();
        if(!req) {
            LOG_DEBUG(g_logger) << "recv http request fail, errno="
                << errno << " errstr=" << strerror(errno)
                << " cliet:" << *client << " keep_alive=" << m_isKeepalive;
            break;
        }
        HttpResponse::ptr rsp(new HttpResponse(req->getVersion()
                            ,req->isClose() || !m_isKeepalive));

		rsp->setBody("hello world");
 		
 		// 发送响应
        session->sendResponse(rsp); 

    }while(m_isKeepalive);
}

3.6 HttpConnection

HTTP客户端,继承自SocketStream。负责发送请求和接收响应。

class HttpConnection : public SocketStream{
public:
	HttpResponse::ptr recvResponse();		// 接收响应
	int sendRequest(HttpRequest::ptr req);	// 发送请求
	// ...
};

为了方便使用,封装了几个发送请求的方法

class HttpConnection : public SocketStream{
public:
	 /**
         * @brief 发送HTTP的GET请求
         * @param[in] url 请求的url
         * @param[in] timeout_ms 超时时间(毫秒)
         * @param[in] is_close 是否自动关闭
         * @param[in] headers HTTP请求头部参数
         * @param[in] body 请求消息体
         * @return 返回HTTP结果结构体
         */
        static HttpResult::ptr DoGet(const std::string& url
                                , uint64_t timeout_ms
                                , bool is_close = true
                                , const std::map<std::string, std::string>& headers = {}
                                , const std::string& body = "");

        /**
         * @brief 发送HTTP的GET请求
         * @param[in] uri URI结构体
         * @param[in] timeout_ms 超时时间(毫秒)
         * @param[in] is_close 是否自动关闭
         * @param[in] headers HTTP请求头部参数
         * @param[in] body 请求消息体
         * @return 返回HTTP结果结构体
         */
        static HttpResult::ptr DoGet(Uri::ptr uri
                                , uint64_t timeout_ms
                                , bool is_close = true
                                , const std::map<std::string, std::string>& headers = {}
                                , const std::string& body = "");

        /**
         * @brief 发送HTTP的POST请求
         * @param[in] url 请求的url
         * @param[in] timeout_ms 超时时间(毫秒)
         * @param[in] is_close 是否自动关闭
         * @param[in] headers HTTP请求头部参数
         * @param[in] body 请求消息体
         * @return 返回HTTP结果结构体
         */
        static HttpResult::ptr DoPost(const std::string& url
                                , uint64_t timeout_ms
                                , bool is_close = true
                                , const std::map<std::string, std::string>& headers = {}
                                , const std::string& body = "");

        /**
         * @brief 发送HTTP的POST请求
         * @param[in] uri URI结构体
         * @param[in] timeout_ms 超时时间(毫秒)
         * @param[in] is_close 是否自动关闭
         * @param[in] headers HTTP请求头部参数
         * @param[in] body 请求消息体
         * @return 返回HTTP结果结构体
         */
        static HttpResult::ptr DoPost(Uri::ptr uri
                                , uint64_t timeout_ms
                                , bool is_close = true
                                , const std::map<std::string, std::string>& headers = {}
                                , const std::string& body = "");

        /**
         * @brief 发送HTTP请求
         * @param[in] method 请求类型
         * @param[in] uri 请求的url
         * @param[in] timeout_ms 超时时间(毫秒)
         * @param[in] is_close 是否自动关闭
         * @param[in] headers HTTP请求头部参数
         * @param[in] body 请求消息体
         * @return 返回HTTP结果结构体
         */
        static HttpResult::ptr DoRequest(HttpMethod method
                                , const std::string& url
                                , uint64_t timeout_ms
                                , bool is_close = true
                                , const std::map<std::string, std::string>& headers = {}
                                , const std::string& body = "");

        /**
         * @brief 发送HTTP请求
         * @param[in] method 请求类型
         * @param[in] uri URI结构体
         * @param[in] timeout_ms 超时时间(毫秒)
         * @param[in] is_close 是否自动关闭
         * @param[in] headers HTTP请求头部参数
         * @param[in] body 请求消息体
         * @return 返回HTTP结果结构体
         */
        static HttpResult::ptr DoRequest(HttpMethod method
                                , Uri::ptr uri
                                , uint64_t timeout_ms
                                , bool is_close = true
                                , const std::map<std::string, std::string>& headers = {}
                                , const std::string& body = "");
private:
		HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req
                            , Uri::ptr uri
                            , uint64_t timeout_ms
                            , bool is_close)
};

每个HttpConnection都应该记录自己发起连接请求的数量创建时间,在HttpConnectionPool时会根据每个连接对象的最大请求数和存活时间来判断是否需要销毁

private:
    uint64_t m_createTime = 0;          // 创建时间
    uint64_t m_requestCount = 0;        // 发起连接请求的数量

3.7 HttpConnectionPool

HTTP连接池,负责统一管理HttpConnection对象。提前预备好一系列已接建立连接的socket,这样,在发起请求时,可以直接从中选择一个进行通信,而不用重复创建套接字→ 发起connect→ 发起请求 的流程。

class HttpConnectionPool {
public:
	// do_get, do_post, doRequest等方法
private:
   	  std::string m_host;             // host
      std::string m_vhost;            // vhost
      uint32_t m_port;                // port
      uint32_t m_maxSize;             // 连接池最大数量
      uint32_t m_maxAliveTime;        // 连接池连接最大存活时间
      uint32_t m_maxRequest;          // 每个连接最大请求次数
      bool m_isHttps;

      MutexType m_mutex;
      std::list<HttpConnection*> m_conns;     // 连接池
      std::atomic<int32_t> m_total = {0};     // 连接池中总的连接数量
};

针对于每个m_host(比如www.baidu.com:80)都可以提前创建好一些连接好的socket。

连接池与发起请求时的keep-alive参数有关,如果使用连接池来发起GET/POST请求,在未设置keep-alive时,连接池不会发生作用。因此,每次都要判断请求头有没有connection字段,如果没有,就要默认加上connection: keep-alive

bool has_connection = false;

for(auto& i : headers) {
    if(!has_connection && strcasecmp(i.first.c_str(), "connection") == 0) {
        if(strcasecmp(i.second.c_str(), "keep-alive") == 0) {
            req->setClose(false);
        }  
        has_connection = true;
        continue;
    }

    if(!has_host && strcasecmp(i.first.c_str(), "host") == 0) {
        has_host = !i.second.empty();
    }

    req->setHeader(i.first, i.second);
}

//设置长连接
if(!has_connection)
{
    req->setHeader("connection", "keep-alive");
} 

连接池每次doRequest时,都会从连接池中直接拿一个连接使用,每次拿了,HttpConnection里面的连接请求数就要+1

HttpResult::ptr HttpConnectionPool::doRequest(HttpRequest::ptr req
                                            , uint64_t timeout_ms) {
 	
    auto conn = getConnection();	// 拿一个连接
    conn->setRequestCount(conn->getRequestCount()+1);   //每做一次请求,多一个请求

    if(!conn) {
        return std::make_shared<HttpResult>((int)HttpResult::Error::POOL_GET_CONNECTION
                , nullptr, "pool host:" + m_host + " port:" + std::to_string(m_port));
    }
    auto sock = conn->getSocket();
    if(!sock) 
    {
        return std::make_shared<HttpResult>((int)HttpResult::Error::POOL_INVALID_CONNECTION
                , nullptr, "pool host:" + m_host + " port:" + std::to_string(m_port));
    }
    sock->setRecvTimeout(timeout_ms);
    int rt = conn->sendRequest(req);
    if(rt == 0) 
    {
        return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_CLOSE_BY_PEER
                , nullptr, "send request closed by peer: " + sock->getRemoteAddress()->toString());
    }
    if(rt < 0) 
    {
        return std::make_shared<HttpResult>((int)HttpResult::Error::SEND_SOCKET_ERROR
                    , nullptr, "send request socket error errno=" + std::to_string(errno)
                    + " errstr=" + std::string(strerror(errno)));
    }
    auto rsp = conn->recvResponse();
    if(!rsp) 
    {
        return std::make_shared<HttpResult>((int)HttpResult::Error::TIMEOUT
                    , nullptr, "recv response timeout: " + sock->getRemoteAddress()->toString()
                    + " timeout_ms:" + std::to_string(timeout_ms));
    }
    return std::make_shared<HttpResult>((int)HttpResult::Error::OK, rsp, "ok");
}

获取一个连接getConnection。先在连接池中找(如果有失效的连接,删除),找到了直接返回,没找到创建一个

HttpConnection::ptr HttpConnectionPool::getConnection() 
{
    uint64_t now_ms = johnsonli::GetCurrentMS();
    std::vector<HttpConnection*> invalid_conns;
    HttpConnection* ptr = nullptr;

    MutexType::Lock lock(m_mutex);
    while(!m_conns.empty())
    {
        auto conn = *m_conns.begin();
        m_conns.pop_front();

        //当前连接已经断开连接,无效
        if(!conn->isConnected())
        {
            invalid_conns.push_back(conn);
            continue;
        }

        //到期,无效
        if((conn->getCreateTime() + m_maxAliveTime) < now_ms)
        {
            invalid_conns.push_back(conn);
            continue;
        }
        ptr = conn;
        break;
    }

    lock.unlock();

    //释放无效的连接
    for(auto i : invalid_conns)
    {
        delete i;
    }
    m_total -= invalid_conns.size();

    //没有可用的连接,创建
    if(!ptr)
    {
        IPAddress::ptr addr = Address::LookupAnyIPAddress(m_host);
        if(!addr) 
        {
            LOG_ERROR(g_logger) << "get addr fail: " << m_host;
            return nullptr;
        }
        addr->setPort(m_port);

        //创建socket
        Socket::ptr sock = Socket::CreateTCP(addr);
        if(!sock) 
        {
            LOG_ERROR(g_logger) << "create sock fail: " << *addr;
            return nullptr;
        }

        //连接
        //ptr->setRequestCount(ptr->getRequestCount()+1);
        if(!sock->connect(addr)) 
        {
            LOG_ERROR(g_logger) << "sock connect fail: " << *addr;
            return nullptr;
        }
        
        ptr = new HttpConnection(sock);
        ptr->setCreateTime(johnsonli::GetCurrentMS());
        ++m_total;
    }

    return HttpConnection::ptr(ptr, std::bind(&HttpConnectionPool::ReleasePtr
                        , std::placeholders::_1, this));
}

对于新创建的连接,这里绑定了一个删除器,如果当前连接使用完后,如果是断开连接,存活到期,请求数量太多的状态,销毁此连接;否则重新放入连接池

HttpConnection::ptr HttpConnectionPool::getConnection() {
	// ...
	return HttpConnection::ptr(ptr, std::bind(&HttpConnectionPool::ReleasePtr
                        , std::placeholders::_1, this));
}
void HttpConnectionPool::ReleasePtr(HttpConnection* ptr, HttpConnectionPool* pool)  {
    //断开连接,存活到期,请求数量太多,销毁此连接
    if(!ptr->isConnected()
        || (ptr->getCreateTime() + pool->m_maxAliveTime) < johnsonli::GetCurrentMS()
        || (ptr->getRequestCount() >= pool->m_maxRequest))
    {
        //LOG_INFO(g_logger) << "ptr->getRequestCount(): " << ptr->getRequestCount();
        delete ptr;
        --pool->m_total;
        return;
    }

    MutexType::Lock lock(pool->m_mutex);
    //如果当前连接还可用,就加入连接池
    pool->m_conns.push_back(ptr);
}
  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值